## 一、前言
最初编写这套视频解析组件的时候,面对的场景是视频监控行业,对应设备都是网络监控摄像机,传过来的都是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;
}
```