最近遇到一个这样的问题:ffmpeg
解码出 yuv 输出至管道,我写的程序从管道读取 yuv。
e.g:
1 | D:\huang_xuezhong\build_win32_VDNAGen>ffmpeg -i test.mkv -c:v rawvideo -s 320x240 -f rawvideo - | my_tool -o output |
就上面这行命令,在 Linux
和 osx
下面运行都正常,唯独在 windows
下面,ffmpeg 报错 av_interleaved_write_frame(): Broken pipe
:
怎么会出错了呢?当时我的表情是这样的:
1 | Output #0, rawvideo, to 'pipe:': |
在我的程序中,my_tool
从 stdin
中每次尝试读取一帧大小的 yuv 文件,若不足,则继续读取,直到满一帧,然后处理这一帧。代码逻辑如下:
1 | int process_yuv (int jpg_width, int jpg_height) |
windows
下出错,打出的 log 十分诡异,读取一段内容后就读不出东西了:
1 | 2016/04/05 15:20:38: a frame size:115200 |
表面和真相
google 一下有关 Broken pipe
的信息:
- 这是个系统错误,字面意思是“管道破裂”。
- 触发原因是该管道的读端被关闭,而写端还尝试往管道里面写,从而系统异常退出。
- 经常发生在
socket
关闭之后(或者其他的描述符关闭之后)的write
操作中。 - 发生此错误时,进程将收到
SIGPIPE
信号,默认动作是进程终止。
回过头去,细看下 my_tool
的这段代码,莫非在 windows
下面,从管道里面读取一段内容后,由于某种原因,feof
条件就为真了吗?导致整个程序执行完毕,管道读端早于写端关闭。
那么,问题来了,是什么导致 feof
了呢?往这个方向搜索下相关资料后,折腾一番,终于找到原因了, 原来是ASCII
码 0x1A
在作怪:
在
Unix
系统中,stdin
,stdout
和stderr
默认都是以二进制模式打开的,众所周知,用二进制模式打开一个文件的时候,文件本身的内容和你编写程序时读到的内容完全相同。但是在windows
下面,stdin
,stdout
和stderr
默认都是以文本模式打开的,这就意味着它会对部分读到的特殊字符进行转义,比如\r\n(0x0D0x0A)
转义成\n(0x0A)
。另外,千万别忽略0x1A
字符(也称Ctrl+Z(^z)
) ,除了EOF
,它也被系统认为是文件结束符。
因此,极有可能,Broken pipe
的原因是:windows
以文本模式从 stdin
中读取 yuv 内容,如果 yuv 中含有 0x1A
字符时, 系统认为已到达文件尾,从而退出 while 循环,结束程序,管道读端关闭,而写端 ffmpeg
还在解码,往管道写……
为了验证在 windows
中以文本方式读取文件时,中途读到0x1A
导致 feof()
条件为真,写个小程序测试下:
1 |
|
运行的结果是:
1 | 61 feof=0 |
从以上结果可见,在读到第四个字符 0x1A
的时候,feof
为真了。这种现象目前只发现存在于 windows
中,unix
中没有。Surprised!
一个表面上看似 Broken pipe
的错误,引发它的最初缘由居然是 windows
的特立独行,NND,又被这奇葩的 windows
坑了一把。
经验教训
由于 ffmpeg
解码出来的 yuv 是用二进制模式写出的,当然,你读也要用二进制模式。在 windows
下,手动设置 stdin
的读取方式为二进制模式。
1 |
|
从 这个 Broken pipe
引发的血案,可以得出两条经验教训:
- 二进制模式写出的文件,要用二进制模式读,同理,文本模式写的文件,要用文本模式读,不然,出了问题,系统可不会为你负责。
- 千万记住,
windows
默认以 文本模式 打开文件。