临近春节假期之际,我司提供给客户用的命令行工具还遭遇一个新需求:
支持管道输入/输出。
需求背景:
原始的视频流体积巨大(一个十分钟视频的原始视频流就占用
2G
多空间),为了节约机器资源,客户希望能将原始的视频流直接通过管道输入,而不必转码生成临时媒体文件。
简而言之,客户希望支持这种调用方式:
1 | cat some_big_rawvideo | ./my_prog |
比如,我们用到的 ffmpeg
和 ffplay
工具也支持通过 -
或 pipe
来指定管道输入输出:
1 | ffmpeg -i input.mp4 -f avi - | ffplay - |
不幸的是,我们现有的工具并不支持,它只能根据命令行中的文件路径来指定输入输出。
1 | my_prog -i /path/to/inputfile -o /path/to/outputfile |
咋办?改!
已知
首先,小明知道,对于标准的命令行程序,它遵从基本的「一进二出」规范。
1 | ---> stdout, pipe:1 |
管道
管道是 unix
设计哲学之一,其核心思想就是将前一个命令的标准输出作为后一个命令的标准输入
。
比如programo0 | program1 | program2
的输入输出示意图如下:
1 |
|
基于这么一条简单到爆的原则,管道通过 |
把一系列命令连接起来:第一个命令的输出会作为第二个命令的输入通过管道传给第二个命令,第二个命令的输出又会作为第三个命令的输入……,最终结果为管道行中最后一个命令的输出。
举个栗子:
1 | cat /etc/passwd | grep /bin/bash | wc -l |
这条命令使用了两个管道,利用第一个管道将 cat
命令(显示passwd
文件的内容)的输出送给grep
命令,grep
命令找出含有/bin/bash
的所有行;第二个管道将grep
的输出送给wc
命令,wc
命令统计出输入中的行数。这个命令的功能在于找出系统中有多少个用户使用bash
。
stdin stdout stderr
在 unix
世界中,一切皆文件
。文件描述符是与打开文件或者数据流相关联的整数,0、1、2
是系统保留的三个文件描述符,分别对应标准输入、标准输出、标准错误。
- 0: stdin 标准输入串流 (键盘輸入)
- 1: stdout 标准输出串流 (终端屏幕)
- 2: stderr 标准错误输出串流 (终端屏幕)
重定向
比如:
1 | my_prog <inputfile >outfile 2>&1 |
将标准输入重定向到 inputfile
(意味着 my_prog
不再从标准输入而是从 inputfile
中读取数据),将标准输出和标准错误结果都重定向到 outfile
。
如何才能让一个命令行程序支持管道?
So,小明上 StackOverflow 上先看看各位同仁怎么说:
To be “pipe compatible” your program will need to read from
stdin
and write tostdout
.
原来如此:「为了支持管道,你的程序需要从 stdin
读取输入并且将输出写到 stdout
」
小试牛刀
1 | //pipe-std.c |
这个示例非常简单,编译、运行:
1 | hxz0170:~/workspace/c++$ gcc -o pipe-std pipe-std.c |
瞧见了没,这个简单的程序通过管道将输入的内容~/test/input/2.mp4
复制到输出copy.mp4
。
实践出真知
原理搞懂了,接下来就是体力活了。在实际码代码过程中,还是发现了几点管道程序需要注意的地方:
由于是从管道读取输入内容,而管道每获取到片段内容就会发送到下一级程序处理,这意味着我们不能事先获知输入文件的大小了。因此,像
get_file_size
之类的方法将不再可用。管道内容只能顺序读取,不可逆回溯,也不可重复读取同一段内容。所以啊,我们的程序要珍惜每一次读取的机会,不能
open
了再open
,只能open
、do_work1
、do_work2
……close
。具体到ffmpeg
解码程序就是,avformat_open_input
这个api
只能用一次,一次就要把需要的信息全部预加载进来。