#include "VideoPlayer.h" #include "DecodeVedio.h" #include #include #include #include #include "spdlog/spdlog.h" #include "FmtLog/fmtlog.h" VideoPlayer::VideoPlayer(QWidget *parent) : QWidget(parent) { // /* 初始化解码线程 */ // m_threadDecode = new QThread(this); // m_decodeVedio = new DecodeVedio(m_threadDecode); m_previewImage = 2; m_fps = 0; m_semRefresh = new QSemaphore(0); m_timerRefreshUI.setSingleShot(false); /* 设置精度毫秒级 */ m_timerRefreshUI.setTimerType(Qt::PreciseTimer); connect(&m_timerRefreshUI, &QTimer::timeout, this, &VideoPlayer::do_refreshUI); connect(this, &VideoPlayer::signal_refreshImage, this, &VideoPlayer::do_refreshSamImage); SPDLOG_TRACE("播放器线程ID:{}", QThread::currentThreadId()); QStringList listDecoder; DecodeVedio::findHWDecoder(listDecoder); if(listDecoder.isEmpty()) { SPDLOG_WARN("没有找到硬件解码器"); }else { SPDLOG_DEBUG("支持的硬件解码器:"); for(auto it : listDecoder) { SPDLOG_DEBUG("{}", it.toStdString()); } } } VideoPlayer::~VideoPlayer() { if(m_timerRefreshUI.isActive()) { m_timerRefreshUI.stop(); } delete m_decodeVedio; if(m_image) { delete m_image; } } /** * @brief 设置播放视频,启动定时器,定时器间隔决定播放的速度 * 视频的宽和高使用QImage进行缩放 * 视频大小在直接设置这个类的resize即可,有最小大小限制 * * @param fileName */ void VideoPlayer::openPlayVedio(const QString& fileName) { if(m_isOpenFile) { m_isOpenFile = false; stop(); } if(m_decodeVedio == nullptr) { /* 初始化解码线程 */ m_threadDecode = new QThread(this); m_decodeVedio = new DecodeVedio(m_threadDecode); connect(m_decodeVedio, &DecodeVedio::signal_playCompleted, this, &VideoPlayer::do_playCompleted); } if(m_decodeVedio->isDecoding()) { m_decodeVedio->stopDecodeVedio(); } if(fileName.isEmpty()) { SPDLOG_WARN("文件名为空"); return; } m_fileName = fileName; m_isOpenFile = true; m_isLocalFile = isLocalFile(fileName); m_decodeVedio->openVedio(fileName); /* 获取原始视频信息 */ m_srcWidth = m_decodeVedio->getSrcVideoSize().width(); m_srcHeight = m_decodeVedio->getSrcVideoSize().height(); m_fps = m_decodeVedio->getFPS(); m_duration = m_decodeVedio->getDuration(); auto totalFarame = m_decodeVedio->getTotalFrame(); SPDLOG_INFO("视频编码格式:{}", m_decodeVedio->getDecoderName().toStdString()); int hh = m_duration / 3600000; int mm = (m_duration % 3600000) / 60000; int ss = (m_duration % 60000) / 1000; int ms = m_duration % 1000; SPDLOG_INFO("视频分辨率:{}x{} 帧率:{} 总帧数:{}", m_srcWidth, m_srcHeight, m_fps, totalFarame); SPDLOG_INFO("时长:{}h:{}m:{}.{}s 总时长:{}ms", hh, mm, ss, ms, m_duration); /* 设置视频宽和高的最小大小 */ this->setMinimumSize(160,90); /* 开启定时器刷新 */ if(m_fps <= 0) { m_fps = 25; } /* 开启解码,手动刷新第一帧 */ m_decodeVedio->startDecodeVedio(); m_semRefresh->release(2); emit signal_refreshImage(); } /* 播放视频 */ bool VideoPlayer::play() { if(!m_isOpenFile) { SPDLOG_ERROR("未打开视频文件!"); return false; } if(m_playStatus) { return false; } /* 设置刷新时间 */ m_timerRefreshUI.setSingleShot(false); m_interval = qRound64(1000.0 / m_fps); SPDLOG_DEBUG("刷新UI的定时间隔:{}",m_interval); m_timerRefreshUI.start(m_interval); m_playStatus = true; return true; } /* 暂停播放 */ void VideoPlayer::pause() { if(!m_isOpenFile) { SPDLOG_ERROR("未打开视频文件!"); return; } if(!m_isLocalFile) { SPDLOG_ERROR("不是本地视频文件,无法暂停!"); return; } if(!m_playStatus) { return; } m_timerRefreshUI.stop(); m_playStatus = false; } /* 停止播放,停止后停止解码,将时间等复位到开始时间 */ void VideoPlayer::stop() { if(!m_isOpenFile) { SPDLOG_ERROR("未打开视频文件!"); return; } SPDLOG_DEBUG("...停止播放..."); m_fileName = QString(); if(m_timerRefreshUI.isActive()) { m_timerRefreshUI.stop(); } // SPDLOG_DEBUG("...停止解码..."); /* 删除解码器 */ delete m_decodeVedio; m_decodeVedio = nullptr; delete m_threadDecode; m_threadDecode = nullptr; m_playStatus = false; m_isOpenFile = false; /* 绘制黑帧 */ SPDLOG_DEBUG("绘制黑帧"); m_image = new QImage(m_nowWidth, m_nowHeight, QImage::Format_RGB32); m_image->fill(Qt::black); update(); } /* 后退,单位ms */ void VideoPlayer::backward(qint64 ms) { if(!m_isOpenFile) { SPDLOG_ERROR("未打开视频文件!"); return; } if(!m_isLocalFile) { SPDLOG_ERROR("不是本地视频文件,无法后退!"); return; } /* 获取当前位置 */ qint64 pos = m_decodeVedio->getCurrentPos(); pos = pos - ms; if(pos < 0) { pos = 0; } setCurrentPos(pos); } /* 前进,单位ms */ void VideoPlayer::forward(qint64 ms) { if(!m_isOpenFile) { SPDLOG_ERROR("未打开视频文件!"); return; } if(!m_isLocalFile) { SPDLOG_ERROR("不是本地视频文件,无法前进!"); return; } /* 获取当前位置 */ qint64 pos = m_decodeVedio->getCurrentPos(); SPDLOG_DEBUG("pos:{} ms:{}", pos, ms); pos = pos + ms; setCurrentPos(pos); } /* 获取视频时长 */ qint64 VideoPlayer::getDuration() { if(!m_isOpenFile) { SPDLOG_ERROR("未打开视频文件!"); return -1; } auto duration = m_decodeVedio->getDuration(); if(duration <= 0) { return 0; } return duration; } /* 获取当前播放位置 */ qint64 VideoPlayer::getCurrentPos() { if(!m_isOpenFile) { SPDLOG_ERROR("未打开视频文件!"); return -1; } auto pos = m_decodeVedio->getCurrentPos(); if(pos < 0) { return 0; } return pos; } /* 设置当前播放位置,单位ms */ void VideoPlayer::setCurrentPos(qint64 pos) { if(!m_isOpenFile) { SPDLOG_ERROR("未打开视频文件!"); return; } if(!m_isLocalFile) { SPDLOG_ERROR("不是本地视频文件,无法设置播放位置!"); return; } if(pos < 0) { pos = 0; } /* 先停止播放 */ bool temp = m_playStatus; if(m_playStatus) { m_timerRefreshUI.stop(); m_playStatus = false; } m_decodeVedio->setCurrentPos(pos); /* 继续播放 */ if(temp) { // SPDLOG_INFO("..........开启定时器.........."); m_timerRefreshUI.start(m_interval); m_playStatus = true; }else { /* 刷新2张照片 */ m_semRefresh->release(m_previewImage); emit signal_refreshImage(); } } /* 设置播放视频大小 */ void VideoPlayer::setPlayWidgetSize(int width,int height) { /* 对宽和高就行缩放,保持比例,同时将其居中放置 * 先计算出比例,和16/9相对比 * 大于16/9,以高为最大极限,计算出宽度和x坐标 * 小于16/9,以宽为最大极限,计算出高度和y坐标 */ double srcRatio = m_srcWidth*1.0 / m_srcHeight; double ratio = width*1.0 / height; long w1 = 0, h1 = 0; int srcX = this->pos().rx(), srcY = this->pos().ry(); int x1 = srcX, y1 = srcY; if(ratio > srcRatio) { w1 = height * srcRatio; x1 = (width - w1) / 2; h1 = height; y1 = srcY; } else if(ratio < srcRatio) { h1 = width / srcRatio; y1 = (height - h1) / 2; w1 = width; x1 = srcX; }else { w1 = width; h1 = height; x1 = srcX; y1 = srcY; } this->move(x1, y1); m_nowWidth = w1; m_nowHeight = h1; this->resize(w1, h1); // SPDLOG_DEBUG("设置窗口位置:{}x{}, 大小:{}x{}, 传入大小:{}x{}", x1, y1, w1, h1, width, height); SPDLOG_DEBUG("现在位置和大小:{}x{}, {}x{}", this->pos().rx(), this->pos().ry(), this->width(), this->height()); } /** * @brief 设置播放窗口,这用于独占一个传入的widget,这里会自动添加一个布局,外面窗口变化,这里也跟随着变化 * * @param widget * @param flag * @arg true:独占widget,并设置一个layout,会随着传入的widget大小变化 * @arg false:不独占 */ void VideoPlayer::setPlayWidget(QWidget* widget, bool flag) { if(widget == nullptr) { SPDLOG_WARN("传入的widget为空"); return; } if(flag) { /* 设置布局 */ QVBoxLayout* layout = new QVBoxLayout(widget); layout->addWidget(this); layout->setMargin(0); layout->setSpacing(0); widget->setLayout(layout); }else { this->setParent(widget); /* 设置窗口大小 */ setPlayWidgetSize(widget->width(), widget->height()); } } /** * @brief 设置预览图片数目,在暂停时跳转,可能会有花屏或者黑帧,可以设置跳转图片个数跳过黑帧 * 默认是2帧 * * @param num */ void VideoPlayer::setPreviewImage(int num) { m_previewImage = num; } /** * @brief 设置帧率,有些视频无法获取到帧率,就会使用默认的25fps,如果需要,可以通过这个函数设置 * 注意:这个函数需要在打开视频文件之后设置,打开一次视频文件会覆盖这个参数 * * @param fps */ void VideoPlayer::setFPS(int fps) { m_fps = fps; if(m_decodeVedio != nullptr) { m_decodeVedio->setFPS(fps); } if(m_timerRefreshUI.isActive()) { m_timerRefreshUI.stop(); m_interval = qRound64(1000.0 / m_fps); m_timerRefreshUI.start(m_interval); } } /* 设置播放回调函数 */ // void VideoPlayer::setPlayCallBack(std::function playCallBack,void* context) // { // m_funcPlayCB = playCallBack; // m_context = context; // } void VideoPlayer::paintEvent(QPaintEvent *event) { if(m_image != nullptr) { // SPDLOG_TRACE("开始绘制画面..."); /* 对图像进行缩放 */ if(m_srcWidth != m_nowWidth || m_srcHeight != m_nowHeight) { *m_image = m_image->scaled(m_nowWidth, m_nowHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); } QPainter painter(this); painter.drawImage(0, 0, *m_image); } } void VideoPlayer::resizeEvent(QResizeEvent *event) { SPDLOG_TRACE("窗口大小改变..."); m_nowWidth = event->size().width(); m_nowHeight = event->size().height(); QWidget::resizeEvent(event); } /* 刷新一张图片,直到有图片为止 */ void VideoPlayer::refreshOneUIUntilHave() { if(m_decodeVedio != nullptr) { // SPDLOG_DEBUG("取出一帧图片..."); /* 删除上一帧图片 */ if(m_image != nullptr) { delete m_image; m_image = nullptr; } /* 如果没有图片,这个函数会阻塞 */ m_image = m_decodeVedio->getOneImageUntilHave(); if(m_image) { if(m_image->isNull()) { SPDLOG_WARN("取出的图片为空..."); return; } // SPDLOG_DEBUG("绘制画面..."); update(); } } } /* 双击事件函数 */ void VideoPlayer::mouseDoubleClickEvent(QMouseEvent *event) { if(event->button() == Qt::LeftButton) { // SPDLOG_DEBUG("双击事件..."); // if(m_funcPlayCB != nullptr) // { // m_funcPlayCB(this, 5, nullptr, 0, m_context); // }else { // SPDLOG_INFO("没有设置回调函数"); // } } } /* 取出画面,刷新UI */ void VideoPlayer::do_refreshUI() { if(m_decodeVedio != nullptr) { // SPDLOG_DEBUG("取出一帧图片..."); /* 删除上一帧图片 */ if(m_image != nullptr) { delete m_image; m_image = nullptr; } m_image = m_decodeVedio->getOneImage(); if(m_image) { if(m_image->isNull()) { SPDLOG_WARN("取出的图片为空..."); return; } // SPDLOG_DEBUG("绘制画面..."); update(); } // m_decodeVedio->wakeUpCondQueueNoEmpty(); } } /* 通过信号刷新第一张图片 */ void VideoPlayer::do_refreshSamImage() { if(!m_isOpenFile) { return; } while(m_semRefresh->tryAcquire(1)) { /* 取出第一张 */ if(m_decodeVedio != nullptr) { // SPDLOG_DEBUG("取出一帧图片..."); /* 删除上一帧图片 */ if(m_image != nullptr) { delete m_image; m_image = nullptr; } /* 等待图片,最多等待50ms */ m_image = m_decodeVedio->getOneImageUntilHave(100); if(m_image) { if(m_image->isNull()) { SPDLOG_WARN("取出的图片为空..."); return; } SPDLOG_DEBUG("绘制预览画面。"); update(); } } } } /* 播放完成 */ void VideoPlayer::do_playCompleted() { SPDLOG_INFO("视频播放完成。"); m_timerRefreshUI.stop(); /* 手动刷新剩余的环形队列中的图片 */ while(true) { if(m_decodeVedio != nullptr) { QImage* image = nullptr; image = m_decodeVedio->getOneImage(); if(image == nullptr) { break; } /* 删除上一帧图片 */ if(m_image != nullptr) { delete m_image; m_image = nullptr; } m_image = image; if(m_image->isNull()) { SPDLOG_WARN("取出的图片为空..."); return; } // SPDLOG_DEBUG("绘制画面..."); update(); } } m_playStatus = false; // if(m_funcPlayCB != nullptr) // { // /* 播放完成的回调函数 */ // m_funcPlayCB(this, 2, nullptr, 0, m_context); // } } /* 判断是否是本地文件 */ bool VideoPlayer::isLocalFile(const QString& fileName) { if(fileName.isEmpty()) { return false; } if(fileName.startsWith("http://") || fileName.startsWith("rtsp://") || fileName.startsWith("rtmp://") || fileName.startsWith("https://")) { return false; } return true; }