最近由于工作计划的需要,开始研究ffmpeg。
对于命令行的推流服务,其实很简单。但是融合在自己的程序里,不好控制。尤其是在父进程异常关闭的时候,不一定能把 ffmpeg进程干掉,造成一些异常。另外,命令行的最大问题是不能精细化的管理ffmpeg的推流,比如网络不好的情况下,不好感知和处理。
ffmpeg网上买了一些课程,发现大部分都是针对ffmpeg3的,目前的ffmpeg主要是4。语法有较大的不同。
花了一周的时间研究,发现网上的很多代码是多少有点问题。
最后自己用ffmpeg4实现了一版摄像头捕捉推流,有些坑在这里标注一下。其中一些难点和诀窍,很多地方没有说明,我在这里记录一下。
完整可运行的代码,我放在了我的github上。
https://github.com/freeeyes/ffmpeg4_camera_to_rtmpgithub.com在windows下,首先,要获得摄像头的列表,目前windows的设备主要依靠的是dx。
在ffmpeg中,获得列表命令行是:
ffmpeg -list_devices true -f dshow -i dummy
对应ffmpeg的C++代码如下:
void show_dshow_device(){
AVFormatContext *pFormatCtx = avformat_alloc_context();
AVDictionary* options = NULL;
av_dict_set(&options,"list_devices","true",0);
AVInputFormat *iformat = av_find_input_format("dshow");
printf("Device Info=============n");
avformat_open_input(&pFormatCtx,"video=dummy",iformat,&options);
printf("========================n");
}
但实际上,这个并不能给代码使用,因为你无法解析输出来的数据。
虽然我在网上查到了一些获得日志输出的回调,但是实际依旧无法操作(你不可能把av_log里面的所有输出全部解析出来,仅仅是为了获得列表)
于是,自己用dx实现了一个,这本身也不难弄。
void
这里返回了一个vector容器。这里包含了所有显示设备列表,包括USB摄像头。
如果是音频,你可以使用CLSID_AudioInputDeviceCategory获得所有的音频设备列表。
具体实现,可以参考,vediolist.h文件。都是dx的函数,在这里不做具体赘述。
摄像头转rtmp推流到远端,在实现代码中,需要几个过程。
(1)设置原始摄像头的相关参数。
这里有一个东西一定要注意。
对于摄像头的获取,并不是所有的参数都是有效的。
ffmpeg支持字典结构化参数。(多一句嘴,这里最好大家详细的学习了解一下ffmpeg头文件options_table.h,对开发ffmpeg的基础非常有帮助)
av_dict_set
这里的ioptions参数,有些可以控制原始摄像头的参数。比如分辨率大小,帧率,以及编码格式。
这里我要强调一下,pixel_format这个参数。
这里面支持的原始视频流编码格式有很多,具体参见pix_fmt.h的枚举类型。有很多,但是硬件摄像头并不是全部都支持的,要看摄像头支持哪种编码流,一般情况下,摄像头输出的流都是原始流,比如mjpg和YUYV422。
这里的输入流获取,最好使用YUYV422原始编码格式。
(2)创建编码器。
这里很简单,编码器顾名思义就是把码流转换成一种输出的码流。并提供编码过程。
默认目前大部分都使用h264,h265规则其实ffmpeg也是支持的,但是由于专利费的问题,价格过高,实际使用h265的设备生产环境目前还不多。我们就以h264为主。
ffmpeg的编码器支持很多。最常用的就是h264
AVCodec
(3)创建推流。
这里其实更简单,有些参数,可以从输入流中直接设置上。
当然,你也可以设置你想要的输出流的一些参数。
基本都是固定的代码。
AVStream
(4)绑定编码器和推流
这里,主要是声明一个emcode工具,告诉ffmpeg你需要把什么格式的编码流,转成什么格式的编码流。
代码也几乎是固定的。
struct
(5)不断获得原始帧,通过编码器将原始帧转化为AV_PIX_FMT_YUV420P编码,然后封装入推流的帧,把数据发送出去。
这里需要一个while循环。(这里你可以控制while循环的条件,实现自由控制流的上传和关闭)
这里要特别注意发出流的pts和dts,不能使用输入流的。因为输入流没有考虑网络延迟的情况,所以你在推流的时候,一定要自己重新计算。
if
以上代码大部分都是固定的。
理解后,你会发现其实ffmpeg的结构非常清晰。
最后放一个测试成功的代码,远端的推流服务器用的是srs。
以上,全部代码达成。