一、前言
爲什麼需要判斷視頻文件是否含有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;
}