• 【秒懂音视频开发】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);
    
  • 相关阅读:
    如何拯救任务栏
    VMware 11 安装 OS X 10.10 虚拟机
    控制台绘制正切曲线
    控制台绘制正弦曲线和余弦曲线同时显示
    控制台绘制正弦/余弦曲线
    一文看懂js中的clientX,clientY,pageX,pageY,screenX,screenY
    一文看懂js中元素的滚动大小(scrollWidth,scrollHeight,scrollTop,scrollLeft)
    一文看懂js中元素的客户区大小(clientWidth,clientHeight)
    一文看懂js中元素偏移量(offsetLeft,offsetTop,offsetWidth,offsetHeight)
    从头认识js-DOM1
  • 原文地址:https://www.cnblogs.com/mjios/p/14595083.html
Copyright © 2020-2023  润新知