• 586阅读
  • 0回复

Qt音视频开发15-动态切换解码内核的设计 [复制链接]

上一主题 下一主题
离线liudianwu
 

只看楼主 倒序阅读 楼主  发表于: 2023-02-21


## 一、前言
动态切换解码内核这个需求也是源自客户的真实需求,既然是动态切换,那肯定是运行期间切换,而不是通过改变标志位重新编译程序来切换,最开始做的就是这种方式,这样就是实现起来简单,但是用起来不够方便,随着编程架构技术的精进,抽象基类的运用水平逐渐提升,发现视频控件UI层可以不用变,不同内核发出的信号一样,UI层接收信号处理就好,至于底层的解码线程,可以动态销毁和指定,可以任意指定使用某种解码线程(ffmpeg内核、vlc内核、mpv内核、厂家sdk内核等),指定后绑定信号到UI就好,UI可以不用管是哪一种内核,拿到数据绘制就好。

为何要这样设计?一方面可以很方便的做不同内核之间差异的对比; 一方面为何满足不同用户需求,比如某些特定格式的文件用vlc可以正常播放,那就可以选择切换到这个内核就好,海康的视频流带了人工智能分析后的方框等,那就用海康的sdk内核就好,当然最通用跨平台最好的当然是ffmpeg内核,性能也是最好的。

- VideoCore_QMedia=采用qmedia解析(qt自带且依赖本地解码器且部分平台支持)
- VideoCore_FFmpeg=采用ffmpeg解析(通用性最好)
- VideoCore_Vlc=采用vlc解析(支持本地文件最好)
- VideoCore_Mpv=采用mpv解析(支持本地文件最好且跨平台最多)
- VideoCore_Qtav=采用qtav解析(框架结构最好/基于ffmpeg)
- VideoCore_HaiKang=采用海康sdk解析
- VideoCore_DaHua=采用大华sdk解析
- VideoCore_YuShi=采用宇视sdk解析
- VideoCore_EasyPlayer=采用easyplayer解析

## 二、效果图



## 三、体验地址
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
void VideoWidget::connectThreadSignal()
{
    if (!videoThread) {
        return;
    }

    //后面带个参数指定信号唯一(如果多次连接信号会自动去重)
    connect(videoThread, SIGNAL(started()), this, SLOT(started()), Qt::UniqueConnection);
    connect(videoThread, SIGNAL(finished()), this, SLOT(finished()), Qt::UniqueConnection);
    connect(videoThread, SIGNAL(receivePlayStart(int)), this, SLOT(receivePlayStart(int)), Qt::UniqueConnection);
    connect(videoThread, SIGNAL(receivePlayFinsh()), this, SLOT(receivePlayFinsh()), Qt::UniqueConnection);

    connect(videoThread, SIGNAL(receiveImage(QImage, int)), this, SLOT(receiveImage(QImage, int)), Qt::UniqueConnection);
    connect(videoThread, SIGNAL(snapImage(QImage, QString)), this, SLOT(snapImage(QImage, QString)), Qt::UniqueConnection);
    connect(videoThread, SIGNAL(receiveFrame(int, int, quint8 *, int)), this, SLOT(receiveFrame(int, int, quint8 *, int)), Qt::UniqueConnection);
    connect(videoThread, SIGNAL(receiveFrame(int, int, quint8 *, quint8 *, quint8 *, quint32, quint32, quint32)),
            this, SLOT(receiveFrame(int, int, quint8 *, quint8 *, quint8 *, quint32, quint32, quint32)), Qt::UniqueConnection);
    connect(videoThread, SIGNAL(receiveFrame(int, int, quint8 *, quint8 *, quint32, quint32)),
            this, SLOT(receiveFrame(int, int, quint8 *, quint8 *, quint32, quint32)), Qt::UniqueConnection);

    connect(videoThread, SIGNAL(receiveLevel(qreal, qreal)), this, SIGNAL(sig_receiveLevel(qreal, qreal)), Qt::UniqueConnection);
    connect(videoThread, SIGNAL(receivePlayStart(int)), this, SIGNAL(sig_receivePlayStart(int)), Qt::UniqueConnection);
    connect(videoThread, SIGNAL(receivePlayFinsh()), this, SIGNAL(sig_receivePlayFinsh()), Qt::UniqueConnection);

    connect(videoThread, SIGNAL(receivePlayFinsh()), bannerWidget, SLOT(receivePlayFinsh()), Qt::UniqueConnection);
    connect(videoThread, SIGNAL(receiveMuted(bool)), bannerWidget, SLOT(receiveMuted(bool)), Qt::UniqueConnection);
    connect(videoThread, SIGNAL(recorderStateChanged(RecorderState, QString)), bannerWidget, SLOT(recorderStateChanged(RecorderState, QString)), Qt::UniqueConnection);
    connect(videoThread, SIGNAL(receiveSizeChanged()), this, SLOT(receiveSizeChanged()));

    //根据默认音量大小和静音状态触发下信号
    if (videoPara.videoCore == VideoCore_FFmpeg) {
        QMetaObject::invokeMethod(videoThread, "receiveVolume", Q_ARG(int, widgetPara.soundValue));
        QMetaObject::invokeMethod(videoThread, "receiveMuted", Q_ARG(bool, widgetPara.soundMuted));
    }
}

void VideoWidget::disconnectThreadSignal()
{
    if (!videoThread) {
        return;
    }

    disconnect(videoThread, SIGNAL(started()), this, SLOT(started()));
    disconnect(videoThread, SIGNAL(finished()), this, SLOT(finished()));
    disconnect(videoThread, SIGNAL(receivePlayStart(int)), this, SLOT(receivePlayStart(int)));
    disconnect(videoThread, SIGNAL(receivePlayFinsh()), this, SLOT(receivePlayFinsh()));

    disconnect(videoThread, SIGNAL(receiveImage(QImage, int)), this, SLOT(receiveImage(QImage, int)));
    disconnect(videoThread, SIGNAL(snapImage(QImage, QString)), this, SLOT(snapImage(QImage, QString)));
    disconnect(videoThread, SIGNAL(receiveFrame(int, int, quint8 *, int)), this, SLOT(receiveFrame(int, int, quint8 *, int)));
    disconnect(videoThread, SIGNAL(receiveFrame(int, int, quint8 *, quint8 *, quint8 *, quint32, quint32, quint32)),
               this, SLOT(receiveFrame(int, int, quint8 *, quint8 *, quint8 *, quint32, quint32, quint32)));
    disconnect(videoThread, SIGNAL(receiveFrame(int, int, quint8 *, quint8 *, quint32, quint32)),
               this, SLOT(receiveFrame(int, int, quint8 *, quint8 *, quint32, quint32)));

    disconnect(videoThread, SIGNAL(receiveLevel(qreal, qreal)), this, SIGNAL(sig_receiveLevel(qreal, qreal)));
    disconnect(videoThread, SIGNAL(receivePlayStart(int)), this, SIGNAL(sig_receivePlayStart(int)));
    disconnect(videoThread, SIGNAL(receivePlayFinsh()), this, SIGNAL(sig_receivePlayFinsh()));

    disconnect(videoThread, SIGNAL(receivePlayFinsh()), bannerWidget, SLOT(receivePlayFinsh()));
    disconnect(videoThread, SIGNAL(receiveMuted(bool)), bannerWidget, SLOT(receiveMuted(bool)));
    disconnect(videoThread, SIGNAL(recorderStateChanged(RecorderState, QString)), bannerWidget, SLOT(recorderStateChanged(RecorderState, QString)));
    disconnect(videoThread, SIGNAL(receiveSizeChanged()), this, SLOT(receiveSizeChanged()));
}

bool VideoWidget::init()
{
    //视频地址不能为空
    if (videoPara.videoUrl.isEmpty()) {
        return false;
    }

    //如果没有解码内核则不用继续
    if (videoPara.videoCore == VideoCore_None) {
        return false;
    }

    //初始化参数
    VideoHelper::initPara(widgetPara, videoPara);

    //线程正在运行不用继续
    if (isRunning) {
        return false;
    }

    //句柄模式则句柄控件在前否则遮罩控件在前
    if (widgetPara.videoMode == VideoMode_Hwnd) {
        coverWidget->stackUnder(hwndWidget);
    } else {
        hwndWidget->stackUnder(coverWidget);
    }

    //已经存在同名的线程则取同名线程
    VideoThread *thread = VideoThread::getVideoThread(widgetPara, videoPara);
    if (thread) {
        isShared = true;
        videoThread = thread;
        //默认音量大小和静音状态取共享线程的
        widgetPara.soundValue = thread->getVolume();
        widgetPara.soundMuted = thread->getMuted();
        //硬件加速也要取共享线程的
        hardware = thread->getHardware();
        videoPara.hardware = thread->getHardware();
    } else {
        //创建新的采集线程
        videoThread = VideoHelper::newVideoThread(hwndWidget, videoPara.videoCore);
        //设置视频通道唯一标识
        videoThread->setFlag(widgetPara.videoFlag);
        //加入到采集线程队列
        if (widgetPara.sharedData) {
            VideoThread::videoThreads << videoThread;
        }
        //设置对应参数
        videoThread->setVideoMode(widgetPara.videoMode);
        VideoHelper::initVideoThread(videoThread, videoPara);
    }

    //绑定信号槽
    connectThreadSignal();
    return true;
}

bool VideoWidget::open(const QString &videoUrl)
{
    //线程正常说明还在运行需要先停止
    if (videoThread) {
        this->stop();
        qApp->processEvents();
    }

    //重新初始化和播放
    videoPara.videoUrl = videoUrl;
    if (this->init()) {
        this->play();
        return true;
    } else {
        return false;
    }
}

void VideoWidget::play()
{
    //如果是图片则只显示图片就行
    image = QImage(videoPara.videoUrl);
    if (!image.isNull()) {
        videoThread->setVideoSize(QString("%1x%2").arg(image.width()).arg(image.height()));
        this->setImage(image);
        return;
    }

    //采用已经存在的采集线程如果处于解码正常阶段则还要发几个信号通知
    if (videoThread->getIsOk()) {
        QMetaObject::invokeMethod(this, "receivePlayStart", Qt::DirectConnection, Q_ARG(int, 0));
        QMetaObject::invokeMethod(videoThread, "receiveSizeChanged", Qt::DirectConnection);
        QMetaObject::invokeMethod(videoThread, "receiveDuration", Qt::DirectConnection, Q_ARG(qint64, videoThread->getDuration()));
    }

    //已经在运行阶段还要发送已经开始的信号
    if (videoThread->isRunning()) {
        isRunning = true;
        QMetaObject::invokeMethod(this, "sig_receivePlayStart", Qt::DirectConnection, Q_ARG(int, 0));
    }

    //启动播放线程
    videoThread->play();
    //初始化标签信息和图形信息
    this->osdChanged();
    this->graphChanged();
}

void VideoWidget::stop()
{
    //立即隐藏悬浮条
    bannerWidget->setVisible(false);
    //关闭的时候将遮罩控件移到最前
    hwndWidget->stackUnder(coverWidget);

    //处于运行状态才可以停止
    if (videoThread && videoThread->isRunning()) {
        //先判断当前线程的引用计数是0才需要真正停止
        if (videoThread->refCount > 0) {
            //减少引用计数
            videoThread->refCount--;
            //执行停止信号
            QMetaObject::invokeMethod(this, "receivePlayFinsh", Qt::DirectConnection);
            QMetaObject::invokeMethod(this, "sig_receivePlayFinsh", Qt::DirectConnection);
            //取消信号关联
            disconnectThreadSignal();
            videoThread = NULL;
        } else {
            //停止播放
            videoThread->stop();
            //取消信号关联
            disconnectThreadSignal();
            //从队列中移除
            VideoThread::videoThreads.removeOne(videoThread);
            //释放线程
            videoThread->debug("删除线程", "");
            videoThread->deleteLater();
            videoThread = NULL;
        }
    }

    //复位标志位并将线程置空(没有这个的话可能会出现野指针的情况)
    isRunning = false;
    isShared = false;
    videoThread = NULL;
    videoPara.reset();
    AbstractVideoWidget::clear();
}
```

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