一、前言

顺应时代的技能发展潮流,逐步学习并掌握音视频技能核心知识,让技能落地,让知识赋能日子,让科技造福千万灯火。

二、音频重采样简介

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

06-音视频技术核心知识|音频重采样【音频重采样简介、用命令行进行重采样、通过编程重采样】

三、经过命令行进行重采样

经过下面的命令行能够将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

四、经过编程重采样

音频重采样需求用到2个库:

  • swresample
  • avutil

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

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

3. 函数完成

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;

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

3.3 初始化重采样上下文

// 初始化重采样上下文
int ret = swr_init(ctx);
if (ret < 0) {
    ERROR_BUF(ret);
    qDebug() << "swr_init error:" << errbuf;
    goto end;
}

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

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

3.6 改写输出缓冲区

// 检查一下输出缓冲区是否还有残留的样本(已经重采样过的,转化过的)
while ((ret = swr_convert(ctx,
                          outData, outSamples,
                          nullptr, 0)) > 0) {
    outFile.write((char *) outData[0], ret * outBytesPerSample);
}

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