解远端视频流
简介
下图展示了mpegCoder.MpegClient
背靠的理论。假设在远端有一个视频服务器正在连续不断地推送实时视频流,即使不读取这个视频流的内容,数据流也不会为了客户端暂停下来。因此,我们设计了以下的双线程工作模式。
当连接到远端服务器时,MpegClient
就会创建一个用于提供后台服务的子线程(即图中的writer, 写者)。即使我们没有进行任何读取操作,该线程也会持续不断地从远端接收视频帧。所接收到的帧会被保存在一个循环缓存里(图中,缓存的大小是12)。读者和写者将各自通过维护一个读指针和一个写指针(即图中分别连接到这些线程的箭头)来控制读写同步。每当接收到新的一帧时,写指针向前步进一格。
读取事件都是由Python-C-API触发的。当一个新的读取事件来到主线程时,读者将会锁住读指针的当前位置,并且从这个位置开始读取需要的数帧。在需要的数据被取出后,读指针将会解锁,并且读指针会更新到读取结束的位置。写者在写新接收到帧的时候,也会通过写指针锁住正在写的位置。当缓存的某个位置被锁住时,从这个位置开始的数据将无法再更新,例如,在读取事件中,如果写指针的位置步进到了读指针锁住的位置,写者就会被阻塞,一直等待到读取事件结束。如果写者被阻塞的时间太长,对远端视频的解流可能失败。所以我们建议用户设置一个合理的缓存大小。例如,如果我们每次需要读5帧,那么建议将缓存大小设置为这个值的2倍,即10帧。
代码
要测试以下代码,我们建议用户使用VLC或FFMpeg来推送远端视频流,因为mpegCoder
不支持免编码地推送视频流,使用VLC或原生的FFMpeg来做这件事可以占用更少的系统资源。
以下代码接收远端视频流的帧,并缩放到480x360的尺寸,重采样到5 FPS。读取数目和缓存大小分别设为5和12。
import os, sys
import time
import mpegCoder
mpegCoder.setGlobal(dumpLevel=2) # 显示完整的日志。
if __name__ == '__main__':
d = mpegCoder.MpegClient() # 创建客户端句柄。
d.setParameter(widthDst=480, heightDst=360, dstFrameRate=(5,1), readSize=5, cacheSize=12) # 进行基础设置。
success = d.FFmpegSetup('rtsp://localhost:8554/video') # 连接到服务器。
print(d)
if not success: # 如果没能连接上,就退出程序。用户可以自行删除这段代码,并观察会发生什么现象。
exit()
d.start() # 启动解流子线程。
time.sleep(5) # 在读取视频之前,等待几秒。
print('Get slept')
p = d.ExtractFrame() # 从缓存里读取几帧。
print(p.shape) # 展示所读取的帧的信息。
for i in range(10): # 运行50秒。
time.sleep(5)
p = d.ExtractFrame() # 从缓存里读取几帧。
d.terminate() # 关闭当前子线程。可以通过start()让该线程重启。
d.clear() # 但是我们在这里选择清除客户端并退出。
在上例里,设置好客户端后,接下来的代码将会执行以下关键步骤。
MpegClient.FFmpegSetup()
接收了一个视频流的地址。视频流的类型将会从协议自动检出。目前,支持http
,ftp
,sftp
,rtsp
和rtmp
。注意只有rtsp
和rtmp
是用来提供实时视频流服务的。http
,ftp
和sftp
协议主要用来传输数据。调用该方法会建立到远端服务器之间的连接。调用
MpegClient.start()
之后,将会创建一个名为“写者”("writer")的子线程。使用
MpegClient.ExtractFrame()
获取实时数据。返回的帧数由参数设置时给定的readSize
决定。当然,用户也可以通过传参覆盖掉这个帧数设置,例如,ExtractFrame(4)
就会强制读者返回4帧。当远端视频流关闭时,
d.ExtractFrame()
就会返回None
。不过,用户也可以选择在任意时间自行中断写者。调用方法MpegClient.terminate()
会关闭子线程。但直到MpegClient.clear()
调用的时候,连接才会被释放。
Github上的几个例子
本文的例子已经作为一个单独的分支,提供在了在Github上。
另外,我们还提供了另一个例子。该例子是基于PyQt5
和mpegCoder
实现的一个简易实时视频流播放器。