【秒懂音视频开发】12_音频重采样

原文地址:【秒懂音视频开发】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);

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值