Qt音视频开发10-ffmpeg控制播放

2020-08-14 17:57:04 浏览数 (1)

一、前言

很多人在用ffmpeg做视频流解码的时候,都会遇到一个问题,如何暂停,如果打开的是本地视频文件,暂停你只需要停止解码即可,但是视频流你会发现根本没用,一旦你停止了解码,下次重新解码的时候,居然还是以前的图片,他是从你最后暂停开始的地方重新解码的,这就懵逼了,为啥呢?我个人的理解是视频流这玩意,一旦你打开了,他就源源不断涌过来,你不处理,他就越来越多,你必须要读取他,从缓冲区拿走这些数据才行,所以如果想要暂停视频流,正确的做法是照常解码,只是不处理和绘制图片就行,说白了其实就是伪暂停,看起来是暂停了,其实后台还在不断的解码中。

用ffmpeg播放本地文件的时候,如果不加延时,你会发现刷刷几秒钟就播放完了,具体看电脑的性能,性能好的电脑也就几秒钟播放一个5分钟的视频,是不是会觉得很奇怪呢,怎么播放的这么快呢,其实ffmpeg解码只管解码,疯狂解码模式,使命的干,榨干你的CPU或者GPU资源(如果开启了硬解码则走GPU),解码后的每一帧都带有pts dts等信息,需要自己根据这些信息来做延时处理,比如还没到下一帧的时候,你就延时一段时间再去解码,至于延时多久有一个通用的计算方法,在打开流后记住开始的时间。然后解码中取出对应流(视频流或者音频流等)的基准时间time_base,调用av_rescale_q计算出pts时间,然后用av_gettime() - startTime得到当前的时间,用pts_time - now_time得到时间差值,如果是正数,则这个时间就是需要延时的微秒数,注意是微秒数而不是毫秒数哦,直接调用av_usleep来延时即可。

二、功能特点

  1. 多线程实时播放视频流 本地视频 USB摄像头等。
  2. 支持windows linux mac,支持ffmpeg3和ffmpeg4,支持32位和64位。
  3. 多线程显示图像,不卡主界面。
  4. 自动重连网络摄像头。
  5. 可设置边框大小即偏移量和边框颜色。
  6. 可设置是否绘制OSD标签即标签文本或图片和标签位置。
  7. 可设置两种OSD位置和风格。
  8. 可设置是否保存到文件以及文件名。
  9. 可直接拖曳文件到ffmpegwidget控件播放。
  10. 支持h265视频流 rtmp等常见视频流。
  11. 可暂停播放和继续播放。
  12. 支持存储单个视频文件和定时存储视频文件。
  13. 自定义顶部悬浮条,发送单击信号通知,可设置是否启用。
  14. 可设置画面拉伸填充或者等比例填充。
  15. 可设置解码是速度优先、质量优先、均衡处理。
  16. 可对视频进行截图(原始图片)和截屏。
  17. 录像文件存储支持裸流和MP4文件。
  18. 支持qsv、dxva2、d3d11va等硬解码。
  19. 支持opengl绘制视频数据,极低CPU占用。
  20. 支持嵌入式linux,交叉编译即可。

三、效果图

QQ截图20200806133243.jpgQQ截图20200806133243.jpg

四、相关站点

  1. 国内站点:https://gitee.com/feiyangqingyun/QWidgetDemo
  2. 国际站点:https://github.com/feiyangqingyun/QWidgetDemo
  3. 个人主页:https://blog.csdn.net/feiyangqingyun
  4. 知乎主页:https://www.zhihu.com/people/feiyangqingyun/
  5. 体验地址:https://blog.csdn.net/feiyangqingyun/article/details/97565652

五、核心代码

代码语言:txt复制
void FFmpegWidget::updateImage(const QImage &image)
{
    //暂停或者不可见 rtsp视频流需要停止绘制
    if (!this->property("isPause").toBool() && this->isVisible() && thread->isRunning()) {
        //拷贝图片有个好处,当处理器比较差的时候,图片不会产生断层,缺点是占用时间
        //默认QImage类型是浅拷贝,可能正在绘制的时候,那边已经更改了图片的上部分数据
        this->image = copyImage ? image.copy() : image;
        this->update();
    }
}

void FFmpegWidget::updateFrame(AVFrame *frame)
{
#ifdef opengl
    //暂停或者不可见 rtsp视频流需要停止绘制
    if (!this->property("isPause").toBool() && (yuvWidget->isVisible() || nv12Widget->isVisible()) && thread->isRunning()) {
        //采用了硬件加速的直接用nv12渲染,否则采用yuv渲染
        if (thread->getHardware() == "none") {
            yuvWidget->setFrameSize(frame->width, frame->height);
            yuvWidget->updateTextures(frame->data[0], frame->data[1], frame->data[2], frame->linesize[0], frame->linesize[1], frame->linesize[2]);
        } else {
            nv12Widget->setFrameSize(frame->width, frame->height);
            nv12Widget->updateTextures(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1]);
        }
    }
#endif
}

void FFmpegThread::delayTime(int streamIndex, AVPacket *packet)
{
    //视频流不用延时
    if (isRtsp) {
        return;
    }

    //没有视频时长的文件和asf的本地文件采用另外的延时计算
    if (streamIndex == videoStreamIndex) {
        if (interval != 1 || videoTime < 0 || url.toLower().endsWith("asf")) {
            sleepVideo();
            return;
        }
    }

    qint64 offset_time = getDelayTime(streamIndex, packet);
    if (offset_time > 0) {
        av_usleep(offset_time);
    }
}

qint64 FFmpegThread::getDelayTime(int streamIndex, AVPacket *packet)
{
    AVRational time_base = formatCtx->streams[streamIndex]->time_base;
    AVRational time_base_q = {1, AV_TIME_BASE};
    int64_t pts_time = av_rescale_q(packet->pts, time_base, time_base_q);
    int64_t now_time = av_gettime() - startTime;
    int64_t offset_time = pts_time - now_time;
    return offset_time;
}

0 人点赞