Qt/C++音視頻開發68-檢查是否含有B幀/轉碼推流/拉流顯示/監控拉流推流/海康大華宇視監控

一、前言

爲什麼需要判斷視頻文件是否含有B幀,這個在推流的時候很容易遇到這個問題,一般來說,沒有B幀的視頻文件,解碼後的數據幀pts和dts都是順序遞增的,而有B幀的則未必,可能有些需要先解碼後面顯示,B幀也是雙向預測圖像B,對它的編碼,即是對它前後幀的像素值之差進行編碼,B幀是雙向差別幀,也就是B幀記錄的是本幀與前後幀的差別換言之,要解碼B幀,不僅要取得之前的緩存畫面,還要解碼之後的畫面,通過前後畫面的與本幀數據的疊加取得最終的畫面。B幀壓縮率高,但是解碼時CPU會比較累。所以一般編碼保存成文件的時候習慣把B幀去掉,但是爲什麼又要有B幀這個東西呢?因爲可以增加壓縮率,減少文件體積,在傳輸的時候也可降低帶寬。所以用戶可以根據實際需要選擇。

如果是含有B幀的視頻文件在推流後,拉流hls、webrtc顯示的時候,很可能出現抖動鬼畜的現象。一般監控攝像頭出來的rtsp流都是沒有B幀的,錄像存儲的MP4文件也是會去掉B幀的,所以在監控行業很少遇到這個現象。一般是視頻網站的在線播放的視頻文件,或者下載的一些音樂視頻文件,幾乎都會帶有B幀,通常這些文件I幀間隔非常大,很可能在10s左右。所以一般在推流的時候,需要判斷該文件是否含有B幀,含有的話則啓動轉碼方式來推流,轉碼那邊會去掉B幀,這樣就永遠不會出現鬼畜的現象,缺點就是轉碼需要佔用一些CPU,而不轉碼則直接原數據包發送,幾乎不佔用CPU。

二、效果圖

五、相關代碼

bool FFmpegUtil::hasB(const QString &fileName, int maxFrame)
{
    bool b = false;
    int ret = -1;
    int frameCount = 0;

    AVFormatContext *formatCtx = NULL;
    AVStream *videoStream = NULL;
    AVCodecx *videoCodec = NULL;
    AVCodecContext *videoCodecCtx = NULL;
    AVPacket *packet = NULL;
    AVFrame *frame = NULL;

    //打開文件
    QString url = FFmpegHelper::getPlayUrl(fileName);
    avformat_open_input(&formatCtx, url.toUtf8().constData(), NULL, NULL);
    avformat_find_stream_info(formatCtx, NULL);
    int videoIndex = av_find_best_stream(formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &videoCodec, 0);
    if (videoIndex < 0) {
        goto end;
    }

    videoStream = formatCtx->streams[videoIndex];
    videoCodecCtx = avcodec_alloc_context3(NULL);
    //videoCodec = avcodec_find_decoder(FFmpegHelper::getCodecId(videoStream));
    FFmpegHelper::copyContext(videoCodecCtx, videoStream, false);
    if ((ret = avcodec_open2(videoCodecCtx, videoCodec, NULL)) < 0) {
        goto end;
    }

    //取出前XX幀解碼出來判斷有沒有B幀
    packet = FFmpegHelper::creatPacket(NULL);
    frame = av_frame_alloc();
    while (av_read_frame(formatCtx, packet) >= 0) {
#if (FFMPEG_VERSION_MAJOR < 3)
        maxFrame = 30;
        if (avcodec_decode_video2(videoCodecCtx, frame, &ret, packet) < 0) {
            continue;
        }
#else
        if (avcodec_send_packet(videoCodecCtx, packet) < 0) {
            continue;
        }
        if (avcodec_receive_frame(videoCodecCtx, frame) < 0) {
            continue;
        }
#endif

        if (frame->pict_type == 3) {
            b = true;
            goto end;
        }

        frameCount++;
        if (frameCount == maxFrame) {
            goto end;
        }
    }

end:
    if (packet) {
        FFmpegHelper::freePacket(packet);
    }
    if (frame) {
        FFmpegHelper::freeFrame(frame);
    }
    if (videoCodecCtx) {
        avcodec_free_context(&videoCodecCtx);
    }
    if (formatCtx) {
        avformat_close_input(&formatCtx);
        avformat_free_context(formatCtx);
    }
    return b;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章