Ffmpeg · 2024年8月25日

Ffmpeg源码开发之读取音频中的标线(Adobe Audition中添加的Cue)

开发环境

C/C++ 导入Ffmpeg(Version 6.1.1),在程序中调用相关API,非命令行形式调用

Cue 存储格式分析

在音频中添加标线,并不是所有音频中的一个标准规定, 同样, 不同的音频编辑软件生成的信息存储格式也都是不尽相同。以 Adobe audition(以下简称AU) 生成 wav 文件为例。在AU中添加Cue后,保存音频文件为 wav 格式。用十六进制工具(例如 HxD Hex Editor)打开该文件。

/*
    在 wav 格式的文件中,前44字节(通常情况下, 也有46字节)为头部信息
    0x00000000-0x00000003: RIFF 0x52 0x49 0x46 0x46, 此为标准, wav文件均相同;
    0x04-0x07: 从 0x00000008 开始到 wav文件 结束字节总数, 注意, 实际的wav字节数会大于此数,
               这就是因为某些信息并不是wav文件的标准,所以某些音频编辑软件会在 wav 标准信息存储结束后,再追加某些信息;
    0x08-0x0B: WAVE 0x57 0x41 0x56 0x45, 此为标准, wav文件均相同;
    0x0C-0x0F: fmt  0x66 0x6D 0x74 0x20, 此为标准, wav文件均相同;
    0x10-0x13:
    0x14-0x15: 格式种类;
    0x16-0x17: 声道个数, 1-单声道; 2-双声道;
    0x18-0x1B: 采样率;
    0x1C-0x1F: 波形数据传输速率, 即每秒传输的字节数;
    0x20-0x21: 暂不知
    0x22-0x23: 采样点数据的位数,即8位PCM或16位PCM等;
    0x24-0x25: 暂不知
    0x26-0x29: data 0x64 0x61 0x74 0x61, 此为标准, wav文件均相同;
    0x2A-0x2D: PCM数据总长度
*/

在AU中添加Cue后,信息会存储在上述所有数据之后,以 0x63 0x75 0x65 0x20 做标识,此后为Cue信息,格式如下:

1. 4个字节: 0x63 0x75 0x65 0x20 表示 cue 标线信息开始,如下图红框;
2. 4个字节: cue 标线信息共占据了多少个字节, 每一个 cue标线 占据 0x18 个字节,如下图绿框;
3. 4个字节: 共有几个 cue 标线,如下图蓝框0000000A,共十个cue;
4. 每个 cue 标线信息,如下图棕框:
    1. 4个字节: 标线序号(顺序同AU中一致);
    2. 4个字节: 标线位置(采样点序号);
    3. 4个字节: 0x64 0x61 0x74 0x61 固定格式, 表示数据信息开始;
    4. 12个字节, 其他信息数据。

file

源代码

extern "C" {
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
}

int AudioEditor::audio_load()
{
    int res = 1;
    int ret = -1;
    const AVInputFormat* format = NULL;
    AVFormatContext* format_context = NULL;

    std::string src_filepath = "";
    std::vector src_cue_ls(0);

    char* error_buffer = NULL;

    // 申请 错误日志 空间
    error_buffer = (char*)malloc(1024 * sizeof(char));
    if (!error_buffer)
    {
        goto err;
    }

    // 分配 音频解析环境 内存
    format_context = avformat_alloc_context();
    if (format_context == NULL)
    {
        goto err;
    }

    // 读取音频
    ret = avformat_open_input(&audio->format_context, src_filepath.c_str(), format, NULL);
    if (ret != 0)
    {
        av_strerror(ret, error_buffer, 1024);
        goto err;
    }

    // 解析 音频信息, 包含 声道数|采样率|比特率 等等
    ret = avformat_find_stream_info(audio->format_context, NULL);
    if (ret < 0)
    {
        goto err;
    }

    // 获取标记信息
    // format_context->nb_chapters 是 cue 的总个数
    for (int i = 0; i < format_context->nb_chapters; i++)
    {
        AVChapter* tmp_chapter = format_context->chapters[i];
        src_cue_ls.push_back(tmp_chapter->start);
    }

    // 将音频帧定位到 0秒 处
    av_seek_frame(format_context, -1, 0, AVSEEK_FLAG_BYTE);

    // 适当时刻,释放申请的 ctx 内存,非则会内存溢出
    avformat_free_context(format_context);

    goto end;

err:
    res = (res == 1) ? 0 : res;
end:
    if (error_buffer) free(error_buffer);
    return res;
}