VideoPlayer.cpp 15 KB

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