FFmpeg開發筆記(十八)FFmpeg相容各種音訊格式的播放

aqi00發表於2024-05-04
FFmpeg結合SDL可以播放音訊檔案,也能播放影片檔案中的音訊流,《FFmpeg開發實戰:從零基礎到短影片上線》一書第10章的示例程式playaudio.c支援播放mp3和aac兩種格式的音訊,卻不支援播放其他格式的音訊。

因為mp3和aac兩個格式擁有標準的規範定義,比如mp3規定每幀音訊固定包含1152個樣本,而aac規定每幀音訊固定包含1024個樣本。在它們的解碼器例項AVCodecContext中,即可從frame_size欄位獲取每幀音訊的樣本數量。
然而其他音訊格式(如ogg、amr、wma等)的每幀樣本數並不固定,從frame_size欄位取到的樣本數量為0,這不僅導致SDL初始化失敗,還導致重取樣過程異常。為了能夠播放其他格式的音訊,需要對playaudio.c做下列三處修改。
1、從解碼器例項獲取音訊樣本數時,如果發現frame_size為0,就要把樣本數變數設為512(注意該數值必須為2的n次冪,如256、512、1024等),修改後的賦值程式碼如下所示:

int out_nb_samples = audio_decode_ctx->frame_size; // 輸出的取樣數量
if (out_nb_samples <= 0) {
    out_nb_samples = 512;
}

2、在遍歷音訊幀的時候,要重新計算實際的取樣位數,以便確定多少音訊資料送給揚聲器。具體的計算過程是這樣的:先呼叫swr_convert函式對音訊重取樣,該函式的返回值為輸出的資料大小;這個輸入大小乘以聲道數量乘以音訊樣本的位深(位深表示每個音訊樣本佔據幾個位元組),最終的乘積便是要送給揚聲器的音訊資料大小。詳細的計算程式碼如下所示:

// 重取樣。也就是把輸入的音訊資料根據指定的取樣規格轉換為新的音訊資料輸出
int swr_size = swr_convert(swr_ctx, // 音訊取樣器的例項
    &out_buff, MAX_AUDIO_FRAME_SIZE, // 輸出的資料內容和資料大小
    (const uint8_t **) frame->data, frame->nb_samples); // 輸入的資料內容和資料大小
audio_pos = (unsigned char *) out_buff; // 把音訊資料同步到緩衝區位置
// 這裡要計算實際的取樣位數
audio_len = swr_size * out_channels * av_get_bytes_per_sample(out_sample_fmt);

3、SDL的音訊回撥函式當中,注意每次要湊足len個位元組。鑑於重取樣後的音訊資料可能較大(主要是amr格式有這種情況),因此要按照len指定的長度切割資料,確保每次回撥函式都剛好把長度為len的音訊資料送往揚聲器。修改後的回撥程式碼如下所示:

// 回撥函式,在獲取音訊資料後呼叫
void fill_audio(void *para, uint8_t *stream, int len) {
    SDL_memset(stream, 0, len); // 將緩衝區清零
    if (audio_len == 0) {
        return;
    }
    while (len > 0) { // 每次都要湊足len個位元組才能退出迴圈
        int fill_len = (len > audio_len ? audio_len : len);
        // 將音訊資料混合到緩衝區
        SDL_MixAudio(stream, audio_pos, fill_len, SDL_MIX_MAXVOLUME);
        audio_pos += fill_len;
        audio_len -= fill_len;
        len -= fill_len;
        stream += fill_len;
        if (audio_len == 0) { // 這裡要延遲一會兒,避免一直佔據IO資源
            SDL_Delay(1);
        }
    }
}

上述修改後的程式碼已經附在了《FFmpeg開發實戰:從零基礎到短影片上線》一書第10章的原始碼chapter10/playaudio2.c,這個c程式碼是playaudio.c的改進版,除了支援原來mp3和aac格式的音訊播放,還支援ogg、amr、wma等格式的音訊播放,以及asf、webm等影片檔案的音訊播放。
接著執行下面的編譯命令。

gcc playaudio2.c -o playaudio2 -I/usr/local/ffmpeg/include -L/usr/local/ffmpeg/lib -I/usr/local/sdl2/include -L/usr/local/sdl2/lib -lsdl2 -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswscale -lswresample -lpostproc -lm

編譯完成後執行以下命令啟動測試程式,期望播放音訊檔案ring.ogg。

./playaudio2 ../ring.ogg

程式執行完畢,發現控制檯輸出以下的日誌資訊。

Success open input_file ../ring.ogg.
out_sample_rate=11025, out_nb_samples=512
out_buffer_size=1024
256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256 256
Success play audio file.
Quit SDL.

同時電腦揚聲器傳來了兩個“叮咚”的鈴聲,表示上述程式碼正確實現了播放ogg音訊的功能。

相關文章