编解码——我多次接触过的技术

距离上次写博客已经有一年半了,这一年半期间,比较忙的时候没办法写博客,闲下来的时候又不想写博客。足足两年前计划写的分析OpenCV中二维滤波的代码,以及泊松图像融合的实现,到现在都没动笔。之前我就说过,写博客是非常非时间费精力的。真的很羡慕那些能够产出高质量博客的人。

再过一周多,我就工作满五年了,兜兜转转换了好几个地方,杂七杂八做了很多事情。图像,计算机视觉,编解码,CUDA,OpenCL。让我自己都难以相信的是,我断断续续和编解码打过几次交道。

第一次接触编解码,是在本科大四做毕业设计,这是整整8年前的事情。后面研究生一年级,也持续看了些编解码相关的东西。包括MPEG2,MPEG4,H.264的论文和书籍。还记得有本毕厚杰写的有关H.264的书,我基本上翻了个遍。当时学习编解码,是考虑到项目中可能会用到,其实最后根本没用上。整个研究生阶段都在写MATLAB代码和推公式。不过,编解码的基本概念我一直都记得,直到现在。

第二次接触编解码,是在13年4月,整整4年前的事情。当时有个任务需要对视频进行切割。OpenCV里面的VideoCapture和VideoWriter可以实现这个功能。调用这两个类做视频切割,需要对视频每帧进行解码再编码。熟悉编解码的人都知道,切割视频其实只需要切割视频文件里面的packet,配上正确的文件头就行了。OpenCV无法显式地对packet进行操作,所以当时就学起了FFmpeg。不过,刚学一个月不到,刚刚适应了C接口的主要函数,任务取消了,╮(╯_╰)╭。

第三次接触编解码,是在15年9月,当时需要写视频文件,OpenCV的VideoCapture和VideoWriter实在是太简陋了。官方给出的opencv_ffmpegxxx.dll不能直接写H.264格式的视频,也不能处理音频。OpenCV中的VideoCapture和VideoWriter是方便图像和视觉的研发人员快速处理编解码问题的类,不是专业解决编解码问题的类。专业的问题需要专业的库。于是再次捡起FFmpeg,从再次认真学习官方的samples开始。

第三次和编解码的故事,足足持续了十四个月。

最早的计划,仅仅是用C++封装简单的音视频文件读写类AudioVideoReader和AudioVideoWriter,保证能够读写音频和视频。期间仔细阅读和借鉴了OpenCV的VideoCapture和VideoWriter的写法。

后面又有新的需求,比如需要调用DirectShow,需要进行RTSP和RTMP推流。好在,这些功能,在FFmpeg里面都有。我只需要不断完善自己的AudioVideoReader和AudioVideoWriter就可以了。

再接下来,出于对性能的考虑,需要采用硬件进行音视频编解码,就是调用Intel Quick Sync Video和NVDIA NVENC。当时主要是在window下进行开发,FFmpeg的库用的是官方给出的windows下的库,这些库不支持上述两种硬件编解码。当时经过一段时间的探索,发现在github上有牛人做了采用cygwin进行FFmpeg编译的脚本,能够支持各种第三方库。当时试了下,编译能够成功。但是调用Intel的硬件编码时各种小问题。记得我还在Intel官方论坛上搜到过有人提问,说为什么通过FFmpeg调用Intel的硬件对H.264文件进行解码,解出来的帧数不对。Intel的人回应,用我们自己的SDK自带的sample进行解码是对的,解码错误的锅要FFmpeg来背。好的,最后就要研究Intel的编解码接口了。自己修改了Intel的sample,重新封装成类,和自己原有的代码进行合并。Intel硬件编码还算好,硬件解码的sample里面各种trick,我自己也理解了为什么FFmpeg调用Intel的硬件解码解出来的帧数不对了,肯定是没有深刻理解Intel解码接口的含义。我仔细读了FFmpeg里面调用intel硬件解码的部分,处理细节和intel SDK的sample有出入。NVIDIA的硬件编码接口比较简单,就是搬搬代码的事。

编解码方面遇到的坑还有不少。上面提到的直接调用Intel和NVIDIA的接口做编解码,这只是做了编解码,文件的mux和demux还是得用FFmpeg进行。我用AVFormatContext创建了一个文件,加入一个AVStream,打开一个Intel的编码器,编码后得到的流封装成AVPacket喂给AVFormatContext进行mux,一切看起来很简单。但是我发现这种方法写出来的文件,在windows资源管理器或者属性中看不到视频文件的尺寸,时长和码率之类的信息。经过好几次的探索我才得知,这是因为文件中缺少SPSPPS。如果没有直接调用AVStream下面的AVCodecContext进行编码,需要往AVCodecContext::extradata写入SPSPPS!!!

还有一个让我印象深刻的坑,FFmpeg读出来的AVPacket不能直接交给intel进行解码,需要增加符合H.264 annex b规范的前导符号!!!

做编解码,使用FFmpeg,主要有两方面的收获吧。一方面熟悉FFmpeg的接口,另一方面锻炼写C++ wrapper的能力。

FFmpeg是一个开发者维护的项目,不少修改是比较激进的,不考虑兼容性。15年我做编解码的时候,FFmpeg还是2.8,现在已经3.2了。从2到3,库的源代码中有很多rewrite,接口方面也有大改。最典型的的是编解码接口的修改,接口完全变了,avcodec_send_packet,avcodec_receive_frame,avcodec_send_frame,avcodec_receive_packet。另外,AVStream中不再包含AVCodecContext,而是使用AVCodecParameters记录编解码器的信息,实现了流AVStream和编解码器AVCodecContext两者相分离。

因为FFmpeg是用C语言写的,在资源管理方面不太方便。平时惯用C++的我用C++对FFmpeg进行封装。所以有了AudioVideoReader和AudioVideoWriter,还有仿造AVFrame写了AudioVideoFrame,仿造AVStream写了AudioVideoStream。AudioVideoStream是一个基类,我自己写了两种继承类。一种继承类是完全利用FFmpeg自带编解码器进行操作的BuiltInAudioVideoStream,另一类是调用intel或者NVIDIA硬件编解码的CustomAudioVideoStream。工作期间发现了一个通过Qt调用FFmpeg制作音视频控件的编解码库QtAV,其针对FFmpeg的C++封装简直是登峰造极。

。。。

16年11月,我又和编解码说再见了。但是,因为自己博客上写了篇如何修改OpenCV中的ffmpeg库让OpenCV支持编码H.264格式的视频,还是有人问我编解码的问题。虽然我不敢说自己精通编解码,但是自己做过的东西都是有很深的印象的。如果我能帮上点忙,我愿意把自己知道的分享出来。

谁知道以后我还会不会再次捡起编解码呢。

你可能感兴趣的:(ffmpeg,视频编解码)