五、使用ffmpeg对音频重采样

前言

音频重采样的经典用途:

有些音频编码器对输入的原始PCM数据是有特定参数要求的,比如要求必须是44100_s16le_2。但是你提供的PCM参数可能是48000_f32le_1。这个时候就需要先将48000_f32le_1转换成44100_s16le_2,然后再使用音频编码器对转换后的PCM进行编码。


测试环境:

  • ffmpeg的shared版本
  • windows环境
  • qt5.12
  • sdl2.0.22(mingw编译器)

命令行实现音频重采样:

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

编程实现:

重采样的主要思路是看创建重采样上下文的方法

struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
                                      int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
                                      int64_t  in_ch_layout, enum AVSampleFormat  in_sample_fmt, int  in_sample_rate,
                                      int log_offset, void *log_ctx);

完整代码:(我这里是将功能封装在线程里,其中还考虑了线程的关闭问题)

audiothread_resample.h

#ifndef AUDIOSTARTTHREAD_H
#define AUDIOSTARTTHREAD_H

#include <QObject>
#include <QThread>
#include <SDL2/SDL.h>

class AudioStartThread : public QThread
{
    
    
    Q_OBJECT
public:
    explicit AudioStartThread(QObject *parent = nullptr);
    ~AudioStartThread();

private:
    void run() override;

signals:

};

#endif // AUDIOSTARTTHREAD_H

audiothread_resample.cpp

#include "audiothread_resample.h"

#include <QDebug>
#include <QFile>

AudioThreadResample::AudioThreadResample(QObject *parent) : QThread(parent) {
    
    
    // 当监听到线程结束时(finished),就调用deleteLater回收内存
    connect(this, &AudioThreadResample::finished,
            this, &AudioThreadResample::deleteLater);
}

AudioThreadResample::~AudioThreadResample() {
    
    
    // 断开所有的连接
    disconnect();
    // 内存回收之前,正常结束线程
    requestInterruption();
    // 安全退出
    quit();
    wait();
    qDebug() << this << "析构(内存被回收)";
}

void AudioThreadResample::resampleAudio(ResampleAudioSpec &in, ResampleAudioSpec &out)
{
    
    
    resampleAudio(in.filename, in.sampleRate, in.sampleFmt, in.chLayout,
                  out.filename, out.sampleRate, out.sampleFmt, out.chLayout);
}

void AudioThreadResample::resampleAudio(const char *inFilename, int inSampleRate, AVSampleFormat inSampleFmt, int inChLayout, const char *outFilename, int outSampleRate, AVSampleFormat outSampleFmt, int outChLayout)
{
    
    
    // 向下取整,AV_ROUND_DOWN(2.66) = 2
    // qDebug() << av_rescale_rnd(8, 1, 3, AV_ROUND_DOWN);

    // 向上取整,AV_ROUND_UP(1.25) = 2
    // qDebug() << av_rescale_rnd(5, 1, 4, AV_ROUND_UP);

    // 文件名
    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);
    // 缓冲区的样本数量
    int outSamples = av_rescale_rnd(outSampleRate, inSamples, inSampleRate, AV_ROUND_UP);
    /*
     inSampleRate     inSamples
     ------------- = -----------
     outSampleRate    outSamples

     outSamples = outSampleRate * inSamples / inSampleRate
     */

    qDebug() << "输入缓冲区" << inSampleRate << inSamples;
    qDebug() << "输出缓冲区" << outSampleRate << outSamples;

    // 返回结果
    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;
    }

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

    /* 指针类型(64bit,8个字节)
    int *;
    double *;
    void *;
    int **;
    int ***;
    int ******;
    */

    // int *p;
    // *(p + i) == p[i]
    // *(p + 0) == p[0]
    // *p == p[0]

    // int *p = new int[15];
    // int *p = av_calloc(15, sizeof (int));
    // int **pp = av_calloc(7, sizeof (int *));

    // uint8_t **inData = av_calloc(1, sizeof(uint8_t *));

    // 创建输入缓冲区
    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;
        }

//        int size = av_samples_get_buffer_size(nullptr, outChs, ret, outSampleFmt, 1);
//        outFile.write((char *) outData[0], size);

        // 将转换后的数据写入到输出文件中
        // 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);

//    void *ptr = malloc(100);
//    freep(&ptr);
//    free(ptr);
//    ptr = nullptr;
}

//void freep(void **ptr) {
    
    
//    free(*ptr);
//    *ptr = nullptr;
//}

void AudioThreadResample::run() {
    
    
    // 44100_s16le_2 -> 48000_f32le_2 -> 48000_s32le_1 -> 44100_s16le_2

    ResampleAudioSpec ras1;
    ras1.filename = "E:/media/44100_s16le_2.pcm";
    ras1.sampleFmt = AV_SAMPLE_FMT_S16;
    ras1.sampleRate = 44100;
    ras1.chLayout = AV_CH_LAYOUT_STEREO;

    ResampleAudioSpec ras2;
    ras2.filename = "E:/media/48000_f32le_1.pcm";
    ras2.sampleFmt = AV_SAMPLE_FMT_FLT;
    ras2.sampleRate = 48000;
    ras2.chLayout = AV_CH_LAYOUT_MONO;

    ResampleAudioSpec ras3;
    ras3.filename = "E:/media/48000_s32le_1.pcm";
    ras3.sampleFmt = AV_SAMPLE_FMT_S32;
    ras3.sampleRate = 48000;
    ras3.chLayout = AV_CH_LAYOUT_MONO;

    ResampleAudioSpec ras4 = ras1;
    ras4.filename = "E:/media/44100_s16le_2_new.pcm";

    resampleAudio(ras1, ras2);
    resampleAudio(ras2, ras3);
    resampleAudio(ras3, ras4);
}

注意:该代码对可能出现的问题都进行了一定的优化,读者使用时只需关注关键代码


线程调用

void MainWindow::on_pushButton_resample_clicked()
{
    
    
    audioThreadResample = new AudioThreadResample(this);
    audioThreadResample->start();
}

注意:mainwindow.h文件中提前声明了以下全局变量

AudioThreadResample *audioThreadResample = nullptr;

注意:本文为个人记录,新手照搬可能会出现各种问题,请谨慎使用


码字不易,如果这篇博客对你有帮助,麻烦点赞收藏,非常感谢!有不对的地方

猜你喜欢

转载自blog.csdn.net/weixin_44092851/article/details/128313266