AAC音频文件解析

1.什么是AAC格式

1.基本定义

  • 全称:Advanced Audio Coding,中文称为“高级音频编码”。
  • 起源:出现于1997年,由Fraunhofer IIS、杜比实验室、AT&T、索尼等公司共同开发,旨在取代MP3格式。
  • 技术基础:基于MPEG-2的音频编码技术,后随着MPEG-4标准的出现,AAC重新集成了其特性,并加入了SBR(Spectral Band Replication,频带重现)技术和PS(Parametric Stereo,参量立体声)技术,因此也被称为MPEG-4 AAC。

2.主要特点

  1. 高压缩效率:AAC相较于MP3等格式,能够在保持较高音质的前提下,实现更高的压缩率,使得文件体积更小,更适合在线流媒体和存储。
  2. 高音质保真度:AAC支持更高的采样率和位深度,能够提供更接近原始录音的音质,尤其在低比特率下表现优异。
  3. 多通道支持:AAC支持多达48个音频通道,适用于环绕声和高级音频应用,满足多样化的音频播放需求。
  4. 广泛兼容性:几乎所有主流的音频播放器、移动设备和操作系统都支持AAC格式,使得用户能够在各种平台上无缝播放和分享音频文件。

3.应用领域

  1. 音乐播放:AAC被广泛用于音乐播放软件中,如iTunes、Apple Music等,因其高质量的音频再现和广泛的兼容性而受到欢迎。
  2. 电影配音:在电影制作中,AAC因其高质量音质和对多声道支持,成为后期制作和发行的首选格式之一。
  3. 无线通信:由于其高效和高质量的音频传输特性,AAC也广泛应用于无线通信领域,如蓝牙和移动网络。
  4. 游戏音效:许多游戏使用AAC来存储和播放游戏内的音效,以提供更真实和生动的游戏体验。

2.AAC文件格式

aac文件目前主要有两种格式类型。

ADIF:

特征

  1. 明确的数据开始点:ADIF格式的特征是可以明确找到音频数据的开始位置,不需要在音频数据流中间开始解码。这意味着解码过程必须从明确定义的数据开始处进行。
  2. 统一的头部信息:与ADTS格式不同,ADIF只有一个统一的头部,包含了音频数据的全局信息。因此,在解码之前,必须获取到所有的数据。
  3. 常用于磁盘文件:由于其特性,ADIF格式常被用于磁盘文件中,便于数据的存储和交换。

用途:

  1. 数据交换:ADIF格式的主要用途是在不同的音频系统或设备之间进行音频数据的交换。它简化了数据共享过程,使得不同系统间的音频数据能够准确、高效地传输。
  2. 存储:由于ADIF格式具有明确的数据开始点和统一的头部信息,它非常适合用于音频数据的长期存储。在磁盘文件中,ADIF格式可以确保音频数据的完整性和可访问性。
  3. 兼容性:虽然ADIF格式可能不如ADTS格式在实时音频传输中流行,但它仍然具有一定的兼容性,可以与支持该格式的软件和硬件兼容。

在这里插入图片描述

ADTS:

  • 定义:ADTS是AAC音频的一种传输流格式,由MPEG-2标准定义,并被MPEG-4标准采用。它允许音频流通过网络传输,具有同步字的比特流特性,可以在流中任何位置开始解码。
  • 特定:
    1. 同步字:ADTS帧以固定的同步字(syncword)开始,通常为0xFFF,用于标识帧的开始位置。
    2. 头部信息:每帧包含头部信息,头部信息分为固定头部和可变头部,包含了音频的采样率、声道数、帧长度等重要信息。
    3. 灵活解码:由于ADTS帧可以在任何位置开始解码,因此非常适合用于实时音频传输,如在线音乐、视频流、网络广播等场景。

在这里插入图片描述

ADTS的格式主要由Heard和一个ES(data)组成一帧,其中Heard中由细分为Fixed_Heard(固定头部信息,每帧都一样,相当于全局信息)。和Variable_Heard(可变头部信息,每一帧可能存在差异),一般情况下每帧的头部信息为7字节。

2.1 ADTS固定头部信息

我们先来看一下,固定头部信息的字段组成,Fixed_Heard的长度大概就是28bits。

{

syncword 同步头,总是0xFFF,代表一个帧的开头。 12bit

ID MPEG标识符,0标识MPEG-4,1标识MPEG-2 1bit

Layer 总是0 2bit

protection_absent 是否存在CRC校验 1bit

profile AAC级别 2bit

sampling_frequency_index 采样频率下标,通过这个

下标,可用在如下数组中找到对应的采样频率信息。 4bit

private_bit 预留作用 1bit

channel_configuration: 表示声道数 3bit

original_copy 表示原始的还是拷贝来的 1bit

home 预留位 1bit

}

sampling_frequency_index 索引下标对应的采集频率

在这里插入图片描述

2.2 ADTS可变头部信息

{

copyright_identification_bit 表示是否含版权信息 1bit

copytight_identification_start 表示是否包含版权信息的其实帧 1bit

aac_frame_length ⼀个ADTS帧的⻓度包括ADTS头和AAC原始流 13bit

adts_buffer_fullness 0x7FF 说明是码率可变的码流。 11bit

number_of_raw_data_blocks_in_frame 表示当前有number_of_raw_data_blocks_in_frame+1个AAC数据块 2bit

}

在这里需要补充一点,aac_frame_length 所表示的是heard+data 而heard的长度位7或者9字节。

ADTS头部长度取决于protection_absent字段的设置:

  • protection_absent=1时,表示不进行CRC(循环冗余校验),此时ADTS头部长度为7字节。
  • protection_absent=0时,表示进行CRC校验,此时ADTS头部长度会增加2字节的CRC校验码,总长度为9字节。

3 使用ffmpeg解aac

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>

#define ADTS_HEADER_LEN  7;

const int sampling_frequencies[] = {
    96000,  // 0x0
    88200,  // 0x1
    64000,  // 0x2
    48000,  // 0x3
    44100,  // 0x4
    32000,  // 0x5
    24000,  // 0x6
    22050,  // 0x7
    16000,  // 0x8
    12000,  // 0x9
    11025,  // 0xa
    8000   // 0xb
    // 0xc d e f是保留的
};

int adts_header(char * const p_adts_header, const int data_length,
                const int profile, const int samplerate,
                const int channels)
{

    int sampling_frequency_index = 3; // 默认使用48000hz
    int adtsLen = data_length + 7;

    int frequencies_size = sizeof(sampling_frequencies) / sizeof(sampling_frequencies[0]);
    int i = 0;
    for(i = 0; i < frequencies_size; i++)
    {
        if(sampling_frequencies[i] == samplerate)
        {
            sampling_frequency_index = i;//得出当前采集索引
            break;
        }
    }
    if(i >= frequencies_size)//没有找到任何采集频率 索引,不正常
    {
        printf("unsupport samplerate:%d\n", samplerate);
        return -1;
    }

    p_adts_header[0] = 0xff;         //syncword:0xfff                          高8bits
    p_adts_header[1] = 0xf0;         //syncword:0xfff                          低4bits
    p_adts_header[1] |= (0 << 3);    //MPEG Version:0 for MPEG-4,1 for MPEG-2  1bit
    p_adts_header[1] |= (0 << 1);    //Layer:0                                 2bits
    p_adts_header[1] |= 1;           //protection absent:1                     1bit

    p_adts_header[2] = (profile)<<6;            //profile:profile               2bits
    p_adts_header[2] |= (sampling_frequency_index & 0x0f)<<2; //sampling frequency index:sampling_frequency_index  4bits
    p_adts_header[2] |= (0 << 1);             //private bit:0                   1bit
    p_adts_header[2] |= (channels & 0x04)>>2; //channel configuration:channels  高1bit

    p_adts_header[3] = (channels & 0x03)<<6; //channel configuration:channels 低2bits
    p_adts_header[3] |= (0 << 5);               //original:0                1bit
    p_adts_header[3] |= (0 << 4);               //home:0                    1bit
    p_adts_header[3] |= (0 << 3);               //copyright id bit:0        1bit
    p_adts_header[3] |= (0 << 2);               //copyright id start:0      1bit
    p_adts_header[3] |= ((adtsLen & 0x1800) >> 11);           //frame length:value   高2bits

    p_adts_header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);     //frame length:value    中间8bits
    p_adts_header[5] = (uint8_t)((adtsLen & 0x7) << 5);       //frame length:value    低3bits
    p_adts_header[5] |= 0x1f;                                 //buffer fullness:0x7ff 高5bits
    p_adts_header[6] = 0xfc;      //‭11111100‬       //buffer fullness:0x7ff 低6bits
    // number_of_raw_data_blocks_in_frame:
    //    表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。

    return 0;
}

int main(int argc, char *argv[])
{
    
    if(argc != 3)
    {
        
        return -1;//直接返回
        
    }
    
    const char *in_filename = argv[1];      // 输入文件
    
    const char* aac_filename = argv[2];     // 输出文件
    int ret = -1;
    char errors[1024];
    FILE *aac_fd = NULL;
    int audio_index = -1;
    int len = 0;
    
    AVFormatContext *ifmt_ctx = NULL;//存储文件容器类型上下文信息
    AVPacket pkt; //未经过解码的帧
    
  
    aac_fd = fopen(aac_filename, "wb");//打开输出文件
    if (!aac_fd)
    {
        av_log(NULL, AV_LOG_DEBUG, "Could not open destination file %s\n", aac_filename);
        return -1;
    }

    // 打开输入文件,并将文件容器信息关联到ifmt_ctx 的信息上下文中。后续可用通过ifmt_ctx 获取容器的信息
    if((ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL)) < 0)
    {
        av_strerror(ret, errors, 1024);
        av_log(NULL, AV_LOG_DEBUG, "Could not open source file: %s, %d(%s)\n",
               in_filename,
               ret,
               errors);
        return -1;
    }

    // 获取解码器信息等内容
    if((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0)
    {
        av_strerror(ret, errors, 1024);
        av_log(NULL, AV_LOG_DEBUG, "failed to find stream information: %s, %d(%s)\n",
               in_filename,
               ret,
               errors);
        return -1;
    }


    // 初始化packet
    av_init_packet(&pkt);

    //找到aac 类型的数据流的索引
    audio_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); 
    if(audio_index < 0)
    {
        av_log(NULL, AV_LOG_DEBUG, "Could not find %s stream in input file %s\n",
               av_get_media_type_string(AVMEDIA_TYPE_AUDIO),
               in_filename);
        return AVERROR(EINVAL);
    }

    // 打印AAC级别
    printf("audio profile:%d, FF_PROFILE_AAC_LOW:%d\n",
           ifmt_ctx->streams[audio_index]->codecpar->profile,
           FF_PROFILE_AAC_LOW);

    if(ifmt_ctx->streams[audio_index]->codecpar->codec_id != AV_CODEC_ID_AAC)//看看是否是AAC编码的音频
    {
        printf("the media file no contain AAC stream, it's codec_id is %d\n",
               ifmt_ctx->streams[audio_index]->codecpar->codec_id);
        goto failed;
    }
    // 读取媒体文件,并把aac数据帧写入到本地文件
    while(av_read_frame(ifmt_ctx, &pkt) >=0 )
    {
        if(pkt.stream_index == audio_index) 
        {
              AVCodecParameters *codecpar = ifmt_ctx->streams[pkt->stream_index]->codecpar;
            if (!(codecpar->codec_id == AV_CODEC_ID_AAC)) {
                  continue;
            }  
            
            char adts_header_buf[7] = {0};
            adts_header(adts_header_buf, pkt.size,
                        ifmt_ctx->streams[audio_index]->codecpar->profile,
                        ifmt_ctx->streams[audio_index]->codecpar->sample_rate,//采集频率索引
                        ifmt_ctx->streams[audio_index]->codecpar->channels);//取出一帧协议头出来
            fwrite(adts_header_buf, 1, 7, aac_fd);  // 写adts header , ts流不适用,ts流分离出来的packet带了adts header
            len = fwrite( pkt.data, 1, pkt.size, aac_fd);   // 写adts data
            if(len != pkt.size)
            {
                av_log(NULL, AV_LOG_DEBUG, "warning, length of writed data isn't equal pkt.size(%d, %d)\n",
                       len,
                       pkt.size);
            }
        }
        av_packet_unref(&pkt);
    }

failed:
    // 关闭输入文件
    if(ifmt_ctx)
    {
        avformat_close_input(&ifmt_ctx);
    }
    if(aac_fd)
    {
        fclose(aac_fd);
    }

    return 0;
}

  • 12
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值