DecodeVedio.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. #include "DecodeVedio.h"
  2. #include "spdlog/spdlog.h"
  3. #include <QImage>
  4. #include <QThread>
  5. extern "C"
  6. {
  7. #include <libavcodec/avcodec.h>
  8. #include <libavformat/avformat.h>
  9. #include <libswscale/swscale.h>
  10. #include <libavutil/imgutils.h>
  11. }
  12. DecodeVedio::DecodeVedio(QThread* thread, QObject* parent) : QObject(parent) , m_thread(thread)
  13. {
  14. /* 在连接之前调用,移动到新的线程 */
  15. this->moveToThread(thread);
  16. m_startThread.setSingleShot(true);
  17. connect(&m_startThread, &QTimer::timeout, this, &DecodeVedio::do_startDecodeVedio);
  18. thread->start();
  19. }
  20. DecodeVedio::~DecodeVedio()
  21. {
  22. exitThread();
  23. if(m_thread != nullptr)
  24. {
  25. if(m_thread->isRunning())
  26. {
  27. m_thread->quit();
  28. m_thread->wait();
  29. }
  30. }
  31. if(m_initFFmpeg)
  32. {
  33. freeFFmpeg();
  34. }
  35. if(!m_queueImage.isEmpty())
  36. {
  37. int size = m_queueImage.count();
  38. for(int i = 0; i < size; i++)
  39. {
  40. auto image = m_queueImage.dequeue();
  41. if (image)
  42. {
  43. delete image;
  44. }
  45. }
  46. }
  47. }
  48. /* 开始解码视频 */
  49. void DecodeVedio::startDecodeVedio()
  50. {
  51. if(m_isRunning)
  52. {
  53. return;
  54. }
  55. if(!m_initFFmpeg)
  56. {
  57. SPDLOG_WARN("未初始化FFMPEG...");
  58. return;
  59. }
  60. /* 开启定时器,进入槽函数,就进入了新的线程 */
  61. m_startThread.start(0);
  62. }
  63. /* 停止解码视频,退出工作函数,线程未停止 */
  64. void DecodeVedio::stopDecodeVedio()
  65. {
  66. if(!m_isRunning)
  67. {
  68. return;
  69. }
  70. m_isRunning = false;
  71. m_fileName = QString();
  72. // /* 等待线程执行结束 */
  73. // while(true)
  74. // {
  75. // if(m_threadStopped)
  76. // {
  77. // break;
  78. // }
  79. // /* 睡眠10ms */
  80. // std::this_thread::sleep_for(std::chrono::milliseconds(10));
  81. // }
  82. }
  83. /**
  84. * @brief 设置当前播放位置,单位是微秒
  85. * 这里需要去掉队列中已有的图片数目对应的时长
  86. *
  87. * @param pos
  88. */
  89. void DecodeVedio::setCurrentPos(quint64 pos)
  90. {
  91. /* 暂停解码 */
  92. m_pauseDecode = true;
  93. /* 去掉队列中已有的图片数目对应的时长 */
  94. int queueNumFPS = m_queueImage.count() * 1000.0 / m_fps;
  95. qint64 target_pos = m_startPos + pos - queueNumFPS;
  96. SPDLOG_DEBUG("设置播放位置:{}",target_pos);
  97. /* 第二个参数设置为-1,表示所有流都跳转 */
  98. int ret = av_seek_frame(m_pFormatContext, -1, target_pos, AVSEEK_FLAG_BACKWARD);
  99. if(ret < 0)
  100. {
  101. SPDLOG_WARN("跳转失败...");
  102. }
  103. /* 清空队列 */
  104. m_mutexQueue.lock();
  105. int size = m_queueImage.count();
  106. for(int i = 0; i < size; i++)
  107. {
  108. auto image = m_queueImage.dequeue();
  109. if (image)
  110. {
  111. delete image;
  112. }
  113. }
  114. m_mutexQueue.unlock();
  115. m_condQueueNoFull.wakeAll();
  116. SPDLOG_INFO("跳转完成 , queue count {}", m_queueImage.count());
  117. /* 继续解码 */
  118. m_pauseDecode = false;
  119. // SPDLOG_DEBUG("继续解码... {}", m_pauseDecode.load());
  120. }
  121. /* 获取当前播放位置 */
  122. qint64 DecodeVedio::getCurrentPos()
  123. {
  124. return m_currentPos - m_startPos;
  125. }
  126. /* 获取视频时长 */
  127. qint64 DecodeVedio::getDuration()
  128. {
  129. return m_duration;
  130. }
  131. /* 唤醒队列不满条件变量 */
  132. void DecodeVedio::wakeUpCondQueueNoEmpty()
  133. {
  134. m_condQueueNoFull.wakeAll();
  135. }
  136. /**
  137. * @brief 获取一帧图像,队列为空就返回nullptr,这个函数应该是运行在UI线程中的
  138. * @warning 传出这个指针后,队列就出队了,内存需要外面获取的实例释放
  139. * @return QImage* 一帧图像的指针
  140. */
  141. QImage* DecodeVedio::getOneImage()
  142. {
  143. if(m_queueImage.count() == 0)
  144. {
  145. // SPDLOG_TRACE("队列为空...");
  146. return nullptr;
  147. }
  148. // SPDLOG_TRACE("******************************** 队列中图片个数:{} ",m_queueImage.count());
  149. m_mutexQueue.lock();
  150. auto image = m_queueImage.dequeue();
  151. m_mutexQueue.unlock();
  152. /* 唤醒可能阻塞住的解码线程,队列中的图片低于20之后再唤醒 */
  153. if(m_queueImage.count() < 20)
  154. {
  155. m_condQueueNoFull.wakeAll();
  156. }
  157. return image;
  158. }
  159. /* 获取一帧图像,直到有图像为止 */
  160. QImage* DecodeVedio::getOneImageUntilHave()
  161. {
  162. QImage* image = nullptr;
  163. if(m_queueImage.count() == 0)
  164. {
  165. m_mutexQueue.lock();
  166. m_condQueueNoEmpty.wait(&m_mutexQueue);
  167. image = m_queueImage.dequeue();
  168. m_mutexQueue.unlock();
  169. }
  170. return image;
  171. }
  172. /**
  173. * @brief 获取每秒的帧数
  174. *
  175. * @return int 正值表示帧数,-1表示未知,-2表示HEVC
  176. */
  177. int DecodeVedio::getFrameCount()
  178. {
  179. AVFormatContext *pFormatCtx = NULL;
  180. avformat_open_input(&pFormatCtx, m_fileName.toStdString().c_str(), NULL, NULL);
  181. avformat_find_stream_info(pFormatCtx, NULL);
  182. /* 找到视频流 */
  183. int videoStreamIndex = -1;
  184. for (int i = 0; i < pFormatCtx->nb_streams; i++)
  185. {
  186. if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
  187. videoStreamIndex = i;
  188. break;
  189. }
  190. }
  191. /* 获取视频格式是hevc还是h.264 */
  192. enum AVCodecID codecID = pFormatCtx->streams[videoStreamIndex]->codecpar->codec_id;
  193. if (codecID == AV_CODEC_ID_HEVC)
  194. {
  195. SPDLOG_DEBUG("视频格式是HEVC");
  196. m_fps = -2;
  197. // AVPacket packet;
  198. // int frameCount = 0;
  199. // double timeBase = av_q2d(pFormatCtx->streams[videoStreamIndex]->time_base);
  200. // double duration = 2.0; // Duration in seconds
  201. // while (av_read_frame(pFormatCtx, &packet) >= 0)
  202. // {
  203. // if (packet.stream_index == videoStreamIndex)
  204. // {
  205. // double packetTime = packet.pts * timeBase;
  206. // if (packetTime <= duration) {
  207. // frameCount++;
  208. // } else {
  209. // break;
  210. // }
  211. // }
  212. // av_packet_unref(&packet);
  213. // }
  214. // fps = frameCount / duration;
  215. }
  216. else if(codecID == AV_CODEC_ID_H264)
  217. {
  218. SPDLOG_DEBUG("视频格式是H.264");
  219. /* 获取到分子和分母,一除就是帧率 */
  220. AVRational frameRate = pFormatCtx->streams[videoStreamIndex]->avg_frame_rate;
  221. m_fps = frameRate.num*1.0 / frameRate.den;
  222. // SPDLOG_DEBUG("分子:{} 分母:{} 帧率:{}",frameRate.num, frameRate.den, fps);
  223. }else
  224. {
  225. m_fps = 30;
  226. }
  227. avformat_close_input(&pFormatCtx);
  228. return m_fps;
  229. }
  230. void DecodeVedio::setVideoSize(int width, int height)
  231. {
  232. m_width = width;
  233. m_height = height;
  234. /* 重新初始化缩放参数 */
  235. // sws_freeContext(m_sws_ctx);
  236. /* 初始化Sws Context,这是转换规则,转换成RGB */
  237. m_sws_ctx = sws_getContext( m_pCodecCtx->width, m_pCodecCtx->height, m_pCodecCtx->pix_fmt, /* 原图像大小和格式 */
  238. m_width, m_height, AV_PIX_FMT_RGB24, /* 目标图像的大小和格式 */
  239. SWS_BICUBIC,
  240. nullptr,
  241. nullptr,
  242. nullptr);
  243. }
  244. /* 初始化函数 */
  245. void DecodeVedio::initFFmpeg(const QString& fileName)
  246. {
  247. if(fileName != m_fileName)
  248. {
  249. m_fileName = fileName;
  250. if(m_initFFmpeg)
  251. {
  252. freeFFmpeg();
  253. }
  254. }else
  255. {
  256. /* 清空队列 */
  257. int size = m_queueImage.count();
  258. for(int i = 0; i < size; i++)
  259. {
  260. auto image = m_queueImage.dequeue();
  261. if (image)
  262. {
  263. delete image;
  264. }
  265. }
  266. m_queueImage.clear();
  267. return;
  268. }
  269. /* 清空队列 */
  270. int size = m_queueImage.count();
  271. for(int i = 0; i < size; i++)
  272. {
  273. auto image = m_queueImage.dequeue();
  274. if (image)
  275. {
  276. delete image;
  277. }
  278. }
  279. m_queueImage.clear();
  280. SPDLOG_DEBUG("开始初始化FFMPEG");
  281. /* 注册所有的解码器,从4.0开始就不需要这个初始化了 */
  282. // av_register_all();
  283. /* 存储文件格式信息 */
  284. /* 打开文件,读取视频文件的头信息,放在第一个参数的结构体中 */
  285. int ret = avformat_open_input(&m_pFormatContext, m_fileName.toStdString().c_str(), nullptr, nullptr);
  286. if(ret != 0)
  287. {
  288. SPDLOG_WARN("打开视频文件错误,错误代码:{}",ret);
  289. return;
  290. }
  291. ret = 0;
  292. /* 检查视频容器内部的流信息,将流存储到了pFormatContext->streams中
  293. * 这个会补充avformat_open_input()没有获取到的信息 */
  294. ret = avformat_find_stream_info(m_pFormatContext, nullptr);
  295. if(ret < 0)
  296. {
  297. SPDLOG_WARN("获取视频流错误,错误代码:{}",ret);
  298. return;
  299. }
  300. m_duration = m_pFormatContext->duration;
  301. m_currentPos = 0;
  302. m_startPos = m_pFormatContext->start_time;
  303. // SPDLOG_DEBUG("开始时间:{} 时长:{}",m_startPos, m_duration);
  304. /* 一个调试函数,将流信息输出到控制台 */
  305. av_dump_format(m_pFormatContext, 0, m_fileName.toStdString().c_str(), 0);
  306. /************ 找到视频流 ************/
  307. int i = 0;
  308. AVCodecParameters *pCodecCtxOrig = nullptr;
  309. for(i = 0;i < m_pFormatContext->nb_streams; i++)
  310. {
  311. if(m_pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
  312. {
  313. m_videoStream = i;
  314. break;
  315. }
  316. }
  317. if(m_videoStream == -1)
  318. {
  319. SPDLOG_WARN("没有找到视频流");
  320. return;
  321. }
  322. SPDLOG_INFO("找到视频流");
  323. pCodecCtxOrig = m_pFormatContext->streams[m_videoStream]->codecpar;
  324. SPDLOG_INFO("获取视频流参数成功!");
  325. /* 使用编码器指向流 */
  326. AVCodec *pCodec = nullptr;
  327. /* 找到解码器 */
  328. pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id);
  329. if(pCodec == nullptr)
  330. {
  331. SPDLOG_WARN("没有找到解码器");
  332. return;
  333. }
  334. SPDLOG_TRACE("找到解码器");
  335. /* 复制上下文,先分配空间,后面记得释放空间 */
  336. m_pCodecCtx = avcodec_alloc_context3(pCodec);
  337. SPDLOG_TRACE("分配空间成功!");
  338. /* 将视频流中的编码器参数拷贝下来,这个函数不是线程安全的 */
  339. if(avcodec_parameters_to_context(m_pCodecCtx, pCodecCtxOrig) != 0)
  340. {
  341. SPDLOG_WARN("复制上下文错误");
  342. return;
  343. }
  344. SPDLOG_INFO("复制上下文成功!");
  345. if(avcodec_open2(m_pCodecCtx, pCodec, nullptr) < 0)
  346. {
  347. SPDLOG_ERROR("打开解码器错误");
  348. return;
  349. }
  350. SPDLOG_TRACE("打开编码器成功!");
  351. /******** 读取数据(解码数据) ********/
  352. m_packet = av_packet_alloc();
  353. av_init_packet(m_packet);
  354. /********* 创建两个pFrame,一个存放原始数据,一个存放转换后的RGB数据 **********/
  355. m_pFrame = av_frame_alloc();
  356. if(m_pFrame == nullptr)
  357. {
  358. SPDLOG_ERROR("创建pFrame错误");
  359. return;
  360. }
  361. m_pFrameRGB = av_frame_alloc();
  362. if(m_pFrameRGB == nullptr)
  363. {
  364. SPDLOG_ERROR("创建pFrameRGB错误");
  365. return;
  366. }
  367. int numBytes = 0;
  368. /* 初始化pFrameRGB */
  369. numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, m_pCodecCtx->width, m_pCodecCtx->height, 1);
  370. m_buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
  371. /* 获取视频相关信息 */
  372. m_width = m_pCodecCtx->width;
  373. m_height = m_pCodecCtx->height;
  374. m_frameCount = m_pFormatContext->streams[m_videoStream]->nb_frames;
  375. /* 这个函数的实际作用是将buffer设置给pFrameRGB作为原始数据的内存区域 */
  376. av_image_fill_arrays(m_pFrameRGB->data, m_pFrameRGB->linesize, m_buffer, AV_PIX_FMT_RGB24, m_pCodecCtx->width, m_pCodecCtx->height, 1);
  377. /********** 创建一个SwsContext结构体,主要为了格式转化的时候使用 ***********/
  378. /* 初始化Sws Context,这是转换规则,转换成RGB */
  379. m_sws_ctx = sws_getContext( m_pCodecCtx->width, m_pCodecCtx->height, m_pCodecCtx->pix_fmt, /* 原图像大小和格式 */
  380. m_width, m_height, AV_PIX_FMT_RGB24, /* 目标图像的大小和格式 */
  381. SWS_BILINEAR, /* 双线性 */
  382. nullptr,
  383. nullptr,
  384. nullptr);
  385. m_initFFmpeg = true;
  386. SPDLOG_INFO("FFMPEG初始化完成!");
  387. /* 这里不能释放,否则会出问题 */
  388. // avcodec_parameters_free(&pCodecCtxOrig);
  389. }
  390. /* 取消ffmpeg初始化函数 */
  391. void DecodeVedio::freeFFmpeg()
  392. {
  393. /************** 清理缓冲区,释放空间 *********************/
  394. av_free(m_buffer);
  395. m_buffer = nullptr;
  396. av_free(m_pFrameRGB);
  397. m_pFrameRGB = nullptr;
  398. /* 释放pFrame */
  399. av_free(m_pFrame);
  400. m_pFrame = nullptr;
  401. av_packet_free(&m_packet);
  402. // sws_freeContext(m_sws_ctx);
  403. /* 关闭解码器 */
  404. avcodec_close(m_pCodecCtx);
  405. /* 关闭文件 */
  406. avformat_close_input(&m_pFormatContext);
  407. QString m_fileName = QString();
  408. int m_videoStream = -1; /* 记录视频流是第几个流 */
  409. bool m_initFFmpeg = false;
  410. }
  411. /**
  412. * @brief 进入之后就会一直解码,完成一个帧就会发送新一个信号
  413. *
  414. */
  415. void DecodeVedio::decodeVedio()
  416. {
  417. int ret = 0;
  418. int retFrame = 0;
  419. int retPacket = 0;
  420. while(m_isRunning)
  421. {
  422. /* 暂停解码 */
  423. while(m_pauseDecode)
  424. {
  425. std::this_thread::sleep_for(std::chrono::milliseconds(1));
  426. }
  427. std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
  428. /* 读取一帧压缩码流,如果使用多线程解码,就从这里分发数据流的包 */
  429. retPacket = av_read_frame(m_pFormatContext, m_packet);
  430. std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
  431. // SPDLOG_TRACE("======================================= 读取数据包耗时(us):{}",std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count());
  432. if(retPacket == AVERROR_EOF)
  433. {
  434. SPDLOG_INFO("读取到文件末尾...");
  435. break;
  436. }
  437. if(retPacket < 0)
  438. {
  439. SPDLOG_WARN("读取帧错误...");
  440. break;
  441. }
  442. /* 判断是否是视频帧 */
  443. if(m_packet->stream_index == m_videoStream)
  444. {
  445. std::chrono::steady_clock::time_point t3 = std::chrono::steady_clock::now();
  446. /* 解码视频帧,现在使用新的API */
  447. ret = avcodec_send_packet(m_pCodecCtx, m_packet);
  448. std::chrono::steady_clock::time_point t4 = std::chrono::steady_clock::now();
  449. if(ret < 0)
  450. {
  451. SPDLOG_ERROR("发送数据包错误...");
  452. continue;
  453. }
  454. // SPDLOG_TRACE("======================================= 发送数据包耗时:{}",std::chrono::duration_cast<std::chrono::milliseconds>(t4 - t3).count());
  455. while (m_isRunning)
  456. {
  457. /* 从解码器接收解码后的帧的函数。*/
  458. retFrame = avcodec_receive_frame(m_pCodecCtx, m_pFrame);
  459. std::chrono::steady_clock::time_point t5 = std::chrono::steady_clock::now();
  460. // SPDLOG_TRACE("======================================= 接收帧耗时:{}",std::chrono::duration_cast<std::chrono::milliseconds>(t5 - t4).count());
  461. if(retFrame == AVERROR(EAGAIN) || retFrame == AVERROR_EOF)
  462. {
  463. // SPDLOG_TRACE("没有更多的帧可以输出,跳出循环。");
  464. break;
  465. }
  466. if(retFrame < 0)
  467. {
  468. SPDLOG_ERROR("解码错误...");
  469. break;
  470. }
  471. if (retFrame == 0)
  472. {
  473. /* 现在的位置,转换成时间,乘上AV_TIME_BASE后,单位是微秒 */
  474. m_currentPos = m_pFrame->pts * av_q2d(m_pFormatContext->streams[m_videoStream]->time_base) * AV_TIME_BASE;
  475. SPDLOG_DEBUG("======== 当前位置:{} ========",m_currentPos);
  476. /* 成功接收到一帧,处理解码后的帧 */
  477. std::chrono::steady_clock::time_point t6 = std::chrono::steady_clock::now();
  478. /* 将帧转换为RGB */
  479. sws_scale(m_sws_ctx, (uint8_t const * const *)m_pFrame->data, m_pFrame->linesize, 0, m_pCodecCtx->height,
  480. m_pFrameRGB->data, m_pFrameRGB->linesize);
  481. std::chrono::steady_clock::time_point t7 = std::chrono::steady_clock::now();
  482. // SPDLOG_TRACE("======================================= 转换RGB耗时:{}",std::chrono::duration_cast<std::chrono::milliseconds>(t7 - t6).count());
  483. /* 一帧图像入队 */
  484. auto image = new QImage(m_pFrameRGB->data[0], m_width, m_height, QImage::Format_RGB888);
  485. m_mutexQueue.lock();
  486. if(m_queueImage.count() >= m_queueMaxNum)
  487. {
  488. // SPDLOG_TRACE("队列满了...");
  489. m_condQueueNoFull.wait(&m_mutexQueue);
  490. }
  491. m_queueImage.enqueue(image);
  492. m_mutexQueue.unlock();
  493. /* 队列有数据,唤醒阻塞函数 */
  494. m_condQueueNoEmpty.wakeAll();
  495. /* 同时发送信号 */
  496. emit signal_oneImage();
  497. }
  498. else if (retFrame == AVERROR(EAGAIN) || ret == AVERROR_EOF)
  499. {
  500. // 没有更多的帧可以输出,跳出循环
  501. break;
  502. }
  503. else if (retFrame < 0)
  504. {
  505. SPDLOG_TRACE("解码错误...");
  506. av_packet_unref(m_packet);
  507. break;
  508. }
  509. av_frame_unref(m_pFrame);
  510. retFrame = -1;
  511. }
  512. }else
  513. {
  514. // SPDLOG_TRACE("不是视频帧...");
  515. }
  516. // av_free_packet(AVPacket *pkt)
  517. av_packet_unref(m_packet);
  518. }
  519. freeFFmpeg();
  520. /* 清空队列 */
  521. m_mutexQueue.lock();
  522. int count = m_queueImage.count();
  523. for (int i = 0; i < count; i++)
  524. {
  525. auto image = m_queueImage.dequeue();
  526. if (image)
  527. {
  528. delete image;
  529. image = nullptr;
  530. }
  531. }
  532. m_mutexQueue.unlock();
  533. /* 线程已停止 */
  534. m_threadStopped = true;
  535. }
  536. /* 退出线程,将所有可能暂停线程运行的条件全部唤醒 */
  537. void DecodeVedio::exitThread()
  538. {
  539. if(m_isRunning)
  540. {
  541. m_isRunning = false;
  542. }
  543. m_pauseDecode = false;
  544. m_condQueueNoFull.wakeAll();
  545. }
  546. /* 开启解码 */
  547. void DecodeVedio::do_startDecodeVedio()
  548. {
  549. SPDLOG_DEBUG("线程ID:{}",QThread::currentThreadId());
  550. // if(!m_initFFmpeg)
  551. // {
  552. // initFFmpeg();
  553. // }
  554. m_isRunning = true;
  555. m_threadStopped = false;
  556. m_pauseDecode = false;
  557. /* 进入解码,直到播放完成或者手动退出 */
  558. decodeVedio();
  559. SPDLOG_TRACE("播放完成...");
  560. }