如何防止使用命名管道时发生死锁?
我有一个捕捉过程,可以把原始的视频和音频数据写入文件。像这样可以捕捉100帧的数据。
./capture -n 100 -f video_file -a audio_file
这样我得到了一个768000字节的音频文件和一个414720000字节的视频文件。看起来这些数据加起来是合理的:
- 414720000 == 1920x1080(像素/帧) * 2(字节/像素) * 100(帧)
- 768000 == 48k(赫兹) * 2(字节/样本) * 2声道 * 100(帧) / 25(帧/秒)
然后当我像这样编码这些数据时:
ffmpeg -i audio_file -i video_file out.flv
我得到了一个可以播放的视频和声音(其实我在命令行中还有很多其他的内容,但这些是这个问题的重点)
现在,我实际上想要的是一个直播流,而不是文件,我可以用下面的方式来处理视频:
./capture -f /dev/stdout | ffmpeg -i - udp://127.0.0.1:10000
这样我得到了一个没有音频的udp视频流,我能够顺利接收和播放这个流。但是当我想把音频加进来的时候,就遇到了一些麻烦。我觉得我不能把它们都发送到stdout
,而且我不能使用stderr
,因为捕捉过程已经在那边输出信息了。所以我尝试用命名管道来实现,像这样:
mkfifo audio_pipe
mkfifo video_pipe
ffmpeg -i audio_pipe -i video_pipe out.flv &
./capture -f video_pipe -a audio_pipe
但这并不奏效,似乎一切都死锁了。我测试过直接运行./capture -f video_file -a audio_file
,然后打开两个新的终端,分别执行cat video_file > /dev/null
和cat audio_file > /dev/null
,一旦这两个命令都在运行,捕捉过程就会解锁,所以看起来它写入管道没有问题。我查看了捕捉代码的源代码,它的工作方式是通过API中的“帧到达”回调,然后按顺序写入视频帧和音频数据(这是阻塞的)。我不知道ffmpeg是怎么处理的,它是顺序读取输入的视频文件或音频文件,还是同时在不同的线程中读取。我尝试把顺序改成ffmpeg -i video_pipe -i audio_pipe out.flv
,但不幸的是一切仍然锁住。只使用一个命名管道来处理视频数据是正常的。
我该如何解决这个问题?一旦我明白了避免阻塞问题的最佳方法,我会用python的subprocess模块来编写脚本。
2 个回答
在Linux系统中,管道的缓冲区大小限制为65千字节。这意味着如果你在处理音频和视频时,可能会出现一种死锁的情况:音频捕捉程序不会继续写入更多音频,直到它可以写入更多视频;而ffmpeg(一个处理音视频的工具)也不会读取更多视频,直到它得到更多音频。这样就会导致两个程序互相等待,谁也动不了。
最后,我没办法让ffmpeg在不被FIFOs阻塞的情况下读取数据,所以我修改了捕获应用的源代码,让它从不同的工作线程中分别写入音频帧和视频帧,而不是一个接一个地写。