音视频封装格式转换器(支持avi格式转换),基于FFmpeg4.1实现(FFmpeg学习笔记二)

之前参照雷霄骅博士的最简单的基于FFMPEG的封装格式转换器(无编解码)的博客和FFmpeg官网的example,实现一个简单的封装格式转换器。但是后来我发现我想从mp4格式转换成avi格式的时候会报如下错误,如图:
在这里插入图片描述

  • 错误

H.264 bitstream malformed, no startcode found, use the video bitstream
filte

  • 原因:主要是因为使用了mp4中的h264编码,而h264有两种封装:
    一种是annexb模式,传统模式,有startcode,SPS和PPS是在ES中;另一种是mp4模式,一般mp4、mkv、avi会没有startcode,SPS和PPS以及其它信息被封装在container中,每一个frame前面是这个frame的长度,很多解码器只支持annexb这种模式,因此需要将mp4做转换;在ffmpeg中用h264_mp4toannexb_filter可以做转换;
  • 解决办法-参考ffmpeg 封装格式转换 MP4转AVI
    这篇文章的api都是旧版本的api,我们需要对其更新,FFmpeg在这部分api做了较大的变动。
  • 用到的API列在下面:

结构体:AVBSFContext;AVBitStreamFilter;
函数:av_bsf_get_by_name()//用于初始化AVBitStreamFilte;
av_bsf_alloc();//为AVBSFContext分配空间
av_bsf_init();//Prepare the filter for use, after all the parameters and options have been set.就是参数设置好以后在运行该函数。
av_bsf_free();//空间释放函数,该函数会释放掉其内部所有内存,在为
其结构体AVBSFContext设置参数后释放可能会导致程序中断,下面会讲到。

  • 使用如下
//初始化
AVBSFContext* v_ctx = NULL;
	const AVBitStreamFilter* v_filter = av_bsf_get_by_name("h264_mp4toannexb");
	if (!v_filter)
	{
		std::cout << "av_bsf_get_by_name failed! could not muxing the format of avi.etc" << std::endl;
	}
	ret=av_bsf_alloc(v_filter, &v_ctx);	
//设置参数
if (in_codecpar->codec_type == AVMEDIA_TYPE_VIDEO&& need_filter==true)
{
	avcodec_parameters_copy(p, in_codecpar);
	v_ctx->par_in =p;
	av_bsf_init(v_ctx);
}

在这里特殊强调一下,我们不需要使用avcodec_parameters_free(&p)
来释放p的内存,因为av_bsf_free会把与其关联的内存全部释放掉,如果使用了avcodec_parameters_free(&p)来释放内存会导致程序在最后中断掉。

/*参数释放
av_bsf_free(&v_ctx);

修改过后的版本为1.1版本,已经支持了各种格式之间相互转换,包括mkv,mov,mp4,flv,avi,wmv,ts,打印log会降低转换速度,如需提速请将一下代码注释掉:

if (frame_index%3000==0)
		{
			std::cout << "Write frames to output file:" << frame_index << std::endl;
		}

完整版1.1

/*
	@USER wangyu 
	@DATE 2020-08-06
	@VERSION 1.1
	@[email protected]
*/
#include<iostream>
extern "C"
{
#include<libavutil/timestamp.h>
#include<libavformat/avformat.h>
}
int main(int argc,char *argv[])
{
	//输出格式
	AVOutputFormat* ofmt = NULL;
	//输入于输出媒体句柄。
	AVFormatContext* ifmt_ctx = NULL, * ofmt_ctx = NULL;
	AVPacket* pkt = NULL;
	//输入输出文件名字。
	const char* in_filename, * out_filename;
	/*in_filename = "test.mp4";
	out_filename = "test1.mov";*/
	int ret = -1;
	//用于存储输入流的索引。
	int* stream_mapping = NULL;
	int stream_index = 0;
	int frame_index = 0;
	int videostream = -1;
	////新的api av_bsf_get_by_name(), av_bsf_alloc(), and av_bsf_init()
	bool need_filter = false;
	AVCodecParameters* p = avcodec_parameters_alloc();
	AVBSFContext* v_ctx = NULL;
	const AVBitStreamFilter* v_filter = av_bsf_get_by_name("h264_mp4toannexb");
	if (!v_filter)
	{
		std::cout << "av_bsf_get_by_name failed! could not muxing the format of avi.etc" << std::endl;
	}
	ret=av_bsf_alloc(v_filter, &v_ctx);	
	if (argc < 2)
	{
		std::cout << "请键入输入文件和输出文件参数重新执行该程序!" << std::endl;
		return -1;
	}
	in_filename = argv[1];
	//不想填写命令可直接在这里更改argv[2]为输出文件名
	//支持mkv,mp4,ts,wmv,avi,mov,flv。
	out_filename =argv[2];
	//打开源文件。
	ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0);
	if (ret < 0)
	{
		std::cout << "could not open the filename :" << in_filename << std::endl;
		goto end;
	}
	else {
		std::cout << "open the filename :" << in_filename << "success" << std::endl;
	}
	ret = avformat_find_stream_info(ifmt_ctx, 0);
	if (ret < 0) {
		std::cout << "avformat_find_stream_info filed!" << std::endl;
		goto end;
	}
	videostream = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
	//打印详细信息。
	av_dump_format(ifmt_ctx, 0, in_filename, 0);
	stream_mapping = new int[ifmt_ctx->nb_streams];
	//为输出的media handle申请空间
	avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
	if (!ofmt_ctx) {
		std::cout << "Could not create output context!" << std::endl;
		avformat_close_input(&ifmt_ctx);
		goto end;
	}
	ofmt = ofmt_ctx->oformat;
	//判断输出视频格式后缀,来判断是否需要使用过滤器。
	ret=strcmp((const char*)ofmt->name, "avi");
	if (ret == 0)
	{
		need_filter = true;
	}
	
	for (int i = 0; i < ifmt_ctx->nb_streams; i++)
	{
		//根据输入流创建输出流(Create output AVStream according to input AVStream)
		AVStream* in_stream = ifmt_ctx->streams[i];
		AVCodecParameters* in_codecpar = in_stream->codecpar;
		AVStream* out_stream = NULL;
		if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
			in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO)
		{
			//这里将丢弃的流值存为-1,方便后续读帧时丢弃。
			stream_mapping[i] = -1;
			continue;
		}
		//将要保留的媒体流进行索引存储。
		stream_mapping[i] = stream_index;
		stream_index++;
		//新建一个媒体流
		out_stream = avformat_new_stream(ofmt_ctx, NULL);
		if (!out_stream)
		{
			std::cout << "avformat_new_stream failed" << std::endl;
			avformat_close_input(&ifmt_ctx);
			goto end;
		}
		//参数拷贝
		ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
		if (ret < 0)
		{
			std::cout << "avcodec_parameters_copy filed!" << std::endl;
			goto end;
		}
		if (in_codecpar->codec_type == AVMEDIA_TYPE_VIDEO&& need_filter==true)
		{

			avcodec_parameters_copy(p, in_codecpar);
			v_ctx->par_in =p;
			av_bsf_init(v_ctx);
		}
		out_stream->codecpar->codec_tag = 0;
	}
	//输出一下格式------------------
	av_dump_format(ofmt_ctx, 0, out_filename, 1);
	//打开输出文件(Open output file)
	if (!(ofmt->flags & AVFMT_NOFILE)) {
		ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
		if (ret < 0) {
			std::cout << "could not open the outputfile:" << out_filename << std::endl;
			goto end;
		}
	}
	//写文件头(Write file header)
	ret = avformat_write_header(ofmt_ctx, NULL);
	if (ret < 0) {
		std::cout << "avformat_write_header failed!" << std::endl;
		goto end;
	}
	pkt = av_packet_alloc();
	while (1)
	{
		AVStream* in_stream=NULL;
		AVStream* out_stream=NULL;
		//获取pkt
		ret = av_read_frame(ifmt_ctx, pkt);
		if (ret < 0)
		{
			std::cout << "读取到文件末尾!" << std::endl;
			break;
		}
		in_stream = ifmt_ctx->streams[pkt->stream_index];
		if (stream_mapping[pkt->stream_index] < 0) {
			av_packet_unref(pkt);
			continue;
		}

		if (pkt->stream_index==videostream&& need_filter == true)
		{ 
			//Use av_bsf_send_packet() and av_bsf_receive_packet() 
			ret = av_bsf_send_packet(v_ctx, pkt);
			if (ret < 0)
			{
				std::cout << "av_bsf_send_packet failed!" << std::endl;
				av_packet_unref(pkt);
				continue;
			}
			av_packet_unref(pkt);
			ret = av_bsf_receive_packet(v_ctx, pkt);
			if (ret < 0)
			{
				std::cout << "av_bsf_receive_packet failed!" << std::endl;
				av_packet_unref(pkt);
				continue;
			}
		}
		pkt->stream_index = stream_mapping[pkt->stream_index];
		out_stream = ofmt_ctx->streams[pkt->stream_index];
		/* copy packet */
		//时间基数转换
		//转换PTS/DTS(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;
		//写入(Write)
		ret = av_interleaved_write_frame(ofmt_ctx, pkt);
		if (ret < 0) {
			std::cout << "av_interleaved_write_frame failed!" << std::endl;
			break;
		}
		frame_index++;
		if (frame_index%3000==0)
		{
			std::cout << "Write frames to output file:" << frame_index << std::endl;
		}
		//记得引用计数减1,否则会引起内存泄漏。
		av_packet_unref(pkt);	
	}
	//写文件尾(Write file trailer)
	av_write_trailer(ofmt_ctx);
	av_packet_free(&pkt);

end:
	av_bsf_free(&v_ctx);
	//不在需要释放掉,因为av_bsf_free(&v_ctx)已经在内部把该指针释放掉了。
	//avcodec_parameters_free(&p);
	delete[] stream_mapping;
	avformat_close_input(&ifmt_ctx);
	/* close output */
	if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
		avio_closep(&ofmt_ctx->pb);
	avformat_free_context(ofmt_ctx);
	if (ret < 0 && ret != AVERROR_EOF) {
		return 1;
	}
	return 0;
}

修改前版本1.0

/*
	@USER wangyu 
	@DATE 2020-08-06
	@VERSION 1.0
*/
#include<iostream>
extern "C"
{
#include<libavutil/timestamp.h>
#include<libavformat/avformat.h>
}
int main(int argc,char *argv[])
{
	//输出格式
	AVOutputFormat* ofmt = NULL;
	//输入于输出媒体句柄。
	AVFormatContext* ifmt_ctx = NULL, * ofmt_ctx = NULL;
	AVPacket* pkt = NULL;
	//输入输出文件名字。
	const char* in_filename, * out_filename;
	/*in_filename = "test.mp4";
	out_filename = "test1.mov";*/
	int ret = -1;
	//用于存储输入流的索引。
	int* stream_mapping = NULL;
	int stream_index = 0;
	int frame_index = 0;
	if (argc < 2)
	{
		std::cout << "请键入输入文件和输出文件参数重新执行该程序!" << std::endl;
		return -1;
	}
	in_filename = argv[1];
	out_filename = argv[2];
	//打开源文件。
	ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0);
	if (ret < 0)
	{
		std::cout << "could not open the filename :" << in_filename << std::endl;
		goto end;
	}
	else {
		std::cout << "open the filename :" << in_filename << "success" << std::endl;
	}
	ret = avformat_find_stream_info(ifmt_ctx, 0);
	if (ret < 0) {
		std::cout << "avformat_find_stream_info filed!" << std::endl;
		goto end;
	}
	//打印详细信息。
	av_dump_format(ifmt_ctx, 0, in_filename, 0);

	stream_mapping = new int[ifmt_ctx->nb_streams];
	//为输出的media handle申请空间
	avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
	if (!ofmt_ctx) {
		std::cout << "Could not create output context!" << std::endl;
		avformat_close_input(&ifmt_ctx);
		goto end;
	}
	ofmt = ofmt_ctx->oformat;
	for (int i = 0; i < ifmt_ctx->nb_streams; i++)
	{
		//根据输入流创建输出流(Create output AVStream according to input AVStream)
		AVStream* in_stream = ifmt_ctx->streams[i];
		AVCodecParameters* in_codecpar = in_stream->codecpar;
		AVStream* out_stream = NULL;
		if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
			in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO)
		{
			//这里将丢弃的流值存为-1,方便后续读帧时丢弃。
			stream_mapping[i] = -1;
			continue;
		}
		//将要保留的媒体流进行索引存储。
		stream_mapping[i] = stream_index;
		stream_index++;
		//新建一个媒体流
		out_stream = avformat_new_stream(ofmt_ctx, NULL);
		if (!out_stream)
		{
			std::cout << "avformat_new_stream failed" << std::endl;
			avformat_close_input(&ifmt_ctx);
			goto end;
		}
		//参数拷贝
		ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
		if (ret < 0)
		{
			std::cout << "avcodec_parameters_copy filed!" << std::endl;
			goto end;
		}
		out_stream->codecpar->codec_tag = 0;
	}
	//输出一下格式------------------
	av_dump_format(ofmt_ctx, 0, out_filename, 1);
	//打开输出文件(Open output file)
	if (!(ofmt->flags & AVFMT_NOFILE)) {
		ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
		if (ret < 0) {
			std::cout << "could not open the outputfile:" << out_filename << std::endl;
			goto end;
		}
	}
	//写文件头(Write file header)
	ret = avformat_write_header(ofmt_ctx, NULL);
	if (ret < 0) {
		std::cout << "avformat_write_header failed!" << std::endl;
		goto end;
	}
	pkt = av_packet_alloc();
	while (1)
	{
		AVStream* in_stream=NULL;
		AVStream* out_stream=NULL;
		//获取pkt
		ret = av_read_frame(ifmt_ctx, pkt);
		if (ret < 0)
		{
			std::cout << "读取到文件末尾!" << std::endl;
			break;
		}
		in_stream = ifmt_ctx->streams[pkt->stream_index];
		if (stream_mapping[pkt->stream_index] < 0) {
			av_packet_unref(pkt);
			continue;
		}
		pkt->stream_index = stream_mapping[pkt->stream_index];
		out_stream = ofmt_ctx->streams[pkt->stream_index];
		/* copy packet */
		//时间基数转换
		//转换PTS/DTS(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;
		//写入(Write)
		ret = av_interleaved_write_frame(ofmt_ctx, pkt);
		if (ret < 0) {
			std::cout << "av_interleaved_write_frame failed!" << std::endl;
			break;
		}
		frame_index++;
		if (frame_index%1000==0)
		{
			std::cout << "Write frames to output file:" << frame_index << std::endl;
		}
		//记得引用计数减1,否则会引起内存泄漏。
		av_packet_unref(pkt);
	}
	//写文件尾(Write file trailer)

end:
	delete[] stream_mapping;
	avformat_close_input(&ifmt_ctx);
	av_write_trailer(ofmt_ctx);
	av_packet_free(&pkt);
	avformat_close_input(&ifmt_ctx);
	if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
		avio_close(ofmt_ctx->pb);
	avformat_free_context(ofmt_ctx);
	if (ret < 0 && ret != AVERROR_EOF) {
		printf("Error occurred.\n");
		return -1;
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_40840000/article/details/107826566