【秒懂音視頻開發】12_音頻重采樣


什么叫音頻重采樣

音頻重采樣(Audio Resample):將音頻A轉換成音頻B,並且音頻A、B的參數(采樣率、采樣格式、聲道數)並不完全相同。比如:

  • 音頻A的參數

    • 采樣率:48000
    • 采樣格式:f32le
    • 聲道數:1
  • 音頻B的參數

    • 采樣率:44100
    • 采樣格式:s16le
    • 聲道數:2

為什么需要音頻重采樣

這里列舉一個音頻重采樣的經典用途。

有些音頻編碼器對輸入的原始PCM數據是有特定參數要求的,比如要求必須是44100_s16le_2。但是你提供的PCM參數可能是48000_f32le_1。這個時候就需要先將48000_f32le_1轉換成44100_s16le_2,然后再使用音頻編碼器對轉換后的PCM進行編碼。

音頻重采樣

命令行

通過下面的命令行可以將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

函數聲明

為了讓音頻重采樣功能更加通用,設計成以下函數:

// 音頻參數
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);
}

函數調用

// 輸入參數
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);

函數實現

變量定義

為了簡化釋放資源的代碼,函數中用到了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;

創建重采樣上下文

// 創建重采樣上下文
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;
}

初始化重采樣上下文

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

創建緩沖區

// 創建輸入緩沖區
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;
}

讀取文件數據

// 打開文件
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);
}

刷新輸出緩沖區

// 檢查一下輸出緩沖區是否還有殘留的樣本(已經重采樣過的,轉換過的)
while ((ret = swr_convert(ctx,
                          outData, outSamples,
                          nullptr, 0)) > 0) {
    outFile.write((char *) outData[0], ret * outBytesPerSample);
}

回收釋放資源

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM