一、前言
为什么需要判断视频文件是否含有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;
}