ffmpeg从内存中读取数据(或者输出到内存)及其注意事项

本文借鉴与雷神的博客:https://blog.csdn.net/leixiaohua1020/article/details/12980423/

天妒英才啊!!

话不多数,项目中需要用到ffmpeg从内存中读取数据转码,然后输出到内存中,在此记录几个注意事项,避免遇到坑;

从内存中读取数据:

int TEST::openInputFile()
{
	int ret = -1;
	unsigned int i;

	AVFormatContext ifmt_ctx = NULL;
	AVFormatContext ofmt_ctx = NULL;

	AVInputFormat* in_fmt = NULL;
	
	ifmt_ctx = avformat_alloc_context();

	// 这里一定要注意,in_fmt要是不设置,可能会导致avformat_open_input()或者avformat_find_stream_info()失败
	// 我使用的是音频转码,故此处填写音频的简称
	in_fmt = av_find_input_format("g729");
	unsigned int inbufferSize = 20; // 

	unsigned char* inbuffer = (unsigned char *)av_mallocz(inbufferSize);
	memset(inbuffer, 0, inbufferSize);

	AVIOContext * pAViIO_in = avio_alloc_context(inbuffer, inbufferSize, 0, this, read_buffer, NULL, NULL);
	if (pAViIO_in == NULL)
		return -1;

	ifmt_ctx->pb = pAViIO_in;
	ifmt_ctx->flags = AVFMT_FLAG_CUSTOM_IO;

	if ((ret = avformat_open_input(&ifmt_ctx, NULL, in_fmt, NULL)) < 0)
	{
		printf("Cannot open input file\n");
		return ret;
	}
	
	if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0)
	{
		printf("Cannot find stream information\n");
		return ret;
	}

 	/* select the audio stream */
	AVCodec *dec;
 	ret = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &dec, 0);
 	if (ret < 0) 
 	{
		printf("Cannot find a audio stream in the input file\n");
 		return ret;
 	}

	for (i = 0; i < ifmt_ctx->nb_streams; i++)
	{
		if (ifmt_ctx->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO)
			continue;

		AVStream *stream = ifmt_ctx->streams[i];
		dec = avcodec_find_decoder(stream->codecpar->codec_id);
		if (!dec)
		{
			printf("Failed to find decoder for stream %d\n", i);
			return AVERROR_DECODER_NOT_FOUND;
		}

		AVCodecContext *codec_ctx;
		codec_ctx = stream->codec;
		if (!codec_ctx)
		{
			printf("Fail to allocate the decoder context");
			return AVERROR(ENOMEM);
		}

		ret = avcodec_parameters_to_context(codec_ctx, stream->codecpar);
		if (ret < 0)
		{
			return ret;
		}

		if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) 
		{
			/* Open decoder */
			ret = avcodec_open2(codec_ctx, dec, NULL);
			if (ret < 0)
			{
				return ret;
			}
		}
	}
	
	return ret;
}

重点一:

in_fmt = av_find_input_format("g729");

这里一定要注意,in_fmt要是不设置,可能会导致avformat_open_input()或者avformat_find_stream_info()失败
我使用的是音频转码,故此处填写音频的简称:简称不确定的话可到ffmpeg源码中查找;adts一般就是aac;g729就是G.729a;

我遇到的问题是:我只转码音频,网上的教程都是一些转码视频的,有些设置在我需求中不适用,之前都没设置这个格式,导致初始化打开文件一直失败;

重点二:

unsigned int inbufferSize = 20; // 

unsigned char* inbuffer = (unsigned char *)av_mallocz(inbufferSize);
memset(inbuffer, 0, inbufferSize);

此处inbufferSize 的大小是重点,一般跟帧大小或者音频采样点有关,这个大小就是下面读取数据的回调函数中固定读取数据的大小,视频一般默认是32K,音频aac是1024,MP3是1152;g729a编码比例是160:1,20个字节一般是两帧;

m_inbuffer 是存放读取出的数据的;

重点三:

AVIOContext * pAViIO_in = avio_alloc_context(inbuffer, inbufferSize, 0, this, fill_iobuffer, NULL, NULL);
if (pAViIO_in == NULL)
	return -1;

ifmt_ctx->pb = pAViIO_in;
ifmt_ctx->flags = AVFMT_FLAG_CUSTOM_IO;

if ((ret = avformat_open_input(&ifmt_ctx, NULL, in_fmt, NULL)) < 0)
{
	printf("Cannot open input file\n");
	return ret;
}

借用雷神的原话:关键要在avformat_open_input()之前初始化一个AVIOContext,而且将原本的AVFormatContext的指针pb(AVIOContext类型)指向这个自行初始化AVIOContext。当自行指定了AVIOContext之后,avformat_open_input()里面的URL参数就不起作用了。示例代码开辟了一块空间iobuffer作为AVIOContext的缓存。
fill_iobuffer则是将数据读取至iobuffer的回调函数。fill_iobuffer()形式(参数,返回值)是固定的,是一个回调函数,如下所示(只是个例子,具体怎么读取数据可以自行设计)。

//读取数据的回调函数-------------------------
//AVIOContext使用的回调函数!
//注意:返回值是读取的字节数
//手动初始化AVIOContext只需要两个东西:内容来源的buffer,和读取这个Buffer到FFmpeg中的函数
//回调函数,功能就是:把buf_size字节数据送入buf即可
//在类中定义时应该是static的
int TEST::read_buffer(void * opaque, uint8_t *buf, int bufsize)
{
	TEST* pThis = (TEST*)opaque;
	if (pThis == NULL)
		return -1;

	/*
	................
	*/

	return bufsize;
}

 

输出到内存:

int TEST::openOutputFile()
{
    int nRet = 0;
	AVStream *out_stream;
	AVStream *in_stream;
	AVCodecContext *dec_ctx, *enc_ctx;
	AVCodec *encoder;
	AVOutputFormat *pAVOutputFormat = NULL;

    // aac
	pAVOutputFormat = av_guess_format("adts", NULL, NULL);
	outbufferSize = 1024;

    ofmt_ctx = NULL;
	//nRet = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, "f:/352x288_aac.ts");
	nRet = avformat_alloc_output_context2(&ofmt_ctx, pAVOutputFormat, NULL, NULL);
	if (nRet < 0)
	{
		printf("Fail to avformat_alloc_output_context2() \n");
		return nRet;
	}
	
	for (int i = 0; i < ifmt_ctx->nb_streams; i++)
	{
		dec_ctx = ifmt_ctx->streams[i]->codec;
		if (dec_ctx->codec_type != AVMEDIA_TYPE_AUDIO)
			continue;

		if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			AVDictionary *op = NULL;
			if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO)
			{
			}
			else
			{
				encoder = avcodec_find_encoder(AV_CODEC_ID_AAC);

				if (!encoder)
				{
					printf("Necessary encoder not found\n");
					return AVERROR_INVALIDDATA;
				}

				out_stream = avformat_new_stream(ofmt_ctx, encoder);
				if (!out_stream)
				{
					printf("Failed allocating output stream\n");
					return AVERROR_UNKNOWN;
				}

				enc_ctx = out_stream->codec;
				if (!enc_ctx)
				{
					printf("Failed to allocate the encoder context\n");
					return AVERROR(ENOMEM);
				}

				// 音频采样率
				enc_ctx->sample_rate = 8000;
				enc_ctx->channel_layout = select_channel_layout(encoder);

				/* take first format from list of supported formats */
				enc_ctx->sample_fmt = encoder->sample_fmts[0];
				enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout);

				enc_ctx->time_base.num = 1;
				enc_ctx->time_base.den = enc_ctx->sample_rate;

				/** Allow the use of the experimental AAC encoder */
				enc_ctx->strict_std_compliance = FF_COMPLIANCE_NORMAL;
			}

			if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
				enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

			nRet = avcodec_open2(enc_ctx, encoder, &op);
			if (nRet < 0)
			{
				printf("Cannot open encoder for stream #%u\n", i);
				return nRet;
			}

			nRet = avcodec_parameters_from_context(out_stream->codecpar, enc_ctx);
			if (nRet < 0)
			{
				printf("Failed to copy encoder parameters to output stream #%u\n", i);
				return nRet;
			}

			out_stream->time_base = enc_ctx->time_base;
		}
	}
	
	outbuffer = (unsigned char*)av_mallocz(outbufferSize);
	memset(m_outbuffer, 0, m_outbufferSize);

	pAViIO_out = avio_alloc_context(outbuffer, outbufferSize, 1, this, NULL, write_buffer, NULL);
	ofmt_ctx->pb = pAViIO_out;
	ofmt_ctx->flags = AVFMT_FLAG_CUSTOM_IO;

	nRet = avformat_write_header(ofmt_ctx, NULL);
	if (nRet < 0)
		return nRet;
		
	return nRet;
}

注意事项同上。

写回调函数:

//写数据的回调函数-------------------------
//AVIOContext使用的回调函数!
//注意:返回值是此次保存的字节数
//回调函数,功能就是:把buf_size字节buf数据保存到另外的缓冲区即可,不要在此函数中执行业务,可能造成阻塞
int TEST::write_buffer(void *opaque, uint8_t *buf, int buf_size)
{
	TEST* pThis = (TEST*)opaque;
	if (pThis == NULL)
		return -1;

	/*
	................
	*/


	return buf_size;
}

至此完毕,其他部分代码没有什么变化,一步步调用即可;
 

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值