FFmpeg开发实战(四):抽取音视频的音频文件



ADTS是Audio Data Transport Stream的简称。是AAC音频文件常见的传输格式。有的时候当你编码AAC裸流的时候,会遇到写出来的AAC文件并不能在PC和手机上播放,很大的可能就是AAC文件的每一帧里缺少了ADTS头信息文件的包装拼接。只需要加入头文件ADTS即可。一个AAC原始数据块长度是可变的,对原始帧加上ADTS头进行ADTS的封装,就形成了ADTS帧。

1. ADTS头文件结构和信息

AAC音频文件的每一帧由ADTS Header和AAC Audio Data组成。
在这里插入图片描述
大家可以用AAC Audio ES Viewer工具来查看AAC的ADTS Header。

在这里插入图片描述
每一帧的ADTS的头文件都包含了音频的采样率,声道,帧长度等信息,这样解码器才能解析读取。

一般情况下ADTS的头信息都是7个字节,分为2部分:

adts_fixed_header();

adts_variable_header();

先来看:adts_fixed_header();

在这里插入图片描述

  • syncword :总是0xFFF, 代表一个ADTS帧的开始, 用于同步.
    解码器可通过0xFFF确定每个ADTS的开始位置.
    因为它的存在,解码可以在这个流中任何位置开始, 即可以在任意帧解码。
  • ID:MPEG Version: 0 for MPEG-4,1 for MPEG-2
  • Layer:always: ‘00’
  • protection_absent:Warning, set to 1 if there is no CRC and 0 if there is CRC
  • profile:表示使用哪个级别的AAC,如01 Low Complexity(LC) – AAC LC
    profile的值等于 Audio Object Type的值减1.
    profile = MPEG-4 Audio Object Type - 1

在这里插入图片描述
在这里插入图片描述

  • channel_configuration:声道数,比如2表示立体声双声道

在这里插入图片描述
接下来看下adts_variable_header();
在这里插入图片描述

  • aac_frame_length:一个ADTS帧的长度包括ADTS头和AAC原始流。frame length, this value must include 7 or 9 bytes of header length:
    aac_frame_length = (protection_absent == 1 ? 7 : 9) + size(AACFrame)
    protection_absent=0时, header length=9bytes
    protection_absent=1时, header length=7bytes
  • adts_buffer_fullness:0x7FF 说明是码率可变的码流。
  • number_of_raw_data_blocks_in_frame:表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。
    所以说number_of_raw_data_blocks_in_frame == 0 表示说ADTS帧中有一个AAC数据块。
    (一个AAC原始帧包含一段时间内1024个采样及相关数据)

2. 测试代码

/*
*ADTS头的实际长度是7个字节或9个字节,9个字节的是有2个Byte校验码的,这里以7字节为例
*设置音频的ADTS头信息;header为头信息的指针;dataLen为音频数据包的大小
*/
void fftest::adts_header(char* header, int dataLen)
{
	// aac级别,0: AAC Main 1:AAC LC (Low Complexity) 2:AAC SSR (Scalable Sample Rate) 3:AAC LTP (Long Term Prediction)
	int aac_type = 1;
	// 采样率下标,下标7表示采样率为48000
	int sampling_frequency_index = 3;
	// 声道数
	int channel_config = 2;

	// ADTS帧长度,包括ADTS长度和AAC声音数据长度的和。
	int adtsLen = dataLen + 7;

	// syncword,标识一个帧的开始,固定为0xFFF,占12bit(byte0占8位,byte1占前4位)
	header[0] = (uint8_t)0xff;
	header[1] = (uint8_t)0xf0;

	// ID,MPEG 标示符。0表示MPEG-4,1表示MPEG-2。占1bit(byte1第5位)
	header[1] |= (0 << 3);

	// layer,固定为0,占2bit(byte1第6、7位)
	header[1] |= (0 << 1);

	// protection_absent,标识是否进行误码校验。0表示有CRC校验,1表示没有CRC校验。占1bit(byte1第8位)
	header[1] |= 1;

	// profile,标识使用哪个级别的AAC。1: AAC Main 2:AAC LC 3:AAC SSR 4:AAC LTP。占2bit(byte2第1、2位)
	header[2] = aac_type << 6;

	// sampling_frequency_index,采样率的下标。占4bit(byte2第3、4、5、6位)
	header[2] |= (sampling_frequency_index & 0x0f) << 2;

	// private_bit,私有位,编码时设置为0,解码时忽略。占1bit(byte2第7位)
	header[2] |= (0 << 1);

	// channel_configuration,声道数。占3bit(byte2第8位和byte3第1、2位)
	header[2] |= (channel_config & 0x04) >> 2;
	header[3] = (channel_config & 0x03) << 6;

	// original_copy,编码时设置为0,解码时忽略。占1bit(byte3第3位)
	header[3] |= (0 << 5);

	// home,编码时设置为0,解码时忽略。占1bit(byte3第4位)
	header[3] |= (0 << 4);

	// copyrighted_id_bit,编码时设置为0,解码时忽略。占1bit(byte3第5位)
	header[3] |= (0 << 3);

	// copyrighted_id_start,编码时设置为0,解码时忽略。占1bit(byte3第6位)
	header[3] |= (0 << 2);

	// aac_frame_length,ADTS帧长度,包括ADTS长度和AAC声音数据长度的和。占13bit(byte3第7、8位,byte4全部,byte5第1-3位)
	header[3] |= ((adtsLen & 0x1800) >> 11);
	header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);
	header[5] = (uint8_t)((adtsLen & 0x7) << 5);

	// adts_buffer_fullness,固定为0x7FF。表示是码率可变的码流 。占11bit(byte5后5位,byte6前6位)      
	header[5] |= 0x1f;
	header[6] = (uint8_t)0xfc;

	// number_of_raw_data_blocks_in_frame,值为a的话表示ADST帧中有a+1个原始帧,(一个AAC原始帧包含一段时间内1024个采样及相关数据)。占2bit(byte6第7、8位)。
	header[6] |= 0;
}

void fftest::extractAudio(const char* src, const char* dst)
{
	// 设置日志输出等级
	av_log_set_level(AV_LOG_INFO);

	AVFormatContext* fmt_ctx = NULL;

	/*
	* 如果传入的两个路径为空
	*/
	if (!src||!dst)
	{
		av_log(NULL, AV_LOG_ERROR, "传入的路径不能为空\n");
		return;
	}

	//打开输入文件
	int flag;
	flag = avformat_open_input(&fmt_ctx, src, NULL, NULL);
	if (flag < 0)
	{
		av_log(NULL, AV_LOG_ERROR, "打开文件失败");
		avformat_close_input(&fmt_ctx);
		return;
	}

	//打开要写入aac音频的文件,没有就会创建一个
	std::ofstream  file;
	file.open(dst, std::ios::binary | std::ios::out);
	//FILE* file/* = fopen(dst, "wb")*/;
	//fopen_s(&file, dst, "wb");
	if (!file.is_open()/*file == NULL*/)
	{
		av_log(NULL, AV_LOG_ERROR, "不能打开或创建存储文件!");
		avformat_close_input(&fmt_ctx);
		return;
	}

	//输出信息
	av_dump_format(fmt_ctx, NULL, src, NULL);

	/*
	  * 获取最好的一路流的资源
	  *第二个参数是流的类型
	  *第三个参数是流的索引号,不知道就写-1
	  *第四个参数是相关的对应的流的索引号,比如音频对应的视频流的索引号,可以不必关心填-1
	  *第五个参数是设置的编解码器,不设置就写NULL
	  *第六个参数是一些标准,暂时不关心填0
	  *返回值是所找到的流的索引值
	  * */
	int audio_index;//音频索引值
	audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
	if (audio_index<0)
	{
		av_log(NULL, AV_LOG_ERROR, "获取流失败!");
		//推出前关闭文件释放内容
		avformat_close_input(&fmt_ctx);
		file.close();
		//fclose(file);
		return;
	}

	//读取流中的包数据
	AVPacket pkt;
	av_init_packet(&pkt); //初始化pkt
	//循环读取流中所有的包,这里注意传进去的是pkt的地址
	while (av_read_frame(fmt_ctx, &pkt) >= 0)
	{
		//判断读取的包所属的流是否是我们找到的流
		if (pkt.stream_index == audio_index)
		{
			//如果读取的包是我们找的流就写入文件
			char adts_header_buf[7];
			adts_header(adts_header_buf, pkt.size);
			file.write(adts_header_buf, 1 * 7);
			//fwrite(adts_header_buf, 1, 7, file);
			if (!file.write((const char*)pkt.data,1*pkt.size)
				/*fwrite(pkt.data,1,pkt.size,file)!=pkt.size*/
				)
			{
				av_log(NULL, AV_LOG_WARNING, "警告,写入的大小与返回的大小不一致!");
			}
		}
		// 因为每次循环都要为pkt分配内存,所以一轮循环结束时要释放内存
		av_packet_unref(&pkt);
	}
	avformat_close_input(&fmt_ctx);

	if (file.is_open()) {
		file.close();
		/*fclose(file);*/
	}
}


声明:本文参考文章https://www.jianshu.com/p/5337260efd97,文件输出及个别地方参数做了修改,如有侵权,请告知。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

i胡说

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

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

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

打赏作者

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

抵扣说明:

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

余额充值