最简单的基于FFmpeg的封装格式处理:视音频复用器(muxer)

转载至: http://blog.csdn.net/leixiaohua1020/article/details/39802913

=====================================================

最简单的基于FFmpeg的封装格式处理系列文章列表:

最简单的基于FFmpeg的封装格式处理:视音频分离器简化版(demuxer-simple)

最简单的基于FFmpeg的封装格式处理:视音频分离器(demuxer)

最简单的基于FFmpeg的封装格式处理:视音频复用器(muxer)

=====================================================


打算记录一下基于FFmpeg的封装格式处理方面的例子。包括了视音频分离,复用,封装格式转换。这是第3篇。

本文记录一个基于FFmpeg的视音频复用器(Simplest FFmpeg muxer)。视音频复用器(Muxer)即是将视频压缩数据(例如H.264)和音频压缩数据(例如AAC)合并到一个封装格式数据(例如MKV)中去。如图所示。在这个过程中并不涉及到编码和解码。



 
本文记录的程序将一个H.264编码的视频码流文件和一个MP3编码的音频码流文件,合成为一个MP4封装格式的文件。

流程

程序的流程如下图所示。从流程图中可以看出,一共初始化了3个AVFormatContext,其中2个用于输入,1个用于输出。3个AVFormatContext初始化之后,通过avcodec_copy_context()函数可以将输入视频/音频的参数拷贝至输出视频/音频的AVCodecContext结构体。然后分别调用视频输入流和音频输入流的av_read_frame(),从视频输入流中取出视频的AVPacket,音频输入流中取出音频的AVPacket,分别将取出的AVPacket写入到输出文件中即可。其间用到了一个不太常见的函数av_compare_ts(),是比较时间戳用的。通过该函数可以决定该写入视频还是音频。


本文介绍的视音频复用器,输入的视频不一定是H.264裸流文件,音频也不一定是纯音频文件。可以选择两个封装过的视音频文件作为输入。程序会从视频输入文件中“挑”出视频流,音频输入文件中“挑”出音频流,再将“挑选”出来的视音频流复用起来。
PS1:对于某些封装格式(例如MP4/FLV/MKV等)中的H.264,需要用到名称为“h264_mp4toannexb”的bitstream filter。
PS2:对于某些封装格式(例如MP4/FLV/MKV等)中的AAC,需要用到名称为“aac_adtstoasc”的bitstream filter。


简单介绍一下流程中各个重要函数的意义:
avformat_open_input():打开输入文件。
avcodec_copy_context():赋值AVCodecContext的参数。
avformat_alloc_output_context2():初始化输出文件。
avio_open():打开输出文件。
avformat_write_header():写入文件头。
av_compare_ts():比较时间戳,决定写入视频还是写入音频。这个函数相对要少见一些。
av_read_frame():从输入文件读取一个AVPacket。
av_interleaved_write_frame():写入一个AVPacket到输出文件。
av_write_trailer():写入文件尾。


代码

下面贴上代码:
[cpp]  view plain copy
  1. /** 
  2.  * 最简单的基于FFmpeg的视音频复用器 
  3.  * Simplest FFmpeg Muxer 
  4.  * 
  5.  * 雷霄骅 Lei Xiaohua 
  6.  * leixiaohua1020@126.com 
  7.  * 中国传媒大学/数字电视技术 
  8.  * Communication University of China / Digital TV Technology 
  9.  * http://blog.csdn.net/leixiaohua1020 
  10.  * 
  11.  * 本程序可以将视频码流和音频码流打包到一种封装格式中。 
  12.  * 程序中将MP3编码的音频码流和H.264编码(MPEG2TS封装中)的视频码流打包成 
  13.  * MP4封装格式的文件。 
  14.  * 需要注意的是本程序并不改变视音频的编码格式。 
  15.  * 
  16.  * This software mux a video bitstream and a audio bitstream  
  17.  * together into a file. 
  18.  * In this example, it mux a H.264 bitstream (in MPEG2TS) and  
  19.  * a MP3 bitstream file together into MP4 format file. 
  20.  * 
  21.  */  
  22.   
  23. #include <stdio.h>  
  24.   
  25. extern "C"  
  26. {  
  27. #include "libavformat/avformat.h"  
  28. };  
  29. /* 
  30. FIX: H.264 in some container format (FLV, MP4, MKV etc.) need  
  31. "h264_mp4toannexb" bitstream filter (BSF) 
  32.   *Add SPS,PPS in front of IDR frame 
  33.   *Add start code ("0,0,0,1") in front of NALU 
  34. H.264 in some container (MPEG2TS) don't need this BSF. 
  35. */  
  36. //'1': Use H.264 Bitstream Filter   
  37. #define USE_H264BSF 0  
  38.   
  39. /* 
  40. FIX:AAC in some container format (FLV, MP4, MKV etc.) need  
  41. "aac_adtstoasc" bitstream filter (BSF) 
  42. */  
  43. //'1': Use AAC Bitstream Filter   
  44. #define USE_AACBSF 0  
  45.   
  46.   
  47.   
  48. int main(int argc, char* argv[])  
  49. {  
  50.     AVOutputFormat *ofmt = NULL;  
  51.     //输入对应一个AVFormatContext,输出对应一个AVFormatContext  
  52.     //(Input AVFormatContext and Output AVFormatContext)  
  53.     AVFormatContext *ifmt_ctx_v = NULL, *ifmt_ctx_a = NULL,*ofmt_ctx = NULL;  
  54.     AVPacket pkt;  
  55.     int ret, i;  
  56.       
  57.     char *in_filename_v = "cuc_ieschool.ts";//输入文件名(Input file URL)  
  58.     //char *in_filename_v = "cuc_ieschool.h264";  
  59.     //char *in_filename_a = "cuc_ieschool.mp3";  
  60.     //char *in_filename_a = "gowest.m4a";  
  61.     //char *in_filename_a = "gowest.aac";  
  62.     char *in_filename_a = "huoyuanjia.mp3";  
  63.   
  64.     char *out_filename = "cuc_ieschool.mp4";//输出文件名(Output file URL)  
  65.     av_register_all();  
  66.     //输入(Input)  
  67.     if ((ret = avformat_open_input(&ifmt_ctx_v, in_filename_v, 0, 0)) < 0) {  
  68.         printf( "Could not open input file.");  
  69.         goto end;  
  70.     }  
  71.     if ((ret = avformat_find_stream_info(ifmt_ctx_v, 0)) < 0) {  
  72.         printf( "Failed to retrieve input stream information");  
  73.         goto end;  
  74.     }  
  75.   
  76.     if ((ret = avformat_open_input(&ifmt_ctx_a, in_filename_a, 0, 0)) < 0) {  
  77.         printf( "Could not open input file.");  
  78.         goto end;  
  79.     }  
  80.     if ((ret = avformat_find_stream_info(ifmt_ctx_a, 0)) < 0) {  
  81.         printf( "Failed to retrieve input stream information");  
  82.         goto end;  
  83.     }  
  84.     printf("Input Information=====================\n");  
  85.     av_dump_format(ifmt_ctx_v, 0, in_filename_v, 0);  
  86.     av_dump_format(ifmt_ctx_a, 0, in_filename_a, 0);  
  87.     printf("======================================\n");  
  88.     //输出(Output)  
  89.     avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);  
  90.     if (!ofmt_ctx) {  
  91.         printf( "Could not create output context\n");  
  92.         ret = AVERROR_UNKNOWN;  
  93.         goto end;  
  94.     }  
  95.     ofmt = ofmt_ctx->oformat;  
  96.     int videoindex_v=-1,videoindex_out=-1;  
  97.     for (i = 0; i < ifmt_ctx_v->nb_streams; i++) {  
  98.         //根据输入流创建输出流(Create output AVStream according to input AVStream)  
  99.         if(ifmt_ctx_v->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){  
  100.         videoindex_v=i;  
  101.         AVStream *in_stream = ifmt_ctx_v->streams[i];  
  102.         AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);  
  103.         if (!out_stream) {  
  104.             printf( "Failed allocating output stream\n");  
  105.             ret = AVERROR_UNKNOWN;  
  106.             goto end;  
  107.         }  
  108.         videoindex_out=out_stream->index;  
  109.         //复制AVCodecContext的设置(Copy the settings of AVCodecContext)  
  110.         if (avcodec_copy_context(out_stream->codec, in_stream->codec) < 0) {  
  111.             printf( "Failed to copy context from input to output stream codec context\n");  
  112.             goto end;  
  113.         }  
  114.         out_stream->codec->codec_tag = 0;  
  115.         if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)  
  116.             out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;  
  117.         break;  
  118.         }  
  119.     }  
  120.   
  121.     int audioindex_a=-1,audioindex_out=-1;  
  122.     for (i = 0; i < ifmt_ctx_a->nb_streams; i++) {  
  123.         //根据输入流创建输出流(Create output AVStream according to input AVStream)  
  124.         if(ifmt_ctx_a->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO){  
  125.             audioindex_a=i;  
  126.             AVStream *in_stream = ifmt_ctx_a->streams[i];  
  127.             AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);  
  128.             if (!out_stream) {  
  129.                 printf( "Failed allocating output stream\n");  
  130.                 ret = AVERROR_UNKNOWN;  
  131.                 goto end;  
  132.             }  
  133.             audioindex_out=out_stream->index;  
  134.             //复制AVCodecContext的设置(Copy the settings of AVCodecContext)  
  135.             if (avcodec_copy_context(out_stream->codec, in_stream->codec) < 0) {  
  136.                 printf( "Failed to copy context from input to output stream codec context\n");  
  137.                 goto end;  
  138.             }  
  139.             out_stream->codec->codec_tag = 0;  
  140.             if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)  
  141.                 out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;  
  142.   
  143.             break;  
  144.         }  
  145.     }  
  146.   
  147.     //输出一下格式------------------  
  148.     printf("Output Information====================\n");  
  149.     av_dump_format(ofmt_ctx, 0, out_filename, 1);  
  150.     printf("======================================\n");  
  151.     //打开输出文件(Open output file)  
  152.     if (!(ofmt->flags & AVFMT_NOFILE)) {  
  153.         if (avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE) < 0) {  
  154.             printf( "Could not open output file '%s'", out_filename);  
  155.             goto end;  
  156.         }  
  157.     }  
  158.     //写文件头(Write file header)  
  159.     if (avformat_write_header(ofmt_ctx, NULL) < 0) {  
  160.         printf( "Error occurred when opening output file\n");  
  161.         goto end;  
  162.     }  
  163.     int frame_index=0;  
  164.     int64_t cur_pts_v=0,cur_pts_a=0;  
  165.   
  166.     //FIX  
  167. #if USE_H264BSF  
  168.     AVBitStreamFilterContext* h264bsfc =  av_bitstream_filter_init("h264_mp4toannexb");   
  169. #endif  
  170. #if USE_AACBSF  
  171.     AVBitStreamFilterContext* aacbsfc =  av_bitstream_filter_init("aac_adtstoasc");   
  172. #endif  
  173.   
  174.     while (1) {  
  175.         AVFormatContext *ifmt_ctx;  
  176.         int stream_index=0;  
  177.         AVStream *in_stream, *out_stream;  
  178.   
  179.   
  180.         //获取一个AVPacket(Get an AVPacket)  
  181.         if(av_compare_ts(cur_pts_v,ifmt_ctx_v->streams[videoindex_v]->time_base,cur_pts_a,ifmt_ctx_a->streams[audioindex_a]->time_base) <= 0){  
  182.             ifmt_ctx=ifmt_ctx_v;  
  183.             stream_index=videoindex_out;  
  184.   
  185.             if(av_read_frame(ifmt_ctx, &pkt) >= 0){  
  186.                 do{  
  187.                     if(pkt.stream_index==videoindex_v){  
  188.                         cur_pts_v=pkt.pts;  
  189.                         break;  
  190.                     }  
  191.                 }while(av_read_frame(ifmt_ctx, &pkt) >= 0);  
  192.             }else{  
  193.                 break;  
  194.             }  
  195.         }else{  
  196.             ifmt_ctx=ifmt_ctx_a;  
  197.             stream_index=audioindex_out;  
  198.             if(av_read_frame(ifmt_ctx, &pkt) >= 0){  
  199.                 do{  
  200.                     if(pkt.stream_index==audioindex_a){  
  201.                         cur_pts_a=pkt.pts;  
  202.                         break;  
  203.                     }  
  204.                 }while(av_read_frame(ifmt_ctx, &pkt) >= 0);  
  205.             }else{  
  206.                 break;  
  207.             }  
  208.   
  209.         }  
  210.   
  211.         in_stream  = ifmt_ctx->streams[pkt.stream_index];  
  212.         out_stream = ofmt_ctx->streams[stream_index];  
  213. //FIX  
  214. #if USE_H264BSF  
  215.         av_bitstream_filter_filter(h264bsfc, in_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);  
  216. #endif  
  217. #if USE_AACBSF  
  218.         av_bitstream_filter_filter(aacbsfc, out_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);  
  219. #endif  
  220.         //FIX:No PTS (Example: Raw H.264)  
  221.         //Simple Write PTS  
  222.         if(pkt.pts==AV_NOPTS_VALUE){  
  223.             //Write PTS  
  224.             AVRational time_base1=in_stream->time_base;  
  225.             //Duration between 2 frames (us)  
  226.             int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);  
  227.             //Parameters  
  228.             pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);  
  229.             pkt.dts=pkt.pts;  
  230.             pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);  
  231.             frame_index++;  
  232.         }  
  233.         /* copy packet */  
  234.         //转换PTS/DTS(Convert PTS/DTS)  
  235.             pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));  
  236.             pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));  
  237.         pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);  
  238.         pkt.pos = -1;  
  239.         pkt.stream_index=stream_index;  
  240.   
  241.         printf("Write 1 Packet. size:%5d\tpts:%8d\n",pkt.size,pkt.pts);  
  242.         //写入(Write)  
  243.         if (av_interleaved_write_frame(ofmt_ctx, &pkt) < 0) {  
  244.             printf( "Error muxing packet\n");  
  245.             break;  
  246.         }  
  247.         av_free_packet(&pkt);  
  248.   
  249.     }  
  250.     //写文件尾(Write file trailer)  
  251.     av_write_trailer(ofmt_ctx);  
  252.   
  253. #if USE_H264BSF  
  254.     av_bitstream_filter_close(h264bsfc);  
  255. #endif  
  256. #if USE_AACBSF  
  257.     av_bitstream_filter_close(aacbsfc);  
  258. #endif  
  259.   
  260. end:  
  261.     avformat_close_input(&ifmt_ctx_v);  
  262.     avformat_close_input(&ifmt_ctx_a);  
  263.     /* close output */  
  264.     if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))  
  265.         avio_close(ofmt_ctx->pb);  
  266.     avformat_free_context(ofmt_ctx);  
  267.     if (ret < 0 && ret != AVERROR_EOF) {  
  268.         printf( "Error occurred.\n");  
  269.         return -1;  
  270.     }  
  271.     return 0;  
  272. }  





结果

输入文件为:
视频:cuc_ieschool.ts

音频:huoyuanjia.mp3


输出文件为:
cuc_ieschool.mp4
输出的文件视频为“cuc_ieschool”,配合“霍元甲”的音频。


下载

SourceForge项目主页:

https://sourceforge.net/projects/simplestffmpegformat/


CSDN下载

http://download.csdn.net/detail/leixiaohua1020/8005317


1.1版更新======================

修复了以下问题:
(1)Release版本下的运行问题
(2)simplest_ffmpeg_muxer分装H.264裸流的时候丢失声音的错误

关于simplest_ffmpeg_muxer分装H.264裸流的时候丢失声音的问题目前已经解决。根源在于H.264裸流没有PTS,因此必须手动写入PTS。写入PTS的代码在在旧版本中已经包含:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //FIX:No PTS  
  2. //Simple Write PTS  
  3. if(pkt.pts==AV_NOPTS_VALUE){  
  4.     //Write PTS  
  5.     AVRational time_base1=in_stream->time_base;  
  6.     //Duration between 2 frames (us)  
  7.     int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);  
  8.     //Parameters  
  9.     pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);  
  10.     pkt.dts=pkt.pts;  
  11.     pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);  
  12.     frame_index++;  
  13. }  
但是旧版本中这段代码的位置放错了,应该放在av_read_frame()之后,cur_pts_a/cur_pts_v赋值之前。换句话说,也就说要把这段代码“前移”。修改后的循环处理AVPacket的源代码如下所示。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. while (1) {  
  2.         AVFormatContext *ifmt_ctx;  
  3.         int stream_index=0;  
  4.         AVStream *in_stream, *out_stream;  
  5.   
  6.         //获取一个AVPacket(Get an AVPacket)  
  7.         if(av_compare_ts(cur_pts_v,ifmt_ctx_v->streams[videoindex_v]->time_base,cur_pts_a,ifmt_ctx_a->streams[audioindex_a]->time_base) <= 0){  
  8.             ifmt_ctx=ifmt_ctx_v;  
  9.             stream_index=videoindex_out;  
  10.   
  11.             if(av_read_frame(ifmt_ctx, &pkt) >= 0){  
  12.                 do{  
  13.                     in_stream  = ifmt_ctx->streams[pkt.stream_index];  
  14.                     out_stream = ofmt_ctx->streams[stream_index];  
  15.   
  16.                     if(pkt.stream_index==videoindex_v){  
  17.                         //FIX:No PTS (Example: Raw H.264)  
  18.                         //Simple Write PTS  
  19.                         if(pkt.pts==AV_NOPTS_VALUE){  
  20.                             //Write PTS  
  21.                             AVRational time_base1=in_stream->time_base;  
  22.                             //Duration between 2 frames (us)  
  23.                             int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);  
  24.                             //Parameters  
  25.                             pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);  
  26.                             pkt.dts=pkt.pts;  
  27.                             pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);  
  28.                             frame_index++;  
  29.                         }  
  30.   
  31.                         cur_pts_v=pkt.pts;  
  32.                         break;  
  33.                     }  
  34.                 }while(av_read_frame(ifmt_ctx, &pkt) >= 0);  
  35.             }else{  
  36.                 break;  
  37.             }  
  38.         }else{  
  39.             ifmt_ctx=ifmt_ctx_a;  
  40.             stream_index=audioindex_out;  
  41.             if(av_read_frame(ifmt_ctx, &pkt) >= 0){  
  42.                 do{  
  43.                     in_stream  = ifmt_ctx->streams[pkt.stream_index];  
  44.                     out_stream = ofmt_ctx->streams[stream_index];  
  45.   
  46.                     if(pkt.stream_index==audioindex_a){  
  47.   
  48.                         //FIX:No PTS  
  49.                         //Simple Write PTS  
  50.                         if(pkt.pts==AV_NOPTS_VALUE){  
  51.                             //Write PTS  
  52.                             AVRational time_base1=in_stream->time_base;  
  53.                             //Duration between 2 frames (us)  
  54.                             int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);  
  55.                             //Parameters  
  56.                             pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);  
  57.                             pkt.dts=pkt.pts;  
  58.                             pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);  
  59.                             frame_index++;  
  60.                         }  
  61.                         cur_pts_a=pkt.pts;  
  62.   
  63.                         break;  
  64.                     }  
  65.                 }while(av_read_frame(ifmt_ctx, &pkt) >= 0);  
  66.             }else{  
  67.                 break;  
  68.             }  
  69.   
  70.         }  
  71.   
  72.   
  73.         //FIX:Bitstream Filter  
  74. #if USE_H264BSF  
  75.         av_bitstream_filter_filter(h264bsfc, in_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);  
  76. #endif  
  77. #if USE_AACBSF  
  78.         av_bitstream_filter_filter(aacbsfc, out_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);  
  79. #endif  
  80.   
  81.   
  82.   
  83.         /* copy packet */  
  84.         //转换PTS/DTS(Convert PTS/DTS)  
  85.         pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));  
  86.         pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));  
  87.         pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);  
  88.         pkt.pos = -1;  
  89.         pkt.stream_index=stream_index;  
  90.   
  91.         printf("Write 1 Packet. size:%5d\tpts:%8d\n",pkt.size,pkt.pts);  
  92.         //写入(Write)  
  93.         if (av_interleaved_write_frame(ofmt_ctx, &pkt) < 0) {  
  94.             printf( "Error muxing packet\n");  
  95.             break;  
  96.         }  
  97.         av_free_packet(&pkt);  
  98.   
  99.     }  

CSDN下载

http://download.csdn.net/detail/leixiaohua1020/8284309

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值