C++ 解析aac-adts的头部信息

24 篇文章 3 订阅


前言

aac的adts封装格式的音频文件是可以直接播放的,因为其内部的数据中每一帧都带有adts头部,头部包含了解码的必要信息。不像wav文件其头部的字段都是基于byte为单位,直接使用内存结构相同的实体即可直接读取,adts的头部字段是以bit为单位的,这就给解析其头部带来了一定的难度,几乎获取每个字段都需要进行位操作,一些跨byte的位还需要进行字节序的转换。本文将提供解析adts头的具体方法及实现。


一、原理说明

1.aac-adts的结构

adts的结构是每一帧都带adts头部,头部后面跟着是aac的原始流(aac es),结构如下:
在这里插入图片描述

2.adts的头结构

adts的头部一共有15个字段,共7bytes,如果有校验位则会在尾部增加2bytesCRC校验。具体如下:

序号字段长度说明
1synword12bit同步头,总是0xFFF,代表着⼀个ADTS帧的开始。
2id1bit设置MPEG标识符,1bit,0标识MPEG-4,1标识MPEG-2。
3layer2bit总是00。
4protection_absent1bit误码校验,标识是否进行误码校验。0表示有CRC校验,1表示没有CRC校验。为0时头部7bytes后面+2bytesCRC检验位。
5profile2bitAAC级别,比如AAC LC=1。profile的值等于Audio Object Type的值减1。
6sampling_frequency_index4bit采样率下标,下标对应的采样率如下 。
0: 96000 Hz
1: 88200 Hz
2 : 64000 Hz
3 : 48000 Hz
4 : 44100 Hz
5 : 32000 Hz
6 : 24000 Hz
7 : 22050 Hz
8 : 16000 Hz
9 : 12000 Hz
10 : 11025 Hz
11 : 8000 Hz
12 : 7350 Hz
13 : Reserved
14 : Reserved
15 : frequency is written explictly
7private_bit1bit私有位,编码时设置为0,解码时忽略。
8channel_configuration3bit声道数。
0: Defined in AOT Specifc Config
1: 1 channel : front - center
2 : 2 channels : front - left, front - right
3 : 3 channels : front - center, front - left, front - right
4 : 4 channels : front - center, front - left, front - right, back - center
5 : 5 channels : front - center, front - left, front - right, back - left, back - right
6 : 6 channels : front - center, front - left, front - right, back - left, back - right, LFE - channel
7 : 8 channels : front - center, front - left, front - right, side - left, side - right, back - left, back - right, LFE - channel
8 - 15 : Reserved
9orininal_copy1bit编码时设置为0,解码时忽略。
10home1bit编码时设置为0,解码时忽略。
11copyrigth_identification_bit1bit编码时设置为0,解码时忽略。
12copyrigth_identification_stat1bit编码时设置为0,解码时忽略。
13aac_frame_length13bit一个ADTS帧的⻓度,包括ADTS头和AAC原始流。
14adts_bufferfullness11bit缓冲区充满度,0x7FF说明是码率可变的码流,不需要此字段。CBR可能需要此字段,不同编码器使用情况不同。具体查看附录。
15number_of_raw_data_blocks_in_frame2bit表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧,为0表示说ADTS帧中只有一个AAC数据.

一个具体的例子如下:
在这里插入图片描述
上面的数据对应的值如下

序号字段长度
1synword12bit111111111111
2id1bit0
3layer2bit00
4protection_absent1bit1
5profile2bit01
6sampling_frequency_index4bit0100(十进制4)
7private_bit1bit0
8channel_configuration3bit010 (十进制2)
9orininal_copy1bit0
10home1bit0
11copyrigth_identification_bit1bit0
12copyrigth_identification_stat1bit0
13aac_frame_length13bit0000101111100(十进制380)
14adts_bufferfullness11bit00010010000
15number_of_raw_data_blocks_in_frame2bit00

3.如何解析?

(1)使用位操作

通过位操作获取具体的字段值,一般就是通过位移、位与、位或。比如:

//adts头部二进制数据
uint8_t data[7];
//获取id的值,id在第13bit,即在第2个byte中的第5位,所以对data[1]右移3位即将其移动到了最低位,再进行位与去掉其他无关位,得到的uint8_t的值即使id的值。
uint8_t id = (data[1] >> 3) & 0x01;

(2)转换字节序

当字段长度超过1byte时就需要考虑字节序的问题了,由于大端小端字节序排列相反,相同的位操作在不同的机器上可能不一致,为了确保结果正确,我们可以统一使用网络字节序(大端)的方式进行位操作,操作完成之后再将得到的字段转换成本地字节序。比如获取synword可以如下操作:

uint8_t* p;
uint16_t synword;
p = (uint8_t*)&synword;
//按网络字节序进行位操作
p[0] = (data[1] >> 4) & 0x0f;
p[1] = data[0];
//将网络字节序转换成本地字节序
synword = ntohs(synword);

二、代码实现

AacADTSHeader.h

#pragma once
#include<stdint.h>
/************************************************************************
* @Project:  	AC::AacADTSHeader
* @Decription:  AAC ADTS头部解析
* @Verision:  	v1.0.0.0
* @Author:  	Xin Nie
* @Create:  	2022/2/24 13:10:17
* @LastUpdate:  2022/2/25 15:50:26
************************************************************************
* Copyright @ 2022. All rights reserved.
************************************************************************/
namespace AC {
	class AacADTSHeader;
	/// <summary>
	/// AAC ADTS头部解析工具
	/// </summary>
	class AacADTSParse {
	public:
		static void BinaryToHeader(const uint8_t data[7], AacADTSHeader&);
		static void HeaderToBinary(const AacADTSHeader& ,uint8_t data[7]);
	};
	/// <summary>
	/// AAC ADTS头部实体
	/// </summary>
	class AacADTSHeader
	{
	public:
		/// <summary>
		/// 同步头,12bit,总是0xFFF,all bits must be 1,代表着⼀个ADTS帧的开始。
		/// </summary>
		uint16_t synword = 0xFFF;						   
		/// <summary>
		/// 设置MPEG标识符,1bit,0标识MPEG-4,1标识MPEG-2。
		/// </summary>
		uint8_t id = 0; 								 
		/// <summary>
		/// 2bit always: '00'
		/// </summary>
		uint8_t layer = 0x00;		
		/// <summary>
		/// 误码校验,1bit,表示是否误码校验。标识是否进行误码校验。0表示有CRC校验,1表示没有CRC校验。为0时头部7bytes后面+2bytesCRC检验位,为1时只有7bytes
		/// </summary>
		/// <returns>误码校验</returns>
		uint8_t protection_absent = 1;     
		/// <summary>
		/// AAC级别,2bit,表示使用哪个级别的AAC,比如AAC LC=1。profile的值等于Audio Object Type的值减1
		/// </summary>
		uint8_t profile;		
		/// <summary>
		/// 采样率下标,4bit,表示使用的采样率下标
		/// 0: 96000 Hz
		/// 1: 88200 Hz
		/// 2 : 64000 Hz
		/// 3 : 48000 Hz
		/// 4 : 44100 Hz
		/// 5 : 32000 Hz
		/// 6 : 24000 Hz
		/// 7 : 22050 Hz
		/// 8 : 16000 Hz
		/// 9 : 12000 Hz
		/// 10 : 11025 Hz
		/// 11 : 8000 Hz
		/// 12 : 7350 Hz
		/// 13 : Reserved
		/// 14 : Reserved
		/// 15 : frequency is written explictly
		/// </summary>
		uint8_t sampling_frequency_index;			
		/// <summary>
		/// 私有位,1bit,编码时设置为0,解码时忽略
		/// </summary>
		/// <returns>私有位</returns>
		uint8_t private_bit = 0;	
		/// <summary>
	    /// 声道数,3bit
		/// 0: Defined in AOT Specifc Config
		/// 1: 1 channel : front - center
		///	2 : 2 channels : front - left, front - right
		///	3 : 3 channels : front - center, front - left, front - right
		///	4 : 4 channels : front - center, front - left, front - right, back - center
		///	5 : 5 channels : front - center, front - left, front - right, back - left, back - right
		///	6 : 6 channels : front - center, front - left, front - right, back - left, back - right, LFE - channel
		///	7 : 8 channels : front - center, front - left, front - right, side - left, side - right, back - left, back - right, LFE - channel
		///	8 - 15 : Reserved
		/// </summary>
		uint8_t channel_configuration;	
		/// <summary>
		/// 1bit,编码时设置为0,解码时忽略。
		/// </summary>
		uint8_t orininal_copy = 0;	
		/// <summary>
		/// 1bit,编码时设置为0,解码时忽略。
		/// </summary>
		uint8_t home = 0;		
		/// <summary>
		/// 1bit,编码时设置为0,解码时忽略。
		/// </summary>
		uint8_t copyrigth_identification_bit = 0;		   
		/// <summary>
		/// 1bit,编码时设置为0,解码时忽略。
		/// </summary>
		uint8_t copyrigth_identification_stat = 0;		   
		/// <summary>
		///  aac帧长度,13bit,一个ADTS帧的⻓度包括ADTS头和AAC原始流
		/// </summary>
		uint16_t aac_frame_length = 0;					  
		/// <summary>
		///  11bit,0x7FF说明是码率可变的码流。
		/// </summary>
		uint16_t adts_bufferfullness = 0x7FF;			  
		/// <summary>
		///  2bit,表示ADTS帧中有value + 1个AAC原始帧,为0表示说ADTS帧中只有一个AAC数据块。
		/// </summary>
		uint8_t number_of_raw_data_blocks_in_frame = 0;   
	};
}

完整代码:
https://download.csdn.net/download/u013113678/85320440


三、使用示例

1.读取aac文件

#include<stdio.h>
#include<stdint.h>
#include<exception>
#include"AacADTSHeader.h"
int main(int argc, char* argv[])
{
	AC::AacADTSHeader h;
	uint8_t buffer[1024];
	uint8_t* aacData;
	int aacDataLength;
	FILE* f = fopen("test.aac", "rb+");
	if (!f)
	{
		printf("open aac file error!");
		return -1;
	}
	try {
		while (1)
		{
			int size;
			size = fread(buffer, 1, 7, f);
			if (size < 7)
				break;
			AC::AacADTSParse::BinaryToHeader(buffer,h);
			size = fread(buffer, 1, h.aac_frame_length - 7, f);
			if (size != h.aac_frame_length - 7)
			{
				throw std::exception("incorrect length!");
			}
			if (h.protection_absent == 0)
				//有校验位
			{
				//处理校验位buffer[0]、buffer[1]
				aacData = buffer + 2;
				aacDataLength = size - 2;
			}
			else
			{
				aacData = buffer;
				aacDataLength = size;
			}
			//TODO:到此取得aac数据aacData、aacDataLength

		}
	}
	catch (const std::exception& e)
	{
		printf("%s,\n", e.what());
	}
	if (f)
	{
		fclose(f);
	}
	return 0;
}

总结

以上就是今天要讲的内容,对于adts的头部解析其实并不算难,只要懂得基本的位操作以及字节序的转换基本就可以实现,对于文件的读取也是需要注意判断CRC校验即可,总的来虽然单纯解析adts不难,对于adts有些细节还是需要去了解的,比如CBR时adts_bufferfullnessadts_bufferfullness的计算方式,number_of_raw_data_blocks_in_frame不0时如何处理数据等等。


附录

adts_bufferfullness的相关资料http://blog.olivierlanglois.net/index.php/2008/09/12/aac_adts_header_buffer_fullness_field

  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

CodeOfCC

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

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

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

打赏作者

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

抵扣说明:

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

余额充值