FFmpeg音頻重采樣

一娱两、什么是音頻重采樣

音頻重采樣就是改變音頻的采樣率街图、采樣格式浇衬、聲道數(shù)等參數(shù),使之按照我們期望的參數(shù)輸出餐济。比如我們將采樣率 48kHz耘擂、采樣格式 f32le、聲道數(shù) 1 的音頻 A 轉(zhuǎn)換成采樣率 44.1kHz絮姆、采樣格式 s16le醉冤、聲道數(shù) 2 的音頻 B。

那么為什么需要對(duì)音頻重采樣篙悯?列舉一個(gè)經(jīng)典用途蚁阳,有些音頻編碼器對(duì)輸入的原始PCM數(shù)據(jù)是有特定參數(shù)要求的,比如要求必須是44100_s16le_2鸽照。但是你提供的PCM參數(shù)可能是48000_f32le_1螺捐。這個(gè)時(shí)候就需要先將48000_f32le_1轉(zhuǎn)換成44100_s16le_2,然后再使用音頻編碼器對(duì)轉(zhuǎn)換后的PCM進(jìn)行編碼矮燎。

二定血、使用 FFmpeg 命令行實(shí)現(xiàn)音頻重采樣

將采樣率 48000 采樣格式 s32le 聲道數(shù) 1 的 PCM 音頻數(shù)據(jù)重采樣成采樣率 44100 采樣格式 s16le 聲道數(shù) 2 的 PCM 音頻數(shù)據(jù):

$ ffmpeg -ar 48000 -ac 1 -f f32le -i ar48000ac1f32le.pcm -ar 44100 -ac 2 -f s16le ar44100ac2s16le.pcm
三、使用 FFmpeg API 編程實(shí)現(xiàn)音頻重采樣

使用 libavresample 音頻重采樣的核心步驟:

1漏峰、定義變量(為了簡(jiǎn)化釋放資源的代碼用到了goto 語(yǔ)句糠悼,需要把用到的變量定義到前面):

    QFile inFile(inFilename);
    QFile outFile(outFilename);

    // 輸入緩沖區(qū)
    // 指向輸入緩沖區(qū)的指針
    uint8_t **inData = nullptr;
    // 緩沖區(qū)大小
    int inLineSize = 0;
    // 聲道數(shù)
    int inChs = av_get_channel_layout_nb_channels(inChLayout);
    // 每個(gè)樣本的大小
    int inBytesPerSample = inChs * av_get_bytes_per_sample(inSampleFormat);
    // 輸入緩沖區(qū)樣本數(shù)量
    int inSamples = 1024;
    
    // 輸出緩沖區(qū)
    // 指向輸出緩沖區(qū)的指針
    uint8_t **outData = nullptr;
    // 緩沖區(qū)大小
    int outLineSize = 0;
    // 聲道數(shù)
    int outChs = av_get_channel_layout_nb_channels(outChLayout);
    // 每個(gè)樣本的大小
    int outBytesPerSample = outChs * av_get_bytes_per_sample(outSampleFormat);
    // 輸出緩沖區(qū)樣本數(shù)量
    int outSamples = av_rescale_rnd(outSampleRate, inSamples, inSampleRate, AV_ROUND_UP);
    
    // 讀取的音頻大小
    int len = 0;
    // 返回結(jié)果
    int ret = 0;

我們?cè)O(shè)置了輸入緩沖區(qū)樣本數(shù)量為 1024,然后根據(jù)輸入輸出采樣率的比例計(jì)算出輸出緩沖區(qū)樣本數(shù)量浅乔,計(jì)算公式如下:

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

outSamples = inSamples * outSampleRate / inSampleRate

FFmpeg 提供了現(xiàn)成的 API 計(jì)算輸出緩沖區(qū)樣本數(shù)量:

/**
* 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;

此函數(shù)的操作等價(jià)于我們上邊的計(jì)算公式倔喂,并且做了防止溢出處理。rnd:取整模式選擇向上取整 AV_ROUND_UP靖苇。實(shí)際上輸入輸出緩沖區(qū)樣本大小全都設(shè)置為 1024 重采樣后的音頻有時(shí)也是可以播放的席噩,聽起來(lái)并沒有什么不同,但是通過觀察轉(zhuǎn)碼后的音頻文件大小你可能會(huì)發(fā)現(xiàn)丟失了部分音頻數(shù)據(jù)贤壁。

2悼枢、創(chuàng)建重采樣上下文:

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馒索、創(chuàng)建輸入緩沖區(qū):

ret = av_samples_alloc_array_and_samples(&inData, &inLineSize, inChs, inSamples, inSampleFormat, 0);
if (ret < 0) {
    ERRBUF(ret);
    qDebug() << "創(chuàng)建輸入緩沖區(qū)失斢ǘ省:" << errbuf;
    goto end;
}

5、創(chuàng)建輸出緩沖區(qū):

ret = av_samples_alloc_array_and_samples(&outData, &outLineSize, outChs, outSamples, outSampleFormat, 0);
if (ret < 0) {
    ERRBUF(ret);
    qDebug() << "創(chuàng)建輸出緩沖區(qū)失敶律稀:" << 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() << "轉(zhuǎn)換:" << ret;
    if (ret < 0) {
        ERRBUF(ret);
        qDebug() << "重采樣失旘诳椤:" << errbuf;
        goto end;
    }
    outFile.write((const char *)outData[0], ret * outBytesPerSample);
}

8鉴腻、檢查輸出緩沖區(qū)是否還有殘留樣本:

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

    // 輸入緩沖區(qū)
    // 指向輸入緩沖區(qū)的指針
    uint8_t **inData = nullptr;
    // 緩沖區(qū)大小
    int inLineSize = 0;
    // 聲道數(shù)
    int inChs = av_get_channel_layout_nb_channels(inChLayout);
    // 每個(gè)樣本的大小
    int inBytesPerSample = inChs * av_get_bytes_per_sample(inSampleFormat);
    // 輸入緩沖區(qū)大小
    int inSamples = 1024;

    // 輸出緩沖區(qū)
    // 指向輸出緩沖區(qū)的指針
    uint8_t **outData = nullptr;
    // 緩沖區(qū)大小
    int outLineSize = 0;
    // 聲道數(shù)
    int outChs = av_get_channel_layout_nb_channels(outChLayout);
    // 每個(gè)樣本的大小
    int outBytesPerSample = outChs * av_get_bytes_per_sample(outSampleFormat);
    // 輸出緩沖區(qū)大小
    int outSamples = av_rescale_rnd(outSampleRate, inSamples, inSampleRate, AV_ROUND_UP);

    // 讀取的音頻大小
    int len = 0;
    // 返回結(jié)果
    int ret = 0;

    // 創(chuàng)建重采樣上下文
    SwrContext *ctx = swr_alloc_set_opts(nullptr,
                                         outChLayout, outSampleFormat, outSampleRate,
                                         inChLayout, inSampleFormat, inSampleRate,
                                         0, nullptr);

    if (!ctx) {
        qDebug() << "創(chuàng)建重采樣上下文失斔ァ!";
        goto end;
    }

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

    // 創(chuàng)建輸入緩沖區(qū)
    ret = av_samples_alloc_array_and_samples(&inData, &inLineSize, inChs, inSamples, inSampleFormat, 0);
    if (ret < 0) {
        ERRBUF(ret);
        qDebug() << "創(chuàng)建輸入緩沖區(qū)失斂涡俊:" << errbuf;
        goto end;
    }

    // 創(chuàng)建輸出緩沖區(qū)
    ret = av_samples_alloc_array_and_samples(&outData, &outLineSize, outChs, outSamples, outSampleFormat, 0);
    if (ret < 0) {
        ERRBUF(ret);
        qDebug() << "創(chuàng)建輸出緩沖區(qū)失敗:" << 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() << "轉(zhuǎn)換:" << 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);
}

調(diào)用函數(shù):

#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);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末产镐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子踢步,更是在濱河造成了極大的恐慌,老刑警劉巖丑掺,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件获印,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡街州,警方通過查閱死者的電腦和手機(jī)兼丰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)唆缴,“玉大人鳍征,你說(shuō)我怎么就攤上這事∶婊眨” “怎么了艳丛?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)趟紊。 經(jīng)常有香客問我氮双,道長(zhǎng),這世上最難降的妖魔是什么霎匈? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任戴差,我火速辦了婚禮,結(jié)果婚禮上铛嘱,老公的妹妹穿的比我還像新娘暖释。我一直安慰自己袭厂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布球匕。 她就那樣靜靜地躺著嵌器,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谐丢。 梳的紋絲不亂的頭發(fā)上爽航,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音乾忱,去河邊找鬼讥珍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛窄瘟,可吹牛的內(nèi)容都是我干的衷佃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼蹄葱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼氏义!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起图云,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤惯悠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后竣况,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體克婶,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年丹泉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了情萤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡摹恨,死狀恐怖筋岛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晒哄,我是刑警寧澤睁宰,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站揩晴,受9級(jí)特大地震影響勋陪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜硫兰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一诅愚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦违孝、人聲如沸刹前。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)喇喉。三九已至,卻和暖如春校坑,著一層夾襖步出監(jiān)牢的瞬間拣技,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工耍目, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留膏斤,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓邪驮,卻偏偏與公主長(zhǎng)得像莫辨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子毅访,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 目錄 參考 lswr功能介紹 lswr使用說(shuō)明 示例代碼 1. 參考 [1] FFmpeg/Libswresamp...
    smallest_one閱讀 19,528評(píng)論 0 17
  • 前言 廣義的音頻重采樣包括:1沮榜、采樣格式轉(zhuǎn)化:比如采樣格式從16位整形變?yōu)楦↑c(diǎn)型2、采樣率的轉(zhuǎn)換:降采樣和升采樣喻粹,...
    仙人掌__閱讀 2,538評(píng)論 2 3
  • 直播流程 一次直播中主播端采集音視頻編碼上傳數(shù)據(jù)到服務(wù)器蟆融,觀眾端不斷的拉取數(shù)據(jù),數(shù)據(jù)解碼音視頻渲染到手機(jī)磷斧。 音頻數(shù)...
    泡芙coder閱讀 366評(píng)論 0 0
  • 2021年4月23日 這是本人在某某網(wǎng)的學(xué)習(xí)音視頻筆記振愿,主要包括音視頻的入門和ffmpeg的實(shí)戰(zhàn)。筆記內(nèi)容按照上課...
    東也_閱讀 1,313評(píng)論 4 14
  • 音頻重采樣步驟 創(chuàng)建采樣上下文 設(shè)置輸入緩沖區(qū) 設(shè)置輸出緩沖區(qū) 打開文件開始重采樣 檢查輸出緩沖區(qū)是否還有殘余的樣...
    lieon閱讀 1,183評(píng)論 0 1