IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++ 解析aac-adts的头部信息 -> 正文阅读

[C++知识库]C++ 解析aac-adts的头部信息


前言

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;   
	};
}

AacADTSHeader.cpp

#include"AacADTSHeader.h"
#include <exception>
//注:使用socket头文件是为了处理网络字节序,用到的方法有ntohs和htons,有需求也可以自己实现以减少依赖,并不算复杂。socket头文件采用了宏区分平台,是可以在windows、linux、mac、android中使用的。
#ifdef _WIN32
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#else
#include <sys/socket.h>
#endif // _WIN32
namespace AC {
	void AacADTSParse::BinaryToHeader(const uint8_t data[7], AacADTSHeader& header)
	{
		uint8_t* p;
		p = (uint8_t*)&header.synword;
		p[0] = (data[1] >> 4) & 0x0f;
		p[1] = data[0];
		header.synword = ntohs(header.synword);
		if (header.synword != (uint16_t)0x0FFF)
			throw std::exception("The data is not an adts header!");
		header.id = (data[1] >> 3) & 0x01;
		header.layer = (data[1] >> 1) & 0x03;
		if (header.layer != 0x00)
			throw std::exception("The data is not an adts header!");
		header.protection_absent = (data[1]) & 0x01;
		header.profile = ((data[2] >> 6) & 0x03);
		header.sampling_frequency_index = (data[2] >> 2 & 0x0f);
		header.private_bit = (data[2] >> 1) & 0x01;
		header.channel_configuration = ((data[2] & 0x01) << 2) | ((data[3] >> 6) & 0x03);
		header.orininal_copy = (data[3] >> 5) & 0x01;
		header.home = (data[3] >> 4) & 0x01;
		header.copyrigth_identification_bit = (data[3] >> 3) & 0x01;
		header.copyrigth_identification_stat = (data[3] >> 2) & 0x01;
		p = (uint8_t*)&header.aac_frame_length;
		p[0] = ((data[3] & 0x03) << 3) | ((data[4] >> 5) & 0x07);
		p[1] = ((data[4] << 3) & 0xf8) | ((data[5] >> 5) & 0x07);
		header.aac_frame_length = ntohs(header.aac_frame_length);
		p = (uint8_t*)&header.adts_bufferfullness;
		p[0] = ((data[5] & 0x07) >> 2);
		p[1] = ((data[5] << 6) & 0xc0) | ((data[6] >> 2) & 0x3f);
		header.adts_bufferfullness = ntohs(header.adts_bufferfullness);
		header.number_of_raw_data_blocks_in_frame = (data[6] & 0x03);
	}
	void AacADTSParse::HeaderToBinary(const AacADTSHeader&header, uint8_t data[7])
	{
		uint8_t* p;
		uint16_t synword = htons(header.synword);
		p = (uint8_t*)&synword;
		data[0] = ((p[0] << 4) & 0xf0) | ((p[1] >> 4) & 0x0f);
		data[1] = ((p[1] << 4) & 0xf0) | ((header.id << 3) & 0x08) | ((header.layer << 1) & 0x06) | (header.protection_absent & 0x01);
		data[2] = ((header.profile << 6) & 0xc0) | ((header.sampling_frequency_index << 2) & 0x36) | ((header.private_bit << 1) & 0x02) | ((header.channel_configuration >> 2) & 0x01);
		uint16_t aac_frame_length = htons(header.aac_frame_length);
		p = (uint8_t*)&aac_frame_length;
		data[3] = ((header.channel_configuration << 6) & 0xc0) | ((header.orininal_copy << 5) & 0x20) | ((header.home << 4) & 0x10) | ((header.copyrigth_identification_bit << 3) & 0x08) | ((header.copyrigth_identification_stat << 2) & 0x04) | ((p[0] >> 3) & 0x03);
		data[4] = ((p[0] << 5) & 0xe0) | ((p[1] >> 3) & 0x1f);
		data[5] = ((p[1] << 5) & 0xe0);
		uint16_t adts_bufferfullness = htons(header.adts_bufferfullness);
		p = (uint8_t*)&adts_bufferfullness;
		data[5] = data[5] | ((p[0] << 2) & 0x1c) | ((p[1] >> 6) & 0x03);
		data[6] = ((p[1] << 2) & 0xfc) | (header.number_of_raw_data_blocks_in_frame & 0x03);
	}
}

三、使用示例

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

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-28 15:10:05  更:2022-02-28 15:13:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:34:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码