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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章