解码AAC裸流为PCM写入文件

本文介绍了如何使用C++和libavcodec库解析AAC裸流文件,并将其转换为PCM数据,同时处理解码过程中的错误和文件I/O操作。
摘要由CSDN通过智能技术生成

使用AAC裸流解析器将aac裸流文件解析为pcm数据,然后写入文件

#include "myLog.h"
#include <iostream>

extern "C"
{
#include <libavcodec\avcodec.h>
}

#define AUDIO_INBUF_SIZE 20480			// 读取 20KB数据
#define AUDIO_REFILL_THRESH 4096

/*
	从src复制size字符到dst(dstBuff 与 srcBuff内存块可重叠)
	memmove(dst, src, size);
*/

static char* av_get_err(int errnum)
{
	char err_buf[128] = { 0 };
	av_strerror(errnum, err_buf, 128);
	return err_buf;
}

static void print_sample_format(const AVFrame *frame)
{
	printf("ar_samplerate: %uHz\n", frame->sample_rate);
	printf("ac_channel: %u\n", frame->channels);
	printf("f_format: %u\n", frame->format);	// 格式需要注意,实际存储到本地文件时已经改成交错模式 FLT
}


static void decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame, FILE* out_fp)
{
	int nRet = avcodec_send_packet(dec_ctx, pkt);
	if (nRet == AVERROR(EAGAIN))		// 需要更多pkt
	{
		LOG_WARNING("need more pkt\n");
		return;
	}
	else if (nRet < 0)
	{
		// 当为mp3文件时,因为前面的一部分字节解码器解不出来, 直接退出程序的话,会导致mp3文件解析失败,应该函数返回解析下一帧
		LOG_WARNING("Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",
			av_get_err(nRet), pkt->size);
		return;
	}

	// 一个pkt可能对应多个frame
	while (nRet >= 0)
	{
		nRet = avcodec_receive_frame(dec_ctx, frame);
		if (nRet == AVERROR(EAGAIN) || nRet == AVERROR_EOF)
		{
			return;
		}
		else if (nRet < 0)
		{
			LOG_WARNING("Error during decoding\n");
			return;
		}

		// 一个采样点字节数
		int data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
		if (data_size < 0)
		{
			LOG_WARNING("av_get_bytes_per_sample error\n");
			return;
		}

		// 打印一次信息
		static bool is_print = 0;
		if (!is_print)
		{
			is_print = !is_print;
			print_sample_format(frame);
		}

		// planar to packed
		for (int i = 0; i < frame->nb_samples; i++)
		{
			for (int ch = 0; ch < frame->channels; ch++)
			{
				fwrite(frame->data[ch] + i * data_size, 1, data_size, out_fp);
			}
		}
	}
}

int main_audio_decodec()
{
	// ffplay -ar 44100 -ac 2 -f f32le -i out_put_48000_2_f32le.pcm
	const char* in_file_name = "./out_put.aac";
	const char* out_file_name = "./out_put_48000_2_f32le.pcm";

	enum AVCodecID audio_codec_id = AV_CODEC_ID_AAC;	// 指定aac

	// 1. 查找解码器
	AVCodec* audio_decoder = avcodec_find_decoder(audio_codec_id);
	if (nullptr == audio_decoder)
	{
		LOG_WARNING("avcodec_find_decoder error\n");
		return -1;
	}

	// 2. 获取(aac)裸流解析器
	AVCodecParserContext* parser = av_parser_init(audio_decoder->id);
	if (nullptr == parser)
	{
		LOG_WARNING("av_parser_init error\n");
		return -2;
	}

	// 3. 根据解码器创建解码上下文
	auto audio_decoder_ctx = avcodec_alloc_context3(audio_decoder);
	if (audio_decoder_ctx == nullptr)
	{
		LOG_WARNING("avcodec_alloc_context3 error\n");
		return -3;
	}

	// 4. 打开解码器
	int nRet = avcodec_open2(audio_decoder_ctx, audio_decoder, NULL);
	if (nRet < 0)
	{
		LOG_WARNING("avcodec_open2 error\n");
		return -4;
	}

	// 5. 打开输入输出文件
	FILE* in_fp = fopen(in_file_name, "rb");
	if (NULL == in_fp)
	{
		LOG_WARNING("fopen in_file_name error\n");
		return -5;
	}

	FILE* out_fp = fopen(out_file_name, "wb");
	if (NULL == out_fp)
	{
		LOG_WARNING("out_fp out_file_name error\n");
		return -5;
	}

	// 6.从输入文件中读取数据
	// AV_INPUT_BUFFER_PADDING_SIZE   在用于解码的输入比特流末尾所需的额外分配的字节数
	uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
	uint8_t *data = NULL;
	size_t   data_size = 0;

	data = inbuf;
	data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, in_fp);

	AVPacket* pkt = av_packet_alloc();
	AVFrame* decoded_frame = av_frame_alloc();
	while (data_size > 0)
	{
		if (decoded_frame == nullptr)
		{
			LOG_WARNING("av_frame_alloc error\n");
			return -6;
		}

		// pkt->data 不会分配内存而是直接指向data的内存
		// av_read_frame():获取媒体的一帧压缩编码数据。其中调用了av_parser_parse2()
		// 返回值为 该函数返回读取的bytes
		nRet = av_parser_parse2(parser, audio_decoder_ctx,
			&pkt->data, &pkt->size,		// 输出 pkt
			data, data_size,			// 输入 buff裸流数据(h264, aac等)
			AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);

		if (nRet < 0)
		{
			LOG_WARNING("av_parser_parse2 error\n");
			return -7;
		}
		data += nRet;		// 将指针挪动到未解析的buff位置
		data_size -= nRet;

		if (pkt->size >= 0)
		{
			// 解码
			decode(audio_decoder_ctx, pkt, decoded_frame, out_fp);
		}

		// 数据不够了继续读取
		if (data_size < AUDIO_REFILL_THRESH)
		{
			memmove(inbuf, data, data_size);	// 将未读取完的拷贝到inbuf开始位置
			data = inbuf;
			int len = fread(data + data_size, 1, AUDIO_INBUF_SIZE - data_size, in_fp);
			if (len > 0)
			{
				data_size += len;
			}
		}
	}

	// 空包冲刷解码器
	pkt->data = NULL;
	pkt->size = 0;
	decode(audio_decoder_ctx, pkt, decoded_frame, out_fp);

	// 释放相关资源
	fclose(in_fp);
	fclose(out_fp);

	av_frame_free(&decoded_frame);
	av_packet_free(&pkt);
	avcodec_free_context(&audio_decoder_ctx);
	av_parser_close(parser);

	getchar();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

石小浪♪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值