ffmpeg利用libav库把yuv视频流转换为TS串流

        今天到月末了,才发我这个月的第一篇文章,因为这个月前三周一直在看ffmpeg的libavcodec和libavformat两个库源码。实验室要做一个“小传大”的软件,就是android手机或平板电脑的屏幕包括操作等全映射到电脑或电视上去。这个首先想到的就是用TS串流来做,一来是符合标准规范,音视频同步方便;二来是接收端非常简单,普通能播放网络串流的播放器都可以胜任,大大降低开发难度。于是我就开始看ffmpeg的libav库,如下是我的小体会。

        ffmpeg库的框架非常漂亮,接口函数在几个主要的头文件中,如avformat.h,avcodec.h等,内部静态函数虽然调用层次非常纷繁复杂,但是命名规范,层次也非常清晰,看起来只要自己不乱,那是非常爽的,这个必须赞一下先哈。如果只是编解码的话,只要调用avcodec.h里面的接口函数就足够了,关于编码有两个结构体,AVCodecContext和AVCodec。AVCodecContext结构体里面的成员是编解码器的一些参数,codec_type,codec_id,width,height,bit_rate,time_base,pix_fmt这些,其中前两个在avcodec_alloc_context3(codec)函数初始化的时候自动赋值,其实也就是用的其参数AVCodec *codec里面设置的值;后面的几个参数要用户自己赋值。AVCodec结构体是由函数avcodec_find_encode(CODEC_ID)来初始化的,这一步通过用户传入的参数CODEC_ID,比如AV_CODEC_ID_MJPEG,不仅把type和id赋了值(这俩就是在AVCodecContext初始化时传入的值),而且把encode等这些函数指针也都赋了值,指向了相应的编解码函数。当做完两个结构体的初始化后,就可以编码了。但是yuv文件和编完码后的码流存在哪里?这就用到ffmpeg的AVFrame和AVPacket两个结构体。有时候也可以用AVPicture来存yuv裸数据,毕竟AVPicture只是AVFrame的子集,我就一直用AVFrame了。AVFrame结构体里面除了width和height以及linesize后,就是一个8个元素的指针数组:data[8],我不知道为啥要给8个,即使是BGRA格式也只用4个哈,这就不管了,现在用YUV420P只占用3个,即data[0]->Y,data[1]->U,data[2]->V。好了,初始化工作完成后,调用avcodec_encode_video2函数就妥了,然后直接从AVPacket结构体里取编成你设置好的格式的码流就行了。

        关键要说的不是上面的这些,因为我3个星期才看出这点玩意就太山寨了。我要把yuv编成ts流,当初不知道,直接上面的步骤只是把CODEC_ID设为AV_CODEC_ID_MPEG2TS,结果各种报错,当时我世界观就踏了,到处发贴问,但都石沉大海,木有人回应,不知道是大家都不做TS流编码还是怎么着。当我再回过头仔细看源码并且看TS的标准18030之后,才发现是怎么一回事。TS流就是个容器,可以包mpeg2或h264等ES流,所以如果要编码TS流就要经过两步,第一步是编码成mpeg2或h264,第二步封装成TS流,后者就是libavformat要干的事儿,这就引出了下面的结构体:AVFormatContext,AVOutputFormat和AVStream。AVFormatContext结构体是标准的始祖,太全了,包罗万象,用avformat_alloc_context()初始化,捎带把AVOutputFormat也初始化了,这个结构体里面的write_header,write_packet,interleave_packet这些函数太重要了,但也就是这里出现了大的问题,我后面说明。AVStream结构体就是保存各种流,如果有音频和视频那么就是两个流。

        方是时,正当我高兴的以为做出来了的时候,才发现,av_interleaved_write_frame()函数里一个参数是AVIOContext,就是这个结构体太坑了,给TA初始化的时候要添加文件名,也就是说必须是硬盘上的文件才可以,而我要把yuv做成TS流的目的就是要实时打成RTP包发出去,TA让我先存到硬盘上,再从硬盘上读取到内存中打包发送是怎么个意思??还怎么实时性?!我第一个想到的就是直接从AVIOContext结构体里面把那个指向编玩码的流指针找出来,这样就可以解决,但是又当我欣喜的时候,我才发现,这个AVIOContext里面的buffer指针指向的根本就不是封装好的TS流!!!我再一次进去扒代码,才发现,经过了层层的函数调用后,TA的过程我描述如下:

        编好的mpeg2或h264 ES流文件存在AVPacket *pkt中,pkt作为输入传入av_interleaved_write_frame()函数,在里面新实例了一个AVPacket *opkt的局部结构体,当调用mpegts_write_packet_internal()函数的时候,输入参数已经是opkt了,写上TS的头,写上opkt的部分长数据,写入文件,free掉buf指针,再写上TS body的数据,再写入部分opkt数据,写入文件,free掉buf指针,。。。不断循环操作,直到写完为止。这样,这个局部TS流文件指针就压根没有!!如果改库的话在AVIOContext结构体里面新建一个TS流指针,把上面的文件操作全部赋给这个指针,我最后也是可以得到的,但是改库的代价和不可预期的错误太大,我稚嫩的肩膀不能hold住。。。。但临近月末,啥都做不出来太没有工作量了,于是我换了一种简单的思路先凑和解决了,就是yuv编码成jpg然后直接udp传,接收端我用QT写了个界面,实时把接到的图片显示出来。虽然做出来了,但是以后音视频同步还是个问题,我还是认为应该用TS流来做,但具体怎么做我要歇几天以后靠灵感了~也期待大家给点灵感~~:-)


        转载请注明: 转自http://blog.csdn.net/littlethunder/article/details/9164929

原文地址:https://www.cnblogs.com/jiangu66/p/3155485.html