FFmpeg開發筆記(五十八)把32位取樣的MP3轉換為16位的PCM音訊

aqi00發表於2024-10-19
《FFmpeg開發實戰:從零基礎到短影片上線》一書的“5.1.2 把音訊流儲存為PCM檔案”介紹瞭如何把媒體檔案中的音訊流轉存為原始的PCM音訊,在樣例程式碼的轉存過程中,解碼後的PCM資料未經任何加工處理,就直接儲存到二進位制檔案。也就是說,原音訊的取樣頻率是多少,PCM檔案的取樣頻率也是多少;原音訊的聲道數量是多少,PCM檔案的聲道數量也是多少;原音訊的取樣位數是多少,PCM檔案的取樣位數也是多少。

原汁原味儲存的PCM檔案本來也沒什麼問題,可是在實際應用中,有的業務場景需要特定規格的PCM音訊。比如某廠家的語音識別引擎,要求只能輸入16位的PCM資料,然而標準的MP3音訊都採用32位取樣,如此一來,得想辦法把32位的MP3音訊轉換為16位的PCM音訊才行。
考慮到使用FFmpeg的命令列轉換比較方便,於是在控制檯執行下面的ffmpeg格式轉換指令,在轉換取樣頻率和聲道數量的同時一起轉換取樣位數。

ffmpeg -i night.mp3 -ar 16000 -ac 1 -acodec pcm_s16le night.pcm

誰知控制檯輸出以下的報錯資訊“pcm_s16le codec not supported”,意思是不支援16位的PCM編碼器。

pcm_s16le codec not supported

咦,FFmpeg怎麼會不支援這麼基本的PCM編碼器呢?繼續執行下面的編碼器檢視命令:

ffmpeg -encoders | grep pcm

發現輸出的查詢結果赫然出現下面的pcm_s16le資訊,說明FFmpeg預設已經支援該編碼器。

A....D pcm_s16le            PCM signed 16-bit little-endian

那麼為啥ffmpeg命令列無法正常轉換PCM音訊的取樣位數呢?
搜了一圈發現沒有使用ffmpeg成功轉換取樣位數的案例,只好先把原音訊轉換為32位取樣的PCM檔案,轉換命令如下所示:

ffmpeg -i night.mp3 -ar 16000 -ac 1 -acodec pcm_f32le -f f32le night.pcm

接下來另外編寫轉換音訊取樣位數的程式碼convertpcm.c,程式碼內容如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int pcm32_to_pcm16(const char *filename)
{  
    FILE *fp =  fopen(filename, "rb");
    FILE *fp1 = fopen("output_16.pcm", "wb");
    unsigned char *sample = (unsigned char*)calloc(1, 4+1);
    while(!feof(fp))
    {
        fread(sample, 4, 1, fp);
        sample[4] = '\0';
        float *sample32 = (float*)sample;
        short sample16 = (short)floor( (*sample32) * 32767 );
        fwrite(&sample16, 2, 1, fp1);
    }
    free(sample);
    fclose(fp);
    fclose(fp1);
    return 0;  
}

int main(int argc, char **argv) {
    const char *src_name = "night.pcm";
    if (argc > 1) {
        src_name = argv[1];
    }
    pcm32_to_pcm16(src_name);
}

儲存程式碼,然後執行下面的編譯命令。

gcc convertpcm.c -o convertpcm 

編譯完成,再執行下面的取樣位數轉換命令。

./convertpcm night.pcm

現在生成的output_16.pcm就是16位取樣的PCM檔案,可以用作語音識別了。

更多詳細的FFmpeg開發知識參見《FFmpeg開發實戰:從零基礎到短影片上線》一書。

相關文章