音视频学习(五十四):基于ffmpeg实现音频重采样

发布于:2025-08-17 ⋅ 阅读:(17) ⋅ 点赞:(0)

概述

音频重采样指的是将音频数据的采样率从一个值转换到另一个值。

ffmpeg重采样

关键概念

  • AVFrame: FFmpeg中用于存储解码后的原始(raw)音视频数据的数据结构。对于音频,它包含了音频样本数据、采样率、声道布局、样本格式等信息。
  • AVCodecContext: 编解码器的上下文,包含了编解码器所需的各种参数,如码率、分辨率、采样率等。在重采样中,虽然我们不直接使用编解码器,但它帮助我们理解音频流的参数。
  • AVSampleFormat: 音频样本的格式,例如 AV_SAMPLE_FMT_S16(16位有符号整数)、AV_SAMPLE_FMT_FLT(32位浮点数)等。FFmpeg支持多种格式,重采样通常需要在不同格式之间进行转换。
  • AVChannelLayout: 声道布局,例如 AV_CHANNEL_LAYOUT_STEREO(立体声)、AV_CHANNEL_LAYOUT_MONO(单声道)等。

相关函数

swr_alloc()

  • 功能: 分配并返回一个 SwrContext 结构体,它是音频重采样的核心上下文。
  • 函数原型: SwrContext *swr_alloc(void);
  • 说明: 这是一个非常重要的步骤,它为后续的重采样操作提供了一个句柄。如果内存分配失败,该函数会返回 nullptr

av_opt_set_int()av_opt_set_sample_fmt()

  • 功能: 设置 SwrContext 的各种选项。
  • 函数原型:
    • int av_opt_set_int(void *obj, const char *name, int64_t val, int search_flags);
    • int av_opt_set_sample_fmt(void *obj, const char *name, enum AVSampleFormat fmt, int search_flags);
  • 说明: libswresample 库的参数设置是通过 libavutil 库的选项系统实现的。我们通过键值对的形式设置输入和输出的参数。
    • obj: 目标对象,这里是 SwrContext
    • name: 选项的名称,如 "in_channel_layout", "in_sample_rate", "out_sample_fmt" 等。
    • valfmt: 选项的值。
    • search_flags: 搜索标志,通常设置为 0。

swr_init()

  • 功能: 使用之前设置的参数初始化 SwrContext
  • 函数原型: int swr_init(SwrContext *s);
  • 说明: 在调用 swr_convert 之前,必须调用此函数。它会检查参数的有效性,并为重采样器分配必要的内部缓冲区和数据结构。初始化成功返回 0,失败返回负数。

av_samples_alloc_array_and_samples()

  • 功能: 分配一个音频缓冲区数组,并为音频样本分配内存。
  • 函数原型: int av_samples_alloc_array_and_samples(uint8_t ***audio_data, int *linesize, int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align);
  • 说明: 这个函数是处理FFmpeg原始音频数据的一个便利工具。
    • audio_data: 一个指向 uint8_t** 的指针,函数会在这里分配缓冲区数组的内存。对于平面(planar)格式,audio_data[i] 指向第 i 个声道的样本数据。
    • linesize: 一个指向 int 的指针,函数会在这里返回缓冲区的大小。
    • nb_channels: 声道数。
    • nb_samples: 每声道的样本数。
    • sample_fmt: 样本格式。
    • align: 内存对齐要求。通常设置为 0,表示使用默认对齐。

av_rescale_rnd()

  • 功能: 按照指定的舍入方式进行整数缩放。
  • 函数原型: int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd);
  • 说明: 在音频重采样中,用于计算重采样后所需的样本数。a 通常是输入样本数,b 是目标采样率,c 是源采样率。rnd 是舍入方式,AV_ROUND_UP 确保缓冲区足够大。

swr_get_delay()

  • 功能: 获取重采样器的当前延迟。
  • 函数原型: int64_t swr_get_delay(SwrContext *s, int64_t base);
  • 说明: 由于重采样器内部的滤波和缓冲机制,它会引入一定的延迟。这个函数可以估算这种延迟,以便在计算输出缓冲区大小时考虑进去,从而避免数据丢失。

swr_convert()

  • 功能: 执行音频重采样。
  • 函数原型: int swr_convert(SwrContext *s, uint8_t **out, int out_count, const uint8_t **in, int in_count);
  • 说明: 这是重采样操作的核心函数。
    • s: SwrContext 句柄。
    • out: 指向目标缓冲区的指针数组。
    • out_count: 目标缓冲区可以容纳的最大样本数。
    • in: 指向源缓冲区的指针数组。如果为 nullptrin_count 为 0,则表示需要刷新重采样器内部的剩余数据。
    • in_count: 源缓冲区中的样本数。
  • 返回值: 返回实际写入目标缓冲区的样本数,负数表示失败。

av_freep()

  • 功能: 释放通过 FFmpeg 分配的内存。
  • 函数原型: void av_freep(void *ptr);
  • 说明: 这是一个安全的内存释放函数,它会先检查指针是否为 nullptr,然后释放内存并把指针设为 nullptr

swr_free()

  • 功能: 释放 SwrContext 及其内部资源。
  • 函数原型: void swr_free(SwrContext **s);
  • 说明: 在程序结束时,调用此函数来清理资源,防止内存泄漏。它会自动将传入的指针设置为 nullptr

示例

// 使用ffmpeg6以上版本
#include <iostream>
#include <vector>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libavutil/error.h>
#include <libavutil/channel_layout.h>
}

// 错误处理宏
#define CHECK_RET(ret) do { \
    if ((ret) < 0) { \
        char errbuf[AV_ERROR_MAX_STRING_SIZE]; \
        av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); \
        std::cerr << "FFmpeg Error: " << errbuf << std::endl; \
        return 1; \
    } \
} while(0)

// 打印音频参数
void print_audio_params(const char* label, int sample_rate, enum AVSampleFormat format, const AVChannelLayout* channel_layout) {
    char layout_str[256];
    av_channel_layout_describe(channel_layout, layout_str, sizeof(layout_str));

    std::cout << "--- " << label << " ---" << std::endl;
    std::cout << "Sample Rate: " << sample_rate << " Hz" << std::endl;
    std::cout << "Sample Format: " << av_get_sample_fmt_name(format) << std::endl;
    std::cout << "Channel Layout: " << layout_str << std::endl;
    std::cout << "Number of Channels: " << channel_layout->nb_channels << std::endl;
}

int main() {
    // 源音频参数
    int src_sample_rate = 44100;
    enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_S16;
    AVChannelLayout src_channel_layout;
    av_channel_layout_default(&src_channel_layout, 2); // 2声道
    int src_nb_channels = src_channel_layout.nb_channels;
    int src_nb_samples = 1024;

    // 目标音频参数
    int dst_sample_rate = 48000;
    enum AVSampleFormat dst_sample_fmt = AV_SAMPLE_FMT_FLT;
    AVChannelLayout dst_channel_layout;
    av_channel_layout_default(&dst_channel_layout, 1); // 1声道
    int dst_nb_channels = dst_channel_layout.nb_channels;

    print_audio_params("Source Audio", src_sample_rate, src_sample_fmt, &src_channel_layout);
    print_audio_params("Destination Audio", dst_sample_rate, dst_sample_fmt, &dst_channel_layout);

    SwrContext *swr_ctx = swr_alloc();
    if (!swr_ctx) {
        std::cerr << "Could not allocate SwrContext" << std::endl;
        return 1;
    }

    av_opt_set_chlayout(swr_ctx, "in_chlayout", &src_channel_layout, 0);
    av_opt_set_int(swr_ctx, "in_sample_rate", src_sample_rate, 0);
    av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);
    av_opt_set_chlayout(swr_ctx, "out_chlayout", &dst_channel_layout, 0);
    av_opt_set_int(swr_ctx, "out_sample_rate", dst_sample_rate, 0);
    av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);

    int ret = swr_init(swr_ctx);
    CHECK_RET(ret);

    uint8_t **src_data = nullptr;
    int src_linesize = 0;
    ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize, src_nb_channels, src_nb_samples, src_sample_fmt, 0);
    CHECK_RET(ret);

    for (int i = 0; i < src_nb_samples * src_nb_channels; ++i) {
        if (src_sample_fmt == AV_SAMPLE_FMT_S16) {
            ((int16_t*)src_data[0])[i] = i;
        }
    }

    int dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, src_sample_rate) + src_nb_samples, dst_sample_rate, src_sample_rate, AV_ROUND_UP);

    uint8_t **dst_data = nullptr;
    int dst_linesize = 0;
    ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels, dst_nb_samples, dst_sample_fmt, 0);
    CHECK_RET(ret);

    ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, (const uint8_t **)src_data, src_nb_samples);
    CHECK_RET(ret);

    std::cout << "Successfully resampled " << src_nb_samples << " samples to " << ret << " samples." << std::endl;

    while (true) {
        ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, nullptr, 0);
        if (ret <= 0) break;
        std::cout << "Flushed " << ret << " remaining samples." << std::endl;
    }

    if (src_data) {
        av_freep(&src_data[0]);
        av_freep(&src_data);
    }
    if (dst_data) {
        av_freep(&dst_data[0]);
        av_freep(&dst_data);
    }
    swr_free(&swr_ctx);

    return 0;
}

编译运行:

g++ resample.cpp -o resample_example -I/usr/local/ffmpeg/include -L/usr/local/ffmpeg/lib -lavcodec -lswresample -lavutil

输出:

--- Source Audio ---
Sample Rate: 44100 Hz
Sample Format: s16
Channel Layout: stereo
Number of Channels: 2
--- Destination Audio ---
Sample Rate: 48000 Hz
Sample Format: flt
Channel Layout: mono
Number of Channels: 1
Successfully resampled 1024 samples to 1098 samples.
Flushed 17 remaining samples.

网站公告

今日签到

点亮在社区的每一天
去签到