ffmpeg:将YUV原始数据编码封装为mp4格式

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

PS:本人实验成功!但是:

VLC,QQ影音: 可以播放。

暴风,  Windows Media Player:无法播放。估计还需要改几个参数!


、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

转载地址:http://blog.csdn.net/cfqcfqcfqcfqcfq/article/details/68496213

    因为需要看了一些关于视频编解码相关的知识,并在学习过程中接触到了ffmepg这个强大的视音频处理工具,针对ffmpeg基础库进行了一个初步的学习,基本把其编解码流程熟悉,这里做一个总结。备以后复习用。本人使用的ffmpeg版本为3.1window版本的。

     

   一、ffmpeg库包在window上配置安装

         下载地址为:http://ffmpeg.org/  

         该网站中的FFMPEG分为3个版本:Static,Shared,Dev。介绍如下:

        前两个版本可以直接在命令行中使用,他们的区别在于:

        Static里面只有3个应用程序:ffmpeg.exe,ffplay.exe,ffprobe.exe,每个exe的体积都很大,相关的Dll已经被编译到exe里面去了。       

       Shared里面除了3个应用程序:ffmpeg.exe,ffplay.exe,ffprobe.exe之外,还有一些Dll,比如说avcodec-54.dll之类的。Shared里面的exe体积很小,他们在运行的时候,到相应的Dll中调用功能。

        Dev版本是用于开发的,里面包含了include(头文件xxx.h)和lib(库文件xxx.lib),这个版本不包含exe文件。

        在vs使用和一般的库包一样只要把Dev版本下的include文件添加到包含目录,lib文件夹下的lib文件添加到附加依赖性项中。不要忘记在环境变量中添加bin目录。bin文件夹位于Shared版本里面。


    二、视频编码封装

       本文中实现的一个小功能是把一个YUV原始视频数据(时间序列图像)经过h264编码为视频码流,然后在使用mp4封装格式封装。流程图如下:




   简单叙述一下ffmpeg编码流程:

          1、首先使用av_register_all()函数注册所有的编码器和复用器(理解为格式封装器)。该步骤必须放在所有ffmpeg代码前第一个执行

          2、avformat_alloc_output_context2():初始化包含有输出码流(AVStream)和解复用器(AVInputFormat)的AVFormatContext

          3、avio_open( )打开输出文件

          4、av_new_stream() 创建视频码流 该函数生成一个空AVstream 该结构存放编码后的视频码流 。视频码流被拆分为AVPacket新式保存在AVStream中。

         5、设置编码器信息,该步骤主要是为AVCodecContext(从AVStream->codec 获取指针)结构体设置一些参数,包括codec_id、codec_type、width、height、pix_fmt .....  根据编码器的不同,还要额外设置一些参数(如 h264 要设置qmax、qmin、qcompress参数才能正常使用h264编码)

         6、查找并打开编码器,根据前一步设置的编码器参数信息,来查找初始化一个编码其,并将其打开。用到函数为av_fine_encoder()和av_open2()。

         7、写头文件  avformat_write_header()。这一步主要是将封装格式的信息写入文件头部位置。

         8、编码帧。用到的函数 avcodec_encode_video2() 将AVFrame编码为AVPacket

         9、在写入文件之前 还需要做一件事情就是设置AVPacket一些信息。这些信息关乎最后封装格式能否被正确读取。后面回详细讲述该部分内容

        10、编码帧写入文件 av_write_frame()

        11、flush_encoder():输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket。

        12、av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。

       

下面贴上完整的程序代码:      

[cpp]  view plain  copy
  1. extern "C"  
  2. {  
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. }  
  8.   
  9. using namespace std;  
  10.   
  11. int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index);  
  12.   
  13. int main(int argc, char *argv[])  
  14. {  
  15.     AVFormatContext *pFormatCtx=nullptr;  
  16.     AVOutputFormat *fmt=nullptr;  
  17.     AVStream *video_st=nullptr;  
  18.     AVCodecContext *pCodecCtx=nullptr;  
  19.     AVCodec *pCodec=nullptr;  
  20.   
  21.     uint8_t *picture_buf=nullptr;  
  22.     AVFrame *picture=nullptr;  
  23.     int size;  
  24.   
  25.     //打开视频  
  26.     FILE *in_file = fopen("F://src01_480x272.yuv""rb");  
  27.     if(!in_file)  
  28.     {  
  29.         cout<<"can not open file!"<
  30.         return -1;  
  31.     }  
  32.   
  33.     int in_w=480,in_h=272;  
  34.     int framenum=50;  
  35.     const char* out_file="src01.mp4";  
  36.   
  37.     //[1] --注册所有ffmpeg组件  
  38.     avcodec_register_all();  
  39.     av_register_all();  
  40.     //[1]  
  41.   
  42.     //[2] --初始化AVFormatContext结构体,根据文件名获取到合适的封装格式  
  43.     avformat_alloc_output_context2(&pFormatCtx,NULL,NULL,out_file);  
  44.     fmt = pFormatCtx->oformat;  
  45.     //[2]  
  46.   
  47.     //[3] --打开文件  
  48.     if(avio_open(&pFormatCtx->pb,out_file,AVIO_FLAG_READ_WRITE))  
  49.     {  
  50.         cout<<"output file open fail!";  
  51.         goto end;  
  52.     }  
  53.     //[3]  
  54.   
  55.     //[4] --初始化视频码流  
  56.     video_st = avformat_new_stream(pFormatCtx,0);  
  57.     if(video_st==NULL)  
  58.     { printf("failed allocating output stram\n");  
  59.         goto end;  
  60.     }  
  61.     video_st->time_base.num = 1;  
  62.     video_st->time_base.den =25;  
  63.     //[4]  
  64.   
  65.     //[5] --编码器Context设置参数  
  66.     pCodecCtx = video_st->codec;  
  67.     pCodecCtx->codec_id = fmt->video_codec;  
  68.     pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;  
  69.     pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;  
  70.     pCodecCtx->width=in_w;  
  71.     pCodecCtx->height=in_h;  
  72.     pCodecCtx->time_base.num = 1;  
  73.     pCodecCtx->time_base.den = 25;  
  74.     pCodecCtx->bit_rate = 400000;  
  75.     pCodecCtx->gop_size = 12;  
  76.   
  77.     if(pCodecCtx->codec_id == AV_CODEC_ID_H264)  
  78.     {  
  79.         pCodecCtx->qmin = 10;  
  80.         pCodecCtx->qmax = 51;  
  81.         pCodecCtx->qcompress = 0.6;  
  82.     }  
  83.     if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)  
  84.         pCodecCtx->max_b_frames = 2;  
  85.     if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)  
  86.         pCodecCtx->mb_decision = 2;  
  87.     //[5]  
  88.   
  89.     //[6] --寻找编码器并打开编码器  
  90.     pCodec = avcodec_find_encoder(pCodecCtx->codec_id);  
  91.     if(!pCodec)  
  92.     {  
  93.         cout<<"no right encoder!"<
  94.         goto end;  
  95.     }  
  96.     if(avcodec_open2(pCodecCtx,pCodec,NULL)<0)  
  97.     {  
  98.         cout<<"open encoder fail!"<
  99.         goto end;  
  100.     }  
  101.     //[6]  
  102.   
  103.     //输出格式信息  
  104.     av_dump_format(pFormatCtx,0,out_file,1);  
  105.   
  106.     //初始化帧  
  107.     picture = av_frame_alloc();  
  108.     picture->width=pCodecCtx->width;  
  109.     picture->height=pCodecCtx->height;  
  110.     picture->format=pCodecCtx->pix_fmt;  
  111.     size = avpicture_get_size(pCodecCtx->pix_fmt,pCodecCtx->width,pCodecCtx->height);  
  112.     picture_buf = (uint8_t*)av_malloc(size);  
  113.     avpicture_fill((AVPicture*)picture,picture_buf,pCodecCtx->pix_fmt,pCodecCtx->width,pCodecCtx->height);  
  114.   
  115.     //[7] --写头文件  
  116.     avformat_write_header(pFormatCtx,NULL);  
  117.     //[7]  
  118.   
  119.     AVPacket pkt; //创建已编码帧  
  120.     int y_size = pCodecCtx->width*pCodecCtx->height;  
  121.     av_new_packet(&pkt,size*3);  
  122.   
  123.     //[8] --循环编码每一帧  
  124.     for(int i=0;i
  125.     {  
  126.         //读入YUV  
  127.         if(fread(picture_buf,1,y_size*3/2,in_file)<0)  
  128.         {  
  129.             cout<<"read file fail!"<
  130.             goto end;  
  131.         }  
  132.         else if(feof(in_file))  
  133.             break;  
  134.   
  135.         picture->data[0] = picture_buf; //亮度Y  
  136.         picture->data[1] = picture_buf+y_size; //U  
  137.         picture->data[2] = picture_buf+y_size*5/4; //V  
  138.         //AVFrame PTS  
  139.         picture->pts=i;  
  140.         int got_picture = 0;  
  141.   
  142.         //编码  
  143.         int ret = avcodec_encode_video2(pCodecCtx,&pkt,picture,&got_picture);  
  144.         if(ret<0)  
  145.         {  
  146.             cout<<"encoder fail!"<
  147.             goto end;  
  148.         }  
  149.   
  150.         if(got_picture == 1)  
  151.         {  
  152.             cout<<"encoder success!"<
  153.   
  154.             // parpare packet for muxing  
  155.             pkt.stream_index = video_st->index;  
  156.             av_packet_rescale_ts(&pkt, pCodecCtx->time_base, video_st->time_base);  
  157.             pkt.pos = -1;  
  158.             ret = av_interleaved_write_frame(pFormatCtx,&pkt);  
  159.             av_free_packet(&pkt);  
  160.         }  
  161.     }  
  162.     //[8]  
  163.   
  164.     //[9] --Flush encoder  
  165.     int ret = flush_encoder(pFormatCtx,0);  
  166.     if(ret < 0)  
  167.     {  
  168.         cout<<"flushing encoder failed!"<
  169.         goto end;  
  170.     }  
  171.     //[9]  
  172.   
  173.     //[10] --写文件尾  
  174.     av_write_trailer(pFormatCtx);  
  175.     //[10]  
  176.   
  177. end:  
  178.     //释放内存  
  179.     if(video_st)  
  180.     {  
  181.         avcodec_close(video_st->codec);  
  182.         av_free(picture);  
  183.         av_free(picture_buf);  
  184.     }  
  185.     if(pFormatCtx)  
  186.     {  
  187.         avio_close(pFormatCtx->pb);  
  188.         avformat_free_context(pFormatCtx);  
  189.     }  
  190.   
  191.     fclose(in_file);  
  192.   
  193.     return 0;  
  194. }  
  195.   
  196. int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index)  
  197. {  
  198.     int ret;  
  199.     int got_frame;  
  200.     AVPacket enc_pkt;  
  201.     if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &  
  202.           CODEC_CAP_DELAY))  
  203.         return 0;  
  204.     while (1) {  
  205.         printf("Flushing stream #%u encoder\n", stream_index);  
  206.         enc_pkt.data = NULL;  
  207.         enc_pkt.size = 0;  
  208.         av_init_packet(&enc_pkt);  
  209.         ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,  
  210.                                      NULL, &got_frame);  
  211.         av_frame_free(NULL);  
  212.         if (ret < 0)  
  213.             break;  
  214.         if (!got_frame)  
  215.         {ret=0;break;}  
  216.         cout<<"success encoder 1 frame"<
  217.   
  218.         // parpare packet for muxing  
  219.         enc_pkt.stream_index = stream_index;  
  220.         av_packet_rescale_ts(&enc_pkt,  
  221.                              fmt_ctx->streams[stream_index]->codec->time_base,  
  222.                              fmt_ctx->streams[stream_index]->time_base);  
  223.         ret = av_interleaved_write_frame(fmt_ctx, &enc_pkt);  
  224.         if (ret < 0)  
  225.             break;  
  226.     }  
  227.     return ret;  
  228. }  

  在步骤9中已说明要完成最后的格式封装(mux),需要对AVPacket的参数进行设置。具体设置项如下:

[cpp]  view plain  copy
  1. // parpare packet for muxing  
  2.        enc_pkt.stream_index = stream_index;  
  3.        av_packet_rescale_ts(&enc_pkt,  
  4.                             fmt_ctx->streams[stream_index]->codec->time_base,  
  5.                             fmt_ctx->streams[stream_index]->time_base);  
1、包括设置AVPacket所属的码流index,

2、将pts(显示时间戳)和dts(编码时间戳)和duration(nextpts - curpts)按照不同time_base(时间基准)进行转换。具体的pts、dts、time_base概念可以见参考文献一。至于转换原因可以参考参考文献二。



参考文献:

   1、pts dts

   2、time_base



你可能感兴趣的:(ffmpeg:将YUV原始数据编码封装为mp4格式)