概述
音频重采样指的是将音频数据的采样率从一个值转换到另一个值。
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"
等。val
或fmt
: 选项的值。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
: 指向源缓冲区的指针数组。如果为nullptr
且in_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.