• 1096阅读
  • 0回复

Qt音视频开发08-ffmpeg内核优化(极速打开/超时回调/实时响应) [复制链接]

上一主题 下一主题
离线liudianwu
 

图酷模式  只看楼主 倒序阅读 楼主  发表于: 2023-01-02


## 一、前言
最初编写这套视频解析组件的时候,面对的场景是视频监控行业,对应设备都是网络监控摄像机,传过来的都是rtsp这种视频流,做过这一块的人都知道,打开某个视频流默认耗时比较大,基本上在2s左右,那是因为ffmpeg接口内部读取的最大数据量 formatCtx->probesize(从源文件中读取的最大字节数)值是5000000,导致这里卡很久最耗时,可以调小来加快打开速度。还有一个参数就是从文件中读取的最大时长 formatCtx->max_analyze_duration,改成5个单位即可,5 * AV_TIME_BASE。当然这参数也不是一层不变的,需要根据实际的网络状态好坏来设置,ffmpeg内部接口值很大就是尽量考虑了网络环境很差的情况,所以默认值很大。基本上改了这两个参数以后在局域网中打开1080P的主码流都只要0.5s左右,比之前的2s提升了3倍以上。

除了对打开速度进行特别优化之外,还有一块就是超时回调,毕竟实时的视频流这种,严重依赖网络环境的好坏,一旦网络环境不好,或者网络设备坏了,网线拔了,很容易卡主读取,在用 avformat_open_input 打开视频流的阶段也特别容易卡主很久,windows上默认30s左右,这么久肯定不能接受,所以需要有个机制可以自由控制最大等待时间,设置超时回调 formatCtx->interrupt_callback.callback 就很有必要的。

以前采用过的策略是打开前去主动连接对应的IP地址和端口,通了说明设备在线,这种策略只适用于打开的时候,如果是运行过程中网络环境变坏了或者网线拔了之类的,依然识别不到,不可能开个定时器或者线程去检测,那样就太垃圾了,所以采用超时回调的做法是最好的最万能的。

## 二、效果图




## 三、体验地址
1. 国内站点:[https://gitee.com/feiyangqingyun](https://gitee.com/feiyangqingyun)
2. 国际站点:[https://github.com/feiyangqingyun](https://github.com/feiyangqingyun)
3. 个人作品:[https://blog.csdn.net/feiyangqingyun/article/details/97565652](https://blog.csdn.net/feiyangqingyun/article/details/97565652)
4. 体验地址:[https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g](https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g) 提取码:01jf 文件名:bin_video_demo/bin_linux_video。

## 四、相关代码
```cpp
bool FFmpegThread::initInput()
{
    //实例化格式处理上下文
    formatCtx = avformat_alloc_context();
    //设置超时回调(有些不存在的地址或者网络不好的情况下要卡很久)
    formatCtx->interrupt_callback.callback = FFmpegHelper::avinterruptCallBackFun;
    formatCtx->interrupt_callback.opaque = this;

    //先判断是否是本地摄像头
    AVInputFormatx *ifmt = NULL;
    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
    }

    //打开输入(通过标志位控制回调那边做超时判断)
    //其他地方调用 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_Fast2 && videoType == VideoType_Rtsp) {
        //接口内部读取的最大数据量(从源文件中读取的最大字节数)
        //默认值5000000导致这里卡很久最耗时(可以调小来加快打开速度)
        formatCtx->probesize = 50000;
        //从文件中读取的最大时长(单位为 AV_TIME_BASE units)
        formatCtx->max_analyze_duration = 5 * AV_TIME_BASE;
        //内部读取的数据包不放入缓冲区
        //formatCtx->flags |= AVFMT_FLAG_NOBUFFER;
    }

    //获取流信息
    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_Fast2) {
        useSync = false;
    }

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

    //本地摄像头设备解码出来的直接就是yuv显示不需要硬解码
    if (videoType == VideoType_Camera) {
        useSync = false;
        hardware = "none";
    }

    //过低版本不支持硬解码
#if (FFMPEG_VERSION_MAJOR < 3)
    hardware = "none";
#endif

    //发送文件时长信号(这里获取到的是秒)
    duration = formatCtx->duration / AV_TIME_BASE;
    duration = duration * 1000;
    if (getIsFile()) {
        //文件必须要音视频同步
        useSync = true;
        emit receiveDuration(duration);
    }

    QString msg = QString("格式: %1 时长: %2 秒 加速: %3").arg(formatName).arg(duration / 1000).arg(hardware);
    debug("文件信息", msg);
    return true;
}

int FFmpegHelper::avinterruptCallBackFun(void *ctx)
{
#ifdef videoffmpeg
    FFmpegThread *thread = (FFmpegThread *)ctx;
    //2021-9-29 增加先判断是否尝试停止线程,有时候不存在的地址反复打开关闭会卡主导致崩溃
    //多了这个判断可以立即停止
    if (thread->getTryStop()) {
        thread->debug("主动停止", "");
        return 1;
    }

    //打开超时判定和读取超时判定
    if (thread->getTryOpen()) {
        //时间差值=当前时间-开始解码的时间(单位微秒)
        qint64 offset = av_gettime() - thread->getStartTime();
        int timeout = thread->getConnectTimeout() * 1000;
        //没有设定对应值的话限定最小值3秒
        timeout = (timeout <= 0 ? 3000000 : timeout);
        if (offset > timeout) {
            //thread->debug("打开超时", QString("设置: %1 当前: %2").arg(timeout).arg(offset));
            return 1;
        }
    } else if (thread->getTryRead()) {
        //时间差值=当前时间-最后一次读取的时间(单位毫秒)
        QDateTime now = QDateTime::currentDateTime();
        qint64 offset = thread->getLastTime().msecsTo(now);
        int timeout = thread->getReadTimeout();
        //没有设定对应值的话限定最小值3秒
        timeout = (timeout <= 0 ? 3000 : timeout);
        if (offset > timeout) {
            //thread->debug("读取超时", QString("设置: %1 当前: %2").arg(timeout).arg(offset));
            return 1;
        }
    }
#endif
    return 0;
}
```

4条评分好评度+1贡献值+1金钱+10威望+1
20091001753 好评度 +1 - 2023-01-02
20091001753 贡献值 +1 - 2023-01-02
20091001753 威望 +1 - 2023-01-02
20091001753 金钱 +10 - 2023-01-02
欢迎关注微信公众号:Qt实战 (各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发)QQ:517216493  WX:feiyangqingyun  QQ群:751439350
快速回复
限100 字节
 
上一个 下一个