FFMpeg example:封装和解封装demux

前言:

本文是下面blog的笔记并增加了步骤解析和注释. 
muxer和demux原理和使用的函数一样,audio,video是一样的,所以4种情况分析一种即可.
最简单的基于FFmpeg的封装格式处理:视音频复用器(muxer)_雷霄骅(leixiaohua1020)的专栏-CSDN博客_muxer

 正文

上图是raw流,压缩,文件封装,流媒体封装的流程

原理

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


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

流程图

  • 1: 打开输入文件和codec
    avformat_open_input():,avformat_find_stream_info
  • 2 : 拷贝new context( avcodec_copy_context)
  • 3 : 打开输出文件 avio_open-->avformat_write_header
  • 4:av_compare_ts 音视频分离
  • 5:  写pts :
    在编码之前写pts,否则编码不会产生pts
  • 6: av_bitstream_filter_filter(h264bsfc...)
    使用bitstream filter add h264 header 0x00 00 00 01
  • 7 Convert PTS/DTS //转不同时间基对应的pts 和响应的duration
  • 8 : av_interleaved write_frame 向文件写一帧

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

代码

int main(int argc, char* argv[])
{
	const char *in_filename_v = "cuc_ieschool.h264";
	const char *in_filename_a = "huoyuanjia.mp3";
	const char *out_filename = "cuc_ieschool.mp4";//Output file URL

	//Input
	avformat_open_input(&ifmt_ctx_v, in_filename_v, 0, 0));
    avformat_find_stream_info(ifmt_ctx_v, 0));
    avformat_open_input(&ifmt_ctx_a, in_filename_a, 0, 0));
    avformat_find_stream_info(ifmt_ctx_a, 0));
	//Output    //1 avformat_new_stream & avcodec_copy_context
	avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
	ofmt = ofmt_ctx->oformat;
 
	for (i = 0; i < ifmt_ctx_v->nb_streams; i++) {
		//Create output AVStream according to input AVStream
		if(ifmt_ctx_v->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
		    AVStream *in_stream = ifmt_ctx_v->streams[i];
    		AVStream *out_stream = avformat_new_stream(ofmt_ctx,in_stream->codec->codec);
    		//Copy the settings of AVCodecContext
		    avcodec_copy_context(out_stream->codec, in_stream->codec);
            ......
        	break;
		}
	}
    //audio is the same, skip 
    ......
    printf("==========Output Information==========\n");
	//Open output file
	avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE) ;
	//Write file header
	avformat_write_header(ofmt_ctx, NULL);
	//FIX
#if USE_H264BSF
	AVBitStreamFilterContext* h264bsfc =  av_bitstream_filter_init("h264_mp4toannexb"); 
#endif
#if USE_AACBSF
	AVBitStreamFilterContext* aacbsfc =  av_bitstream_filter_init("aac_adtstoasc"); 
#endif
 
	while (1) {
		AVFormatContext *ifmt_ctx;
		int stream_index=0;
		AVStream *in_stream, *out_stream;
		//Get an AVPacket //2 av_compare_ts比较音视频pts,大于0表示视频帧在前,音频需要连续编码。小于0表示,音频帧在前,应该至少编码一帧视频
		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){
			ifmt_ctx=ifmt_ctx_v;
			stream_index=videoindex_out;
 
			if(av_read_frame(ifmt_ctx, &pkt) >= 0){
				do{
					in_stream  = ifmt_ctx->streams[pkt.stream_index];
					out_stream = ofmt_ctx->streams[stream_index];
 
					if(pkt.stream_index==videoindex_v){
						//FIX:No PTS (Example: Raw H.264)
						//Simple Write PTS
						if(pkt.pts==AV_NOPTS_VALUE){
							//Write PTS
							AVRational time_base1=in_stream->time_base;
							//Duration between 2 frames (us)//按ffmpeg中的1秒(即90000)来计算每帧的间隔;90000 / 25 = 3600(ffmpeg)
							int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);
							//Parameters//计算一桢在整个视频中的时间位置timestamp(秒) = pts * av_q2d(st->time_base);
							pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
							pkt.dts=pkt.pts;
							pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
							frame_index++;
						}
 
						cur_pts_v=pkt.pts;
                        //注意:读取一帧之后break
						break;
					}
				}while(av_read_frame(ifmt_ctx, &pkt) >= 0);
			}
            else { //skip audio
                    ...
                }
		}
		//3 FIX:Bitstream Filter
#if USE_H264BSF
		av_bitstream_filter_filter(h264bsfc, in_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
#endif
#if USE_AACBSF
		av_bitstream_filter_filter(aacbsfc, out_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
#endif 
		//Convert PTS/DTS //转不同时间基对应的pts
		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));
		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));
		pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
		pkt.pos = -1;
		pkt.stream_index=stream_index;
		//Write
		av_interleaved_write_frame(ofmt_ctx, &pkt);
		av_free_packet(&pkt);
	}
	//Write file trailer
	av_write_trailer(ofmt_ctx);
 ....}
  • 1:打开输入文件和codec
    avformat_open_input(),avformat_find_stream_info...
  • 2 : 拷贝new context( avcodec_copy_context)
    1) 根据codec codec_type 分流audio 和video .
    2) 创建new stream
    3) 拷贝context到new stream的context (avcodec_copy_context).
    audio,video不同stream对应的context不同,
    对应上面流程图的acodec_copy_context-->AVCodecContext部分.
    avcodec_copy_context作用是复制AVCodecContext的设置(Copy the settings of AVCodecContext)
    常用于codec设置拷贝avcodec_copy_context(out_stream->codec, in_stream->codec)
    audio,video原理一样
  • 3 : 打开输出文件 avio_open-->avformat_write_header
  • 4: av_compare_ts比较音视频pts,大于0表示视频帧在前,音频需要连续编码。小于0表示,音频帧在前,应该至少编码一帧视频.
    video: av_read_frame-->Get AVPacket
    输入的视频不一定是H.264裸流文件,可以选择封装过的视音频文件作为输入。
    av_read_frame可以解封装.
    读取一帧之后会break,在外层循环读下一帧
    audio,video原理一样
  • 5:  写pts :
    在编码之前写pts,否则编码不会产生pts
//FIX:No PTS (Example: Raw H.264)
//Simple Write PTS
if(pkt.pts==AV_NOPTS_VALUE){
    //Write PTS
    AVRational time_base1=in_stream->time_base;
    //Duration between 2 frames (us)//按ffmpeg中的1秒(即90000)来计算每帧的间隔;90000 / 25 = 3600(ffmpeg)
    int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);
    //Parameters//计算一桢在整个视频中的时间位置timestamp(秒) = pts * av_q2d(st->time_base);
pkt.pts=(double)
(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
    pkt.dts=pkt.pts;
    pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
    frame_index++;
}
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));
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));
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);

        时间基转换详见:https://blog.csdn.net/fdsafwagdagadg6576/article/details/122844785
        3.6 转封装过程中的时间基转换

  • 8 : av_interleaved write_frame 向文件写一帧

小结: 对于文件格式的转换,业务逻辑是使用封装好的函数读写,不需要自己根据协议实现文件格式。比如:调用avio_open,av_interleaved_write_frame,实现文件格式转换。
自己只需做时间戳转换和bsf 添加nalu. 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值