AAC格式及音频码流解析

AAC格式及音频码流解析,第1张

AAC的音频文件格式有ADIF & ADTS:

总结:ADTS可以在任意帧解码,也就是说它每一帧都有头信息。ADIF只有一个统一的头,所以必须得到所有的数据后解码。且这两种的header的格式也是不同的,目前一般编码后的和抽取出的都是ADTS格式的音频流。

从图上可以总结出两点:

ADTS Frame = ADTS头+AAC ES(AAC音频数据)

ADTS头包含了AAC文件的采样率、通道数、帧数据长度等信息。ADTS头分为固定头信息和可变头信息两个部分,固定头信息在每个帧中的是一样的,可变头信息在各个帧中并不是固定值。ADTS头一般是7个字节((28+28)/ 8)长度,如果需要对数据进行CRC校验,则会有2个Byte的校验码,所以ADTS头的实际长度是7个字节或9个字节。

固定头信息:adts_fixed_header()

这部分来自雷神的博客,不过在解析的地方进行了大量的注解,方便理解解析过程。

本文中的程序是一个AAC码流解析程序。该程序可以从AAC码流中分析得到它的基本单元ADTS frame,并且可以简单解析ADTS frame首部的字段。通过修改该程序可以实现不同的AAC码流处理功能。

AAC原始码流(又称为“裸流”)是由一个一个的ADTS frame组成的。他们的结构如下图所示。

其中每个ADTS frame之间通过syncword(同步字)进行分隔。同步字为0xFFF(二进制“111111111111”)。AAC码流解析的步骤就是首先从码流中搜索0x0FFF,分离出ADTS frame;然后再分析ADTS frame的首部各个字段。本文的程序即实现了上述的两个步骤

整个程序位于simplest_aac_parser()函数中,如下所示。

AAC格式ADTS+实例剖析 >

AAC(Advanced Audio Coding),高级音频编码,是基于MPEG-2的音频编码技术,于1997年推出,主要用于取代MP3格式。2000年,随着MPEG-4标准的推出,AAC重新集成了新的技术(如SBR、PS等)特性,称之为MEPG-4 AAC。AAC共有9种规格,具体如下所示:

AAC是一种高压缩比的音频压缩算法,本文主要从数据处理的角度对AAC音频码流进行分析。

AAC音频格式有两种:具体如下:

ADIF (Audio Data Interchange Format),音频数据交换格式:只有一个统一的头,必须得到所有数据后解码,适用于本地文件。

ADTS (Audio Data Transport Stream),音视数据传输流:每一帧都有头信息,可在任意帧解码,适用于传输流。

ADIF格式如下所示:

ADIF Header格式定义如下所示:

ADTS码流是由连续的多个ADTS Frame组成的,ADTS Frame是由ADTS Header和AAC ES组成的,具体如下所示:

ADTS Header,一般为7字节或9字节(有CRC校验),包含采样率、声道数、帧长度等信息,主要由adts_fixed_header、adts_variable_header、crc三部分组成。

adts_fixed_header :固定头,内容是不变的,每一帧的内容都相同,具体格式如下所示:

syncword(12bit):同步字值为0xFFF,所有位为1。

ID(1bit):0表示MPEG-4,1表示MPEG-2。

layer(2bit):所有位值为0。

protection_absent(1bit):0表示有CRC,1表示没有CRC。

profile(2bit):配置级别,MPEG-2中定义profile取值如下图所示:

sampling_frequency_index(4bit):采样频率,具体取值如下图所示:

private_bit(1bit):see ISO/IEC 11172-3, subclause 2423 (Table 8)。

channel_configuration(3bit):表示声道数。

original/copy(1bit):编码默认值为0,解码忽略此值。

home(1bit):编码默认值为0,解码忽略此值。

adts_variable_header :可变头,内容是变化的,每一帧的内容存在变化,具体格式如下所示:

copyright_identification_bit(1bit):编码默认值为0,解码忽略此值。

copyright_identification_start(1bit):编码默认值为0,解码忽略此值。

frame_length(13bit):帧长度,即整个ADTS Frame的长度。

adts_buffer_fullness(11bit):默认值为0x7FF,表示可变码流。

number_of_raw_data_blocks_in_frame(2bit):其值加1表示ADTS Frame中原始数据块的个数。

利用UItraEdit工具打开一个AAC文件进行数据分析,如下图所示:

红色标注部分为ADTS Header,根据前文所述,第一个Header详细数据分析如下:

syncword(12bit):值为0xFFF。

ID(1bit):值为1,表示MPEG-2。

layer(2bit):值为0。

protection_absent(1bit):值为1,表示没有CRC。

profile(2bit):值为1,表示LC。

sampling_frequency_index(4bit):值为4,表示44100Hz。

private_bit(1bit):值为0。

channel_configuration(3bit):值为2,表示双通道。

original/copy(1bit):值为0。

home(1bit):值为0。

copyright_identification_bit(1bit):值为0。

copyright_identification_start(1bit):值为0。

frame_length(13bit):值为106,表示整个ADTS Frame的长度。

adts_buffer_fullness(11bit):值为0x7FF,表示可变码流。

number_of_raw_data_blocks_in_frame(2bit):值为0,表示ADTS Frame中原始数据块的个数为1。

给你提供一个思路,首先需要对噪声和信号建模

我假设信号是均值为0的平稳过程,噪声是高斯过程,噪声和信号独立

假设r=s+n 表示混有信号s 和噪声n 的信号

E(r)=E(s+n)=0 没有信息量

E(r^2)=E[(s+n)^2]=E(s^2)+E(n^2)+E(2sn)=Ps+Pn

E(r^2)可以通过信号样值就平方和然后平均得到(r1^2+r2^2+rN^2)/N,所以得到一个方程

Ps+Pn=E(r^2)

E(r^3)=0

E(r^4)=E((s+n)^4)=E(s^4)+E(n^4)+2E(s^2)E(n^2)=??/

好久没推导了,所以接下来的地方你自己试试

目的就是建立一个方程组,解出Ps,Pn,最后就可以求出信号的信噪比了

#include <stdioh>

#include <stringh>

#define RIFF_SIGN_ID 0x46464952ul

#define WAVE_SIGN_ID 0x45564157ul

#define FMT__SIGN_ID 0x20746D66ul

#define FACT_SIGN_ID 0x74636166ul

#define DATA_SIGN_ID 0x61746164ul

#ifndef DWORD 

typedef unsigned long  DWORD;

#endif

#ifndef WORD 

typedef unsigned short WORD;

#endif

#ifndef BYTE

typedef unsigned char  BYTE;

#endif

struct RIFF_HEADER

{

DWORD RiffID;     // 资源交换文件标志 0x46464952 'R','I','F','F'

DWORD RiffSize;   // 从下个地址开始到文件尾的总字节数

DWORD RiffFormat; // WAV文件标志 0x45564157 'W','A','V','E'

};

struct WAVE_FORMAT

{

WORD  FormatTag;      // 格式种类(值为1时,表示数据为线性PCM编码)

WORD  Channels;       // 通道数,单声道为1,双声道为2

DWORD SamplesPerSec;  // 采样频率

DWORD AvgBytesPerSec; // 每秒所需字节数 

WORD  BlockAlign;     // 数据块对齐单位(每个采样需要的字节数)

WORD  BitsPerSample;  // 每个采样需要的bit数 

WORD  otherInfo;      // 附加信息(可选,通过Size来判断有无)

};

struct FMT_BLOCK

{

DWORD       FmtID;     // 波形格式标志 0x20746D66 'f','m','t',' '

DWORD       FmtSize;   // 波形格式部分长度(一般为00000010H)

WAVE_FORMAT wavFormat; // 波形数据格式

};

struct UNKNOW_BLOCK

{

DWORD ID;   // 未知块

DWORD Size; // 未知块长度

};

struct FACT_BLOCK

{

DWORD FactID;   // 可选部分标识 0x74636166 'f','a','c','t'

DWORD FactSize; // 可选部分长度

BYTE  Data[1];  // 可选部分数据

};

struct DATA_BLOCK

{

DWORD DataID;   // 数据标志符 0x61746164, 'd','a','t','a'

DWORD DataSize; // DATA总数据长度字节

BYTE  Data[1];  // 数据

};

BYTE  openWaveFile(const char name);

BYTE  getWaveData(BYTE  wav, int  dLen);

void   printWaveFormat(BYTE  wav);

int    saveWaveFile(const char  name, BYTE  wav);

BYTE  catWave(BYTE & wav1, BYTE & wav2);

size_t getTotalLen(BYTE  wav);

int main(int argc, char argv[])

{

int dLen;

BYTE  data1 = openWaveFile("1wav");

printWaveFormat(data1);

BYTE  data2 = openWaveFile("2wav");

printWaveFormat(data2);

data1 = catWave(data1, data2);

printWaveFormat(data1);

saveWaveFile("3wav", data1);

return 0;

}

BYTE  openWaveFile(const char name)

{

size_t readByte;

FILE  fp = fopen(name, "rb");

if(fp==NULL) return NULL;

RIFF_HEADER fh;

if(fread(&fh, sizeof(fh), 1, fp) != 1)

{

fclose(fp);

printf("Riff Header 文件长度错误\n");

return NULL;

}

if(fhRiffFormat != WAVE_SIGN_ID || fhRiffID != RIFF_SIGN_ID)

{

fclose(fp);

printf("文件标识符错误 ID:%08X Format:%08X\n", fhRiffID, fhRiffFormat);

return NULL;

}

BYTE  r = new BYTE[fhRiffSize + 10], pr;

if(r==NULL)

{

fclose(fp);

printf("内存申请错误\n");

return NULL;

}

readByte = fread(r, 1, fhRiffSize-4, fp);

if(readByte != fhRiffSize-4)

{

delete [] r;

printf("wave 文件长度错误 %d %d\n", readByte, fhRiffSize);

return NULL;

}

fclose(fp);

FMT_BLOCK fb = (FMT_BLOCK )r;

if(fb->FmtID != FMT__SIGN_ID)

{

printf("格式标识符错误或格式大小错误ID:%08X\n", fb->FmtID);

delete [] r;

return NULL;

}

if(fb->wavFormatFormatTag != 1)

{

delete [] r;

printf("不支持的格式 Format:%d\n", fb->wavFormatFormatTag);

return NULL;

}

pr = r + 8 + fb->FmtSize;

while(1)

{

UNKNOW_BLOCK  ub = (UNKNOW_BLOCK )pr;

if(ub->ID == FACT_SIGN_ID)

{

printf("Fact 标签 length: %d\n", ub->Size);

pr += 8 + ub->Size ;

}

else break;

}

DATA_BLOCK  db = (DATA_BLOCK )pr;

if(db->DataID  != DATA_SIGN_ID)

{

delete [] r;

printf("数据错误\n");

return NULL;

}

return r;

}

BYTE  getWaveData(BYTE  wav, int  dLen)

{

UNKNOW_BLOCK  ub = (UNKNOW_BLOCK )wav;

while(ub->ID != DATA_SIGN_ID)

{

switch(ub->ID)

{

case DATA_SIGN_ID:

break;

case FMT__SIGN_ID:

case FACT_SIGN_ID:

ub = (UNKNOW_BLOCK )(((BYTE )ub) + ub->Size + 8);

break;

default:

printf("错误标签 %08X\n", ub->ID );

return NULL;

}

}

DATA_BLOCK  db = (DATA_BLOCK )ub;

dLen = db->DataSize;

return db->Data;

}

size_t getTotalLen(BYTE  wav)

{

size_t r = 0;

UNKNOW_BLOCK  ub = (UNKNOW_BLOCK )wav;

while(1)

{

switch(ub->ID)

{

case DATA_SIGN_ID:

r += ub->Size + 8;

return r;

case FMT__SIGN_ID:

case FACT_SIGN_ID:

r += ub->Size + 8;

ub = (UNKNOW_BLOCK )(((BYTE )ub) + ub->Size + 8);

break;

default:

printf("错误标签 %08X\n", ub->ID );

return NULL;

}

}

return -1;

}

void printWaveFormat(BYTE  wav)

{

int len;

getWaveData(wav, &len);

FMT_BLOCK fb = (FMT_BLOCK )wav;

printf("Wave 格式:PCM\n");

printf("通道数量:%d\n", fb->wavFormatChannels );

printf("采样频率:%dHz\n", fb->wavFormatSamplesPerSec );

printf("每秒所需字节数:%d字节\n", fb->wavFormatAvgBytesPerSec );

printf("数据块对齐单位:%d字节\n", fb->wavFormatBlockAlign );

printf("每个采样需要的bit数:%dbit\n", fb->wavFormatBitsPerSample );

printf("长度:%2f 秒\n", (double)len / fb->wavFormatAvgBytesPerSec);

}

BYTE  catWave(BYTE & wav1, BYTE & wav2)

{

FMT_BLOCK  fb1 = (FMT_BLOCK )wav2;

const FMT_BLOCK  fb2 = (const FMT_BLOCK )wav2;

if(

fb1->wavFormatAvgBytesPerSec == fb2->wavFormatAvgBytesPerSec &&

fb1->wavFormatBitsPerSample  == fb2->wavFormatBitsPerSample  &&

fb1->wavFormatBlockAlign     == fb2->wavFormatBlockAlign     &&

fb1->wavFormatChannels       == fb2->wavFormatChannels       &&

fb1->wavFormatFormatTag      == fb2->wavFormatFormatTag      &&

fb1->wavFormatSamplesPerSec  == fb2->wavFormatSamplesPerSec)

{

int len1 = getTotalLen(wav1), len2;

BYTE  Data2 = getWaveData(wav2, &len2);

BYTE  nD = new BYTE[len1 + len2 + 10];

if(nD == NULL) return NULL;

memcpy(nD, wav1, len1);

delete [] wav1;

wav1 = nD;

BYTE  Data1 = getWaveData(wav1, &len1);

DATA_BLOCK  db1 = (DATA_BLOCK )(Data1 - 8);

db1->DataSize += len2;

memcpy(Data1 + len1, Data2, len2);

return wav1;

}

return NULL;

}

int saveWaveFile(const char  name, BYTE  wav)

{

FILE fp = fopen(name, "wb");

if(fp == 0) return 0;

int len = getTotalLen(wav);

RIFF_HEADER rh;

rhRiffFormat = WAVE_SIGN_ID;

rhRiffID     = RIFF_SIGN_ID;

rhRiffSize   = len + 4;

fwrite(&rh, sizeof(rh), 1, fp);

fwrite(wav, 1, len, fp);

fclose(fp);

return 1;

}

Core Audio 是iOS和MAC系统中的关于数字音频处理的基础,它是应用程序用来处理音频的一组软件框架,所有关于iOS音频开发的接口都是由Core Audio来提供或者经过它提供的接口来进行封装的。

其实一句话,它是任何iOS或者MAC系统音频处理框架的基础。

具体可以用官方文档的一张图表示。

接下来我们就一起分析一下。

这里的高级别服务,更加接近于顶层,基本上我们很多关于音频开发的工作在这一层就可以完成。

它位于框架 AudioToolbox 中。

提供录制、播放、暂停、循环、和同步音频它自动采用必要的编解码器处理压缩的音频格式。

要在iOS设备上播放和录制音频,苹果推荐我们使用 AVFoundation 框架中的 AVAudioPlayer 和 AVAudioRecorder 类。虽然用法比较简单,但是不支持流式;这就意味着:在播放音频前,必须等到整个音频加载完成后,才能开始播放音频;录音时,也必须等到录音结束后,才能获取到录音数据。这给应用造成了很大的局限性。为了解决这个问题,我们就需要使用 Audio Queue Services 来播放和录制音频。感兴趣的可以看我前面写的几篇关于 Audio Queue Services 的文章。这里只是简单的给出录音和播放的原理图,具体原理和流程,看我前面写的那几篇,都有详细的介绍。

它位于框架 AVFoundation 中。

是专为IOS平台提供的基于Objective-C接口的音频播放类,可以支持iOS所支持的所有音频的播放,它主要支持以下音频格式。

这个是纯OC的实现,特点就是调用简单,下面简单的看一下他的API。

由 Audio File 与 Audio Converter 组合而成,提供压缩及无压缩音频文件的读写能力。

它与 Audio File Services 、 Audio File Stream Services 和 Audio Queue Services 等同时存在 AudioToolbox 框架中。 ExtendedAudioFile 相对 Audio File Services 和 Audio Converter Services ,API调用非常简单和明确,并且不需要去处理 AudioStreamPacketDescription ,在实际开发中逻辑更为清晰。

它就是存在框架 OpenAL 中。

是CoreAudio对OpenAL标准的实现,可以播放3D混音效果。

OpenAL 主要的功能是在来源物体、音效缓冲和收听者中编码。来源物体包含一个指向缓冲区的指标、声音的速度、位置和方向,以及声音强度。收听者物体包含收听者的速度、位置和方向,以及全部声音的整体增益。缓冲里包含 8 或 16 位元、单声道或立体声 PCM 格式的音效资料,表现引擎进行所有必要的计算,如距离衰减、多普勒效应等。

不同于 OpenGL 规格,OpenAL 规格包含两个API分支;以实际 OpenAL 函式组成的核心,和 ALC API , ALC 用于管理表现内容、资源使用情况,并将跨平台风格封在其中。还有 “ALUT ”程式库,提供高阶“易用”的函式,其定位相当于 OpenGL 的 GLUT 。

该层功能比较齐全,包括音频数据格式转换,音频文件读写,音频流解析,插件工作支持等。

它位于框架 AudioToolbox 中。

负责音频数据格式的转换

它位于框架 AudioToolbox 中。

负责音频数据的读写。

它位于框架 AudioToolbox 中。

支持均衡器和混音器等数字信号处理的插件。

它位于框架 AudioToolbox 中。

负责流解析。

它位于框架 Core Audio 中。

负责音频音频时钟同步。

该主要在MAC上的音频APP实现中并且需要最大限度的实时性能的情况下使用,大部分音频APP不需要使用该层的服务。而且,在iOS上也提供了具备较高实时性能的高层API达到你的需求。例如 OpenAL ,在游戏中具备与I/O直接调用的实时音频处理能力。

它在 IOKit 框架中,与硬件驱动交互。

获得用户空间访问硬件设备和驱动程序。 I / O Kit 框架通过设备接口机制实现对I / O Kit对象(驱动程序和结点)的非内核访问。

音频硬件抽象层,使API调用与实际硬件相分离,保持独立。

它位于 Core MIDI 框架中,与MIDI设备(如硬件键盘和合成器)进行通信。

Core MIDI 框架提供了用于与MIDI(乐器数字接口)设备(包括硬件键盘和合成器)进行通信的API。 使用基座连接器或网络从iOS设备进行连接。 有关使用基座连接器的更多信息,请参阅Apple的 MFi program 。

访问电脑硬件时钟。

只实现音频的播放,没有其他需求, AVAudioPlayer 就可以满足需求。它的接口使用简单,不用关心其中的细节,通常只提供给它一个播放源的URL地址,并且调用其play、pause、stop等方法进行控制,observer其播放状态更新UI即可。

APP需要对音频进行流播放,就需要 AudioFileStreamer 加 Audio Queue ,将网络或者本地的流读取到内存,提交给 AudioFileStreamer 解析分离音频帧,分离出来的音频帧可以送给 AudioQueue 进行解码和播放,可参考下面。

AudioStreamer

FreeStreamer

AFSoundManager

APP需要需要对音频施加音效(均衡器、混响器),就是除了数据的读取和解析以外还需要用到AudioConverter或者Codec来把音频数据转换成PCM数据,再由AudioUnit+AUGraph来进行音效处理和播放,可参考下面。

DouAudioStreamer

TheAmazingAudioEngine

AudioKit

1 iOS Audio Unit(一)

1 先将你的音频文件载入到单事件编辑窗口。

2 先解决“音量小”的问题,如下图:

1)

2)

执行上面两步后,你会发现音频波形变大了,听一听。

3 停顿的量化值只能是“时长”,音频块中,没有“波形”(只有直线)的地方,则是停顿的地方。框选那个地方,可在右下角的“Length” 参数框中,看到“停顿”的时长,单位为“分:秒:毫秒”。(发音的“时长”同样,只是框选对象为“波形”处。)

4 停顿次数也可直观地看到,但不要指望 CEP 能给你自动检测出“发音”和“停顿”的频次。

以上就是关于AAC格式及音频码流解析全部的内容,包括:AAC格式及音频码流解析、如何用51系列单片机产生DTMF双音频信号,核心是关于怎样用C语言写两个中断程序、音视频数据处理(三)-AAC音频码流分析等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/9624768.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-30
下一篇 2023-04-30

发表评论

登录后才能评论

评论列表(0条)

保存