美文网首页
AAC格式及音频码流解析

AAC格式及音频码流解析

作者: BohrIsLay | 来源:发表于2022-06-04 16:55 被阅读0次

介绍

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

相关文章

网友评论

      本文标题:AAC格式及音频码流解析

      本文链接:https://www.haomeiwen.com/subject/nftlmrtx.html