总结: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音频码流分析等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)