如何通过python实现H.264视频推流与接收
Posted mynameisdqy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何通过python实现H.264视频推流与接收相关的知识,希望对你有一定的参考价值。
目录
2.3.1 基于 GstRtspServer 的 RTSP 推流
0. 背景
0.1 任务描述
发送端捕获摄像头视频并逐帧进行处理,对处理后的图像序列进行H.264编码并实时传输至接收端。
关于H.264编码,比较简单的实现方法是收集全部待传输序列,并通过OpenCV的VideoWriter进行编码、写入文件,后对视频文件进行一次性传输或流式,但不具有实时性。本文章对网络上可见的处理方法进行了尝试与验证。
0.2 测试环境
本文的测试主机为Linux内核的Ubuntu系统,包括服务器版的Ubuntu 18.04与Windows 10子系统Ubuntu 18.04 LTS。Python版本为3.6.12。
1. 视频流式传输协议
常用的视频流式传输协议包括RTMP和RTSP,其中前者基于TCP维持端到端连接,支持的视频格式为.flv;后者也基于TCP,支持的视频格式为.mp4。两种协议均可以通过h.264进行视频编解码。更多关于流媒体传输协议的介绍见DaveBobo的知乎专栏。
实际上,对于视频的流式传输不一定必须使用以上协议。如果需要,通过一般的网络传输协议,如 TCP/UDP 也能实现所需要的效果。
2. 网络上基于GStreamer的视频推流
GStreamer是针对多媒体流式传输的软件层实现,支持RTP和RTSP协议,其基本操作基于管道(Pipeline)实现。
2.1 GStreamer安装
本文的应用场景为在Python环境中调用GStreamer,常见的方法是使用OpenCV中的GStreamer工具来实现。通过pip命令安装的OpenCV包默认不包含GStreamer工具,因此需要从源码层面对OpenCV包进行编译安装。安装教程参考Ridgerun的博客,基本思路在于下载OpenCV的源代码,并在编译时指定Python3路径为虚拟环境中的解释器、指定启用GStreamer工具。
2.2 GStreamer基本命令
OpenCV调用GStreamer的方法与GStreamer软件的命令行调用方式相似,这里介绍其基本命令格式(软件层面),主要介绍工具 gst-launch-1.0。
gst-launch-1.0 的基本格式为:
gst-launch-1.0 [OPTIONS] PIPELINE-DESCRIPTION
其中 [OPTIONS] 包括诸如 --help 等的辅助性指令,PIPELINE-DESCRIPTION 是由感叹号 ! 分隔开的描述,描述可以是单一的指示,也可以是“属性=值”的赋值,或“对象 属性=值”的复杂赋值。管道的基本属性包括元素(Elements)和链接(Links),元素可以放入不同类型的容器(Bins)中。对于三者的描述可以以任意顺序陈列在管道描述符中。示例如下:
gst-launch-1.0 filesrc location=music.mp3 ! mad ! audioconvert ! audioresample ! osssink
其中除命令名称外的所有字符均为管道描述,filesrc location=music.mp3 表示指定管道的源为文件,文件位置在 music.mp3;mad(MPEG Audio Decoder) 表示指定音频解码器;audioconvert 表示对解码后的音频数据进行诸如数据类型的转换;audioresample 表示改变音频数据的采样率;osssink 表示管道的输出为 OSS(Open Sound System) 设备如音频播放设备等。
以上命令的功能为播放 .mp3 音频文件,其中包含 GStreamer 命令中的必要元素:源类型(src)、解码方式及操作、端类型(sink)等。其中对于视频而言常用的操作包括:
源类型 | 编/解码类型 | 端类型 | |||
---|---|---|---|---|---|
filesrc | 文件 | mad | mp3解码器 | osssink | 音频播放设备 |
gnomevfssrc | gnome虚拟文件 | oggdemux | OGG文件解复用器 | filesink | 存储到文件 |
cdparanoiasrc | CD设备 | wavparse | WAV文件解码器 | xvimagesink | 视频播放设备(X Display) |
osssrc | 音频录制设备 | dvddemux | DVD文件解复用器 | sdlvideosink | 视频播放设别(SDL) |
v4l2src | Video4linux2视频文件 | h264parse | 解析H.264流 | udpsink | 通过UDP传输到网络 |
udpsrc | UDP连接 | x264enc | H.264编码 | appsink | 传输至应用程序 |
tcpclientsrc | 作为用户端接收网络中的TCP流。也有tcpserversrc | tcpclientsink | 作为用户端向网络中传输TCP流。也有tcpserversink | ||
appsrc | 来自应用程序的数据 | x265enc | H.265编码 | curlhttpsink | 通过http传输至网络 |
rtmpsrc | 网络中的RTMP流 | rtmpsink | 通过RTMP将FLV视频传输至网络 | ||
rtspsrc | 网络中的RTSP流 | rtspclientsink | 通过RTSP将视频传输至网络 |
由此可知,GStreamer 能够处理与转换各种不同的视频源(包括文件、硬件设备、网络数据流),理论上能够实现本文所要求的功能。
2.3 基于 GStreamer 的 RTSP推流
2.3.1 基于 GstRtspServer 的 RTSP 推流
WisdomPill 基于 GstRtspServer 实现了对本地摄像头数据逐帧进行 RTSP 推流,但其并未指定目的 IP,即省略了 GStreamer 管道的端;JulyLi2019 借鉴其代码,实现了对 RTSP 服务器流的逐帧解析与推流,同样没有指定管道的端,但声称推流到了本地(通过VLC查看 localhost )。两者的核心代码如下:
self.launch_string = 'appsrc name=source is-live=true block=true format=GST_FORMAT_TIME ' \\
'caps=video/x-raw,format=BGR,width=640,height=480,framerate=/1 ' \\
'! videoconvert ! video/x-raw,format=I420 ' \\
'! x264enc speed-preset=ultrafast tune=zerolatency ' \\
'! rtph264pay config-interval=1 name=pay0 pt=96'.format(self.fps)
Gst.parse_launch(self.launch_string)
其中使用的参数及含义为:
- appsrc - 管道的源为 app,即python程序,也就是我们将要传输的图像帧;
- name - 管道源的名称;
- is-live - 管道只在源处于“播放”状态时推出缓存;
- block - 当管道达到最大字节且 "enough-data" 信号发出后,阻塞所有的推入缓存操作;
- format - 分块行为所用的格式,当源产生的数据自带时间戳时,必须设置为 GST_FORMAT_TIME;
- caps - 将被放在推出缓存中的属性,这里分别指定了以下设置:
- video/x-raw:x-raw格式的视频
- format:BGR,表示流需要传输的视频帧格式
- width:640,流需要传输的视频帧宽度(height 同)
- framerate:视频帧速率,分数形式
- videoconvert - 进行视频格式的转换;
- video/x-raw, format=I420 - 将视频格式转换为 I420 的 x-raw格式;
- x264enc - 进行 H.264 编码,这里对编码的细节进行了设置:
- speed-preset:ultrafast,设置速率与质量之间的权衡
- tune:zerolatency,调整管道为无延迟
- 可以设定 bitrate 属性指定数据率,单位为 kbps
- rtph264pay - 将 H.264 视频流由RTP包载荷,这里对以下属性进行了设定:
- config-interval:码流中SPS与PPS帧之间的间隔
- name:名称
- pt:payload type,指定有效载荷类型,96表示动态载荷,也是H.264编码方式常用的值
当使用另一台计算机通过网络访问时,或许可以考虑访问推流计算机的 IP 与端口,有效性有待验证。
2.3.2 简单的UDP推流
WinterMax 对摄像头数据通过 UDP 进行推流,其核心语句为:
# server
PIPELINE = 'appsrc ! videoconvert ! x264enc tune=zerolatency speed-preset=fast ! rtph264pay ! udpsink host=192.168.0.171 port=5004'
out_send = cv2.VideoWriter(PIPELINE, cv2.CAP_GSTREAMER, 0, 16, frame_size, True)
# client
PIPELINE = 'udpsrc port=5004 caps = "application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H264, payload=(int)96" ! rtph264depay ! decodebin ! videoconvert ! appsink'
cap_receive = cv2.VideoCapture(PIPELINE, cv2.CAP_GSTREAMER)
能实现推流,但由于UDP的不可靠特性,丢包时码流无法得到正确的解码。此外,如果 client 在码流传输中途接入,将导致解码错误。
结合以上实现,考虑在2.3.1的语句末尾添加 "! rtspclientsink"。但官方文档中该属性似乎并非用来支持RTSP传输,而RTMP有更可靠的实现,如:
gst-launch-1.0 -v videotestsrc ! ffenc_flv ! flvmux ! rtmpsink location='rtmp://localhost/path/to/stream live=1'
2.3.3 简单的RTSP流接收
Eslam Ahmed 实现了RTSP流的接收,其核心语句为:
CLI="rtspsrc name=src location=rtsp://192.168.1.20:554/live/ch01_0 latency=10 !decodebin ! appsink name=sink"
pipline=Gst.parse_launch(CLI)
但其有效性有待验证。
3. 网络上基于FFmpeg的视频推流
3.1 FFmpeg 安装
FFmpeg 是一种高效的音视频转换器,也能够对流数据进行处理。Python 程序中调用 FFmpeg 接口常用的方法依然是通过 OpenCV 实现。为 OpenCV 添加 FFmpeg 支持的方法类似于 GStreamer,也需要从源码安装,并指定特定的安装选项。详细步骤参考 Goet 的博客。
3.2 FFmpeg 的基本指令
Python 通过 OpenCV 调用 FFmpeg 指令的方式与 GStreamer 相似,这里仍然先介绍其软件层面的实现语句。FFmpeg 的基本语句结构如下:
ffmpeg [input options] -i input_name [output options] output_name
其中输出文件前没有任何标识,实际上任何无法解析的字符串都将被解释为输出文件。
ffmpeg 命令中所有的赋值描述均由一对 -name option 构成,位于输入/出文件名前的描述表示对输入/出的设定。对于视频而言,常见的操作有:
指示符 | 含义 | 示例 |
-r | 帧率(或输入设备的采样率),fps | 25 |
-s | 帧尺寸,宽×高 | 640x480 |
-pix_fmt | 像素格式 | rgb24 |
-b:v | 视频数据率,bps | 64k |
-vcodec(-codec:v, -c:v) | 视频编码方式 | libx264 |
-f | 视频格式,通常自动指定 | raw, rtsp |
-t | 读取或写入视频的时间 | 10 |
值得注意的是,以上描述只针对 ffmpeg 本身,当使用 h.264 进行编码时,需要在指定编码器:-c:v libx264 之后进一步指定编码选项,其格式为:
ffmpeg -i input_name -c:v libx264 -x264opts name1=value1:name2=value2 output_name
其中 [options] 为以“:”分隔开的“name=value”一系列描述词。可用描述词见官方文档,一个简单示例如下:
ffmpeg -i input_name -c:v libx264 -x264opts b=20 out_name
表示以 20 kbps 的速率对输入进行编码。-x264-params 与之等效。
3.3 基于 FFmpeg 的 RTSP 推流
3.3.1 基于管道的本地推流
Mateen Ulhaq 在问题中描述了基于管道实现“图片生成→编码→解码”的流程,是对 FFmpeg 与管道结合使用的较好范例。其总体逻辑为:
[RGB frame] ------ encoder ------> [h264 stream] ------ decoder ------> [RGB frame]
^ ^ ^ ^
encoder_write encoder_read decoder_write decoder_read
其核心代码如下:
cmd = (
"ffmpeg "
"-f rawvideo -pix_fmt rgb24 -s 224x224 "
"-i pipe: "
"-f h264 "
"-tune zerolatency "
"pipe:"
) # 原始 ffmpeg 命令
encoder_process = subprocess.Popen(
cmd.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE
) # 子线程以管道与程序进行数据交互
writer = encoder_process.stdin # 向管道的输入端写入数据
writer.write(frame.tobytes())
该代码从管道中直接取出 H.264 流并写入队列中,解码端从队列中取出码流进行解码。可以考虑将队列换为网络(如 RTSP)实现码流在计算机之间的传输。该实例据作者描述存在延时较大的问题,这个问题的解决可以在 FFmpeg 的 StreamingGuide 页面找到。
3.3.2 基于 FFmpeg 的网络推流示例
上一节提到的 StreamingGuide 网页中详细介绍了 FFmpeg 的推流方法和注意事项,
其中也包含了一些对于 FFmpeg 推流的简单示例,如与本文主题相关的点对点对流:
# 基于 RTP 的音频推流
ffmpeg -i INPUT -acodec libmp3lame -ar 11025 --f rtp rtp://host:port
# 基于 UDP 的视频推流,容易因丢包而导致乱码
ffmpeg -i INPUT -f mpegts udp://host:port
# 基于 TCP 的视频推流
ffmpeg -i INPUT -f mpegts tcp://host:port # 发送端
ffmpeg -i tcp://local_hostname:port?listen # 接收端
# 基于 RTSP 的视频推流
ffmpeg -i input -f rtsp -rtsp_transport tcp rtsp://localhost:8888/live.sdp # 发送端
ffplay -rtsp_flags listen rtsp://localhost:8888/live.sdp?tcp # 接收端
网页中也指出,TCP 连接需要先开启接收端的侦听,再开始发送端的连接与传输。这可能是导致之前的尝试过程中 “TCP connection refused” 的原因。
4. 实例与验证
本节对以上提到的实例进行完整的实现和功能验证,包括基于不同的软件、通过不同的网络连接以及基于不同的代码组织方式。
4.1 基于 GStreamer 的推流实现
基于 GStreamer 的实现方法需要较多的依赖包,如 gi 库等。对于没有服务器 root 权限的账号而言操作有些复杂,因此暂时略过。
4.2 基于 FFmpeg 的推流实现
基于 FFmpeg 的推流实现较为方便,比较可靠的参考程序参照 Ηλίας Κωνσταντινίδης 和 Rotem 的实现。其核心代码主要包括发送端的:
command = ['ffmpeg',
'-y',
'-i', '-',
'-an',
'-c:v', 'mpeg4',
'-r', '50',
'-f', 'rtsp',
'-rtsp_transport', 'tcp',
'rtsp://192.168.1.xxxx:5555/live.sdp']
p = subprocess.Popen(command, stdin=subprocess.PIPE)
以及接收端的:
# get decoded frames in rawvideo format and BGR pixel format
command = ['C:/ffmpeg/bin/ffmpeg.exe',
'-rtsp_flags', 'listen',
'-i', 'rtsp://192.168.1.xxxx:5555/live.sdp?tcp?',
'-f', 'image2pipe', # Use image2pipe demuxer
'-pix_fmt', 'bgr24', # Set BGR pixel format
'-vcodec', 'rawvideo', # Get rawvideo output format.
'-']
# Read the raw video frame from p1.stdout
raw_frame = p1.stdout.read(width*height*3)
# Convert the bytes read into a NumPy array, and reshape it to video frame dimensions
frame = np.fromstring(raw_frame, np.uint8)
frame = frame.reshape((height, width, 3))
参考以上程序,我设计了针对当前场景的推流程序,代码参考:https://github.com/Ton-lee/RTSP-Streaming
4.3 码流比特率获取
通过 FFmpeg 实现推流只完成了第一步,下一步我们需要获取码流的比特率,或者是在对每一帧完成编码后获取编码后码流的大小。实现该目标的思路之一在于为 FFmpeg 指定多路输出,其中一路为 RTSP 链接用于传输,另一路为管道用于直接获取码流并计算大小。下面对该思路进行必要的验证。
4.3.1 基于 tee 的多路输出
通过以下命令实现多路输出:
self.command = [ # multiple output for convenience of bandwidth calculation
'ffmpeg',
# 're',#
# '-y', # 无需询问即可覆盖输出文件
'-f', 'rawvideo', # 强制输入或输出文件格式
'-vcodec', 'rawvideo', # 设置视频编解码器。这是-codec:v的别名
'-pix_fmt', 'bgr24', # 设置像素格式
'-s', self.size_str, # 设置图像大小
'-r', str(fps), # 设置帧率
'-i', '-', # 输入
'-timeout', '10', # 设置TCP连接的等待时间
'-b:v', '%dk' % self.rate, # 设置数据率
'-c:v', 'libx264',
# '-x264opts', 'bitrate=%d' % self.rate, # 设置比特率(kbps)
'-pix_fmt', 'yuv444p',
'-preset', 'ultrafast',
'-rtsp_transport', 'tcp', # 使用TCP推流
'-map', '0:v', # 需要指定各个输入文件所要提取的流
'-f', 'tee', # 输出格式指定为 tee
"temp.mp4|[f=rtsp]%s" % self.rtspUrl # 用 | 分隔各路输出
]
由以上代码可以实现将编码后的数据流同时通过 RTSP 传输和保存到本地 .mp4 文件中。
4.3.2 将输出指定为 PIPE
这里我们需要一个 pipe,其输入为原始帧,输出为经过编码的 H.264 数据流。程序运行时,实时从 pipe 的输出端读取数据,根据其大小计算带宽。
将 FFmpeg 的输入和输出同时指定为 pipe 可以通过如下代码实现:
self.command = [ # output to a pipe
'ffmpeg',
# 're',#
# '-y', # 无需询问即可覆盖输出文件
'-f', 'rawvideo', # 强制输入或输出文件格式
'-vcodec', 'rawvideo', # 设置视频编解码器。这是-codec:v的别名
'-pix_fmt', 'bgr24', # 设置像素格式
'-s', self.size_str, # 设置图像大小
'-r', str(fps), # 设置帧率
'-i', 'pipe:', # 输入
'-timeout', '10', # 设置TCP连接的等待时间
'-b:v', '%dk' % self.rate, # 设置数据率
'-c:v', 'libx264',
# '-x264opts', 'bitrate=%d' % self.rate, # 设置比特率(kbps)
'-pix_fmt', 'yuv444p',
'-preset', 'ultrafast',
'-f', 'h264',
'pipe:']
但在读取 pipe 的输出时遇到了困难。通过 pipe.communicate() 可以读取管道的输出,但只能使用一次,该函数一经调用,pipe 就终止了数据的读写过程,无法实现后续的读写。通过 pipe.stdout.readline() 可以读出一行的内容,但由于 H.264 编码需要引入一定的缓存,导致 pipe 的输出与输入不同步,从而使程序阻塞。该矛盾在 StackOverFlow 中也有相应的话题。
It is not safe to assume that the child process will immediately get the complete data you send to its stdin, as buffers can get in the way. If you must hold the file open for further output then you should at least invoke its
flush()
method.Furthermore, it is not safe to assume that the child process's output will be immmediately available to you to read. If it does not flush (or close) its output stream then the EOL could be buffered, and if you do nothing to prompt the child process to act further, then your
readline()
may wait forever. But your program then CAN'T do anything, because it's stuck inreadline()
. If the child process is built for this then you might get it to work, but otherwise you need to use a safer method, such assubprocess.communicate()
.As you observe, it does not work to call
communicate()
more than once on the same subprocess. That's as you should expect from its documentation: it reads all output until end-of-file, and waits for the subprocess to terminate. To send multiple inputs in this mode, build a string containing all of them, pass it tocommunicate()
and then read all the answers.Alternatively, if you really need to alternate between writing and reading, and your subprocess is not specifically tooled for that, then it is safer to do the reading and writing in separate threads, without any assumption of the writes and reads interlacing perfectly.
最终的解决办法是,将对 pipe 的读写分别通过两个线程运行,不过会引入带宽计算的延时。与此同时,由于无法逐帧读取码流,对带宽的计算也要发生一定的变化,可以通过读取固定字节数码流所花费的平均时间来计算带宽。读取 pipe 输出的代码如下所示:
def read_pipe():
while True:
time_start = time.time()
content = streamer.pipe.stdout.read(1024)
time_end = time.time()
time_duration = time_end - time_start
print('read time: ', time_duration) # 根据该时间计算带宽
4.3.3 将 H.264 同时输出到 RTSP 与 pipe
结合以上两节内容,通过 tee 指定两个输出:
self.command = [ # multiple output to rtsp and local file
...
'-rtsp_transport', 'tcp', # 使用TCP推流
'-map', '0:v',
'-f', 'tee', # 强制输入或输出文件格式
"[f=h264]pipe:|[f=rtsp]%s" % self.rtspUrl
]
self.pipe = sp.Popen(self.command, stdin=sp.PIPE, stdout=sp.PIPE)
由此可实现在通过 RTSP 进行传输的同时对比特率进行监测。
4.3.4 接收端的码率监测
此外,我们希望在接收端也对码率进行监测。由于无法将码流同时输出到两个 pipe 分别用于码率计算和解码,因此我们考虑使用串联管道结构:
- 第一个管道接收原始码流,不进行任何解码,直接输出原始码流;
- 第二个管道从第一个管道接收码流,进行解码后输出视频帧。
我们通过对该结构进行实现与验证发现,如此操作会大大增加解码的延时,且由于接收端第二个管道完成一帧的解码需要第一个管道多次写入数据,导致成堆解码,节奏不稳定(多帧的解码瞬间完成)。因此我们仍需要考虑一个管道两个输出的情况。
通过 tee 命令可以为 FFmpeg 的解码结果指定多个输出,或对同一个输入流进行多种处理方式,分别输出到不同的文件。但 FFmpeg 命令的执行以管道的形式实现,管道同时有两个输出暂时没有办法实现。
最终我们退而求其次,采用第一种思路,在读取第二个管道的输出时根据当前帧率,通过 time.sleep() 来控制显示的时间,从而实现相对平缓的读取。
4.4 比特率实时调整
程序运行时我们希望通过可视化界面随时调整 H.264 编码的比特速率。由于 pipe 与一条固定的指令绑定,所以我们在需要调整比特率时,先停止当前传输,再绑定一个新的 pipe。可以通过 terminate 函数终止管道的传输,但一端终止时,另一端会发生错误。我们下面解决这个矛盾,同时顾及比特率监测的实现。
4.4.1 pipe.terminate 对 pipe 输出的影响
我们不希望 pipe 终止后对带宽计算产生影响,实际上,pipe 被终止后调用 pipe.stdout.read() 函数会返回空比特串,因此我们在带宽计算函数中添加相应的条件即可。
def h264Bandwidth(unit=1024):
while True:
time_start = time.time()
out = globals_qt.RS.pipe.stdout.read(unit)
if out == b'':
continue
time_end = time.time()
time_duration = time_end - time_start
byte_per_frame = unit / time_duration * globals_qt.fps
4.4.2 pipe.terminate 对 RTSP 传输的影响
更改传输比特率之前需要先后终止发送端和接收端的 pipe,我们需要实现终止一端的 pipe 之后,另一端能够捕获错误或者不发生错误,以便于重新初始化。我们尝试通过 try-except 语句捕获错误。
经测试,接收端终止管道时,发送端能够捕获到错误;而发送端终止管道时,接收端会因等待从管道中读取数据而发生阻塞。(实验发现,通过 terminate 终止管道后相关端口仍在占用,只有通过 kill 才能彻底终止一次传输)
因此当需要改变传输比特率时,发送端先向接收端发送信号(同时保持传输连接),接收端接收信号后断开接收管道并开启新的连接;发送端捕获异常后断开发送管道,重新启动新的连接。
5. 链接汇总
本节对正文中提及的链接进行汇总整理,方便后期查询。
GStreamer:
描述 | 链接 |
Wiki: Real-Time Messaging Protocol | |
维基:实时流协议 | |
知乎专栏:流媒体传输协议(rtp/rtcp/rtsp/rtmp/mms/hls) | |
官网:GStreamer | |
Blog: Compiling OpenCV from Source | https://developer.ridgerun.com/wiki/index.php?title=Compiling_OpenCV_from_Source |
StackOverFlow: Write opencv frames into gstreamer rtsp server pipeline | https://stackoverflow.com/questions/47396372/write-opencv-frames-into-gstreamer-rtsp-server-pipeline |
博客:GStreamer与opencv实现rtsp推流 | |
GStreamer: appsrc | https://gstreamer.freedesktop.org/documentation/app/appsrc.html?gi-language=c |
GStreamer: GstCaps | https://gstreamer.freedesktop.org/documentation/gstreamer/gstcaps.html?gi-language=python |
StackOverFlow: Stream h264 video with opencv+gstreamer artifacts | https://stackoverflow.com/questions/62481010/stream-h264-video-with-opencvgstreamer-artifacts |
StackOverFlow: reading a h264 RTSP stream into python and opencv | https://stackoverflow.com/questions/22582031/reading-a-h264-rtsp-stream-into-python-and-opencv |
FFmpeg:
描述 | 链接 |
官网:FFmpeg Documents | https://ffmpeg.org/ffmpeg.html |
博客:Ubuntu16.04 install OpenCV with ffmpeg | https://www.jianshu.com/p/f4ca1039eadf |
FFmpeg: Options | https://ffmpeg.org/ffmpeg-codecs.html#Supported-Pixel-Formats |
StackOverFlow: ffmpeg delay in decoding h264 | https://stackoverflow.com/questions/60462840/ffmpeg-delay-in-decoding-h264 |
FFmpeg: Streaming Guide | https://trac.ffmpeg.org/wiki/StreamingGuide |
StackOverFlow: Display stream with FFmpeg, python and opencv | https://stackoverflow.com/questions/66332694/display-stream-with-ffmpeg-python-and-opencv |
GitHub: Ton-lee/RTSP-Streaming | https://github.com/Ton-lee/RTSP-Streaming |
StackOverFlow: process.stdout.readline() hangs. How to use it properly? | https://stackoverflow.com/questions/24640079/process-stdout-readline-hangs-how-to-use-it-properly |
音视频流媒体-推流与拉流简介
一、概念
话不多说,先了解概念,再看示意图更直观:
1.1 推流
推流:将直播的内容推送至服务器的过程。即指的是把采集阶段封包好的内容传输到服务器的过程。其实就是将现场的视频信号传到网络的过程。“推流”对网络要求比较高,如果网络不稳定,直播效果就会很差,观众观看直播时就会发生卡顿等现象,观看体验很是糟糕。要想用于推流还必须把音视频数据使用传输协议进行封装,变成流数据。常用的流传输协议有RTSP、RTMP、HLS等,使用RTMP传输的延时通常在1–3秒,对于手机直播这种实时性要求非常高的场景,RTMP也成为手机直播中最常用的流传输协议。最后通过一定的Qos算法将音视频流数据推送到网络断,通过CDN进行分发。
1.2 拉流
拉流:指服务器已有直播内容,用指定地址进行拉取的过程。即是指服务器里面有流媒体视频文件,这些视频文件根据不同的网络协议类型(如RTMP、RTSP、HTTP等)被读取的过程,称之为拉流,说的简单点,你观看优酷视频就可以看成是拉流,视频文件存储在优酷的服务器上面,你通过HTTP(或者RTMP/RTSP)协议,也就是网页的形式去获取视频观看,这就是拉流的过程,在这个过程中有三个要素:1-服务器【提供视频文件存储的地方】 2-传输协议【就是你要通过什么方式传输视频】3-读取终端【就是通过什么播放出来】
二、示意图
以下是从网络上搜寻到的有关推流与拉流的示意图:
推流与拉流示意图(1)
一张示意图没有看明白,没有关系,再来一张示意图:
推流与拉流示意图(2)
其实可以简要的理解为推流就是直播端,而拉流就是客户端哦。
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
三、RTMP传输协议
流媒体中的传输协议有很多种,以下先介绍一种,其他可以参照此协议方便理解。
RTMP是Real Time Messaging Protocol(实时消息传输协议)的首字母缩写。一种设计用来进行实时数据通信的网络协议。
每一个推流码地址唯一指向单个的直播活动。它由rtmp://开头,包含了上传服务器地址,上传目录名和上传节点,三部分组成。所有的rtmp地址都是这种结构组成,基本同一个平台不同直播的地址前两部分是不变的。
四、流媒体协议与格式
各种流媒体协议与格式示意图
以上是关于如何通过python实现H.264视频推流与接收的主要内容,如果未能解决你的问题,请参考以下文章