原文地址:【秒懂音视频开发】12_音频重采样 - M了个J - 博客园
1 什么叫音频重采样
音频重采样(Audio Resample):将音频A转换成音频B,并且音频A、B的参数(采样率、采样格式、声道数)并不完全相同。比如:
-
音频A的参数
- 采样率:48000
- 采样格式:f32le
- 声道数:1
-
音频B的参数
- 采样率:44100
- 采样格式:s16le
- 声道数:2
2 为什么需要音频重采样
这里列举一个音频重采样的经典用途。
有些音频编码器对输入的原始PCM数据是有特定参数要求的,比如要求必须是44100_s16le_2。但是你提供的PCM参数可能是48000_f32le_1。这个时候就需要先将48000_f32le_1转换成44100_s16le_2,然后再使用音频编码器对转换后的PCM进行编码。
音频重采样
3 命令行
通过下面的命令行可以将44100_s16le_2转换成48000_f32le_1。
ffmpeg -ar 44100 -ac 2 -f s16le -i 44100_s16le_2.pcm -ar 48000 -ac 1 -f f32le 48000_f32le_1.pcm |
4 编程
音频重采样需要用到2个库:
- swresample
- avutil
4.1 函数声明
为了让音频重采样功能更加通用,设计成以下函数:
// 音频参数 | |
typedef struct { | |
const char *filename; | |
int sampleRate; | |
AVSampleFormat sampleFmt; | |
int chLayout; | |
} ResampleAudioSpec; | |
class FFmpegs { | |
public: | |
static void resampleAudio(ResampleAudioSpec &in, | |
ResampleAudioSpec &out); | |
static void resampleAudio(const char *inFilename, | |
int inSampleRate, | |
AVSampleFormat inSampleFmt, | |
int inChLayout, | |
const char *outFilename, | |
int outSampleRate, | |
AVSampleFormat outSampleFmt, | |
int outChLayout); | |
}; | |
// 导入头文件 | |
extern "C" { | |
#include <libswresample/swresample.h> | |
#include <libavutil/avutil.h> | |
} | |
// 处理错误码 | |
#define ERROR_BUF(ret) \ | |
char errbuf[1024]; \ | |
av_strerror(ret, errbuf, sizeof (errbuf)); | |
void FFmpegs::resampleAudio(ResampleAudioSpec &in, | |
ResampleAudioSpec &out) { | |
resampleAudio(in.filename, in.sampleRate, in.sampleFmt, in.chLayout, | |
out.filename, out.sampleRate, out.sampleFmt, out.chLayout); | |
} |
4.2 函数调用
// 输入参数 | |
ResampleAudioSpec in; | |
in.filename = "F:/44100_s16le_2.pcm"; | |
in.sampleFmt = AV_SAMPLE_FMT_S16; | |
in.sampleRate = 44100; | |
in.chLayout = AV_CH_LAYOUT_STEREO; | |
// 输出参数 | |
ResampleAudioSpec out; | |
out.filename = "F:/48000_f32le_1.pcm"; | |
out.sampleFmt = AV_SAMPLE_FMT_FLT; | |
out.sampleRate = 48000; | |
out.chLayout = AV_CH_LAYOUT_MONO; | |
// 进行音频重采样 | |
FFmpegs::resampleAudio(in, out); |
4.3 函数实现
4.3.1 变量定义
为了简化释放资源的代码,函数中用到了goto语句,所以把需要用到的变量都定义到了前面。
// 文件名 | |
QFile inFile(inFilename); | |
QFile outFile(outFilename); | |
// 输入缓冲区 | |
// 指向缓冲区的指针 | |
uint8_t **inData = nullptr; | |
// 缓冲区的大小 | |
int inLinesize = 0; | |
// 声道数 | |
int inChs = av_get_channel_layout_nb_channels(inChLayout); | |
// 一个样本的大小 | |
int inBytesPerSample = inChs * av_get_bytes_per_sample(inSampleFmt); | |
// 缓冲区的样本数量 | |
int inSamples = 1024; | |
// 读取文件数据的大小 | |
int len = 0; | |
// 输出缓冲区 | |
// 指向缓冲区的指针 | |
uint8_t **outData = nullptr; | |
// 缓冲区的大小 | |
int outLinesize = 0; | |
// 声道数 | |
int outChs = av_get_channel_layout_nb_channels(outChLayout); | |
// 一个样本的大小 | |
int outBytesPerSample = outChs * av_get_bytes_per_sample(outSampleFmt); | |
// 缓冲区的样本数量(AV_ROUND_UP是向上取整) | |
int outSamples = av_rescale_rnd(outSampleRate, inSamples, inSampleRate, AV_ROUND_UP); | |
/* | |
inSampleRate inSamples | |
------------- = ----------- | |
outSampleRate outSamples | |
outSamples = outSampleRate * inSamples / inSampleRate | |
*/ | |
// 返回结果 | |
int ret = 0; |
4.3.2 创建重采样上下文
// 创建重采样上下文 | |
SwrContext *ctx = swr_alloc_set_opts(nullptr, | |
// 输出参数 | |
outChLayout, outSampleFmt, outSampleRate, | |
// 输入参数 | |
inChLayout, inSampleFmt, inSampleRate, | |
0, nullptr); | |
if (!ctx) { | |
qDebug() << "swr_alloc_set_opts error"; | |
goto end; | |
} |
4.3.3 初始化重采样上下文
// 初始化重采样上下文 | |
int ret = swr_init(ctx); | |
if (ret < 0) { | |
ERROR_BUF(ret); | |
qDebug() << "swr_init error:" << errbuf; | |
goto end; | |
} |
4.3.4 创建缓冲区
// 创建输入缓冲区 | |
ret = av_samples_alloc_array_and_samples( | |
&inData, | |
&inLinesize, | |
inChs, | |
inSamples, | |
inSampleFmt, | |
1); | |
if (ret < 0) { | |
ERROR_BUF(ret); | |
qDebug() << "av_samples_alloc_array_and_samples error:" << errbuf; | |
goto end; | |
} | |
// 创建输出缓冲区 | |
ret = av_samples_alloc_array_and_samples( | |
&outData, | |
&outLinesize, | |
outChs, | |
outSamples, | |
outSampleFmt, | |
1); | |
if (ret < 0) { | |
ERROR_BUF(ret); | |
qDebug() << "av_samples_alloc_array_and_samples error:" << errbuf; | |
goto end; | |
} |
4.3.5 读取文件数据
// 打开文件 | |
if (!inFile.open(QFile::ReadOnly)) { | |
qDebug() << "file open error:" << inFilename; | |
goto end; | |
} | |
if (!outFile.open(QFile::WriteOnly)) { | |
qDebug() << "file open error:" << outFilename; | |
goto end; | |
} | |
// 读取文件数据 | |
// inData[0] == *inData | |
while ((len = inFile.read((char *) inData[0], inLinesize)) > 0) { | |
// 读取的样本数量 | |
inSamples = len / inBytesPerSample; | |
// 重采样(返回值转换后的样本数量) | |
ret = swr_convert(ctx, | |
outData, outSamples, | |
(const uint8_t **) inData, inSamples | |
); | |
if (ret < 0) { | |
ERROR_BUF(ret); | |
qDebug() << "swr_convert error:" << errbuf; | |
goto end; | |
} | |
// 将转换后的数据写入到输出文件中 | |
// outData[0] == *outData | |
outFile.write((char *) outData[0], ret * outBytesPerSample); | |
} |
4.3.6 刷新输出缓冲区
// 检查一下输出缓冲区是否还有残留的样本(已经重采样过的,转换过的) | |
while ((ret = swr_convert(ctx, | |
outData, outSamples, | |
nullptr, 0)) > 0) { | |
outFile.write((char *) outData[0], ret * outBytesPerSample); | |
} |
4.3.7 回收释放资源
end: | |
// 释放资源 | |
// 关闭文件 | |
inFile.close(); | |
outFile.close(); | |
// 释放输入缓冲区 | |
if (inData) { | |
av_freep(&inData[0]); | |
} | |
av_freep(&inData); | |
// 释放输出缓冲区 | |
if (outData) { | |
av_freep(&outData[0]); | |
} | |
av_freep(&outData); | |
// 释放重采样上下文 | |
swr_free(&ctx); |