videocodec.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. #include "videocodec.h"
  2. #include <QDebug>
  3. extern "C" { // 用C规则编译指定的代码
  4. #include "libavcodec/avcodec.h"
  5. #include "libavformat/avformat.h"
  6. #include "libavutil/avutil.h"
  7. #include "libswscale/swscale.h"
  8. #include "libavutil/imgutils.h"
  9. #include "libavdevice/avdevice.h"
  10. }
  11. #define ERROR_LEN 1024 // 异常信息数组长度
  12. #define PRINT_LOG 1
  13. VideoCodec::VideoCodec()
  14. {
  15. }
  16. VideoCodec::~VideoCodec()
  17. {
  18. close();
  19. }
  20. /**
  21. * @brief 初始化打开编码保存文件
  22. * @param codecContext
  23. * @param point
  24. * @param fileName
  25. * @return
  26. */
  27. bool VideoCodec::open(AVCodecContext *codecContext, QPoint point, const QString &fileName)
  28. {
  29. if(!codecContext || fileName.isEmpty()) return false;
  30. // 通过输出文件名为输出格式分配AVFormatContext。参数3编码器设置为空,由参数4文件名后缀推测合适的编码器
  31. int ret = avformat_alloc_output_context2(&m_formatContext, nullptr, nullptr, fileName.toStdString().data());
  32. if(ret < 0)
  33. {
  34. close();
  35. showError(ret);
  36. return false;
  37. }
  38. // 创建并初始化AVIOContext以访问url所指示的资源。
  39. ret = avio_open(&m_formatContext->pb, fileName.toStdString().data(), AVIO_FLAG_WRITE);
  40. if(ret < 0)
  41. {
  42. close();
  43. showError(ret);
  44. return false;
  45. }
  46. // 查询编码器
  47. const AVCodec* codec = avcodec_find_encoder(m_formatContext->oformat->video_codec);
  48. if(!codec)
  49. {
  50. close();
  51. showError(AVERROR(ENOMEM));
  52. return false;
  53. }
  54. qDebug() << codec->id <<" " << codec->name;
  55. // 分配AVCodecContext并将其字段设置为默认值。
  56. m_codecContext = avcodec_alloc_context3(codec);
  57. if(!m_codecContext)
  58. {
  59. close();
  60. showError(AVERROR(ENOMEM));
  61. return false;
  62. }
  63. // 设置编码器上下文参数
  64. m_codecContext->width = codecContext->width; // 图片宽度/高度
  65. m_codecContext->height = codecContext->height;
  66. m_codecContext->pix_fmt = codec->pix_fmts[0]; // 像素格式(这里通过编码器赋值,不需要自己指定)
  67. m_codecContext->time_base = {point.y(), point.x()}; //设置时间基,20为分母,1为分子,表示以1/20秒时间间隔播放一帧图像
  68. m_codecContext->framerate = {point.x(), point.y()};
  69. m_codecContext->bit_rate = 1000000; // 目标的码率,即采样的码率;显然,采样码率越大,视频大小越大,画质越高
  70. m_codecContext->gop_size = 12; // I帧间隔(值越大,视频文件越小,编解码延时越长)
  71. m_codecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
  72. // 打开编码器
  73. ret = avcodec_open2(m_codecContext, nullptr, nullptr);
  74. if(ret < 0)
  75. {
  76. close();
  77. showError(ret);
  78. return false;
  79. }
  80. // 向媒体文件添加新流
  81. m_videoStream = avformat_new_stream(m_formatContext, nullptr);
  82. if(!m_videoStream)
  83. {
  84. close();
  85. showError(AVERROR(ENOMEM));
  86. return false;
  87. }
  88. //拷贝一些参数,给codecpar赋值
  89. ret = avcodec_parameters_from_context(m_videoStream->codecpar,m_codecContext);
  90. if(ret < 0)
  91. {
  92. close();
  93. showError(ret);
  94. return false;
  95. }
  96. // 写入文件头
  97. ret = avformat_write_header(m_formatContext, nullptr);
  98. if(ret < 0)
  99. {
  100. close();
  101. showError(ret);
  102. return false;
  103. }
  104. m_writeHeader = true;
  105. // 分配一个AVPacket
  106. m_packet = av_packet_alloc();
  107. if(!m_packet)
  108. {
  109. close();
  110. showError(AVERROR(ENOMEM));
  111. return false;
  112. }
  113. m_frame = av_frame_alloc();
  114. if(!m_frame)
  115. {
  116. close();
  117. showError(AVERROR(ENOMEM));
  118. return false;
  119. }
  120. m_frame->format = codec->pix_fmts[0];
  121. qDebug() << "开始录制视频!";
  122. return true;
  123. }
  124. /**
  125. * @brief 将图像帧编码写入视频文件
  126. * @param frame
  127. */
  128. void VideoCodec::write(AVFrame *frame)
  129. {
  130. QMutexLocker locker(&m_mutex);
  131. if(!m_packet)
  132. {
  133. return;
  134. }
  135. if(!swsFormat(frame)) // 由于解码的图像格式和编码需要的图像格式不一定相同,所以需要转换一下格式
  136. {
  137. return;
  138. }
  139. if(m_frame)
  140. {
  141. m_frame->pts = m_index; // pts从0开始增加,保存的视频才会时间从0开始增加
  142. m_index++;
  143. }
  144. avcodec_send_frame(m_codecContext, m_frame); // 将图像传入编码器
  145. // 循环读取所有编码完的帧
  146. while (true)
  147. {
  148. // 从编码器中读取图像帧
  149. int ret = avcodec_receive_packet(m_codecContext, m_packet);
  150. if(ret < 0)
  151. {
  152. break;
  153. }
  154. // 将数据包中的有效时间字段(时间戳/持续时间)从一个时基转换为 输出流的时间
  155. av_packet_rescale_ts(m_packet, m_codecContext->time_base, m_videoStream->time_base);
  156. av_write_frame(m_formatContext, m_packet); // 将数据包写入输出媒体文件
  157. av_packet_unref(m_packet);
  158. }
  159. }
  160. void VideoCodec::close()
  161. {
  162. write(nullptr); // 传入空帧,读取所有编码数据
  163. QMutexLocker locker(&m_mutex); // 如果不加锁可能在点击关闭时,write函数正在写入数据,导致崩溃
  164. if(m_formatContext)
  165. {
  166. // 写入文件尾
  167. if(m_writeHeader)
  168. {
  169. m_writeHeader = false;
  170. int ret = av_write_trailer(m_formatContext);
  171. if(ret < 0)
  172. {
  173. showError(ret);
  174. return;
  175. }
  176. }
  177. int ret = avio_close(m_formatContext->pb);
  178. if(ret < 0)
  179. {
  180. showError(ret);
  181. return;
  182. }
  183. avformat_free_context(m_formatContext);
  184. m_formatContext = nullptr;
  185. m_videoStream = nullptr;
  186. }
  187. // 释放编解码器上下文并置空
  188. if(m_codecContext)
  189. {
  190. avcodec_free_context(&m_codecContext);
  191. }
  192. if(m_packet)
  193. {
  194. av_packet_free(&m_packet);
  195. }
  196. // 释放上下文swsContext。
  197. if(m_swsContext)
  198. {
  199. sws_freeContext(m_swsContext);
  200. m_swsContext = nullptr; // sws_freeContext不会把上下文置NULL
  201. }
  202. if(m_frame)
  203. {
  204. av_frame_free(&m_frame);
  205. }
  206. m_index = 0;
  207. }
  208. void VideoCodec::showError(int err)
  209. {
  210. #if PRINT_LOG
  211. static char m_error[ERROR_LEN]; // 保存异常信息
  212. memset(m_error, 0, ERROR_LEN); // 将数组置零
  213. av_strerror(err, m_error, ERROR_LEN);
  214. qWarning() << "VideoSave Error:" << m_error;
  215. #else
  216. Q_UNUSED(err)
  217. #endif
  218. }
  219. /**
  220. * @brief 将解码图像帧的像素格式转换未编码图像帧的像素格式
  221. * @param frame
  222. * @return true:转换成功 false:转换失败
  223. */
  224. bool VideoCodec::swsFormat(AVFrame *frame)
  225. {
  226. if(!frame || frame->width <= 0 || frame->height <= 0)
  227. {
  228. return false;
  229. }
  230. // 为什么图像转换上下文要放在这里初始化呢,是因为m_frame->format,如果使用硬件解码,解码出来的图像格式和m_codecContext->pix_fmt的图像格式不一样,就会导致无法转换为QImage
  231. // 由于解码后的图像格式不一定支持保存裸流,或者不支持直接编码为H264,所以需要转换格式
  232. if(!m_swsContext)
  233. {
  234. // 获取缓存的图像转换上下文。首先校验参数是否一致,如果校验不通过就释放资源;然后判断上下文是否存在,如果存在直接复用,如不存在进行分配、初始化操作
  235. m_swsContext = sws_getCachedContext(m_swsContext,
  236. frame->width, // 输入图像的宽度
  237. frame->height, // 输入图像的高度
  238. (AVPixelFormat)frame->format, // 输入图像的像素格式
  239. frame->width, // 输出图像的宽度
  240. frame->height, // 输出图像的高度
  241. (AVPixelFormat)m_frame->format, // 输出图像的像素格式
  242. SWS_BILINEAR, // 选择缩放算法(只有当输入输出图像大小不同时有效),一般选择SWS_FAST_BILINEAR
  243. nullptr, // 输入图像的滤波器信息, 若不需要传NULL
  244. nullptr, // 输出图像的滤波器信息, 若不需要传NULL
  245. nullptr); // 特定缩放算法需要的参数(?),默认为NULL
  246. if(!m_swsContext)
  247. {
  248. #if PRINT_LOG
  249. qWarning() << "sws_getCachedContext() Error!";
  250. #endif
  251. av_frame_unref(frame);
  252. return false;
  253. }
  254. if(m_frame)
  255. {
  256. // 创建一个图像帧用于保存YUV420P图像
  257. m_frame->width = frame->width;
  258. m_frame->height = frame->height;
  259. av_frame_get_buffer(m_frame, 3 * 8);
  260. }
  261. }
  262. if(m_frame->width <= 0 || m_frame->height <= 0) // 如果m_frame没有分配空间则返回
  263. {
  264. return false;
  265. }
  266. // 开始转换格式
  267. bool ret = sws_scale(m_swsContext, // 缩放上下文
  268. frame->data, // 原图像数组
  269. frame->linesize, // 包含源图像每个平面步幅的数组
  270. 0, // 开始位置
  271. frame->height, // 行数
  272. m_frame->data, // 目标图像数组
  273. m_frame->linesize); // 包含目标图像每个平面的步幅的数组
  274. av_frame_unref(frame);
  275. return ret;
  276. }