videodecode.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. #include "videodecode.h"
  2. #include <QDebug>
  3. #include <QImage>
  4. #include <QMutex>
  5. #include <qdatetime.h>
  6. extern "C" { // 用C规则编译指定的代码
  7. #include "libavcodec/avcodec.h"
  8. #include "libavformat/avformat.h"
  9. #include "libavutil/avutil.h"
  10. #include "libswscale/swscale.h"
  11. #include "libavutil/imgutils.h"
  12. #include "libavdevice/avdevice.h" // 调用输入设备需要的头文件
  13. }
  14. #define ERROR_LEN 1024 // 异常信息数组长度
  15. #define PRINT_LOG 1
  16. VideoDecode::VideoDecode()
  17. {
  18. initFFmpeg();
  19. m_error = new char[ERROR_LEN];
  20. /**
  21. * dshow: Windows 媒体输入设备。目前仅支持音频和视频设备。
  22. * gdigrab:基于 Win32 GDI 的屏幕捕获设备
  23. * video4linux2:Linux输入视频设备
  24. * x11grab:x11屏幕捕获设备
  25. */
  26. #if defined(Q_OS_WIN)
  27. m_inputFormat = av_find_input_format("gdigrab"); // Windows下如果没有则不能打开设备
  28. #elif defined(Q_OS_LINUX)
  29. m_inputFormat = av_find_input_format("x11grab");
  30. #elif defined(Q_OS_MAC)
  31. // m_inputFormat = av_find_input_format("avfoundation");
  32. #endif
  33. if(!m_inputFormat)
  34. {
  35. qWarning() << "查询AVInputFormat失败!";
  36. }
  37. }
  38. VideoDecode::~VideoDecode()
  39. {
  40. close();
  41. }
  42. /**
  43. * @brief 初始化ffmpeg库(整个程序中只需加载一次)
  44. * 旧版本的ffmpeg需要注册各种文件格式、解复用器、对网络库进行全局初始化。
  45. * 在新版本的ffmpeg中纷纷弃用了,不需要注册了
  46. */
  47. void VideoDecode::initFFmpeg()
  48. {
  49. static bool isFirst = true;
  50. static QMutex mutex;
  51. QMutexLocker locker(&mutex);
  52. if(isFirst)
  53. {
  54. // av_register_all(); // 已经从源码中删除
  55. /**
  56. * 初始化网络库,用于打开网络流媒体,此函数仅用于解决旧GnuTLS或OpenSSL库的线程安全问题。
  57. * 一旦删除对旧GnuTLS和OpenSSL库的支持,此函数将被弃用,并且此函数不再有任何用途。
  58. */
  59. avformat_network_init();
  60. // 初始化libavdevice并注册所有输入和输出设备。
  61. avdevice_register_all();
  62. isFirst = false;
  63. }
  64. }
  65. /**
  66. * @brief 打开媒体文件,或者流媒体,例如rtmp、strp、http
  67. * @param url 视频地址
  68. * @return true:成功 false:失败
  69. */
  70. bool VideoDecode::open(const QString &url)
  71. {
  72. if(url.isNull()) return false;
  73. AVDictionary* dict = nullptr;
  74. // 所有参数:https://ffmpeg.org/ffmpeg-devices.html
  75. av_dict_set(&dict, "framerate", "20", 0); // 设置帧率,默认的是30000/1001,但是实际可能达不到30的帧率,所以最好手动设置
  76. av_dict_set(&dict, "draw_mouse", "1", 0); // 指定是否绘制鼠标指针。0:不包含鼠标,1:包含鼠标
  77. av_dict_set(&dict, "video_size", "2560x1440", 0); // 录制视频的大小(宽高),默认为全屏
  78. #if defined(Q_OS_WIN)
  79. // av_dict_set(&dict, "offset_x", "100", 0); // 录制视频的起点X坐标
  80. // av_dict_set(&dict, "offset_y", "500", 0); // 录制视频的起点Y坐标
  81. #elif defined(Q_OS_LINUX)
  82. // av_dict_set(&dict, "select_region", "1", 0); // 1:指定是否使用指针以图形方式选择抓取区域 0:不使用
  83. // 当video_size设置,并且video_size加上grab_x、grab_y后不超出桌面区域时,可以通过grab_x、grab_y设置录屏的起始坐标,如果超出桌面区域则会设置失败
  84. // av_dict_set(&dict, "grab_x", "300", 0); // 录制视频的起点X坐标
  85. // av_dict_set(&dict, "grab_y", "500", 0); // 录制视频的起点Y坐标
  86. #endif
  87. // 打开输入流并返回解封装上下文
  88. int ret = avformat_open_input(&m_formatContext, // 返回解封装上下文
  89. url.toStdString().data(), // 打开视频地址
  90. m_inputFormat, // 如果非null,此参数强制使用特定的输入格式。自动选择解封装器(文件格式)
  91. &dict); // 参数设置
  92. // 释放参数字典
  93. if(dict)
  94. {
  95. av_dict_free(&dict);
  96. }
  97. // 打开视频失败
  98. if(ret < 0)
  99. {
  100. showError(ret);
  101. free();
  102. return false;
  103. }
  104. // 读取媒体文件的数据包以获取流信息。
  105. ret = avformat_find_stream_info(m_formatContext, nullptr);
  106. if(ret < 0)
  107. {
  108. showError(ret);
  109. free();
  110. return false;
  111. }
  112. m_totalTime = m_formatContext->duration / (AV_TIME_BASE / 1000); // 计算视频总时长(毫秒)
  113. #if PRINT_LOG
  114. qDebug() << QString("视频总时长:%1 ms,[%2]").arg(m_totalTime).arg(QTime::fromMSecsSinceStartOfDay(int(m_totalTime)).toString("HH:mm:ss zzz"));
  115. #endif
  116. // 通过AVMediaType枚举查询视频流ID(也可以通过遍历查找),最后一个参数无用
  117. m_videoIndex = av_find_best_stream(m_formatContext, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
  118. if(m_videoIndex < 0)
  119. {
  120. showError(m_videoIndex);
  121. free();
  122. return false;
  123. }
  124. AVStream* videoStream = m_formatContext->streams[m_videoIndex]; // 通过查询到的索引获取视频流
  125. // 获取视频图像分辨率(AVStream中的AVCodecContext在新版本中弃用,改为使用AVCodecParameters)
  126. m_size.setWidth(videoStream->codecpar->width);
  127. m_size.setHeight(videoStream->codecpar->height);
  128. m_frameRate = rationalToDouble(&videoStream->avg_frame_rate); // 视频帧率
  129. m_avgFrameRate.setX(videoStream->avg_frame_rate.num);
  130. m_avgFrameRate.setY(videoStream->avg_frame_rate.den);
  131. // 通过解码器ID获取视频解码器(新版本返回值必须使用const)
  132. const AVCodec* codec = avcodec_find_decoder(videoStream->codecpar->codec_id);
  133. m_totalFrames = videoStream->nb_frames;
  134. #if PRINT_LOG
  135. qDebug() << QString("分辨率:[w:%1,h:%2] 帧率:%3 总帧数:%4 解码器:%5")
  136. .arg(m_size.width()).arg(m_size.height()).arg(m_frameRate).arg(m_totalFrames).arg(codec->name);
  137. #endif
  138. // 分配AVCodecContext并将其字段设置为默认值。
  139. m_codecContext = avcodec_alloc_context3(codec);
  140. if(!m_codecContext)
  141. {
  142. #if PRINT_LOG
  143. qWarning() << "创建视频解码器上下文失败!";
  144. #endif
  145. free();
  146. return false;
  147. }
  148. // 使用视频流的codecpar为解码器上下文赋值
  149. ret = avcodec_parameters_to_context(m_codecContext, videoStream->codecpar);
  150. if(ret < 0)
  151. {
  152. showError(ret);
  153. free();
  154. return false;
  155. }
  156. m_codecContext->flags2 |= AV_CODEC_FLAG2_FAST; // 允许不符合规范的加速技巧。
  157. m_codecContext->thread_count = 8; // 使用8线程解码
  158. // 初始化解码器上下文,如果之前avcodec_alloc_context3传入了解码器,这里设置NULL就可以
  159. ret = avcodec_open2(m_codecContext, nullptr, nullptr);
  160. if(ret < 0)
  161. {
  162. showError(ret);
  163. free();
  164. return false;
  165. }
  166. // 分配AVPacket并将其字段设置为默认值。
  167. m_packet = av_packet_alloc();
  168. if(!m_packet)
  169. {
  170. #if PRINT_LOG
  171. qWarning() << "av_packet_alloc() Error!";
  172. #endif
  173. free();
  174. return false;
  175. }
  176. // 分配AVFrame并将其字段设置为默认值。
  177. m_frame = av_frame_alloc();
  178. if(!m_frame)
  179. {
  180. #if PRINT_LOG
  181. qWarning() << "av_frame_alloc() Error!";
  182. #endif
  183. free();
  184. return false;
  185. }
  186. m_end = false;
  187. return true;
  188. }
  189. /**
  190. * @brief 读取图像并将图像转换为YUV420P格式
  191. * @return
  192. */
  193. AVFrame* VideoDecode::read()
  194. {
  195. // 如果没有打开则返回
  196. if(!m_formatContext)
  197. {
  198. return nullptr;
  199. }
  200. // 读取下一帧数据
  201. int readRet = av_read_frame(m_formatContext, m_packet);
  202. if(readRet < 0)
  203. {
  204. avcodec_send_packet(m_codecContext, m_packet); // 读取完成后向解码器中传如空AVPacket,否则无法读取出最后几帧
  205. }
  206. else
  207. {
  208. if(m_packet->stream_index == m_videoIndex) // 如果是图像数据则进行解码
  209. {
  210. // 将读取到的原始数据包传入解码器
  211. int ret = avcodec_send_packet(m_codecContext, m_packet);
  212. if(ret < 0)
  213. {
  214. showError(ret);
  215. }
  216. }
  217. }
  218. av_packet_unref(m_packet); // 释放数据包,引用计数-1,为0时释放空间
  219. av_frame_unref(m_frame);
  220. int ret = avcodec_receive_frame(m_codecContext, m_frame);
  221. if(ret < 0)
  222. {
  223. av_frame_unref(m_frame);
  224. if(readRet < 0)
  225. {
  226. m_end = true; // 当无法读取到AVPacket并且解码器中也没有数据时表示读取完成
  227. }
  228. return nullptr;
  229. }
  230. return m_frame;
  231. }
  232. /**
  233. * @brief 关闭视频播放并释放内存
  234. */
  235. void VideoDecode::close()
  236. {
  237. clear();
  238. free();
  239. m_totalTime = 0;
  240. m_videoIndex = 0;
  241. m_totalFrames = 0;
  242. m_obtainFrames = 0;
  243. m_frameRate = 0;
  244. m_size = QSize(0, 0);
  245. }
  246. /**
  247. * @brief 视频是否读取完成
  248. * @return
  249. */
  250. bool VideoDecode::isEnd()
  251. {
  252. return m_end;
  253. }
  254. /**
  255. * @brief 显示ffmpeg函数调用异常信息
  256. * @param err
  257. */
  258. void VideoDecode::showError(int err)
  259. {
  260. #if PRINT_LOG
  261. memset(m_error, 0, ERROR_LEN); // 将数组置零
  262. av_strerror(err, m_error, ERROR_LEN);
  263. qWarning() << "DecodeVideo Error:" << m_error;
  264. #else
  265. Q_UNUSED(err)
  266. #endif
  267. }
  268. /**
  269. * @brief 将AVRational转换为double,用于计算帧率
  270. * @param rational
  271. * @return
  272. */
  273. qreal VideoDecode::rationalToDouble(AVRational* rational)
  274. {
  275. qreal frameRate = (rational->den == 0) ? 0 : (qreal(rational->num) / rational->den);
  276. return frameRate;
  277. }
  278. /**
  279. * @brief 清空读取缓冲
  280. */
  281. void VideoDecode::clear()
  282. {
  283. // 因为avformat_flush不会刷新AVIOContext (s->pb)。如果有必要,在调用此函数之前调用avio_flush(s->pb)。
  284. if(m_formatContext && m_formatContext->pb)
  285. {
  286. avio_flush(m_formatContext->pb);
  287. }
  288. if(m_formatContext)
  289. {
  290. avformat_flush(m_formatContext); // 清理读取缓冲
  291. }
  292. }
  293. void VideoDecode::free()
  294. {
  295. // 释放编解码器上下文和与之相关的所有内容,并将NULL写入提供的指针
  296. if(m_codecContext)
  297. {
  298. avcodec_free_context(&m_codecContext);
  299. }
  300. // 关闭并失败m_formatContext,并将指针置为null
  301. if(m_formatContext)
  302. {
  303. avformat_close_input(&m_formatContext);
  304. }
  305. if(m_packet)
  306. {
  307. av_packet_free(&m_packet);
  308. }
  309. if(m_frame)
  310. {
  311. av_frame_free(&m_frame);
  312. }
  313. }