VideoPlayer.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. #include "VideoPlayer.h"
  2. #include "DecodeVedio.h"
  3. #include <QPainter>
  4. #include <QResizeEvent>
  5. #include <QEventLoop>
  6. #include <QVBoxLayout>
  7. #include "spdlog/spdlog.h"
  8. #include "FmtLog/fmtlog.h"
  9. VideoPlayer::VideoPlayer(QWidget *parent) : QWidget(parent)
  10. {
  11. // /* 初始化解码线程 */
  12. // m_threadDecode = new QThread(this);
  13. // m_decodeVedio = new DecodeVedio(m_threadDecode);
  14. m_previewImage = 2;
  15. m_fps = 0;
  16. m_semRefresh = new QSemaphore(0);
  17. m_timerRefreshUI.setSingleShot(false);
  18. /* 设置精度毫秒级 */
  19. m_timerRefreshUI.setTimerType(Qt::PreciseTimer);
  20. connect(&m_timerRefreshUI, &QTimer::timeout, this, &VideoPlayer::do_refreshUI);
  21. connect(this, &VideoPlayer::signal_refreshImage, this, &VideoPlayer::do_refreshSamImage);
  22. SPDLOG_TRACE("播放器线程ID:{}", QThread::currentThreadId());
  23. QStringList listDecoder;
  24. DecodeVedio::findHWDecoder(listDecoder);
  25. if(listDecoder.isEmpty())
  26. {
  27. SPDLOG_WARN("没有找到硬件解码器");
  28. }else {
  29. SPDLOG_DEBUG("支持的硬件解码器:");
  30. for(auto it : listDecoder)
  31. {
  32. SPDLOG_DEBUG("{}", it.toStdString());
  33. }
  34. }
  35. }
  36. VideoPlayer::~VideoPlayer()
  37. {
  38. if(m_timerRefreshUI.isActive())
  39. {
  40. m_timerRefreshUI.stop();
  41. }
  42. delete m_decodeVedio;
  43. if(m_image)
  44. {
  45. delete m_image;
  46. }
  47. SPDLOG_DEBUG("视频播放器已关闭");
  48. }
  49. /**
  50. * @brief 设置播放视频,启动定时器,定时器间隔决定播放的速度
  51. * 视频的宽和高使用QImage进行缩放
  52. * 视频大小在直接设置这个类的resize即可,有最小大小限制
  53. *
  54. * @param fileName
  55. */
  56. void VideoPlayer::openPlayVedio(const QString& fileName)
  57. {
  58. if(m_isOpenFile)
  59. {
  60. m_isOpenFile = false;
  61. stop();
  62. }
  63. if(m_decodeVedio == nullptr)
  64. {
  65. /* 初始化解码线程 */
  66. m_threadDecode = new QThread(this);
  67. m_decodeVedio = new DecodeVedio(m_threadDecode);
  68. connect(m_decodeVedio, &DecodeVedio::signal_playCompleted, this, &VideoPlayer::do_playCompleted);
  69. }
  70. if(m_decodeVedio->isDecoding())
  71. {
  72. m_decodeVedio->stopDecodeVedio();
  73. }
  74. if(fileName.isEmpty())
  75. {
  76. SPDLOG_WARN("文件名为空");
  77. return;
  78. }
  79. m_fileName = fileName;
  80. m_isOpenFile = true;
  81. m_isLocalFile = isLocalFile(fileName);
  82. m_decodeVedio->openVedio(fileName);
  83. /* 获取原始视频信息 */
  84. m_srcWidth = m_decodeVedio->getSrcVideoSize().width();
  85. m_srcHeight = m_decodeVedio->getSrcVideoSize().height();
  86. m_fps = m_decodeVedio->getFPS();
  87. m_duration = m_decodeVedio->getDuration();
  88. auto totalFarame = m_decodeVedio->getTotalFrame();
  89. SPDLOG_INFO("视频编码格式:{}", m_decodeVedio->getDecoderName().toStdString());
  90. int hh = m_duration / 3600000;
  91. int mm = (m_duration % 3600000) / 60000;
  92. int ss = (m_duration % 60000) / 1000;
  93. int ms = m_duration % 1000;
  94. SPDLOG_INFO("视频分辨率:{}x{} 帧率:{} 总帧数:{}", m_srcWidth, m_srcHeight, m_fps, totalFarame);
  95. SPDLOG_INFO("时长:{}h:{}m:{}.{}s 总时长:{}ms", hh, mm, ss, ms, m_duration);
  96. /* 设置视频宽和高的最小大小 */
  97. this->setMinimumSize(160,90);
  98. /* 开启定时器刷新 */
  99. if(m_fps <= 0)
  100. {
  101. m_fps = 25;
  102. }
  103. /* 开启解码,手动刷新第一帧 */
  104. m_decodeVedio->startDecodeVedio();
  105. m_semRefresh->release(2);
  106. emit signal_refreshImage();
  107. this->show();
  108. SPDLOG_DEBUG("打开视频成功:{}", fileName.toStdString());
  109. }
  110. /* 播放视频 */
  111. bool VideoPlayer::play()
  112. {
  113. if(!m_isOpenFile)
  114. {
  115. SPDLOG_ERROR("未打开视频文件!");
  116. return false;
  117. }
  118. if(m_playStatus)
  119. {
  120. return false;
  121. }
  122. /* 设置刷新时间 */
  123. m_timerRefreshUI.setSingleShot(false);
  124. m_interval = qRound64(1000.0 / m_fps);
  125. SPDLOG_DEBUG("刷新UI的定时间隔:{}",m_interval);
  126. m_timerRefreshUI.start(m_interval);
  127. m_playStatus = true;
  128. return true;
  129. }
  130. /* 暂停播放 */
  131. void VideoPlayer::pause()
  132. {
  133. if(!m_isOpenFile)
  134. {
  135. SPDLOG_ERROR("未打开视频文件!");
  136. return;
  137. }
  138. if(!m_isLocalFile)
  139. {
  140. SPDLOG_ERROR("不是本地视频文件,无法暂停!");
  141. return;
  142. }
  143. if(!m_playStatus)
  144. {
  145. return;
  146. }
  147. m_timerRefreshUI.stop();
  148. m_playStatus = false;
  149. }
  150. /* 停止播放,停止后停止解码,将时间等复位到开始时间 */
  151. void VideoPlayer::stop()
  152. {
  153. if(!m_isOpenFile)
  154. {
  155. SPDLOG_ERROR("未打开视频文件!");
  156. return;
  157. }
  158. SPDLOG_DEBUG("...停止播放...");
  159. m_fileName = QString();
  160. if(m_timerRefreshUI.isActive())
  161. {
  162. m_timerRefreshUI.stop();
  163. }
  164. // SPDLOG_DEBUG("...停止解码...");
  165. /* 删除解码器 */
  166. delete m_decodeVedio;
  167. m_decodeVedio = nullptr;
  168. delete m_threadDecode;
  169. m_threadDecode = nullptr;
  170. m_playStatus = false;
  171. m_isOpenFile = false;
  172. /* 绘制黑帧 */
  173. SPDLOG_DEBUG("绘制黑帧");
  174. m_image = new QImage(m_nowWidth, m_nowHeight, QImage::Format_RGB32);
  175. m_image->fill(Qt::black);
  176. update();
  177. }
  178. /* 后退,单位ms */
  179. void VideoPlayer::backward(qint64 ms)
  180. {
  181. if(!m_isOpenFile)
  182. {
  183. SPDLOG_ERROR("未打开视频文件!");
  184. return;
  185. }
  186. if(!m_isLocalFile)
  187. {
  188. SPDLOG_ERROR("不是本地视频文件,无法后退!");
  189. return;
  190. }
  191. /* 获取当前位置 */
  192. qint64 pos = m_decodeVedio->getCurrentPos();
  193. pos = pos - ms;
  194. if(pos < 0)
  195. {
  196. pos = 0;
  197. }
  198. setCurrentPos(pos);
  199. }
  200. /* 前进,单位ms */
  201. void VideoPlayer::forward(qint64 ms)
  202. {
  203. if(!m_isOpenFile)
  204. {
  205. SPDLOG_ERROR("未打开视频文件!");
  206. return;
  207. }
  208. if(!m_isLocalFile)
  209. {
  210. SPDLOG_ERROR("不是本地视频文件,无法前进!");
  211. return;
  212. }
  213. /* 获取当前位置 */
  214. qint64 pos = m_decodeVedio->getCurrentPos();
  215. SPDLOG_DEBUG("pos:{} ms:{}", pos, ms);
  216. pos = pos + ms;
  217. setCurrentPos(pos);
  218. }
  219. /* 获取视频时长 */
  220. qint64 VideoPlayer::getDuration()
  221. {
  222. if(!m_isOpenFile)
  223. {
  224. SPDLOG_ERROR("未打开视频文件!");
  225. return -1;
  226. }
  227. auto duration = m_decodeVedio->getDuration();
  228. if(duration <= 0)
  229. {
  230. return 0;
  231. }
  232. return duration;
  233. }
  234. /* 获取当前播放位置 */
  235. qint64 VideoPlayer::getCurrentPos()
  236. {
  237. if(!m_isOpenFile)
  238. {
  239. SPDLOG_ERROR("未打开视频文件!");
  240. return -1;
  241. }
  242. auto pos = m_decodeVedio->getCurrentPos();
  243. if(pos < 0)
  244. {
  245. return 0;
  246. }
  247. return pos;
  248. }
  249. /* 设置当前播放位置,单位ms */
  250. void VideoPlayer::setCurrentPos(qint64 pos)
  251. {
  252. if(!m_isOpenFile)
  253. {
  254. SPDLOG_ERROR("未打开视频文件!");
  255. return;
  256. }
  257. if(!m_isLocalFile)
  258. {
  259. SPDLOG_ERROR("不是本地视频文件,无法设置播放位置!");
  260. return;
  261. }
  262. if(pos < 0)
  263. {
  264. pos = 0;
  265. }
  266. /* 先停止播放 */
  267. bool temp = m_playStatus;
  268. if(m_playStatus)
  269. {
  270. m_timerRefreshUI.stop();
  271. m_playStatus = false;
  272. }
  273. m_decodeVedio->setCurrentPos(pos);
  274. /* 继续播放 */
  275. if(temp)
  276. {
  277. // SPDLOG_INFO("..........开启定时器..........");
  278. m_timerRefreshUI.start(m_interval);
  279. m_playStatus = true;
  280. }else
  281. {
  282. /* 刷新2张照片 */
  283. m_semRefresh->release(m_previewImage);
  284. emit signal_refreshImage();
  285. }
  286. SPDLOG_INFO("设置播放位置:{} s", m_decodeVedio->getCurrentPos() / 1000.0);
  287. }
  288. /* 设置播放视频大小 */
  289. void VideoPlayer::setPlayWidgetSize(int width,int height)
  290. {
  291. /* 对宽和高就行缩放,保持比例,同时将其居中放置
  292. * 先计算出比例,和16/9相对比
  293. * 大于16/9,以高为最大极限,计算出宽度和x坐标
  294. * 小于16/9,以宽为最大极限,计算出高度和y坐标 */
  295. double srcRatio = m_srcWidth*1.0 / m_srcHeight;
  296. double ratio = width*1.0 / height;
  297. long w1 = 0, h1 = 0;
  298. int srcX = this->pos().rx(), srcY = this->pos().ry();
  299. int x1 = srcX, y1 = srcY;
  300. if(ratio > srcRatio)
  301. {
  302. w1 = height * srcRatio;
  303. x1 = (width - w1) / 2;
  304. h1 = height;
  305. y1 = srcY;
  306. }
  307. else if(ratio < srcRatio)
  308. {
  309. h1 = width / srcRatio;
  310. y1 = (height - h1) / 2;
  311. w1 = width;
  312. x1 = srcX;
  313. }else {
  314. w1 = width;
  315. h1 = height;
  316. x1 = srcX;
  317. y1 = srcY;
  318. }
  319. this->move(x1, y1);
  320. m_nowWidth = w1;
  321. m_nowHeight = h1;
  322. this->resize(w1, h1);
  323. // SPDLOG_DEBUG("设置窗口位置:{}x{}, 大小:{}x{}, 传入大小:{}x{}", x1, y1, w1, h1, width, height);
  324. SPDLOG_DEBUG("现在位置和大小:{}x{}, {}x{}", this->pos().rx(), this->pos().ry(), this->width(), this->height());
  325. }
  326. /**
  327. * @brief 设置播放窗口,这用于独占一个传入的widget,这里会自动添加一个布局,外面窗口变化,这里也跟随着变化
  328. *
  329. * @param widget
  330. * @param flag
  331. * @arg true:独占widget,并设置一个layout,会随着传入的widget大小变化
  332. * @arg false:不独占
  333. */
  334. void VideoPlayer::setPlayWidget(QWidget* widget, bool flag)
  335. {
  336. if(widget == nullptr)
  337. {
  338. SPDLOG_WARN("传入的widget为空");
  339. return;
  340. }
  341. if(flag)
  342. {
  343. /* 设置布局 */
  344. QVBoxLayout* layout = new QVBoxLayout(widget);
  345. layout->addWidget(this);
  346. layout->setMargin(0);
  347. layout->setSpacing(0);
  348. widget->setLayout(layout);
  349. }else
  350. {
  351. this->setParent(widget);
  352. /* 设置窗口大小 */
  353. setPlayWidgetSize(widget->width(), widget->height());
  354. }
  355. }
  356. /**
  357. * @brief 设置预览图片数目,在暂停时跳转,可能会有花屏或者黑帧,可以设置跳转图片个数跳过黑帧
  358. * 默认是2帧
  359. *
  360. * @param num
  361. */
  362. void VideoPlayer::setPreviewImage(int num)
  363. {
  364. m_previewImage = num;
  365. }
  366. /**
  367. * @brief 设置帧率,有些视频无法获取到帧率,就会使用默认的25fps,如果需要,可以通过这个函数设置
  368. * 注意:这个函数需要在打开视频文件之后设置,打开一次视频文件会覆盖这个参数
  369. *
  370. * @param fps
  371. */
  372. void VideoPlayer::setFPS(int fps)
  373. {
  374. m_fps = fps;
  375. if(m_decodeVedio != nullptr)
  376. {
  377. m_decodeVedio->setFPS(fps);
  378. }
  379. if(m_timerRefreshUI.isActive())
  380. {
  381. m_timerRefreshUI.stop();
  382. m_interval = qRound64(1000.0 / m_fps);
  383. m_timerRefreshUI.start(m_interval);
  384. }
  385. }
  386. /* 设置播放回调函数 */
  387. // void VideoPlayer::setPlayCallBack(std::function<Play_CallBack> playCallBack,void* context)
  388. // {
  389. // m_funcPlayCB = playCallBack;
  390. // m_context = context;
  391. // }
  392. void VideoPlayer::paintEvent(QPaintEvent *event)
  393. {
  394. if(m_image != nullptr)
  395. {
  396. // SPDLOG_TRACE("开始绘制画面...");
  397. /* 对图像进行缩放 */
  398. if(m_srcWidth != m_nowWidth || m_srcHeight != m_nowHeight)
  399. {
  400. *m_image = m_image->scaled(m_nowWidth, m_nowHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
  401. }
  402. QPainter painter(this);
  403. painter.drawImage(0, 0, *m_image);
  404. }
  405. }
  406. void VideoPlayer::resizeEvent(QResizeEvent *event)
  407. {
  408. SPDLOG_TRACE("窗口大小改变...");
  409. m_nowWidth = event->size().width();
  410. m_nowHeight = event->size().height();
  411. QWidget::resizeEvent(event);
  412. }
  413. /* 刷新一张图片,直到有图片为止 */
  414. void VideoPlayer::refreshOneUIUntilHave()
  415. {
  416. if(m_decodeVedio != nullptr)
  417. {
  418. // SPDLOG_DEBUG("取出一帧图片...");
  419. /* 删除上一帧图片 */
  420. if(m_image != nullptr)
  421. {
  422. delete m_image;
  423. m_image = nullptr;
  424. }
  425. /* 如果没有图片,这个函数会阻塞 */
  426. m_image = m_decodeVedio->getOneImageUntilHave();
  427. if(m_image)
  428. {
  429. if(m_image->isNull())
  430. {
  431. SPDLOG_WARN("取出的图片为空...");
  432. return;
  433. }
  434. // SPDLOG_DEBUG("绘制画面...");
  435. update();
  436. }
  437. }
  438. }
  439. /* 双击事件函数 */
  440. // void VideoPlayer::mouseDoubleClickEvent(QMouseEvent *event)
  441. // {
  442. // if(event->button() == Qt::LeftButton)
  443. // {
  444. // // SPDLOG_DEBUG("双击事件...");
  445. // // if(m_funcPlayCB != nullptr)
  446. // // {
  447. // // m_funcPlayCB(this, 5, nullptr, 0, m_context);
  448. // // }else {
  449. // // SPDLOG_INFO("没有设置回调函数");
  450. // // }
  451. // }
  452. // }
  453. /* 取出画面,刷新UI */
  454. void VideoPlayer::do_refreshUI()
  455. {
  456. if(m_decodeVedio != nullptr)
  457. {
  458. // SPDLOG_DEBUG("取出一帧图片...");
  459. /* 删除上一帧图片 */
  460. if(m_image != nullptr)
  461. {
  462. delete m_image;
  463. m_image = nullptr;
  464. }
  465. m_image = m_decodeVedio->getOneImage();
  466. if(m_image)
  467. {
  468. if(m_image->isNull())
  469. {
  470. SPDLOG_WARN("取出的图片为空...");
  471. return;
  472. }
  473. // SPDLOG_DEBUG("绘制画面...");
  474. update();
  475. }
  476. // m_decodeVedio->wakeUpCondQueueNoEmpty();
  477. }
  478. }
  479. /* 通过信号刷新第一张图片 */
  480. void VideoPlayer::do_refreshSamImage()
  481. {
  482. if(!m_isOpenFile)
  483. {
  484. return;
  485. }
  486. while(m_semRefresh->tryAcquire(1))
  487. {
  488. /* 取出第一张 */
  489. if(m_decodeVedio != nullptr)
  490. {
  491. // SPDLOG_DEBUG("取出一帧图片...");
  492. /* 删除上一帧图片 */
  493. if(m_image != nullptr)
  494. {
  495. delete m_image;
  496. m_image = nullptr;
  497. }
  498. /* 等待图片,最多等待50ms */
  499. m_image = m_decodeVedio->getOneImageUntilHave(100);
  500. if(m_image)
  501. {
  502. if(m_image->isNull())
  503. {
  504. SPDLOG_WARN("取出的图片为空...");
  505. return;
  506. }
  507. SPDLOG_DEBUG("绘制预览画面。");
  508. update();
  509. }
  510. }
  511. }
  512. }
  513. /* 播放完成 */
  514. void VideoPlayer::do_playCompleted()
  515. {
  516. SPDLOG_INFO("视频播放完成。");
  517. m_timerRefreshUI.stop();
  518. /* 手动刷新剩余的环形队列中的图片 */
  519. while(true)
  520. {
  521. if(m_decodeVedio != nullptr)
  522. {
  523. QImage* image = nullptr;
  524. image = m_decodeVedio->getOneImage();
  525. if(image == nullptr)
  526. {
  527. break;
  528. }
  529. /* 删除上一帧图片 */
  530. if(m_image != nullptr)
  531. {
  532. delete m_image;
  533. m_image = nullptr;
  534. }
  535. m_image = image;
  536. if(m_image->isNull())
  537. {
  538. SPDLOG_WARN("取出的图片为空...");
  539. return;
  540. }
  541. // SPDLOG_DEBUG("绘制画面...");
  542. update();
  543. }
  544. }
  545. m_playStatus = false;
  546. // if(m_funcPlayCB != nullptr)
  547. // {
  548. // /* 播放完成的回调函数 */
  549. // m_funcPlayCB(this, 2, nullptr, 0, m_context);
  550. // }
  551. }
  552. /* 判断是否是本地文件 */
  553. bool VideoPlayer::isLocalFile(const QString& fileName)
  554. {
  555. if(fileName.isEmpty())
  556. {
  557. return false;
  558. }
  559. if(fileName.startsWith("http://") || fileName.startsWith("rtsp://")
  560. || fileName.startsWith("rtmp://") || fileName.startsWith("https://"))
  561. {
  562. return false;
  563. }
  564. return true;
  565. }