【FFmpeg雜記】音頻解碼輸出PCM格式數據分析

  FFmpeg音頻解碼後輸出的爲PCM數據,PCM中的聲音數據沒有被壓縮。
  FFmpeg中音視頻數據基本上都有Packed和Planar兩種存儲方式,對於雙聲道音頻來說,Packed方式爲兩個聲道的數據交錯存儲,交織在一起;Planar方式爲兩個聲道分開存儲,也就是平鋪分開。假設一個L/R爲一個採樣點的話(一個採樣點可能是8位16位32位等),可以這麼表示:
    Packed: L R L R L R L R   Planar: L L L L R R R R
  FFmpeg音頻解碼後的數據是存放在AVFrame frame結構中的,如果是Packed格式的話,所有的音頻數據都放在frame.data[0]結構中;如果是Planar格式的話,不同聲道的數據分別放在frame.data[0]和frame.data[1]中。
  下面爲FFmpeg音頻採樣格式,所有的Planar格式後面都有字母P標識。

enum AVSampleFormat {
    AV_SAMPLE_FMT_NONE = -1,
    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
    AV_SAMPLE_FMT_S16,         ///< signed 16 bits
    AV_SAMPLE_FMT_S32,         ///< signed 32 bits
    AV_SAMPLE_FMT_FLT,         ///< float
    AV_SAMPLE_FMT_DBL,         ///< double

    AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
    AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
    AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
    AV_SAMPLE_FMT_FLTP,        ///< float, planar
    AV_SAMPLE_FMT_DBLP,        ///< double, planar
    AV_SAMPLE_FMT_S64,         ///< signed 64 bits
    AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar

    AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically
};

  不同的格式的輸入音頻解碼後輸出的音頻採樣格式不是固定的,最常見的音頻格式有AAC和MP3兩種,我測試中,其中AAC解碼輸出的數據爲浮點型的 AV_SAMPLE_FMT_FLTP 格式,MP3解碼輸出的數據爲 AV_SAMPLE_FMT_S16P 格式(使用的mp3文件爲16位深)。具體採樣格式可以查看解碼後的AVFrame中的format成員或解碼器的AVCodecContext中的sample_fmt成員。
  這裏AAC和MP3音頻解碼後的數據都是Planar模式的,兩個聲道的聲音數據分別存在frame.data[0]和frame.data[1]中,多聲道音頻可能還會使用data[2] data[3]等。需要注意的是: 我們剛分析的Planar和Packed模式是ffmpeg內部存儲模式,我們實際使用的音頻文件都是LRLR左右聲道交替存儲的,設想如果音頻文件3MB大小的話,不可能前面1.5MB存左聲道,後面1.5MB存右聲道。
  Planar或者Packed模式直接影響到保存文件時寫文件的操作,所以操作數據的時候一定要先檢測音頻採樣格式。下面以Planar格式來演示如何保存音頻文件。

保存PCM格式數據到文件

// 前面代碼讀音頻文件,初始化FFmpeg並打開了AVCodecContext
// 下面代碼進行解碼和保存文件
bool AudioDecoder::readFrameProc()
{
    FILE *fd = fopen("out.pcm", "wb");
    AVPacket packet;    
    //av_init_packet(&packet);

    AVFrame *frame = av_frame_alloc();

    // 讀取一個幀packet的音頻數據
    while (int num = av_read_frame(mFormatCtx, &packet) >= 0) {

        // 解碼(發送一個packet,獲取到的frame就是解碼後的數據了),這是FFmpeg 3的新解碼函數
        avcodec_send_packet(mCodecCtx, &packet);
        int ret = avcodec_receive_frame(mCodecCtx, frame);
        if (!ret) {

        // 獲取一個採樣點字節數,比如16位採樣點值爲2字節
        int data_size = av_get_bytes_per_sample(mCodecCtx->sample_fmt);

        // frame->nb_samples爲這個frame中一個聲道的採樣點的個數
        for (int i = 0; i < frame->nb_samples; i++)
            for (int ch = 0; ch < mCodecCtx->channels; ch++)
                fwrite(frame->data[ch] + data_size*i, 1, data_size, fd);
        }
        av_packet_unref(&packet);
    }

    av_frame_free(&frame);
    fclose(fd);
    return false;
}

  上面這段代碼frame爲解碼後的數據,因爲是Planar模式的數據,所以寫文件的時候,存儲每採樣點的時候兩個聲道LRLRLR這樣交錯寫入文件,相當於把AV_SAMPLE_FMT_S16P 採樣格式保存爲 不帶字母P的AV_SAMPLE_FMT_S16 採樣格式,。
  舉個例子,假如這個frame中有20個採樣點(nb_samples=20),每個採樣點爲2字節(16位深,每個聲道的一個採樣點2字節)。左聲道數據爲data[0][40]數組,右聲道數據爲data[1][40]數組。寫文件的時候依次寫入 data[0][0],data[0][1] – data[1][0],data[1][1] – data[0][2],data[0][3] – data[1][2]-data[1][3]。

播放PCM音頻文件

  保存的PCM文件可以使用ffplay指定參數進行播放:

   ffplay -f 格式名 -ac 聲道數 -ar 採樣率 文件名

  本文沒有討論大端存儲還是小端存儲的問題,因爲我們通用的PC機默認都是小端存儲的。比如我使用的MP3解碼爲AV_SAMPLE_FMT_S16P格式的,對應解碼器格式名爲s16le ,ffplay播放指令爲:
  ffplay -f s16le -ac 2 -ar 44100 out.pcm
  AAC解碼爲AV_SAMPLE_FMT_FLTP格式,對應格式名爲f32le

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章