Qt與FFmpeg聯合開發指南(三)-- 解碼播放本地音視頻

FFmpeg音視頻解碼流程

  • AAC視頻格式解碼爲PCM編碼格式
  • h264視頻格式解碼爲YUV編碼格式

音頻解碼流程與視頻解碼流程大同小異(視頻解碼流程詳細見:Qt與FFmpeg聯合開發指南(二)-- 解碼播放本地視頻), 使用不同的解碼器對音頻視頻流進行解碼操作。初始化操作兩者一致。

核心代碼:

先添加音頻採樣數據格式庫:

//音頻採樣數據格式庫
​​​​​​​#include <libswresample/swresample.h>
第一步:獲取當前解碼器是屬於什麼類型解碼器
    qDebug()<<"第四步:查找解碼器";
    int av_stream_index = -1;
    int audio_index = -1;
    for (int i = 0; i < avformat_context->nb_streams; ++i) {
        //循環遍歷每一流
        //視頻流、音頻流、字幕流等等...
        if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            //找到了視頻流
            av_stream_index = i;
        }
        if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            //找到了音頻
            audio_index = i;
        }
    }
    if (av_stream_index == -1 )
    {
        qDebug()<<QString("沒有找到視頻流");
        return false;
    }
    if (audio_index == -1 )
    {
        qDebug()<<QString("沒有找到音頻流");
        return false;
    }

 第二步:根據流信息打開對應解碼器

    //第二點:根據視頻流->查找到視頻解碼器上下文->視頻壓縮數據
    AVCodecContext* avcodec_context = avformat_context->streams[av_stream_index]->codec;

    //第三點:根據解碼器上下文->獲取解碼器ID
    AVCodec* avcodec = avcodec_find_decoder(avcodec_context->codec_id);
    if (avcodec == NULL)
    {
        qDebug()<<QString("沒有找到視頻解碼器");
        return false;
    }

    qDebug()<<"第五步:打開視頻解碼器";
    //第五步:打開解碼器
    int avcodec_open2_result = avcodec_open2(avcodec_context,avcodec,NULL);
    if (avcodec_open2_result != 0)
    {
        char* error_info = new char[32];
        av_strerror(avformat_find_stream_info_result, error_info, 1024);
        qDebug()<<QString("異常信息 %1").arg(error_info);
        return false;
    }
    ////////音頻

    //第二點:根據音頻流->查找到音頻解碼器上下文->音頻壓縮數據
    AVCodecContext* avcodec_context_audio = avformat_context->streams[audio_index]->codec;

    //第三點:根據解碼器上下文->獲取解碼器ID
    AVCodec* avcodec_audio = avcodec_find_decoder(avcodec_context_audio->codec_id);
    if (avcodec_audio == NULL)
    {
        qDebug()<<QString("沒有找到音頻解碼器");
        return false;
    }

    qDebug()<<"      打開音頻解碼器";
    //第五步:打開音頻解碼器
    int avcodec_open2_result1 = avcodec_open2(avcodec_context_audio,avcodec_audio,NULL);
    if (avcodec_open2_result1 != 0)
    {
        char* error_info = new char[32];
        av_strerror(avformat_find_stream_info_result, error_info, 1024);
        qDebug()<<QString("異常信息 %1").arg(error_info);
        return false;
    }

輸出解碼器名稱

  qDebug()<<QString("視頻解碼器名稱: %1").arg(avcodec->name);
  qDebug()<<QString("音頻解碼器 %1").arg(avcodec_audio->name);

第三步:創建音視頻壓縮數據幀

  //循環讀取視頻幀,進行循環解碼->輸出YUV420P視頻->格式:yuv格式
    qDebug()<<"第六步:循環讀取視頻幀,進行循環解碼";
    //讀取幀數據換成到哪裏->緩存到packet裏面
    AVPacket* av_packet = (AVPacket*)av_malloc(sizeof(AVPacket));
    //輸入->環境一幀數據->緩衝區->類似於一張圖
    AVFrame* av_frame_in = av_frame_alloc();
    //輸出->幀數據->視頻像素數據格式->yuv420p
    AVFrame* av_frame_out_yuv420p = av_frame_alloc();
    //緩衝區分配內存
    uint8_t *out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height));
    //初始化緩衝區
    avpicture_fill((AVPicture *)av_frame_out_yuv420p, out_buffer, AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height);
    //解碼的狀態類型(0:表示解碼完畢,非0:表示正在解碼)
    int  av_decode_result, y_size, u_size, v_size, current_frame_index = 0,current_audio_index = 0;
    //準備一個視頻像素數據格式上下文
    //參數一:輸入幀數據寬
    //參數二:輸入幀數據高
    //參數三:輸入幀數據格式
    //參數四:輸出幀數據寬
    //參數五:輸出幀數據高
    //參數六:輸出幀數據格式->AV_PIX_FMT_YUV420P
    //參數七:視頻像素數據格式轉換算法類型
    //參數八:字節對齊類型(C/C++裏面)->提高讀取效率
    SwsContext* sws_context = sws_getContext(avcodec_context->width,
                                             avcodec_context->height,
                                             avcodec_context->pix_fmt,
                                             avcodec_context->width,
                                             avcodec_context->height,
                                             AV_PIX_FMT_YUV420P,
                                             SWS_BICUBIC,NULL,NULL,NULL);
    /////////////////////////////////////////////////////////////視頻處理
 
    //創建音頻壓縮數據幀->acc格式、mp3格式
    AVPacket* avpacket_audio = (AVPacket*)av_malloc(sizeof(AVPacket));
    //創建音頻採樣數據幀
    AVFrame* avframe_audio = av_frame_alloc();
    // 確保解碼爲pcm我們需要轉換爲pcm格式
    SwrContext*swr_context_audio = swr_alloc();
    swr_alloc_set_opts(swr_context_audio,
                       AV_CH_LAYOUT_STEREO,//立體聲 輸出聲道佈局類型
                       AV_SAMPLE_FMT_S16, //輸出採樣精度
                       avcodec_context_audio->sample_rate, //輸出採樣率
                       avcodec_context_audio->channel_layout, //輸入聲道佈局類型
                       avcodec_context_audio->sample_fmt, //輸入採樣精度
                       avcodec_context_audio->sample_rate, //輸入採樣率
                       NULL, NULL);
    //初始化音頻採樣數據上下文
    swr_init(swr_context_audio);
    //緩衝區大小 = 採樣率(44100HZ) * 採樣精度(16位 = 2字節)
    int MAX_AUDIO_SIZE = 44100 * 2;
    uint8_t *out_audio= (uint8_t *) av_malloc(MAX_AUDIO_SIZE);;
    //獲取輸出的聲道個數
    int out_nb_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);

    int audio_decode_result = 0;

第四步:循環讀取音頻壓縮數據

  FILE* out_file_pcm = fopen("diao.pcm","wb+");
    if (out_file_pcm == NULL){
        qDebug()<<QString("文件不存在 ");
        return false;
    }   
     while (av_read_frame(avformat_context,av_packet) >= 0)
    {
         //解碼什麼類型流(視頻流、音頻流、字幕流等等...)
        if (av_packet->stream_index == av_stream_index)
        {
           //略 (參照上一篇)
        }
        //解碼什麼類型流(視頻流、音頻流、字幕流等等...)
        if (av_packet->stream_index == audio_index)
        {
            //音頻解碼
            //1、發送一幀音頻壓縮數據包->音頻壓縮數據幀
            avcodec_send_packet(avcodec_context_audio, av_packet);

            //2、解碼一幀音頻壓縮數據包->得到->一幀音頻採樣數據->音頻採樣數據幀
            av_decode_result = avcodec_receive_frame(avcodec_context_audio, avframe_audio);
            // 解碼成功
            if ( av_decode_result == 0 )
            {
                //3、類型轉換(音頻採樣數據格式有很多種類型)
                //我希望我們的音頻採樣數據格式->pcm格式->保證格式統一->輸出PCM格式文件
                //swr_convert:表示音頻採樣數據類型格式轉換器
                //參數一:音頻採樣數據上下文
                //參數二:輸出音頻採樣數據
                //參數三:輸出音頻採樣數據->大小
                //參數四:輸入音頻採樣數據
                //參數五:輸入音頻採樣數據->大小
                swr_convert(swr_context_audio,
                            &out_audio,
                            MAX_AUDIO_SIZE,
                            (const uint8_t **)avframe_audio->data,
                            avframe_audio->nb_samples);
                //4、獲取緩衝區實際存儲大小
                //參數一:行大小
                //參數二:輸出聲道數量
                //參數三:輸入大小
                int nb_samples = avframe_audio->nb_samples;
                //參數四:輸出音頻採樣數據格式
                //參數五:字節對齊方式
                int out_buffer_size = av_samples_get_buffer_size(NULL,
                                                                 out_nb_channels,
                                                                 nb_samples,
                                                                 AV_SAMPLE_FMT_S16,
                                                                 1);

                //5、寫入文件
                fwrite(out_audio, 1, out_buffer_size, out_file_pcm);
                current_audio_index++;
                qDebug()<<QString("音頻當前遍歷第 %1 幀").arg(current_audio_index);

            }
        }
    }

第五步:關閉音頻解碼器

   av_packet_free(&avpacket_audio);
    swr_free(&swr_context_audio);
    av_free(out_buffer);
    avcodec_close(avcodec_context_audio);
    av_frame_free(&avframe_audio);

解碼後存儲地pcm音頻文件使用:Softe Audio Converter軟件進行查看

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