1. 程式人生 > 實用技巧 >Qt音視訊開發6-ffmpeg解碼處理

Qt音視訊開發6-ffmpeg解碼處理

一、前言

採用ffmpeg解碼,是所有視訊監控開發人員必備的技能,繞不過去的一個玩意,甚至可以說是所有音視訊開發人員的必備技能。FFmpeg是一套可以用來記錄、轉換數字音訊、視訊,並能將其轉化為流的開源計算機程式。它包括了目前領先的音/視訊編碼庫libavcodec。 FFmpeg是在 Linux 下開發出來的,但它可以在包括 Windows 在內的大多數作業系統中編譯。這個專案是由 Fabrice Bellard 發起的,現在由 Michael Niedermayer 主持。可以輕易地實現多種視訊格式之間的相互轉換,例如可以將攝錄下的視訊avi等轉成現在視訊網站所採用的flv格式。

關於ffmpeg解碼,網上搜索到的程式碼絕對是一大堆一大堆,而且很多都講得很詳細,解碼的函式流程圖非常清晰,關於ffmpeg這塊的學習本人推薦雷神的部落格,分析的相當細緻,我在很久以前剛用Qt+ffmpeg解碼的時候,參考的就是雷神的例子,當然這些demo其實在ffmpeg的開發包dev下的examples也是非常詳細的,只不過沒有什麼分析過程,參考雷神的部落格可以看到很多分析過程。

本人總結的解碼過程:

  1. 註冊解碼庫相關(av_register_all、avformat_network_init等)
  2. 初始化各種引數比如快取大小等(av_dict_set)
  3. 開啟視訊流或者檔案(avformat_alloc_context、avformat_open_input)
  4. 獲取流資訊(avformat_find_stream_info)
  5. 獲取視訊流並初始化視訊解碼器(av_find_best_stream、avcodec_find_decoder)
  6. 獲取音訊流並初始化音訊解碼器(av_find_best_stream、avcodec_find_decoder、avcodec_open2)
  7. 預分配幀記憶體(av_frame_alloc)
  8. 迴圈讀取音視訊幀(av_read_frame、av_packet_unref)
  9. 解碼視訊(avcodec_decode_video2或者avcodec_send_packet、avcodec_receive_frame)
  10. 解碼音訊(avcodec_decode_audio4)
  11. 處理結束釋放資源(sws_freeContext、av_frame_free、av_free)

二、功能特點

  1. 多執行緒實時播放視訊流+本地視訊+USB攝像頭等。
  2. 支援windows+linux+mac,支援ffmpeg3和ffmpeg4,支援32位和64位。
  3. 多執行緒顯示影象,不卡主介面。
  4. 自動重連網路攝像頭。
  5. 可設定邊框大小即偏移量和邊框顏色。
  6. 可設定是否繪製OSD標籤即標籤文字或圖片和標籤位置。
  7. 可設定兩種OSD位置和風格。
  8. 可設定是否儲存到檔案以及檔名。
  9. 可直接拖曳檔案到ffmpegwidget控制元件播放。
  10. 支援h265視訊流+rtmp等常見視訊流。
  11. 可暫停播放和繼續播放。
  12. 支援回撥模式和控制代碼兩種模式。
  13. 支援執行緒讀取進度等資訊和事件回撥兩種處理模式。
  14. 自動將當前播放位置和音量大小是否靜音以訊號發出去。
  15. 提供介面設定播放位置和音量及設定靜音。
  16. 支援儲存單個視訊檔案和定時儲存視訊檔案。
  17. 自定義頂部懸浮條,傳送單擊訊號通知,可設定是否啟用。
  18. 支援qsv、dxva2、d3d11va等硬解碼。
  19. 支援opengl繪製視訊資料,極低CPU佔用。
  20. 支援嵌入式linux。

三、效果圖

四、相關站點

  1. 國內站點:https://gitee.com/feiyangqingyun/QWidgetDemo
  2. 國際站點:https://github.com/feiyangqingyun/QWidgetDemo
  3. 個人主頁:https://blog.csdn.net/feiyangqingyun
  4. 知乎主頁:https://www.zhihu.com/people/feiyangqingyun/
  5. 體驗地址:https://blog.csdn.net/feiyangqingyun/article/details/97565652

五、核心程式碼

bool FFmpegThread::init()
{
    //判斷該攝像機是否能聯通
    if (checkConn && isRtsp) {
        if (!checkUrl(url, checkTime)) {
            return false;
        }
    }

    //初始化引數
    this->initOption();
    //初始化輸入
    if (!initInput()) {
        return false;
    }
    //初始化視訊
    if (!initVideo()) {
        return false;
    }
    //初始化音訊
    if (!initAudio()) {
        return false;
    }
    //初始化幀
    this->initFrame();

    //輸出視訊資訊
    //av_dump_format(formatCtx, 0, url.toStdString().data(), 0);
    //qDebug() << TIMEMS << "init ffmpeg finsh";
    return true;
}

void FFmpegThread::initOption()
{
    //在開啟碼流前指定各種引數比如:探測時間/超時時間/最大延時等
    //設定快取大小,1080p可將值調大
    av_dict_set(&options, "buffer_size", "8192000", 0);
    //以tcp方式開啟,如果以udp方式開啟將tcp替換為udp
    av_dict_set(&options, "rtsp_transport", transport.toLatin1().constData(), 0);
    //設定超時斷開連線時間,單位微秒,3000000表示3秒
    av_dict_set(&options, "stimeout", "3000000", 0);
    //設定最大時延,單位微秒,1000000表示1秒
    av_dict_set(&options, "max_delay", "1000000", 0);
    //自動開啟執行緒數
    av_dict_set(&options, "threads", "auto", 0);

    //設定USB攝像機解析度
    if (url.startsWith("video")) {
        QString size = QString("%1x%2").arg(videoWidth).arg(videoHeight);
        av_dict_set(&options, "video_size", size.toLatin1().constData(), 0);
    }
}

bool FFmpegThread::initInput()
{
    //例項化格式處理上下文
    formatCtx = avformat_alloc_context();

    //先判斷是否是本地裝置(video=裝置名字串),開啟的方式不一樣
    int result = -1;
    if (url.startsWith("video")) {
#if defined(Q_OS_WIN)
        AVInputFormat *ifmt = av_find_input_format("dshow");
#elif defined(Q_OS_LINUX)
        AVInputFormat *ifmt = av_find_input_format("video4linux2");
#elif defined(Q_OS_MAC)
        AVInputFormat *ifmt = av_find_input_format("avfoundation");
#endif
        result = avformat_open_input(&formatCtx, url.toStdString().data(), ifmt, &options);
    } else {
        result = avformat_open_input(&formatCtx, url.toStdString().data(), NULL, &options);
    }

    if (result < 0) {
        qDebug() << TIMEMS << "open input error" << url;
        return false;
    }

    //釋放設定引數
    if (options != NULL) {
        av_dict_free(&options);
    }

    //獲取流資訊
    result = avformat_find_stream_info(formatCtx, NULL);
    if (result < 0) {
        qDebug() << TIMEMS << "find stream info error";
        return false;
    }

    return true;
}