【FFmpeg 3.x API應用三】音頻解碼

摘要

這篇文章介紹音頻解碼,示例程序是讀取一個mp3格式或者aac格式的音頻文件,解碼輸出爲未壓縮的pcm音頻文件。

初始化FFmepg和FormatContext

和視頻解碼一樣,先使用av_register_all註冊所有相關組件,然後使用avformat_open_input打開指定的媒體文件,並使用avformat_find_stream_info獲取媒體流相關信息,把這些格式信息映射到AVFormatContext *mFormatCtx這個結構中。
使用函數av_dump_format可以從控制檯輸出媒體文件相關信息。

bool AudioDecoding::init(const char * audioFile)
{
    av_register_all();

    if ((avformat_open_input(&mFormatCtx, audioFile, 0, 0)) < 0) {
        printf("Failed to open input file\n");
        return true;
    }

    if ((avformat_find_stream_info(mFormatCtx, 0)) < 0) {
        printf("Failed to retrieve input stream information\n");
        return true;
    }

    av_dump_format(mFormatCtx, 0, audioFile, 0);
    return false;
}

配置編解碼器CodecContext

  1. 根據輸入文件格式查找相應的解碼器,因爲使用的是aac/mp3格式的音頻文件,只有一個音頻流,所以就不用查找流Index了,直接使用streams[0]就可以了。
  2. 根據解碼器申請CodecContext。
  3. 填充CodecContext信息
  4. 完成CodecContext初始化。
bool AudioDecoding::initCodecContext()
{
    // 根據id查找對應解碼器
    AVCodec *dec = avcodec_find_decoder(mFormatCtx->streams[0]->codecpar->codec_id);
    if (!dec) {
        printf("Failed to find codec!\n");
        return true;
    }

    // 申請CodecContext
    if (!(mCodecCtx = avcodec_alloc_context3(dec))) {
        printf("Failed to allocate the codec context\n");
        return true;
    }

    // 填充CodecContext信息
    if (avcodec_parameters_to_context(mCodecCtx, mFormatCtx->streams[0]->codecpar) < 0) {
        printf("Failed to copy codec parameters to decoder context!\n");
        return true;
    }

    // 初始化AVCodecContext結構
    if (avcodec_open2(mCodecCtx, dec, NULL) < 0) {
        printf("Failed to open codec\n");
        return true;
    }
    return false;
}

音頻解碼,保存pcm文件

  1. 循環讀取輸入文件的數據,直接讀取完畢。
  2. 依次處理各個讀取到的packet,進行解碼。
  3. 解碼出來的是未壓縮的音頻數據,把這些音頻數據寫入文件。

需要注意的是,輸入音頻格式不同時,解碼後輸出的pcm音頻數據格式也會不同,我們這裏沒有進行重採樣處理。本程序當使用echo.mp3(sample_fmt = AV_SAMPLE_FMT_S16P)作爲輸入文件時,,一個採樣點是16bit,輸出文件取名爲out_s16le.pcm;使用echo.aac(sample_fmt = AV_SAMPLE_FMT_FLTP)作爲輸入文件,一個採樣點是32bit,輸出文件取名爲out_f32le.pcm,以便於播放。
關於音頻解碼輸出PCM數據的分析,請參考另一篇文章《音頻解碼輸出PCM格式數據分析》。

bool AudioDecoding::readFrameProc()
{
    // 使用echo.mp3作爲輸入文件,文件名爲s16le.pcm
    // 使用echo.aac作爲輸入文件,文件名則爲f32le.pcm
    FILE *fd = fopen("../assets/out_s16le.pcm", "wb");
    if (!fd) {
        printf("Failed to open input file\n");
        return true;
    }

    AVPacket packet;
    av_init_packet(&packet);

    AVFrame *frame = av_frame_alloc();

    //循環讀取音頻數據packet
    while (int num = av_read_frame(mFormatCtx, &packet) >= 0) {

        //音頻解碼,同視頻解碼一樣都使用這對函數
        avcodec_send_packet(mCodecCtx, &packet);
        int ret = avcodec_receive_frame(mCodecCtx, frame);
        if (!ret) {

            // number of bytes per sample, 16bit is 2 Bytes
            //根據格式查詢音頻採樣點深度,一個採樣點使用多少字節表示。
            //本文使用的mp3文件(sample_fmt = AV_SAMPLE_FMT_S16P),一個採樣點是16bit,則返回字節數爲2;
            //使用的aac文件(sample_fmt = AV_SAMPLE_FMT_FLTP),一個採樣點是32bit,則返回字節數4。
            int sampleBytes = av_get_bytes_per_sample(mCodecCtx->sample_fmt);

            // 使用Planar格式寫音頻文件,nb_samples爲這個frame中一個聲道的採樣點的個數,channels爲聲道個數
            for (int i = 0; i < frame->nb_samples; i++)
                for (int ch = 0; ch < mCodecCtx->channels; ch++)
                    fwrite(frame->data[ch] + sampleBytes*i, 1, sampleBytes, fd);

        }
        av_packet_unref(&packet);
    }

    av_frame_free(&frame);

    fclose(fd);

    return false;
}

釋放系統資源

AudioDecoding::~AudioDecoding()
{
    avcodec_free_context(&mCodecCtx);
    avformat_close_input(&mFormatCtx);
}

PCM音頻文件播放方法

上面說過使用MP3輸入時,輸出文件名爲out_s16le.pcm,使用aac格式文件作爲輸入時,輸出文件名爲out_f32le.pcm。可以使用ffplay播放pcm文件,這兩個文件對應的播放命令爲:

ffplay -f s16le -ac 2 -ar 44100 out_s16le.pcm
ffplay -f f32le -ac 2 -ar 44100 out_f32le.pcm

如果播放不正常,很可能是播放參數不正確。

示例程序代碼

上述示例的完整代碼可以從Github下載: https://github.com/lmshao/FFmpeg-Basic

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