FFmpeg学习:复用器的使用(读取MP3和h264文件输出mp4文件)

复用器

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

流程

程序的流程如下图所示。从流程图中可以看出,一共初始化了3个AVFormatContext,其中2个用于输入,1个用于输出。3个AVFormatContext初始化之后,通过avcodec_copy_context()函数可以将输入视频/音频的参数拷贝至输出视频/音频的AVCodecContext结构体。然后分别调用视频输入流和音频输入流的av_read_frame(),从视频输入流中取出视频的AVPacket,音频输入流中取出音频的AVPacket,分别将取出的AVPacket写入到输出文件中即可。其间用到了一个不太常见的函数av_compare_ts(),是比较时间戳用的。通过该函数可以决定该写入视频还是音频。
image
本文介绍的视音频复用器,输入的视频不一定是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的参数。
(新版本avcodec_copy_context被avcodec_parameters_to_context和avcodec_parameters_from_context所替代)
avformat_alloc_output_context2():初始化输出文件。
avio_open():打开输出文件。
avformat_write_header():写入文件头。
av_compare_ts():比较时间戳,决定写入视频还是写入音频。这个函数相对要少见一些。
av_read_frame():从输入文件读取一个AVPacket。
av_interleaved_write_frame():写入一个AVPacket到输出文件。
av_write_trailer():写入文件尾。

代码

点击查看代码

int main()
{
	AVOutputFormat* OFormat = NULL;
	AVFormatContext* pvFormatCtx = NULL;//保存视频数据
	AVFormatContext* paFormatCtx = NULL;//保存音频数据
	AVFormatContext* poFormatCtx = NULL;//输出数据上下文
	int videoindex = -1, audioindex = -1;
	int videoindex_out = -1, audioindex_out = -1;
	int frame_index = 0;
	int64_t cur_pts_v = 0, cur_pts_a = 0;
	const char* out_filename = "shuchu.mp4";//输出文件
	const char* audio_filename = "audio.mp3";//输出文件
	const char* video_filename = "video.h264";//输出文件
	AVPacket* pkt;
	int ret, i;


	pvFormatCtx = avformat_alloc_context();
	paFormatCtx = avformat_alloc_context();
	poFormatCtx = avformat_alloc_context();
	//设备初始化
	avdevice_register_all();
	//打开摄像头——视频流	
	AVInputFormat* ifmt = av_find_input_format("dshow");
	AVDictionary* opt_v = NULL;
	av_dict_set(&opt_v, "rtbufsize", "10*1280*720", 0);//设置循环缓冲区大小
	av_dict_set(&opt_v, "video_size", "1280x720", 0);
	av_dict_set(&opt_v, "framerate", "30", 0);
	av_dict_set(&opt_v, "vcodec", "libx264", 0);//指定视频编码格式
	if (avformat_open_input(&pvFormatCtx, video_filename, NULL, NULL) != 0) {//"video=Integrated Camera"
		printf("Couldn't open video input stream.\n");
		return -1;
	}
	if (avformat_find_stream_info(pvFormatCtx, NULL) < 0)
	{
		printf("Couldn't find stream information.\n");
		return -1;
	}
	//打开麦克风——音频流
	AVDictionary* opt_a = NULL;
	av_dict_set(&opt_a, "acodec", "libfdk_aac",0);//指定音频编码格式
	const char* audio_name = u8"audio=麦克风 (2- Realtek(R) Audio)";
	if (avformat_open_input(&paFormatCtx, audio_filename, NULL, NULL) != 0) {
		printf("Couldn't open audio input stream.\n");
		return -1;
	}
	if (avformat_find_stream_info(paFormatCtx, NULL) < 0)
	{
		printf("Couldn't find stream information.\n");
		return -1;
	}
	
	//开始输出
	avformat_alloc_output_context2(&poFormatCtx, NULL, NULL, out_filename);//初始化输出文件
	if (!poFormatCtx) {
		printf("Could not create output context\n");
		return -1;
	}
	OFormat = poFormatCtx->oformat;

	//给输出添加视频流
	for (i = 0; i < pvFormatCtx->nb_streams; i++) {
		//Create output AVStream according to input AVStream
		if (pvFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
			/*
			* 步骤:
			* 1、首先根据视频流创建新的输出流(要添加初始化编码器)
			* 2、将原视频流的编码器上下文 复制 给输出流
			*/
			AVStream* in_stream = pvFormatCtx->streams[i];
			AVCodec* in_codec = avcodec_find_decoder(in_stream->codecpar->codec_id);
			AVStream* out_stream = avformat_new_stream(poFormatCtx, in_codec);
			videoindex = i;
			if (!out_stream) {
				printf("Failed allocating output stream\n");
				return -1;
			}
			videoindex_out = out_stream->index;
			//Copy the settings of AVCodecContext
			AVCodecContext* codec_ctx = avcodec_alloc_context3(in_codec);
			ret = avcodec_parameters_to_context(codec_ctx,in_stream->codecpar);
			if (ret < 0) {
				printf("Failed to copy in_stream codecpar to codec context\n");
				return -1;
			}
			codec_ctx->codec_tag = 0;
			if (poFormatCtx->oformat->flags & AVFMT_GLOBALHEADER)
				codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

			ret = avcodec_parameters_from_context(out_stream->codecpar, codec_ctx);
			if (ret < 0) {
				printf("Failed to copy codec context to out_stream codecpar context\n");
				return -1;
			}
		}
	}

	//给输出添加音频流
	for (i = 0; i < paFormatCtx->nb_streams; i++) {
		//Create output AVStream according to input AVStream
		if (paFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
			AVStream* in_stream = paFormatCtx->streams[i];
			AVCodec* in_codec = avcodec_find_decoder(in_stream->codecpar->codec_id);
			AVStream* out_stream = avformat_new_stream(poFormatCtx, in_codec);
			audioindex = i;
			if (!out_stream) {
				printf("Failed allocating output stream\n");
				return -1;
			}

			audioindex_out = out_stream->index;
			//Copy the settings of AVCodecContext
			AVCodecContext* codec_ctx = avcodec_alloc_context3(in_codec);
			ret = avcodec_parameters_to_context(codec_ctx, in_stream->codecpar);
			if (ret < 0) {
				printf("Failed to copy in_stream codecpar to codec context\n");
				return -1;
			}
			codec_ctx->codec_tag = 0;
			if (poFormatCtx->oformat->flags & AVFMT_GLOBALHEADER)
				codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

			ret = avcodec_parameters_from_context(out_stream->codecpar, codec_ctx);
			if (ret < 0) {
				printf("Failed to copy codec context to out_stream codecpar context\n");
				return -1;
			}
		}
	}
	printf("==========Output Information==========\n");
	av_dump_format(poFormatCtx, 0, out_filename, 1);
	printf("======================================\n");
	//Open output file
	if (!(OFormat->flags & AVFMT_NOFILE)) {
		if (avio_open(&poFormatCtx->pb, out_filename, AVIO_FLAG_WRITE) < 0) {
			printf("Could not open output file '%s'", out_filename);
			return -1;
		}
	}
	//Write file header
	if (avformat_write_header(poFormatCtx, NULL) < 0) {
		printf("Error occurred when opening output file\n");
		return -1;
	}

	//开始写文件
	pkt = av_packet_alloc();
	while (1) {
		AVFormatContext* ifmt_ctx;
		int stream_index = 0;
		AVStream* in_stream, * out_stream;

		//Get an AVPacket
		if (av_compare_ts(cur_pts_v, pvFormatCtx->streams[videoindex]->time_base, cur_pts_a, paFormatCtx->streams[audioindex]->time_base) <= 0) {
			ifmt_ctx = pvFormatCtx;
			stream_index = videoindex_out;

			if (av_read_frame(ifmt_ctx, pkt) >= 0) {
				do {
					in_stream = ifmt_ctx->streams[pkt->stream_index];
					out_stream = poFormatCtx->streams[stream_index];

					if (pkt->stream_index == videoindex) {
						//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)
							int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(in_stream->r_frame_rate);
							//Parameters
							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;
					}
				} while (av_read_frame(ifmt_ctx, pkt) >= 0);
			}
			else {
				break;
			}
		}
		else {
			ifmt_ctx = paFormatCtx;
			stream_index = audioindex_out;
			if (av_read_frame(ifmt_ctx, pkt) >= 0) {
				do {
					in_stream = ifmt_ctx->streams[pkt->stream_index];
					out_stream = poFormatCtx->streams[stream_index];

					if (pkt->stream_index == audioindex) {

						//FIX:No PTS
						//Simple Write PTS
						if (pkt->pts == AV_NOPTS_VALUE) {
							//Write PTS
							AVRational time_base1 = in_stream->time_base;
							//Duration between 2 frames (us)
							int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(in_stream->r_frame_rate);
							//Parameters
							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_a = pkt->pts;

						break;
					}
				} while (av_read_frame(ifmt_ctx, pkt) >= 0);
			}
			else {
				break;
			}

		}
		//Convert PTS/DTS
		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;

		printf("Write 1 Packet. size:%5d\tpts:%lld\n", pkt->size, pkt->pts);
		//Write
		if (av_interleaved_write_frame(poFormatCtx, pkt) < 0) {
			printf("Error muxing packet\n");
			break;
		}
	}
	av_packet_free(&pkt);
	//Write file trailer
	av_write_trailer(poFormatCtx);

	avformat_close_input(&pvFormatCtx);
	avformat_close_input(&paFormatCtx);
	/* close output */
	if (poFormatCtx && !(OFormat->flags & AVFMT_NOFILE))
		avio_close(poFormatCtx->pb);
	avformat_free_context(poFormatCtx);
	if (ret < 0 && ret != AVERROR_EOF) {
		printf("Error occurred.\n");
		return -1;
	}

	//avformat_close_input(&poFormatCtx);//关闭之后就不用free了
	//avformat_close_input(&pvFormatCtx);//关闭之后就不用free了
	//avformat_close_input(&paFormatCtx);//关闭之后就不用free了
	return 0;
}


注意事项:

  • 含有中文字符要采用utf8编码
  • 新版本FFmpeg:avcodec_copy_contextavcodec_parameters_to_context和avcodec_parameters_from_context所替代

参考大神

雷神——复用器

posted @ 2022-07-19 17:02  小超不挑食  阅读(1266)  评论(0编辑  收藏  举报