AAC格式及音频码流解析

介绍

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

  • ADIF:Audio Data Interchange Format 音频数据交换格式。这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进行。故这种格式常用在磁盘文件中。

  • ADTS:Audio Data Transport Stream。是AAC音频的传输流格式。这种格式的特征是它是一个有同步字的比特流,解码可以在这个流中任何位置开始。

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

ADTS 格式

image.png

从图上可以总结出两点:

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

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

固定头信息:adts_fixed_header()

image.png
  • syncword :同步头 总是0xFFF, all bits must be 1,代表着一个ADTS帧的开始
  • Layer:always: '00'
  • profile:表示使用哪个级别的AAC,有些芯片只支持AAC LC 。在MPEG-2 AAC中定义了4种:
0: AAC Main
1:AAC LC (Low Complexity)
2:AAC SSR (Scalable Sample Rate)
3:AAC LTP (Long Term Prediction)

  • sampling_frequency_index:表示使用的采样率下标
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

  • channel_configuration: 表示声道数
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,

  • 可变头信息:adts_variable_header()
image.png
  • copyrighted_id_bit:编码时设置为0,解码时忽略
  • copyrighted_id_start:编码时设置为0,解码时忽略
  • aac_frame_length:ADTS帧长度包括ADTS长度和AAC声音数据长度的和。即 - - aac_frame_length = (protection_absent == 0 ? 9 : 7) + audio_data_length
  • adts_buffer_fullness:固定为0x7FF。表示是码率可变的码流
  • number_of_raw_data_blocks_in_frame:表示当前帧有number_of_raw_data_blocks_in_frame + 1 个原始帧(一个AAC原始帧包含一段时间内1024个采样及相关数据)。

实例分析

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

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

原理

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

image.png

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

代码

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/// 从buffer里获取ADTS帧数据到data里,获取大小为data_size
/// @param buffer buffer地址
/// @param buf_size buffer大小
/// @param data data数据地址
/// @param data_size data数据大小
int getADTSframe(unsigned char* buffer, int buf_size, unsigned char* data ,int* data_size){
    int size = 0;
 
    if(!buffer || !data || !data_size ){
        return -1;
    }
 
    while(1){
        if(buf_size  < 7 ){// ADTS帧的ADTS header为7个字节或者9个字节,所以判断<7,则肯定不存在一个ADTS,因为ADTS = ADTS header + AAC ES
            return -1;
        }
        //Sync words
        if((buffer[0] == 0xff) && ((buffer[1] & 0xf0) == 0xf0) ){
            /**
             1. aac_frame_length 为13bit, 从第31bit开始,到43bit结束
             2. 按位或|= 运算:   a |= b 为 a = a | b, 只要有一个为1, 则结果为1
              3. >>, << 运算符优先级比按位或|=优先级高
             4. 0x03,代表一个字节,其二进制为0000 0011
            5. ((buffer[3] & 0x03)这个是取第4个字节和(0000 0011)进行与运算,结果是取第4个字节后2位,即第31位开始,到32位结束
             6. (第31位到第32位)<< 11, 一共13bit,左移动11bit,则这2bit为13bit中的高2bit,假如(buffer[3] & 0x03)为二进制11, 则左移11bit后,为1 1000 0000 0000,  一共13位,11就变成13位里的高2位
             7.size = size | (1 1000 0000 0000) = 1 1000 0000 0000
             */
            size |= ((buffer[3] & 0x03) <<11);     //high 2 bit
            /**
             1.buffer[4]拿到第5个字节,即33bit到40bit,
             2.左移3位,假如buffer[4]为1111 1111 , 则左移动3位,为 111 1111 1000 一共11位
             3. size = size |  (buffer[4]<<3) 即 1 1000 0000 0000 |111 1111 1000 = 1 1111 1111 1000, 可以看出这8位是13bit中间的8位
             */
            size |= buffer[4]<<3;                //middle 8 bit
            /**
             1. buffer[5]拿到第6个字节,即41bit到48bit,
             2.0xe0的二进制为1110 0000
             3. (buffer[5] & 0xe0) 即获取字节的高3位,即41bit到43bit,右移5位,即1110 0000 变成0000 0111
             4. size = size | ((buffer[5] & 0xe0)>>5) = 1 1111 1111 1000 |0 000 0000 0111 = 1 1111 1111 1111
            5. 可以看出这个是13bit里的低三位
             */
            size |= ((buffer[5] & 0xe0)>>5);        //low 3bit
            break;
        }
        // 从下一个字节继续查找Sync words
        --buf_size;
        ++buffer;
    }
 
    if(buf_size < size){
        return 1;
    }
 //
    memcpy(data, buffer, size);
    *data_size = size;
 
    return 0;
}

int simplest_aac_parser(char *url)
{
    int data_size = 0;
    int size = 0;
    int cnt=0;
    int offset=0;
 
    //FILE *myout=fopen("output_log.txt","wb+");
    FILE *myout=stdout;
 
    unsigned char *aacframe=(unsigned char *)malloc(1024*5);
    unsigned char *aacbuffer=(unsigned char *)malloc(1024*1024);
 
    // 打开aac文件
    FILE *ifile = fopen(url, "rb");
    if(!ifile){
        printf("Open file error");
        return -1;
    }
 
    printf("-----+- ADTS Frame Table -+------+\n");
    printf(" NUM | Profile | Frequency| Size |\n");
    printf("-----+---------+----------+------+\n");
 
    while(!feof(ifile)){
        /**
         ptr -- 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
         size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
         size_t     fread(void * __restrict __ptr, size_t __size, size_t __nitems, FILE * __restrict __stream);
         size -- 这是要读取的每个元素的大小,以字节为单位。
         nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
         stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
         从给定流 stream 读取数据到 ptr 所指向的数组中。
         成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。
         读取1024*1024-offset 个元素大小为1个字节的数据到aacbuffer+offset地址
         data_size为字节个数
         */
         
        data_size = fread(aacbuffer+offset, 1, 1024*1024-offset, ifile);
        fprintf(myout,"----------offset: %d, data_size: %d, aacbuffer: %p\n",offset, data_size, aacbuffer);
        unsigned char* input_data = aacbuffer;
 
        while(1)
        {
            // 从input_data里获取aacframe,input_data的大小为data_size, 获取的大小为size
            int ret=getADTSframe(input_data, data_size, aacframe, &size);
            if(ret==-1){
                break;
            }else if(ret==1){
                /**
                 void    *memcpy(void *__dst, const void *__src, size_t __n);
                 从存储区 __src 复制 n 个字节到存储区 __dst
                 当获取时,data_size < 一帧的大小时,则将剩余的data_size大小写入到aacbuffer里
                 */
                memcpy(aacbuffer,input_data,data_size);
                offset=data_size;
                break;
            }
 
            char profile_str[10]={0};
            char frequence_str[10]={0};
            /**
             profile:表示使用哪个级别的AAC,有些芯片只支持AAC LC 。2bit, 从第17bit开始,到18bit结束,即第三个字节前2位
             在MPEG-2 AAC中定义了3种:
             0: AAC Main
             1:AAC LC (Low Complexity)
             2:AAC SSR (Scalable Sample Rate)
             3:AAC LTP (Long Term Prediction)
             
             0xC0------11000000
             aacframe[2]&0xC0取第3个字节的前2位
             */
            unsigned char profile=aacframe[2]&0xC0;
            profile=profile>>6;
            switch(profile){
            case 0: sprintf(profile_str,"Main");break;
            case 1: sprintf(profile_str,"LC");break;
            case 2: sprintf(profile_str,"SSR");break;
            default:sprintf(profile_str,"unknown");break;
            }
            /**
             0x3C 二进制为 0011 1100
             aacframe[2]&0x3C取第三个字节中间4位,即从第19位开始,到22位结束
             比如aacframe[2]为 0111 0101, 则aacframe[2]&0x3C为0011 0100
             0011 0100右移2位,则为0000 1101
             */
            unsigned char sampling_frequency_index=aacframe[2]&0x3C;
            sampling_frequency_index=sampling_frequency_index>>2;
            switch(sampling_frequency_index){
            case 0: sprintf(frequence_str,"96000Hz");break;
            case 1: sprintf(frequence_str,"88200Hz");break;
            case 2: sprintf(frequence_str,"64000Hz");break;
            case 3: sprintf(frequence_str,"48000Hz");break;
            case 4: sprintf(frequence_str,"44100Hz");break;
            case 5: sprintf(frequence_str,"32000Hz");break;
            case 6: sprintf(frequence_str,"24000Hz");break;
            case 7: sprintf(frequence_str,"22050Hz");break;
            case 8: sprintf(frequence_str,"16000Hz");break;
            case 9: sprintf(frequence_str,"12000Hz");break;
            case 10: sprintf(frequence_str,"11025Hz");break;
            case 11: sprintf(frequence_str,"8000Hz");break;
            default:sprintf(frequence_str,"unknown");break;
            }
 
 
            fprintf(myout,"%5d| %8s|  %8s| %5d|\n",cnt,profile_str ,frequence_str,size);
            // 读取后data_size减去一帧ADTS Frame的大小, 数据buffer则向前移动到下一帧的位置
            data_size -= size;
            input_data += size;
            cnt++;
        }
 
    }
    fclose(ifile);
    free(aacbuffer);
    free(aacframe);
 
    return 0;
}

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self parseAAC];
}

- (void)parseAAC {
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"131" ofType:@"aac"];
    NSLog(@"filePath: %@", filePath);
    if (filePath) {
        const char *c_filePath = [filePath UTF8String];
        if (c_filePath) {
            simplest_aac_parser(c_filePath);
        }
    }
}

@end

结果

image.png

参考

AAC格式ADTS+实例剖析 https://www.jianshu.com/p/c48cac7eb962

视音频数据处理入门:AAC音频码流解析 https://blog.csdn.net/leixiaohua1020/article/details/50535042

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容