VideoPlayer.cpp 16 KB

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