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进行编码。

WAV音频重采样的python实现 音频重采样原理_音视频

音频重采样

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);