天天看點

Qt音視訊開發43-采集螢幕桌面并推流(實時性極高)

作者:Qt自定義控件

一、前言

采集電腦螢幕桌面并推流一般是用來做共享桌面、遠端協助、投屏之類的應用,最簡單入門的做法可能會采用開個定時器或者線程抓圖,将整個螢幕截圖下來,然後将圖檔傳出去,這種方式很簡單但是性能要低不少,一般采用ffmpeg來做桌面推流的居多,畢竟如果不采用代碼直接ffmpeg一行指令即可(ffmpeg -f gdigrab -r 30 -i desktop -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -f rtsp -g 5 -an rtsp://192.168.0.110:6907/stream), 最起碼這個還沒開始寫代碼直接就可以體驗起來這個感覺很好。很多開源項目,就因為無法保證直接編譯就能跑起來,導緻熄火,都跑不起來何來的看下去的興趣,尤其是初學者而言更是如此。

采集大緻步驟:

  • 查找格式 av_find_input_format,參數 gdigrab/x11grab/avfoundation
  • 打開桌面 avformat_open_input,參數 desktop
  • 查找視訊流 av_find_best_stream
  • 查找解碼器 avcodec_find_decoder
  • 打開解碼器 avcodec_open2
  • 循環讀取 av_read_frame
  • 解碼視訊 avcodec_send_packet/avcodec_receive_frame
  • 關閉釋放 avcodec_free_context/avformat_close_input

推流大緻步驟:

  • 建立輸出 avformat_alloc_output_context2
  • 建立視訊流 avformat_new_stream
  • 打開輸出 avio_open,參數填推流完整位址
  • 寫入開始符 avformat_write_header
  • 寫入幀資料 av_interleaved_write_frame
  • 關閉釋放 avio_close/avformat_free_context

二、效果圖

Qt音視訊開發43-采集螢幕桌面并推流(實時性極高)

三、體驗位址

  1. 國内站點:https://gitee.com/feiyangqingyun
  2. 國際站點:https://github.com/feiyangqingyun
  3. 個人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. 體驗位址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取碼:01jf 檔案名:bin_video_push。

四、相關代碼

void FFmpegThread::initInputFormat()
{
    //本地攝像頭/桌面錄屏
    if (videoType == VideoType_Camera) {
#if defined(Q_OS_WIN)
        //ifmt = av_find_input_format("vfwcap");
        ifmt = av_find_input_format("dshow");
#elif defined(Q_OS_LINUX)
        //可以打開cheese程式檢視本地攝像頭(如果是在虛拟機中需要設定usb選項3.1)
        //ifmt = av_find_input_format("v4l2");
        ifmt = av_find_input_format("video4linux2");
#elif defined(Q_OS_MAC)
        ifmt = av_find_input_format("avfoundation");
#endif
    } else if (videoType == VideoType_Desktop) {
#if defined(Q_OS_WIN)
        ifmt = av_find_input_format("gdigrab");
#elif defined(Q_OS_LINUX)
        ifmt = av_find_input_format("x11grab");
#elif defined(Q_OS_MAC)
        ifmt = av_find_input_format("avfoundation");
#endif
    }
}

bool FFmpegThread::initInput()
{
    //執行個體化格式處理上下文
    formatCtx = avformat_alloc_context();
    //設定逾時回調(有些不存在的位址或者網絡不好的情況下要卡很久)
    formatCtx->interrupt_callback.callback = FFmpegHelper::avinterruptCallBackFun;
    formatCtx->interrupt_callback.opaque = this;

    //打開輸入(通過标志位控制回調那邊做逾時判斷)
    //其他地方調用 formatCtx->url formatCtx->filename 可以拿到設定的位址(兩個變量值一樣)
    tryOpen = true;
    QByteArray urlData = VideoHelper::getRightUrl(videoType, videoUrl).toUtf8();
    int result = avformat_open_input(&formatCtx, urlData.data(), ifmt, &options);
    tryOpen = false;
    if (result < 0) {
        debug("打開出錯", "錯誤: " + FFmpegHelper::getError(result));
        return false;
    }

    //根據自己項目需要開啟下面部分代碼加快視訊流打開速度
    //開啟後由于值太小可能會出現部分視訊流擷取不到分辨率
    if (decodeType == DecodeType_Fastest && videoType == VideoType_Rtsp) {
        //接口内部讀取的最大資料量(從源檔案中讀取的最大位元組數)
        //預設值5000000導緻這裡卡很久最耗時(可以調小來加快打開速度)
        formatCtx->probesize = 50000;
        //從檔案中讀取的最大時長(機關為 AV_TIME_BASE units)
        formatCtx->max_analyze_duration = 5 * AV_TIME_BASE;
        //内部讀取的資料包不放入緩沖區
        //formatCtx->flags |= AVFMT_FLAG_NOBUFFER;
        //設定解碼錯誤驗證過濾花屏
        //formatCtx->error_recognition |= AV_EF_EXPLODE;
    }

    //擷取流資訊
    result = avformat_find_stream_info(formatCtx, NULL);
    if (result < 0) {
        debug("找流失敗", "錯誤: " + FFmpegHelper::getError(result));
        return false;
    }

    //解碼格式
    formatName = formatCtx->iformat->name;
    //某些格式比如視訊流不做音視訊同步(響應速度快)
    if (formatName == "rtsp" || videoUrl.endsWith(".sdp")) {
        useSync = false;
    }

    //設定了最快速度則不啟用音視訊同步
    if (decodeType == DecodeType_Fastest) {
        useSync = false;
    }

    //有些格式不支援硬解碼
    if (formatName.contains("rm") || formatName.contains("avi") || formatName.contains("webm")) {
        hardware = "none";
    }

    //本地攝像頭裝置解碼出來的直接就是yuv顯示不需要硬解碼
    if (videoType == VideoType_Camera || videoType == VideoType_Desktop) {
        useSync = false;
        hardware = "none";
    }

    //過低版本不支援硬解碼
#if (FFMPEG_VERSION_MAJOR < 3)
    hardware = "none";
#endif

    //擷取檔案時長(這裡擷取到的是秒)
    double length = (double)formatCtx->duration / AV_TIME_BASE;
    duration = length * 1000;
    this->checkVideoType();

    //有時候網絡位址也可能是純音頻
    if (videoType == VideoType_FileHttp) {
        onlyAudio = VideoHelper::getOnlyAudio(videoUrl, formatName);
    }

    if (getIsFile()) {
        //檔案必須要音視訊同步
        useSync = true;
        //發送檔案時長信号
        emit receiveDuration(duration > 0 ? duration : 0);
    }

    QString msg = QString("格式: %1 時長: %2 秒 加速: %3").arg(formatName).arg(duration / 1000).arg(hardware);
    debug("媒體資訊", msg);
    return true;
}           

五、功能特點

5.1 檔案推流

  1. 指定網卡和監聽端口,接收網絡請求推送音視訊等各種檔案。
  2. 實時統計顯示每個檔案對應的通路數量、總通路數量、不同IP位址通路數量。
  3. 可指定多種模式,0-直接播放、1-下載下傳播放。
  4. 實時列印顯示各種收發請求和應答資料。
  5. 每個檔案對應MD5加密的唯一辨別符,用于請求位址字尾區分通路哪個檔案。
  6. 支援各種浏覽器(谷歌chromium/微軟edge/火狐firefox等)、各種播放器(vlc/mpv/ffplay/potplayer/mpchc等)打開請求。
  7. 播放過程中可以任意切換播放進度,支援倍速播放。
  8. 需要推流的檔案名稱曆史記錄自動存儲和打開加載應用。
  9. 切換檔案擷取通路位址,自動拷貝位址到剪切闆友善直接粘貼測試使用。
  10. 極低CPU占用,128路1080P同時推流不到1%CPU占用,異步發送資料機制。
  11. 純QTcpSocket通信,不依賴流媒體服務程式,核心源碼不到500行,注釋詳細,功能完整。
  12. 支援Qt4/Qt5/Qt6任意版本,支援任意系統(windows/linux/macos/android/嵌入式linux等)。

5.2 網絡推流

  1. 支援各種本地視訊檔案和網絡視訊檔案。
  2. 支援各種網絡視訊流,網絡攝像頭,協定包括rtsp、rtmp、http。
  3. 支援将本地攝像頭裝置推流,可指定分辨率和幀率等。
  4. 支援将本地桌面推流,可指定螢幕區域和幀率等。
  5. 自動啟動流媒體服務程式,預設mediamtx(原rtsp-simple-server),可選用srs、EasyDarwin、LiveQing、ZLMediaKit等。
  6. 可實時切換預覽視訊檔案。
  7. 推流的清晰度和品質可調。
  8. 可動态添加檔案、目錄、位址。
  9. 視訊檔案自動循環推流,如果視訊源是視訊流,在掉線後會自動重連。
  10. 網絡視訊流自動重連,重連成功自動繼續推流。
  11. 網絡視訊流實時性極高,延遲極低,延遲時間大概在100ms左右。
  12. 推流後除了用rtmp位址通路以外,還支援直接hls/webrtc通路,可以直接浏覽器打開看實時畫面。
  13. 支援Qt4/Qt5/Qt6任意版本,支援任意系統(windows/linux/macos/android/嵌入式linux等)。

繼續閱讀