关于AAC 数据包前写入 ADTS 头的详解

前言

在音频处理领域中,AAC(Advanced Audio Coding)是一种常用的音频编码格式,因其高效的压缩性能和良好的音质广泛应用于各类多媒体文件中。通常情况下,AAC 音频流需要被封装在某种容器格式中,例如 MP4。然而,在某些情况下,尤其是当你希望从 MP4 文件中提取原始 AAC 音频数据并进行存储时,添加 ADTS(Audio Data Transport Stream)头部信息至每个 AAC 数据包变得至关重要。

ADTS 是 AAC 的一种封装格式,它允许解码器正确地解码数据包。本文将详细介绍如何在从 MP4 文件中提取的每个 AAC 数据包前添加 ADTS 头,并给出完整的代码示例。

什么是 ADTS 头?

ADTS 是一种 AAC 音频流封装格式,用于将 AAC 编码的音频数据打包为帧。这些帧可以在传输过程中独立传输,并由接收方正确解码。ADTS 头为解码器提供了帧的必要信息,如采样率、声道配置、数据大小等。

ADTS 头的格式如下:

  • Syncword (12 bits): 标志帧的开始,固定为 0xFFF
  • ID (1 bit): 标记 MPEG 标准,0 表示 MPEG-4,1 表示 MPEG-2。
  • Layer (2 bits): 固定为 00
  • Protection_absent (1 bit): 标记是否有 CRC 校验。
  • Profile (2 bits): 表示 AAC 的编解码器配置。
  • Sampling_frequency_index (4 bits): 表示采样率的索引值。
  • Private_bit (1 bit): 私有位,通常忽略。
  • Channel_configuration (3 bits): 表示声道配置。
  • Original/copy (1 bit): 标记原始音频还是拷贝音频。
  • Home (1 bit): 通常忽略。
  • Copyright_identification_bit (1 bit): 通常忽略。
  • Copyright_identification_start (1 bit): 通常忽略。
  • Aac_frame_length (13 bits): 包括 ADTS 头和 AAC 数据在内的帧总长度。
  • Adts_buffer_fullness (11 bits): 表示缓冲区的占用情况。
  • Number_of_raw_data_blocks_in_frame (2 bits): 表示当前帧包含的原始数据块数量。
从 MP4 中提取 AAC 并添加 ADTS 头

在提取 AAC 音频数据时,MP4 文件本身不会包含 ADTS 头。因此,为了将音频数据保存为独立的 .aac 文件并保持其可解码性,我们需要手动为每个音频数据包添加 ADTS 头。

代码实现

以下是实现从 MP4 文件中提取 AAC 数据并为每个数据包添加 ADTS 头的完整代码:

cpp 复制代码
#include <iostream>
#include <string>
#include <fstream>
extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/log.h>
#include <libavutil/error.h>
}

#define GET_DIR(file) (std::string(file)).substr(0, std::string(file).find_last_of("/\\"))

// 获取音频采样率索引
int getSampleRateIndex(int sample_rate) {
    switch (sample_rate) {
        case 96000: return 0;
        case 88200: return 1;
        case 64000: return 2;
        case 48000: return 3;
        case 44100: return 4;
        case 32000: return 5;
        case 24000: return 6;
        case 22050: return 7;
        case 16000: return 8;
        case 12000: return 9;
        case 11025: return 10;
        case 8000: return 11;
        case 7350: return 12;
        default: return 15; // reserved
    }
}

// ADTS header generator
void writeADTSHeader(std::ofstream &outFile, const AVCodecParameters *codecPar, const int size)
{
    uint8_t adts[7] = {0};

    int profile = codecPar->profile + 1; // AAC LC (Low Complexity) profile is 1
    int freqIdx = getSampleRateIndex(codecPar->sample_rate);
    int chanCfg = codecPar->ch_layout.nb_channels;

    adts[0] = 0xFF; // Syncword
    adts[1] = 0xF1; // Syncword, MPEG-2 Layer (0 for MPEG-4), protection absent
    adts[2] = (profile << 6) + (freqIdx << 2) + (chanCfg >> 2);
    adts[3] = ((chanCfg & 3) << 6) + ((size + 7) >> 11);
    adts[4] = ((size + 7) & 0x7FF) >> 3;
    adts[5] = (((size + 7) & 7) << 5) + 0x1F;
    adts[6] = 0xFC; // Number of raw data blocks in frame

    outFile.write((char *)adts, 7);
}

int main(int argc, char *argv[])
{
    AVFormatContext *fmt_ctx = nullptr;
    av_log_set_level(AV_LOG_INFO);

    std::string input_file = GET_DIR(__FILE__) + "/../Res/video.mp4";
    std::string output_file = GET_DIR(__FILE__) + "/video.aac";

    av_log(nullptr, AV_LOG_INFO, "%s\n", input_file.c_str());
    char buf[1024];

    int ret = avformat_open_input(&fmt_ctx, input_file.c_str(), nullptr, nullptr);
    if (ret < 0)
    {
        av_strerror(ret, buf, sizeof(buf));
        av_log(nullptr, AV_LOG_ERROR, "can't open input : %s\n", buf);
        return -1;
    }
    av_dump_format(fmt_ctx, 0, input_file.c_str(), 0);

    avformat_find_stream_info(fmt_ctx, nullptr);

    int audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
    if (audio_index < 0)
    {
        av_log(nullptr, AV_LOG_ERROR, "can't find best stream");
        avformat_close_input(&fmt_ctx);
        return -1;
    }

    AVPacket *packet = av_packet_alloc();
    if (!packet)
    {
        av_log(nullptr, AV_LOG_ERROR, "Memory allocation failed");
        return -1;
    }

    const AVCodec *codec = avcodec_find_decoder(fmt_ctx->streams[audio_index]->codecpar->codec_id);
    if (!codec)
    {
        av_log(nullptr, AV_LOG_WARNING, "Decoder not found");
        return -1;
    }

    AVCodecContext *codecCtx = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(codecCtx, fmt_ctx->streams[audio_index]->codecpar);

    if (avcodec_open2(codecCtx, codec, nullptr) < 0)
    {
        av_log(nullptr, AV_LOG_ERROR, "Failed to open codec");
        return -1;
    }

    std::ofstream outputFile(output_file.c_str(), std::ofstream::binary);
    if (!outputFile.is_open())
    {
        av_log(nullptr, AV_LOG_ERROR, "Failed to open output file");
        return -1;
    }

    while (av_read_frame(fmt_ctx, packet) >= 0)
    {
        if (packet->stream_index == audio_index)
        {
            // Write ADTS header
            writeADTSHeader(outputFile, fmt_ctx->streams[audio_index]->codecpar, packet->size);
            outputFile.write((char *)packet->data, packet->size);
            
        }
        av_packet_unref(packet);
    }
    avformat_close_input(&fmt_ctx);
    av_packet_free(&packet);
    avcodec_free_context(&codecCtx);
    outputFile.close();
    return 0;
}
代码解析
  1. 获取音频采样率索引 : ADTS 头的采样率使用索引来表示,不同的采样率对应不同的索引值。通过 getSampleRateIndex 函数,可以根据采样率返回相应的索引。

  2. 生成 ADTS 头writeADTSHeader 函数用于生成 ADTS 头并写入到输出文件。ADTS 头包含了音频数据的各种信息,如编码配置、采样率、声道数以及当前帧的数据大小。

  3. 从 MP4 文件提取 AAC 数据 : 使用 FFmpeg 的 avformat_open_inputav_find_best_stream 函数打开 MP4 文件并找到音频流。然后通过 av_read_frame 函数逐帧读取音频数据。

  4. 写入 ADTS 头与 AAC 数据: 每读取一帧音频数据,就为其添加 ADTS 头,并将其写入到输出文件中。输出文件将保存为带有 ADTS 头的 AAC 格式文件,可直接播放或进一步处理。

结语

通过上述步骤,你可以轻松从 MP4 文件中提取原始的 AAC 数据并为其添加 ADTS 头,确保其可解码性。这种方法不仅能够保留音频数据的原始质量,还能确保在各种播放器和解码器中正常播放。

了解并掌握如何手动处理 ADTS 头,对于音频开发人员以及多媒体应用开发者来说,具有重要意义。

相关推荐
MonkeyKing_sunyuhua17 小时前
FFmpeg将mp4的文件转化为m4a
ffmpeg
zanglengyu18 小时前
RK3568硬解码并与Qt界面融合显示深入探究
开发语言·qt·ffmpeg·rk3568硬解码rtsp
橘子味的茶二2 天前
ffmpeg内存模型
ffmpeg
TPCloud2 天前
windows 11编译安装ffmpeg(包含ffplay)
windows·ffmpeg·源码安装·mysys
runing_an_min2 天前
ffmpeg视频滤镜:缓入缓出-fade
ffmpeg·音视频·fade·缓出·缓入
ssslar3 天前
FFMPEG录屏(22)--- Linux 下基于X11枚举所有显示屏,并获取大小和截图等信息
linux·运维·ffmpeg
MonkeyKing_sunyuhua3 天前
FFmpeg 怎么裁剪m4a的音频,从一个时间点开始,裁剪15秒钟的视频
ffmpeg·音视频
DO_Community3 天前
教程:FFmpeg结合GPU实现720p至4K视频转换
ffmpeg·音视频
x66ccff3 天前
使用NVIDIA GPU加速FFmpeg视频压制:完全指南
ffmpeg·音视频
冷眼Σ(-᷅_-᷄๑)3 天前
如何使用ffmpeg命令行进行录屏
ffmpeg