FFmpeg音频重采样

一、什么是音频重采样

音频重采样就是改变音频的采样率、采样格式、声道数等参数,使之按照我们期望的参数输出。比如我们将采样率 48kHz、采样格式 f32le、声道数 1 的音频 A 转换成采样率 44.1kHz、采样格式 s16le、声道数 2 的音频 B。

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

二、使用 FFmpeg 命令行实现音频重采样

将采样率 48000 采样格式 s32le 声道数 1 的 PCM 音频数据重采样成采样率 44100 采样格式 s16le 声道数 2 的 PCM 音频数据:

$ ffmpeg -ar 48000 -ac 1 -f f32le -i ar48000ac1f32le.pcm -ar 44100 -ac 2 -f s16le ar44100ac2s16le.pcm
三、使用 FFmpeg API 编程实现音频重采样

使用 libavresample 音频重采样的核心步骤:

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(inSampleFormat);
    // 输入缓冲区样本数量
    int inSamples = 1024;
    
    // 输出缓冲区
    // 指向输出缓冲区的指针
    uint8_t **outData = nullptr;
    // 缓冲区大小
    int outLineSize = 0;
    // 声道数
    int outChs = av_get_channel_layout_nb_channels(outChLayout);
    // 每个样本的大小
    int outBytesPerSample = outChs * av_get_bytes_per_sample(outSampleFormat);
    // 输出缓冲区样本数量
    int outSamples = av_rescale_rnd(outSampleRate, inSamples, inSampleRate, AV_ROUND_UP);
    
    // 读取的音频大小
    int len = 0;
    // 返回结果
    int ret = 0;

我们设置了输入缓冲区样本数量为 1024,然后根据输入输出采样率的比例计算出输出缓冲区样本数量,计算公式如下:

inSamples     inSampleRate
—————————— = ——————————————— 
outSamples    outSampleRate

outSamples = inSamples * outSampleRate / inSampleRate

FFmpeg 提供了现成的 API 计算输出缓冲区样本数量:

/**
* Rescale a 64-bit integer with specified rounding.
*
* The operation is mathematically equivalent to `a * b / c`, but writing that
* directly can overflow, and does not support different rounding methods.
*
* @see av_rescale(), av_rescale_q(), av_rescale_q_rnd()
*/
int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const;

此函数的操作等价于我们上边的计算公式,并且做了防止溢出处理。rnd:取整模式选择向上取整 AV_ROUND_UP。实际上输入输出缓冲区样本大小全都设置为 1024 重采样后的音频有时也是可以播放的,听起来并没有什么不同,但是通过观察转码后的音频文件大小你可能会发现丢失了部分音频数据。

2、创建重采样上下文:

SwrContext *ctx = swr_alloc_set_opts(nullptr,
                                     outChLayout, outSampleFormat, outSampleRate,
                                     inChLayout, inSampleFormat, inSampleRate,
                                     0, nullptr);

3、初始化重采样上下文:

ret = swr_init(ctx);
if (ret < 0) {
    ERRBUF(ret);
    qDebug() << "初始化上下文失败:" << errbuf;
    goto end;
}

4、创建输入缓冲区:

ret = av_samples_alloc_array_and_samples(&inData, &inLineSize, inChs, inSamples, inSampleFormat, 0);
if (ret < 0) {
    ERRBUF(ret);
    qDebug() << "创建输入缓冲区失败:" << errbuf;
    goto end;
}

5、创建输出缓冲区:

ret = av_samples_alloc_array_and_samples(&outData, &outLineSize, outChs, outSamples, outSampleFormat, 0);
if (ret < 0) {
    ERRBUF(ret);
    qDebug() << "创建输出缓冲区失败:" << errbuf;
    goto end;
}

6、打开文件:

if (!inFile.open(QFile::ReadOnly)) {
    qDebug() << "打开输入文件失败";
    goto end;
}

if (!outFile.open(QFile::WriteOnly)) {
    qDebug() << "打开输出文件失败";
    goto end;
}

7、重采样:

while ((len = inFile.read((char *)inData[0], inLineSize)) > 0) {
    inSamples = len / inBytesPerSample;
    ret = swr_convert(ctx, outData, outSamples, (const uint8_t **)inData, inSamples);
    qDebug() << "转换:" << ret;
    if (ret < 0) {
        ERRBUF(ret);
        qDebug() << "重采样失败:" << errbuf;
        goto end;
    }
    outFile.write((const char *)outData[0], ret * outBytesPerSample);
}

8、检查输出缓冲区是否还有残留样本:

while ((ret = swr_convert(ctx, outData, outSamples, nullptr, 0)) > 0) {
    outFile.write((const char *)outData[0], ret);
    qDebug() << "残留:" << ret;
}

9、回收释放资源:

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);
三、代码
#include "ffmpegutils.h"

#include <QDebug>
#include <QFile>

#define ERRBUF(ret) \
    char errbuf[1024]; \
    av_strerror(ret, errbuf, sizeof (errbuf))

FFmpegUtils::FFmpegUtils(QObject *parent) : QObject(parent)
{

}

void FFmpegUtils::resampleAudio(const char *inFilename, int inSampleRate, AVSampleFormat inSampleFormat, int inChLayout,
                                const char *outFilename, int outSampleRate, AVSampleFormat outSampleFormat, int outChLayout)

{

    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(inSampleFormat);
    // 输入缓冲区大小
    int inSamples = 1024;

    // 输出缓冲区
    // 指向输出缓冲区的指针
    uint8_t **outData = nullptr;
    // 缓冲区大小
    int outLineSize = 0;
    // 声道数
    int outChs = av_get_channel_layout_nb_channels(outChLayout);
    // 每个样本的大小
    int outBytesPerSample = outChs * av_get_bytes_per_sample(outSampleFormat);
    // 输出缓冲区大小
    int outSamples = av_rescale_rnd(outSampleRate, inSamples, inSampleRate, AV_ROUND_UP);

    // 读取的音频大小
    int len = 0;
    // 返回结果
    int ret = 0;

    // 创建重采样上下文
    SwrContext *ctx = swr_alloc_set_opts(nullptr,
                                         outChLayout, outSampleFormat, outSampleRate,
                                         inChLayout, inSampleFormat, inSampleRate,
                                         0, nullptr);

    if (!ctx) {
        qDebug() << "创建重采样上下文失败!";
        goto end;
    }

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

    // 创建输入缓冲区
    ret = av_samples_alloc_array_and_samples(&inData, &inLineSize, inChs, inSamples, inSampleFormat, 0);
    if (ret < 0) {
        ERRBUF(ret);
        qDebug() << "创建输入缓冲区失败:" << errbuf;
        goto end;
    }

    // 创建输出缓冲区
    ret = av_samples_alloc_array_and_samples(&outData, &outLineSize, outChs, outSamples, outSampleFormat, 0);
    if (ret < 0) {
        ERRBUF(ret);
        qDebug() << "创建输出缓冲区失败:" << errbuf;
        goto end;
    }

    // 打开文件
    if (!inFile.open(QFile::ReadOnly)) {
        qDebug() << "打开输入文件失败";
        goto end;
    }

    if (!outFile.open(QFile::WriteOnly)) {
        qDebug() << "打开输出文件失败";
        goto end;
    }

    while ((len = inFile.read((char *)inData[0], inLineSize)) > 0) {
        inSamples = len / inBytesPerSample;
        ret = swr_convert(ctx, outData, outSamples, (const uint8_t **)inData, inSamples);
        qDebug() << "转换:" << ret;
        if (ret < 0) {
            ERRBUF(ret);
            qDebug() << "重采样失败:" << errbuf;
            goto end;
        }
        outFile.write((const char *)outData[0], ret * outBytesPerSample);
    }

    while ((ret = swr_convert(ctx, outData, outSamples, nullptr, 0)) > 0) {
        outFile.write((const char *)outData[0], ret);
        qDebug() << "残留:" << ret;
    }

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

调用函数:

#define IN_FILE_NAME "/Users/mac/Downloads/music/ar48000ac1f32le.pcm”
#define OUT_FILE_NAME "/Users/mac/Downloads/music/ar44100ac2s16le.pcm"
int inSampleRate = 48000;
AVSampleFormat inSampleFormat = AV_SAMPLE_FMT_FLT;
int inChLayout = AV_CH_LAYOUT_MONO;
int outSampleRate = 44100;
AVSampleFormat outSampleFormat = AV_SAMPLE_FMT_S16;
int outChLayout = AV_CH_LAYOUT_STEREO;
FFmpegUtils::resampleAudio(IN_FILE_NAME, inSampleRate, inSampleFormat, inChLayout,
                           OUT_FILE_NAME, outSampleRate, outSampleFormat, outChLayout);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 目录 参考 lswr功能介绍 lswr使用说明 示例代码 1. 参考 [1] FFmpeg/Libswresamp...
    smallest_one阅读 19,156评论 0 17
  • 前言 广义的音频重采样包括:1、采样格式转化:比如采样格式从16位整形变为浮点型2、采样率的转换:降采样和升采样,...
    仙人掌__阅读 2,499评论 2 3
  • 直播流程 一次直播中主播端采集音视频编码上传数据到服务器,观众端不断的拉取数据,数据解码音视频渲染到手机。 音频数...
    泡芙coder阅读 346评论 0 0
  • 2021年4月23日 这是本人在某某网的学习音视频笔记,主要包括音视频的入门和ffmpeg的实战。笔记内容按照上课...
    东也_阅读 1,300评论 4 14
  • 音频重采样步骤 创建采样上下文 设置输入缓冲区 设置输出缓冲区 打开文件开始重采样 检查输出缓冲区是否还有残余的样...
    lieon阅读 1,176评论 0 1