DecodeVedio.cpp 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079
  1. #include "DecodeVedio.h"
  2. #include "spdlog/spdlog.h"
  3. // #include "FmtLog/fmtlog.h"
  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. /*=================================================================================================
  13. * @brief FFmpeg获取GPU硬件解码帧格式的回调函数
  14. ===================================================================================================*/
  15. static enum AVPixelFormat g_hw_pix_fmt;
  16. /**
  17. * @brief 回调函数,获取GPU硬件解码帧的格式
  18. *
  19. * @param ctx
  20. * @param pix_fmts
  21. * @return AVPixelFormat
  22. */
  23. AVPixelFormat get_hw_format(AVCodecContext *ctx, const AVPixelFormat *pix_fmts)
  24. {
  25. Q_UNUSED(ctx)
  26. const AVPixelFormat* p;
  27. for(p = pix_fmts; *p != -1; p++)
  28. {
  29. if(*p == g_hw_pix_fmt)
  30. {
  31. return *p;
  32. }
  33. }
  34. SPDLOG_WARN("无法获取硬件解码器表面格式。");
  35. return AV_PIX_FMT_NONE;
  36. }
  37. /**
  38. * @brief Construct a new Decode Vedio:: Decode Vedio object
  39. *
  40. * @param thread
  41. * @param parent
  42. */
  43. DecodeVedio::DecodeVedio(QThread* thread, QObject* parent) : QObject(parent) , m_thread(thread)
  44. {
  45. /* 初始化成员变量 */
  46. m_threadRuning = false;
  47. m_initFFmpeg = false;
  48. m_pauseDecode = false;
  49. m_decodeStatus = false;
  50. m_isSeek = false;
  51. m_flushDecoder = false;
  52. m_decodeState = DecodeState::NONE;
  53. m_pts.store(0);
  54. /* 在连接之前调用,移动到新的线程 */
  55. this->moveToThread(thread);
  56. connect(this, &DecodeVedio::signal_startDecode, this, &DecodeVedio::do_startDecodeVedio);
  57. thread->start();
  58. findHWDecoder();
  59. // if(m_supportHWDecoder)
  60. // {
  61. // SPDLOG_DEBUG("支持的硬件解码器:");
  62. // for(auto it : m_listDecoderName)
  63. // {
  64. // SPDLOG_DEBUG("{}", it.toStdString());
  65. // }
  66. // }else {
  67. // SPDLOG_WARN("未找到可用的硬件解码器。");
  68. // }
  69. }
  70. DecodeVedio::~DecodeVedio()
  71. {
  72. stopDecodeVedio();
  73. if(m_thread != nullptr)
  74. {
  75. if(m_thread->isRunning())
  76. {
  77. m_thread->quit();
  78. m_thread->wait();
  79. }
  80. }
  81. }
  82. /* 开始解码视频 */
  83. void DecodeVedio::startDecodeVedio()
  84. {
  85. if(m_threadRuning)
  86. {
  87. return;
  88. }
  89. if(!m_initFFmpeg)
  90. {
  91. SPDLOG_WARN("未初始化FFMPEG...");
  92. return;
  93. }
  94. m_threadRuning = true;
  95. // decodeUsingCPU();
  96. /* 发送信号,开启新线程 */
  97. emit signal_startDecode();
  98. }
  99. /* 停止解码视频,退出工作函数,线程未停止 */
  100. void DecodeVedio::stopDecodeVedio()
  101. {
  102. if(!m_threadRuning)
  103. {
  104. return;
  105. }
  106. exitThread();
  107. /* 唤醒阻塞住的解码线程 */
  108. // /* 等待线程执行结束 */
  109. while(m_decodeState.load() != DecodeState::DecodeExit)
  110. {
  111. std::this_thread::sleep_for(std::chrono::milliseconds(5));
  112. }
  113. freeAll();
  114. m_threadRuning = false;
  115. }
  116. /**
  117. * @brief 设置当前播放位置,单位是毫秒
  118. * 这里需要去掉队列中已有的图片数目对应的时长
  119. *
  120. * @param pos 要跳转的位置,范围从0~duration
  121. */
  122. void DecodeVedio::setCurrentPos(qint64 pos)
  123. {
  124. if(!m_threadRuning)
  125. {
  126. return;
  127. }
  128. m_isSeek = true;
  129. /* 先暂停解码 */
  130. pauseDecode();
  131. SPDLOG_DEBUG("跳转到:{}ms",pos);
  132. /*在环形队列中有已解码的视频帧数,需要去掉已有的帧数所占的时间 */
  133. pos = pos - m_queueImage.QueueSize() * (1000 / m_fps);
  134. if(pos < 0) {
  135. pos = 0;
  136. }
  137. if(pos > m_duration) {
  138. pos = m_duration;
  139. }
  140. pos = pos + m_startPos;
  141. qint64 targetPos = qRound64((double)pos / (1000 * rationalToDouble(&m_pFormatContext->streams[m_videoStream]->time_base)));
  142. /* 开始跳转,这里设置标志为AVSEEK_FLAG_BACKWARD,跳转到目标位置的前一个关键帧中,然后开始解码,直到到达目标位置为止 */
  143. int ret = av_seek_frame(m_pFormatContext, m_videoStream, targetPos, AVSEEK_FLAG_BACKWARD);
  144. if(ret < 0)
  145. {
  146. SPDLOG_ERROR("跳转失败!");
  147. }
  148. m_targetPos = pos;
  149. /* 刷新解码器缓冲区 */
  150. m_flushDecoder.store(true);
  151. /* 清空环形队列中的视频 */
  152. SPDLOG_DEBUG("清空环形队列中的视频。");
  153. QImage* image = 0;
  154. while (m_queueImage.QueueSize() > 0)
  155. {
  156. image = nullptr;
  157. m_queueImage.front_pop_NoBlock(image);
  158. if(image != nullptr)
  159. {
  160. delete image;
  161. }
  162. }
  163. /* 继续解码 */
  164. continueDecode();
  165. }
  166. /* 获取当前播放位置,单位ms */
  167. qint64 DecodeVedio::getCurrentPos()
  168. {
  169. return m_pts.load() - m_startPos;
  170. }
  171. /* 获取视频时长 */
  172. qint64 DecodeVedio::getDuration()
  173. {
  174. return m_duration;
  175. }
  176. /**
  177. * @brief 获取一帧图像,队列为空就返回nullptr,这个函数应该是运行在UI线程中的
  178. * @warning 传出这个指针后,队列就出队了,内存需要外面获取的实例释放
  179. * @return QImage* 一帧图像的指针
  180. */
  181. QImage* DecodeVedio::getOneImage()
  182. {
  183. if(!m_threadRuning)
  184. {
  185. return nullptr;
  186. }
  187. QImage* image = nullptr;
  188. if(!m_queueImage.front_pop_NoBlock(image))
  189. {
  190. return nullptr;
  191. }
  192. return image;
  193. }
  194. /**
  195. * @brief 获取一帧图像,直到有图像为止
  196. *
  197. * @param timeOut 设为-1是一直等待,设置正整数是等待的时间,单位ms
  198. * @return QImage*
  199. */
  200. QImage* DecodeVedio::getOneImageUntilHave(int timeOut)
  201. {
  202. if(!m_threadRuning)
  203. {
  204. return nullptr;
  205. }
  206. if(timeOut < 0)
  207. {
  208. QImage* image = m_queueImage.front_pop();
  209. return image;
  210. }
  211. for(int i = 0; i < timeOut; i++)
  212. {
  213. QImage* image = nullptr;
  214. if(m_queueImage.front_pop_NoBlock(image))
  215. {
  216. return image;
  217. }
  218. std::this_thread::sleep_for(std::chrono::milliseconds(1));
  219. }
  220. return nullptr;
  221. }
  222. /* 查找硬件解码器 */
  223. void DecodeVedio::findHWDecoder(QStringList& listDecoderName)
  224. {
  225. AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
  226. listDecoderName.clear();
  227. while( (type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)
  228. {
  229. /* 获取硬件解码器的名称 */
  230. const char* typeName = av_hwdevice_get_type_name(type);
  231. if(typeName)
  232. {
  233. listDecoderName.append(QString(typeName));
  234. }
  235. }
  236. }
  237. /* 获取硬件解码器 */
  238. void DecodeVedio::findHWDecoder()
  239. {
  240. AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
  241. // QStringList strTypes;
  242. m_listDecoderName.clear();
  243. while( (type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)
  244. {
  245. m_listHWDeviceType.append(type);
  246. /* 获取硬件解码器的名称 */
  247. const char* typeName = av_hwdevice_get_type_name(type);
  248. if(typeName)
  249. {
  250. m_listDecoderName.append(QString(typeName));
  251. }
  252. }
  253. if(m_listHWDeviceType.isEmpty())
  254. {
  255. m_supportHWDecoder = false;
  256. }else {
  257. m_supportHWDecoder = true;
  258. }
  259. }
  260. /* 初始化硬件解码器 */
  261. void DecodeVedio::initHWDecoder(const AVCodec* codec)
  262. {
  263. if(codec == nullptr)
  264. {
  265. return;
  266. }
  267. for(int i = 0;;i++)
  268. {
  269. /* 获取编解码器支持的硬件配置 */
  270. const AVCodecHWConfig* hwConfig = avcodec_get_hw_config(codec, 0);
  271. if(hwConfig == nullptr)
  272. {
  273. SPDLOG_WARN("没有找到支持{}的硬件配置", codec->name);
  274. return;
  275. }
  276. /* 判断是否是设备类型 */
  277. if(hwConfig->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
  278. {
  279. /* 在已有的解码器列表中查找 */
  280. for(auto i : m_listHWDeviceType)
  281. {
  282. if(hwConfig->device_type == AVHWDeviceType(i))
  283. {
  284. /* 获取像素格式 */
  285. g_hw_pix_fmt = hwConfig->pix_fmt;
  286. /* 打开指定类型的设备,并为其创建AVHWDeviceContext */
  287. int ret = av_hwdevice_ctx_create(&m_hw_device_ctx, hwConfig->device_type, nullptr, nullptr, 0);
  288. if(ret < 0)
  289. {
  290. SPDLOG_ERROR("打开硬件解码器失败!");
  291. return;
  292. }
  293. SPDLOG_INFO("打开硬件解码器:{}", av_hwdevice_get_type_name(hwConfig->device_type));
  294. m_pCodecContext->hw_device_ctx = av_buffer_ref(m_hw_device_ctx);
  295. m_pCodecContext->get_format = get_hw_format;
  296. return;
  297. }
  298. }
  299. }
  300. }
  301. }
  302. /* 拷贝数据,从GPU显存拷贝到内存中 */
  303. bool DecodeVedio::copyDataFromGPU(AVFrame* pFrameHW, AVFrame* pFrameSRC)
  304. {
  305. if(m_pFrameSRC->format != g_hw_pix_fmt)
  306. {
  307. av_frame_unref(m_pFrameSRC);
  308. return false;
  309. }
  310. /* 这一步可能会耗费较长时间 */
  311. int ret = av_hwframe_transfer_data(pFrameHW, pFrameSRC, 0);
  312. if(ret < 0)
  313. {
  314. SPDLOG_ERROR("从GPU拷贝数据失败!");
  315. av_frame_unref(pFrameSRC);
  316. return false;
  317. }
  318. av_frame_copy_props(pFrameHW, pFrameSRC);
  319. return true;
  320. }
  321. /* 打开视频,同时初始化解码器) */
  322. void DecodeVedio::openVedio(const QString& fileName)
  323. {
  324. if(fileName.isEmpty())
  325. {
  326. SPDLOG_WARN("文件名为空...");
  327. return;
  328. }
  329. m_fileName = fileName;
  330. if(m_initFFmpeg)
  331. {
  332. freeAll();
  333. }
  334. /* 清空队列 */
  335. if(m_queueImage.QueueSize() > 0)
  336. {
  337. for(int i = 0; i < m_queueImage.QueueSize(); i++)
  338. {
  339. QImage* image = nullptr;
  340. if (m_queueImage.front_pop_NoBlock(image))
  341. {
  342. delete image;
  343. }
  344. }
  345. m_queueImage.clearQueue();
  346. }
  347. m_queueImage.setQueueCapacity(30);
  348. SPDLOG_DEBUG("开始初始化FFMPEG");
  349. /* 设置网络缓冲区大小和错误恢复选项 */
  350. AVDictionary *options = nullptr;
  351. /* 设置接收缓冲区 */
  352. av_dict_set(&options, "buffer_size", "1024000", 0);
  353. /* 设置最大复用或解复用延迟(以微秒为单位)。当通过【UDP】 接收数据时,解复用器尝试重新排序接收到的数据包
  354. * (因为它们可能无序到达,或者数据包可能完全丢失)。这可以通过将最大解复用延迟设置为零
  355. * (通过max_delayAVFormatContext 字段)来禁用。 */
  356. av_dict_set(&options, "max_delay", "500000", 0);
  357. /* 设置rtsp流使用tcp打开,如果打开失败错误信息为【Error number -135 occurred】可以切换(UDP、tcp、udp_multicast、http),比如vlc推流就需要使用udp打开 */
  358. av_dict_set(&options, "rtsp_transport", "tcp", 0);
  359. /* 以微秒为单位设置套接字 TCP I/O 超时,如果等待时间过短,也可能会还没连接就返回了。 */
  360. av_dict_set(&options, "stimeout", "20000000", 0);
  361. /************ 存储文件格式信息 ************/
  362. /* 打开文件,读取视频文件的头信息,放在第一个参数的结构体中 */
  363. int ret = avformat_open_input( &m_pFormatContext, /* 解封装上下文 */
  364. m_fileName.toStdString().data(), /* 打开视频地址 */
  365. nullptr, /* 这个参数强制使用特定的输入格式,可以设置为null */
  366. &options); /* 参数设置 */
  367. if(ret != 0)
  368. {
  369. SPDLOG_WARN("打开视频文件错误,错误代码:{}",ret);
  370. freeAll();
  371. return;
  372. }
  373. /* 释放参数 */
  374. if(options != nullptr)
  375. {
  376. av_dict_free(&options);
  377. }
  378. /************ 找到视频流 ************/
  379. /* 检查视频容器内部的流信息,将所有流存储到了pFormatContext->streams中
  380. * 查找到视频流,并获取视频时长相关的信息 */
  381. ret = 0;
  382. ret = avformat_find_stream_info(m_pFormatContext, nullptr);
  383. if(ret < 0)
  384. {
  385. SPDLOG_WARN("获取视频流错误,错误代码:{}",ret);
  386. freeAll();
  387. return;
  388. }
  389. m_duration = m_pFormatContext->duration / (AV_TIME_BASE / 1000); /* 获取视频时长,单位是毫秒 */
  390. m_startPos = m_pFormatContext->start_time / (AV_TIME_BASE / 1000); /* 获取视频开始时间,单位是毫秒 */
  391. // SPDLOG_DEBUG("开始时间:{} 时长:{}",m_startPos, m_duration);
  392. /* 一个调试函数,将流信息输出到控制台 */
  393. av_dump_format(m_pFormatContext, 0, m_fileName.toStdString().c_str(), 0);
  394. /* 找到视频流 */
  395. m_videoStream = av_find_best_stream(m_pFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
  396. if(m_videoStream < 0)
  397. {
  398. SPDLOG_WARN("没有找到视频流");
  399. freeAll();
  400. return;
  401. }
  402. SPDLOG_DEBUG("找到视频流");
  403. /* 获取视频流的编解码信息,主要是分辨率等信息 */
  404. AVStream *pStream = m_pFormatContext->streams[m_videoStream]; /* 获取视频流 */
  405. AVCodecParameters* pCodecParams = pStream->codecpar; /* 获取视频流的编解码信息 */
  406. SPDLOG_DEBUG("获取视频流参数成功!");
  407. /* 获取视频相关信息 */
  408. m_srcSize.setWidth(pCodecParams->width);
  409. m_srcSize.setHeight(pCodecParams->height);
  410. m_fps = rationalToDouble(&pStream->avg_frame_rate);
  411. m_totalFrame = m_pFormatContext->streams[m_videoStream]->nb_frames;
  412. m_pts.store(0);
  413. /************ 查找并设置解码器 ************/
  414. /* 找到解码器 */
  415. const AVCodec* pCodec = avcodec_find_decoder(pCodecParams->codec_id);
  416. if(pCodec == nullptr)
  417. {
  418. SPDLOG_WARN("没有找到解码器");
  419. freeAll();
  420. return;
  421. }
  422. m_decoderName = pCodec->name;
  423. // SPDLOG_INFO("找到解码器:{}",pCodec->name);
  424. /* 获取视频信息的上下文,先分配空间,后面记得释放空间 */
  425. m_pCodecContext = avcodec_alloc_context3(pCodec);
  426. /* 将视频流中的编码器参数拷贝下来,这个函数不是线程安全的 */
  427. if(avcodec_parameters_to_context(m_pCodecContext, pCodecParams) != 0)
  428. {
  429. SPDLOG_WARN("复制上下文错误");
  430. freeAll();
  431. return;
  432. }
  433. SPDLOG_DEBUG("设置解码器参数成功!");
  434. // m_pCodecContext->flags2 |= AV_CODEC_FLAG2_FAST; /* 使用快速解码(允许使用不符合规范的解码) */
  435. m_pCodecContext->thread_count = 8; /* 使用8线程解码 */
  436. /* 初始化硬件解码器 */
  437. if(m_supportHWDecoder)
  438. {
  439. initHWDecoder(pCodec);
  440. }
  441. /* 打开解码器,(初始化解码器上下文,如果调用了avcodec_alloc_context3,第二个参数可以设置为nullptr) */
  442. if(avcodec_open2(m_pCodecContext, nullptr, nullptr) < 0)
  443. {
  444. SPDLOG_ERROR("打开解码器错误");
  445. freeAll();
  446. return;
  447. }
  448. SPDLOG_DEBUG("打开解码器成功!");
  449. /************ 初始化数据包 ************/
  450. m_packet = av_packet_alloc();
  451. av_new_packet(m_packet, m_pCodecContext->width * m_pCodecContext->height);
  452. /* 创建两个pFrame,一个存放原始数据,一个存放转换后的RGB数据 */
  453. m_pFrameSRC = av_frame_alloc();
  454. if(m_pFrameSRC == nullptr)
  455. {
  456. SPDLOG_ERROR("创建pFrame错误");
  457. freeAll();
  458. return;
  459. }
  460. // m_pFrameRGB = av_frame_alloc();
  461. // if(m_pFrameRGB == nullptr)
  462. // {
  463. // SPDLOG_ERROR("创建pFrameRGB错误");
  464. // freeAll();
  465. // return;
  466. // }
  467. m_pFrameHW = av_frame_alloc();
  468. if(m_pFrameHW == nullptr)
  469. {
  470. SPDLOG_ERROR("创建pFrameHW错误");
  471. freeAll();
  472. return;
  473. }
  474. if(m_buffer != nullptr)
  475. {
  476. av_free(m_buffer);
  477. m_buffer = nullptr;
  478. }
  479. /* 分配图像空间 */
  480. int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, m_pCodecContext->width, m_pCodecContext->height, 1);
  481. /* 这里多分配了一些空间,防止某些视频图像在使用sws_scale()拷贝后超出数组长度 */
  482. m_buffer = (uint8_t *)av_malloc(numBytes + 1000);
  483. m_initFFmpeg = true;
  484. SPDLOG_INFO("FFMPEG初始化完成!");
  485. // SPDLOG_INFO("视频宽度:{} 高度:{} 帧率:{} 总时长:{} 总帧数:{}",m_srcSize.width(), m_srcSize.height(), m_fps, m_duration, m_totalFrame);
  486. /* 再次判断帧数是否正常,如果没读取到,就使用 总帧数 / 时长 */
  487. if(m_fps == 0)
  488. {
  489. if((m_duration > 0) && (m_totalFrame > 0))
  490. {
  491. m_fps = m_totalFrame / (m_duration / 1000.0);
  492. }
  493. /* 到这一步,无法确定帧数了,就按照25帧来计算了 */
  494. if(m_fps == 0)
  495. {
  496. m_fps = 25;
  497. }
  498. }
  499. }
  500. /**
  501. * @brief 软解码线程,使用CPU解码,使用环境队列存储解码的数据
  502. *
  503. */
  504. void DecodeVedio::threadDecodeUsingCPU()
  505. {
  506. /******** 初始化局部变量 ********/
  507. bool isEnd = false;
  508. int ret = 0;
  509. int retFrame = 0;
  510. int retPacket = 0;
  511. m_pauseDecode = false;
  512. m_decodeStatus = true;
  513. m_decodeState.store(DecodeState::DecodeRun);
  514. /* 开始解码 */
  515. SPDLOG_DEBUG("开始解码...");
  516. while(m_threadRuning)
  517. {
  518. /******** 判断是否在暂停状态 ********/
  519. while(m_pauseDecode)
  520. {
  521. m_decodeState.store(DecodeState::DecodePause);
  522. std::this_thread::sleep_for(std::chrono::microseconds(100));
  523. }
  524. m_decodeState.store(DecodeState::DecodeRun);
  525. /* 刷新解码器缓冲区,清除掉里面残留的解码文件 */
  526. if(m_flushDecoder.load())
  527. {
  528. avcodec_flush_buffers(m_pCodecContext);
  529. m_flushDecoder.store(false);
  530. }
  531. /******** 读取数据包 av_read_frame ********/
  532. int retRead = av_read_frame(m_pFormatContext, m_packet);
  533. if(retRead == AVERROR_EOF)
  534. {
  535. /* 读取到末尾后,需要传入一个空的packet,才能读取到最后几帧 */
  536. avcodec_send_packet(m_pCodecContext, m_packet);
  537. }
  538. else if(retRead < 0)
  539. {
  540. SPDLOG_ERROR("读取帧错误...");
  541. break;
  542. }
  543. else
  544. {
  545. if(m_packet->stream_index == m_videoStream)
  546. {
  547. // SPDLOG_DEBUG("源pts:{}", m_packet->pts);
  548. /* pts 表示显示时间戳(Presentation Timestamp),它指示解码后的帧应该在什么时候显示。pts 是用于同步音视频的关键时间戳。
  549. dts 表示解码时间戳(Decoding Timestamp),它指示解码器应该在什么时候解码这个数据包。dts 用于确保解码器按照正确的顺序解码数据包。 */
  550. #if 1
  551. /* 计算当前帧时间,两种方法,第一种适用于所有场景,但是存在一定的误差 */
  552. m_packet->pts = qRound64(m_packet->pts * (1000 * rationalToDouble(&m_pFormatContext->streams[m_videoStream]->time_base)));
  553. m_packet->dts = qRound64(m_packet->dts * (1000 * rationalToDouble(&m_pFormatContext->streams[m_videoStream]->time_base)));
  554. #else
  555. /* 适用于本地视频,直接计算本地视频文件的每一帧时间 */
  556. m_currentFrame++;
  557. // m_packet->pts = qRound64(m_currentFrame * (qreal(m_duration) / m_totalFrame));
  558. #endif
  559. /* 将数据传给解码器 */
  560. int ret = avcodec_send_packet(m_pCodecContext, m_packet);
  561. if(ret < 0)
  562. {
  563. SPDLOG_ERROR("发送数据包错误...");
  564. av_packet_unref(m_packet);
  565. continue;
  566. }
  567. }else {
  568. // SPDLOG_INFO("不是视频流。");
  569. av_packet_unref(m_packet);
  570. continue;
  571. }
  572. }
  573. // SPDLOG_DEBUG("读取到数据包packet,pts:{}",m_packet->pts);
  574. /* 解码packet包的内容,一个packet包内可能包含好多帧视频 */
  575. while(m_threadRuning)
  576. {
  577. /* 读取出解码器返回的帧 avcodec_receive_frame */
  578. int ret = avcodec_receive_frame(m_pCodecContext, m_pFrameSRC);
  579. if(ret == AVERROR_EOF)
  580. {
  581. SPDLOG_INFO("读取到视频流末尾。");
  582. isEnd = true;
  583. break;
  584. }
  585. else if (ret == AVERROR(EAGAIN))
  586. {
  587. /* packet中的内容无法解码成一帧,需要更多的输入包,可能已经取出了几帧,也肯能就不够一帧
  588. * 这时候就需要往解码器送数据包了 */
  589. // SPDLOG_WARN("packet无法解码成一帧,需要更多的输入包");
  590. av_frame_unref(m_pFrameSRC);
  591. break;
  592. }
  593. else if(ret < 0)
  594. {
  595. av_frame_unref(m_pFrameSRC);
  596. if(retRead < 0)
  597. {
  598. SPDLOG_ERROR("读取错误,错误值:{}",ret);
  599. break;
  600. }
  601. }
  602. /* 解码成功,获取当前时间,现在已经是准的时间了
  603. * 如果在跳转状态,在这里判断是否到了目标位置 */
  604. m_pts = m_pFrameSRC->pts;
  605. if(m_isSeek.load())
  606. {
  607. if(m_pts < m_targetPos)
  608. {
  609. SPDLOG_DEBUG("目标位置:{} 当前位置:{}",m_targetPos, m_pts.load());
  610. av_frame_unref(m_pFrameSRC);
  611. continue;
  612. }else {
  613. m_isSeek = false;
  614. m_targetPos = -1;
  615. SPDLOG_DEBUG("跳转结束");
  616. }
  617. }
  618. // SPDLOG_DEBUG("当前帧的pts:{}", m_pts.load());
  619. /* 转换解码后的帧格式,转换成RGBA格式,Qt可以识别 */
  620. if(m_sws_ctx == nullptr)
  621. {
  622. /* 选择在这里创建,为了兼容硬件解码,硬件解码出来的格式可能和解码器传出的不一样 */
  623. m_sws_ctx = sws_getCachedContext(m_sws_ctx,
  624. m_pFrameSRC->width, m_pFrameSRC->height, /* 原图像大小和格式 */
  625. m_pCodecContext->pix_fmt, /* 输入图像的像素格式 */
  626. m_srcSize.width(), m_srcSize.height(), /* 目标图像的大小 */
  627. AV_PIX_FMT_RGBA, /* 目标图像的格式 */
  628. SWS_BILINEAR, /* 图像缩放算法,双线性 */
  629. nullptr, /* 输入图像的滤波器信息,不需要传NULL */
  630. nullptr, /* 输出图像的滤波器信息,不需要传NULL */
  631. nullptr); /* 特定缩放算法需要的参数,不需要传NULL */
  632. if(m_sws_ctx == nullptr)
  633. {
  634. SPDLOG_ERROR("创建SwsContext错误...");
  635. goto label_ThreadDecodeExit;
  636. }
  637. SPDLOG_INFO("创建SwsContext成功...");
  638. }
  639. /* 转换成RGBA格式 */
  640. // uint8_t* data[1] = { m_buffer };
  641. int lines[4];
  642. /* 使用像素格式pix_fmt和宽度填充图像的平面线条的大小(一行大小?) */
  643. av_image_fill_linesizes(lines, AV_PIX_FMT_RGBA, m_pFrameSRC->width);
  644. sws_scale( m_sws_ctx, /* 缩放的上下文 */
  645. m_pFrameSRC->data, /* 源图像数组 */
  646. m_pFrameSRC->linesize, /* 包含源图像每个平面步幅的数组 */
  647. 0, /* 开始位置 */
  648. m_pFrameSRC->height, /* 行数 */
  649. &m_buffer, /* 目标图像数组 */
  650. lines); /* 目标图像行数 */
  651. if(m_buffer != nullptr)
  652. {
  653. /* 将数据拷贝到QImage中 */
  654. auto image = new QImage(m_buffer, m_srcSize.width(), m_srcSize.height(), QImage::Format_RGBA8888);
  655. /* 如果队列满,线程会阻塞在这里 */
  656. m_queueImage.push(image);
  657. // av_frame_unref(m_pFrameRGB);
  658. // SPDLOG_DEBUG("一帧视频入队");
  659. }
  660. av_frame_unref(m_pFrameSRC);
  661. /* 如果在跳转过程中,直接退出,防止一个packet中有多个视频帧,再次阻塞在上面 */
  662. if(m_isSeek)
  663. {
  664. break;
  665. }
  666. }
  667. av_packet_unref(m_packet); /* 释放数据包,引用计数-1,为0时释放空间 */
  668. if(isEnd)
  669. {
  670. emit signal_playCompleted();
  671. m_decodeState.store(DecodeState::DecodeStop);
  672. /* 读取到结尾,但是不退出解码线程,可能还会使用倒退功能,后退读取 */
  673. while(m_decodeState.load() != DecodeState::DecodeRun)
  674. {
  675. std::this_thread::sleep_for(std::chrono::milliseconds(2));
  676. }
  677. isEnd = false;
  678. }
  679. }
  680. label_ThreadDecodeExit:
  681. /* 释放空间 */
  682. av_packet_free(&m_packet);
  683. m_decodeState.store(DecodeState::DecodeExit);
  684. m_threadRuning = false;
  685. }
  686. /* 退出线程,将所有可能暂停线程运行的条件全部唤醒 */
  687. void DecodeVedio::exitThread()
  688. {
  689. if(m_threadRuning)
  690. {
  691. m_threadRuning = false;
  692. }
  693. /* 设置成运行状态,唤醒可能阻塞在了解码结束的位置 */
  694. m_decodeState.store(DecodeState::DecodeRun);
  695. m_pauseDecode = false;
  696. /* 先退出可能阻塞住的解码线程 */
  697. m_queueImage.exit();
  698. }
  699. /**
  700. * @brief 硬件解码线程,使用GPU解码,使用环境队列存储解码的数据
  701. *
  702. */
  703. void DecodeVedio::threadDecodeUsingGPU()
  704. {
  705. /******** 初始化局部变量 ********/
  706. bool isEnd = false;
  707. int ret = 0;
  708. int retFrame = 0;
  709. int retPacket = 0;
  710. m_pauseDecode = false;
  711. m_decodeStatus = true;
  712. m_decodeState.store(DecodeState::DecodeRun);
  713. /* 开始解码 */
  714. SPDLOG_DEBUG("开始解码...");
  715. while(m_threadRuning)
  716. {
  717. /******** 判断是否在暂停状态 ********/
  718. while(m_pauseDecode)
  719. {
  720. m_decodeState.store(DecodeState::DecodePause);
  721. std::this_thread::sleep_for(std::chrono::microseconds(100));
  722. }
  723. m_decodeState.store(DecodeState::DecodeRun);
  724. /* 刷新解码器缓冲区,清除掉里面残留的解码文件 */
  725. if(m_flushDecoder.load())
  726. {
  727. avcodec_flush_buffers(m_pCodecContext);
  728. m_flushDecoder.store(false);
  729. }
  730. /******** 读取数据包 av_read_frame ********/
  731. int retRead = av_read_frame(m_pFormatContext, m_packet);
  732. if(retRead == AVERROR_EOF)
  733. {
  734. /* 读取到末尾后,需要传入一个空的packet,才能读取到最后几帧 */
  735. avcodec_send_packet(m_pCodecContext, m_packet);
  736. }
  737. else if(retRead < 0)
  738. {
  739. SPDLOG_ERROR("读取帧错误...");
  740. break;
  741. }
  742. else
  743. {
  744. if(m_packet->stream_index == m_videoStream)
  745. {
  746. // SPDLOG_DEBUG("源pts:{}", m_packet->pts);
  747. /* pts 表示显示时间戳(Presentation Timestamp),它指示解码后的帧应该在什么时候显示。pts 是用于同步音视频的关键时间戳。
  748. dts 表示解码时间戳(Decoding Timestamp),它指示解码器应该在什么时候解码这个数据包。dts 用于确保解码器按照正确的顺序解码数据包。 */
  749. #if 1
  750. /* 计算当前帧时间,两种方法,第一种适用于所有场景,但是存在一定的误差 */
  751. m_packet->pts = qRound64(m_packet->pts * (1000 * rationalToDouble(&m_pFormatContext->streams[m_videoStream]->time_base)));
  752. m_packet->dts = qRound64(m_packet->dts * (1000 * rationalToDouble(&m_pFormatContext->streams[m_videoStream]->time_base)));
  753. #else
  754. /* 适用于本地视频,直接计算本地视频文件的每一帧时间 */
  755. m_currentFrame++;
  756. // m_packet->pts = qRound64(m_currentFrame * (qreal(m_duration) / m_totalFrame));
  757. #endif
  758. /* 将数据传给解码器 */
  759. int ret = avcodec_send_packet(m_pCodecContext, m_packet);
  760. if(ret < 0)
  761. {
  762. SPDLOG_ERROR("发送数据包错误...");
  763. av_packet_unref(m_packet);
  764. continue;
  765. }
  766. }else {
  767. // SPDLOG_INFO("不是视频流。");
  768. av_packet_unref(m_packet);
  769. continue;
  770. }
  771. }
  772. // SPDLOG_DEBUG("读取到数据包packet,pts:{}",m_packet->pts);
  773. /* 解码packet包的内容,一个packet包内可能包含好多帧视频 */
  774. while(m_threadRuning)
  775. {
  776. /* 读取出解码器返回的帧 avcodec_receive_frame */
  777. int ret = avcodec_receive_frame(m_pCodecContext, m_pFrameSRC);
  778. if(ret == AVERROR_EOF)
  779. {
  780. SPDLOG_INFO("读取到视频流末尾。");
  781. isEnd = true;
  782. break;
  783. }
  784. else if (ret == AVERROR(EAGAIN))
  785. {
  786. /* packet中的内容无法解码成一帧,需要更多的输入包,可能已经取出了几帧,也肯能就不够一帧
  787. * 这时候就需要往解码器送数据包了 */
  788. // SPDLOG_WARN("packet无法解码成一帧,需要更多的输入包");
  789. av_frame_unref(m_pFrameSRC);
  790. break;
  791. }
  792. else if(ret < 0)
  793. {
  794. av_frame_unref(m_pFrameSRC);
  795. if(retRead < 0)
  796. {
  797. SPDLOG_ERROR("读取错误,错误值:{}",ret);
  798. break;
  799. }
  800. }
  801. /* 硬件解码,上面读取到的帧m_pFrameSRC->data[0] = nullptr
  802. * 数据要从GPU中拷贝出来 */
  803. if(!copyDataFromGPU(m_pFrameHW, m_pFrameSRC))
  804. {
  805. continue;
  806. }
  807. /* 解码成功,获取当前时间,现在已经是准的时间了
  808. * 如果在跳转状态,在这里判断是否到了目标位置 */
  809. m_pts = m_pFrameHW->pts;
  810. if(m_isSeek.load())
  811. {
  812. if(m_pts < m_targetPos)
  813. {
  814. SPDLOG_DEBUG("目标位置:{} 当前位置:{}",m_targetPos, m_pts.load());
  815. av_frame_unref(m_pFrameHW);
  816. continue;
  817. }else {
  818. m_isSeek = false;
  819. m_targetPos = -1;
  820. SPDLOG_INFO("跳转结束。");
  821. }
  822. }
  823. // SPDLOG_DEBUG("当前帧的pts:{}", m_pts.load());
  824. /* 转换解码后的帧格式,转换成RGBA格式,Qt可以识别 */
  825. if(m_sws_ctx == nullptr)
  826. {
  827. /* 选择在这里创建,为了兼容硬件解码,硬件解码出来的格式可能和解码器传出的不一样 */
  828. m_sws_ctx = sws_getCachedContext(m_sws_ctx,
  829. m_pFrameHW->width, m_pFrameHW->height, /* 原图像大小和格式 */
  830. m_pCodecContext->pix_fmt, /* 输入图像的像素格式 */
  831. m_srcSize.width(), m_srcSize.height(), /* 目标图像的大小 */
  832. AV_PIX_FMT_RGBA, /* 目标图像的格式 */
  833. SWS_BILINEAR, /* 图像缩放算法,双线性 */
  834. nullptr, /* 输入图像的滤波器信息,不需要传NULL */
  835. nullptr, /* 输出图像的滤波器信息,不需要传NULL */
  836. nullptr); /* 特定缩放算法需要的参数,不需要传NULL */
  837. if(m_sws_ctx == nullptr)
  838. {
  839. SPDLOG_ERROR("创建SwsContext错误...");
  840. goto label_ThreadDecodeExit;
  841. }
  842. SPDLOG_INFO("创建SwsContext成功...");
  843. }
  844. /* 转换成RGBA格式 */
  845. // uint8_t* data[1] = { m_buffer };
  846. int lines[4];
  847. /* 使用像素格式pix_fmt和宽度填充图像的平面线条的大小(一行大小?) */
  848. av_image_fill_linesizes(lines, AV_PIX_FMT_RGBA, m_pFrameHW->width);
  849. sws_scale( m_sws_ctx, /* 缩放的上下文 */
  850. m_pFrameHW->data, /* 源图像数组 */
  851. m_pFrameHW->linesize, /* 包含源图像每个平面步幅的数组 */
  852. 0, /* 开始位置 */
  853. m_pFrameHW->height, /* 行数 */
  854. &m_buffer, /* 目标图像数组 */
  855. lines); /* 目标图像行数 */
  856. if(m_buffer != nullptr)
  857. {
  858. /* 将数据拷贝到QImage中 */
  859. auto image = new QImage(m_buffer, m_srcSize.width(), m_srcSize.height(), QImage::Format_RGBA8888);
  860. /* 如果队列满,线程会阻塞在这里 */
  861. m_queueImage.push(image);
  862. // av_frame_unref(m_pFrameRGB);
  863. // SPDLOG_DEBUG("一帧视频入队");
  864. }
  865. av_frame_unref(m_pFrameSRC);
  866. /* 如果在跳转过程中,直接退出,防止一个packet中有多个视频帧,再次阻塞在上面 */
  867. if(m_isSeek)
  868. {
  869. break;
  870. }
  871. }
  872. av_packet_unref(m_packet); /* 释放数据包,引用计数-1,为0时释放空间 */
  873. if(isEnd)
  874. {
  875. emit signal_playCompleted();
  876. m_decodeState.store(DecodeState::DecodeStop);
  877. /* 读取到结尾,但是不退出解码线程,可能还会使用倒退功能,后退读取 */
  878. while(m_decodeState.load() != DecodeState::DecodeRun)
  879. {
  880. std::this_thread::sleep_for(std::chrono::milliseconds(2));
  881. }
  882. isEnd = false;
  883. }
  884. }
  885. label_ThreadDecodeExit:
  886. /* 释放空间 */
  887. av_packet_free(&m_packet);
  888. m_decodeState.store(DecodeState::DecodeExit);
  889. m_threadRuning = false;
  890. }
  891. /* 暂停解码,会阻塞到线程暂停为止 */
  892. void DecodeVedio::pauseDecode()
  893. {
  894. if(!m_threadRuning)
  895. {
  896. return;
  897. }
  898. if( (m_decodeState.load() == DecodeState::DecodeExit)
  899. || (m_decodeState.load() == DecodeState::DecodePause) )
  900. {
  901. return;
  902. }
  903. /* 设置成运行状态,唤醒可能阻塞在了解码结束的位置 */
  904. m_decodeState.store(DecodeState::DecodeRun);
  905. m_pauseDecode = true;
  906. /* 队列出队两张图片,防止解码线程阻塞到环形队列满上面 */
  907. QImage* image = nullptr;
  908. m_queueImage.front_pop_NoBlock(image);
  909. if(image != nullptr)
  910. {
  911. delete image;
  912. image = nullptr;
  913. }
  914. m_queueImage.front_pop_NoBlock(image);
  915. if(image != nullptr)
  916. {
  917. delete image;
  918. image = nullptr;
  919. }
  920. /* 等待线程状态变为暂停为止 */
  921. while (m_decodeState.load() != DecodeState::DecodePause)
  922. {
  923. std::this_thread::sleep_for(std::chrono::microseconds(100));
  924. }
  925. }
  926. /* 继续解码 */
  927. void DecodeVedio::continueDecode()
  928. {
  929. m_pauseDecode = false;
  930. m_decodeState.store(DecodeState::DecodeRun);
  931. }
  932. /**
  933. * @brief AVRational存储的是分子和分母,这里相除转换为double,用于计算帧率
  934. * 这个函数就等同于av_q2d()
  935. * @param rational
  936. * @return
  937. */
  938. qreal DecodeVedio::rationalToDouble(AVRational* rational)
  939. {
  940. return ( (rational->den == 0) ? 0 : (qreal(rational->num) / rational->den) );
  941. }
  942. /* 开启解码 */
  943. void DecodeVedio::do_startDecodeVedio()
  944. {
  945. SPDLOG_DEBUG("解码线程ID:{}",QThread::currentThreadId());
  946. // if(!m_initFFmpeg)
  947. // {
  948. // initFFmpeg();
  949. // }
  950. m_threadRuning = true;
  951. m_pauseDecode = false;
  952. /* 进入解码,直到播放完成或者手动退出 */
  953. threadDecodeUsingCPU();
  954. SPDLOG_TRACE("Decode解码结束。");
  955. }
  956. /* 释放所有资源 */
  957. void DecodeVedio::freeAll()
  958. {
  959. if(m_sws_ctx)
  960. {
  961. sws_freeContext(m_sws_ctx);
  962. m_sws_ctx = nullptr;
  963. }
  964. // if(m_pFrameRGB)
  965. // {
  966. // av_frame_free(&m_pFrameRGB);
  967. // }
  968. if(m_pFrameSRC)
  969. {
  970. av_frame_free(&m_pFrameSRC);
  971. }
  972. if(m_pFrameHW)
  973. {
  974. av_frame_free(&m_pFrameHW);
  975. }
  976. if(m_packet)
  977. {
  978. av_packet_free(&m_packet);
  979. }
  980. if(m_pCodecContext)
  981. {
  982. avcodec_free_context(&m_pCodecContext);
  983. }
  984. if(m_pFormatContext)
  985. {
  986. avformat_close_input(&m_pFormatContext);
  987. }
  988. if(m_buffer)
  989. {
  990. av_free(m_buffer);
  991. m_buffer = nullptr;
  992. }
  993. if(m_hw_device_ctx)
  994. {
  995. av_buffer_unref(&m_hw_device_ctx);
  996. }
  997. for(int i = 0; i < m_queueImage.QueueSize(); i++)
  998. {
  999. QImage* image = nullptr;
  1000. if (m_queueImage.front_pop_NoBlock(image))
  1001. {
  1002. delete image;
  1003. }
  1004. }
  1005. m_queueImage.clearQueue();
  1006. }