yuque_diagram.jpg

  • 打开输入流设备
  • 打开编码器
  • 采集并准备数据 AVFrame
  • 转换YUYV422到YUV420P(NV12转YUV420P)
  • H264编码

    打开编码器

  • 使用哪个编码器 libx264

  • GOP设置,码率设置,宽高设置

    1. /**
    2. * @brief
    3. * @param[in]
    4. * @param[in]
    5. * @param[out]
    6. */
    7. static int open_encoder(int width, int height, AVCodecContext** enc_ctx)
    8. {
    9. int ret = 0;
    10. AVCodec* codec = NULL;
    11. codec = avcodec_find_encoder_by_name("libx264");
    12. if (!codec)
    13. {
    14. printf("Codec libx264 not found\n");
    15. exit(1);
    16. }
    17. *enc_ctx = avcodec_alloc_context3(codec);
    18. if (!enc_ctx)
    19. {
    20. printf("Counld out allocate video codec context\n\n");
    21. exit(1);
    22. }
    23. //SPS
    24. (*enc_ctx)->profile = FF_PROFILE_H264_HIGH_444;
    25. (*enc_ctx)->level = 50; //表示LEVELE是5.0
    26. //设置分辨率
    27. (*enc_ctx)->width = width;
    28. (*enc_ctx)->height = height;
    29. //GOP
    30. (*enc_ctx)->gop_size = 250;
    31. //图像有很多变化的时候可以自动插入一个I帧,即最小的gop size
    32. (*enc_ctx)->keyint_min = 25; //option 可选
    33. //设置B帧的数量
    34. (*enc_ctx)->max_b_frames = 3;//option 可选
    35. (*enc_ctx)->has_b_frames = 1;//option 可选
    36. //设置参考帧的数量
    37. (*enc_ctx)->refcounted_frames = 3; //option 可选
    38. //输入的YUV格式
    39. (*enc_ctx)->pix_fmt = AV_PIX_FMT_YUV420P;
    40. //设置码率
    41. (*enc_ctx)->bit_rate = 600000; //600kbps
    42. //设置帧率
    43. AVRational time_base_t = { 1, 25 };
    44. AVRational framerate_t = { 25, 1 };
    45. (*enc_ctx)->time_base = time_base_t; //帧与帧之间的间隔是time_base
    46. (*enc_ctx)->framerate = framerate_t; //帧率, 每秒25帧
    47. ret = avcodec_open2((*enc_ctx), codec, NULL);
    48. if (ret < 0)
    49. {
    50. //printf("Counld not open codec: %s\n", av_err2str(ret));
    51. printf("Counld not open codec: %d\n", ret);
    52. exit(1);
    53. }
    54. }

    采集并准备数据 AVFrame

    获取到的av package是个输出,需要把av package数据导入av frame中

    1. /**
    2. * @brief xxxx
    3. * @param[in] width
    4. * @param[in] height
    5. * @return AVFrame*
    6. */
    7. static AVFrame* create_frame(int width, int height, AVPixelFormat pix_fmt)
    8. {
    9. int ret = 0;
    10. AVFrame* frame = NULL;
    11. frame = av_frame_alloc();
    12. if (!frame)
    13. {
    14. printf("Error, No Memory!\n");
    15. goto __ERROR;
    16. }
    17. //设置参数
    18. frame->width = width;
    19. frame->height = height;
    20. frame->format = pix_fmt;
    21. //alloc inner memory
    22. ret = av_frame_get_buffer(frame, 32); //按32位对齐 【视频必须是32位对齐】
    23. if (ret < 0)
    24. {
    25. printf("Error, Failed to alloc buffer for frame!\n");
    26. goto __ERROR;
    27. }
    28. return frame;
    29. __ERROR:
    30. if (frame)
    31. {
    32. av_frame_free(&frame);
    33. }
    34. return NULL;
    35. }

    转换YUYV422到YUV420P(NV12转YUV420P)

  • 自己编写代码转换(NV12 —-> YUV420p)

    1. //YYYYYYYYUVUV NV12
    2. //YYYYYYYYUUVV YUV420P
    3. //Y
    4. memcpy(frame->data[0], pkt->data, V_WIDTH * V_HEIGHT);
    5. //V_WIDTH * V_HEIGHT之后是UV
    6. for (i = 0; i < V_WIDTH * V_HEIGHT / 4; i++)
    7. {
    8. frame->data[1][i] = pkt->data[V_WIDTH * V_HEIGHT + i*2];
    9. frame->data[2][i] = pkt->data[V_WIDTH * V_HEIGHT + i*2 + 1];
    10. }
    11. fwrite(frame->data[0], 1, V_WIDTH * V_HEIGHT, yuvoutfile);
    12. fwrite(frame->data[1], 1, V_WIDTH * V_HEIGHT/4, yuvoutfile);
    13. fwrite(frame->data[2], 1, V_WIDTH * V_HEIGHT/4, yuvoutfile);
  • 使用sws_scale函数转换

    1. //sws_scale函数转换 yuyv422 -> yuv420p
    2. //avpicture_fill((AVPicture*)frame_yuyv422, (unsigned char*)pkt->data, (AVPixelFormat)in_pix_fmt, V_WIDTH, V_HEIGHT); //该函数ffmpeg弃用了
    3. av_image_fill_arrays(frame_yuyv422->data, frame_yuyv422->linesize, pkt->data, in_pix_fmt, V_WIDTH, V_HEIGHT, 4);
    4. //yuyv422 -> yuv420p
    5. sws_scale(img_convert_ctx, frame_yuyv422->data, frame_yuyv422->linesize,
    6. 0, V_HEIGHT, frame_yuv420->data, frame_yuv420->linesize);
    7. fwrite(frame_yuv420->data[0], 1, V_WIDTH * V_HEIGHT, yuvoutfile);
    8. fwrite(frame_yuv420->data[1], 1, V_WIDTH * V_HEIGHT / 4, yuvoutfile);
    9. fwrite(frame_yuv420->data[2], 1, V_WIDTH * V_HEIGHT / 4, yuvoutfile);

    H264编码

  • avcodec_send_frame(enc_ctx, frame_yuv420);

  • avcodec_receive_packet(enc_ctx, newpkt); ```cpp while ((ret = av_read_frame(fmt_ctx, pkt)) == 0) {

    if 1 //sws_scale函数转换 yuyv422 -> yuv420p

    1. //avpicture_fill((AVPicture*)frame_yuyv422, (unsigned char*)pkt->data, (AVPixelFormat)in_pix_fmt, V_WIDTH, V_HEIGHT);
    2. av_image_fill_arrays(frame_yuyv422->data, frame_yuyv422->linesize, pkt->data, in_pix_fmt, V_WIDTH, V_HEIGHT, 4);
    3. //yuyv422 -> yuv420p
    4. sws_scale(img_convert_ctx, frame_yuyv422->data, frame_yuyv422->linesize,
    5. 0, V_HEIGHT, frame_yuv420->data, frame_yuv420->linesize);
    6. fwrite(frame_yuv420->data[0], 1, V_WIDTH * V_HEIGHT, yuv_outfile);
    7. fwrite(frame_yuv420->data[1], 1, V_WIDTH * V_HEIGHT / 4, yuv_outfile);
    8. fwrite(frame_yuv420->data[2], 1, V_WIDTH * V_HEIGHT / 4, yuv_outfile);

else //代码处理将pkt->data里的数据按yuv420p的方式存储

  1. //YYYYYYYYUVUV NV12
  2. //YYYYYYYYUUVV YUV420P
  3. //Y
  4. memcpy(frame->data[0], pkt->data, V_WIDTH * V_HEIGHT);
  5. //V_WIDTH * V_HEIGHT之后是UV
  6. for (i = 0; i < V_WIDTH * V_HEIGHT / 4; i++)
  7. {
  8. frame->data[1][i] = pkt->data[V_WIDTH * V_HEIGHT + i*2];
  9. frame->data[2][i] = pkt->data[V_WIDTH * V_HEIGHT + i*2 + 1];
  10. }
  11. fwrite(frame->data[0], 1, V_WIDTH * V_HEIGHT, yuvoutfile);
  12. fwrite(frame->data[1], 1, V_WIDTH * V_HEIGHT/4, yuvoutfile);
  13. fwrite(frame->data[2], 1, V_WIDTH * V_HEIGHT/4, yuvoutfile);

endif

  1. frame_yuv420->pts = base++; //告知编码器输入帧与帧之间的关系,否则编出来的数据会花屏
  2. encode(enc_ctx, frame_yuv420, newpkt, h264_outfile);
  3. }
  4. encode(enc_ctx, NULL, newpkt, h264_outfile);//传入NULL的frame,告诉编码器数据结束了,
  5. //让编码器输出所有的数据,防止丢帧。
  1. <a name="JrrL5"></a>
  2. # 完整代码
  3. ```cpp
  4. #include <iostream>
  5. using namespace std;
  6. extern "C"
  7. {
  8. #include "libavcodec/avcodec.h"
  9. #include "libavutil/avutil.h"
  10. #include "libavutil/opt.h"
  11. #include "libavutil/channel_layout.h"
  12. #include "libavdevice/avdevice.h"
  13. #include "libswscale/swscale.h"
  14. #include "libswresample/swresample.h"
  15. #include "libavutil/samplefmt.h"
  16. #include "libavutil/error.h"
  17. #include "libavutil/imgutils.h"
  18. }
  19. #define V_WIDTH 640
  20. #define V_HEIGHT 480
  21. /**
  22. * @brief
  23. * @param[in]
  24. * @param[in]
  25. * @param[out]
  26. */
  27. static int open_encoder(int width, int height, AVCodecContext** enc_ctx)
  28. {
  29. int ret = 0;
  30. AVCodec* codec = NULL;
  31. codec = avcodec_find_encoder_by_name("libx264");
  32. if (!codec)
  33. {
  34. printf("Codec libx264 not found\n");
  35. exit(1);
  36. }
  37. *enc_ctx = avcodec_alloc_context3(codec);
  38. if (!enc_ctx)
  39. {
  40. printf("Counld out allocate video codec context\n\n");
  41. exit(1);
  42. }
  43. //SPS
  44. (*enc_ctx)->profile = FF_PROFILE_H264_HIGH_444;
  45. (*enc_ctx)->level = 50; //表示LEVELE是5.0
  46. //设置分辨率
  47. (*enc_ctx)->width = width;
  48. (*enc_ctx)->height = height;
  49. //GOP
  50. (*enc_ctx)->gop_size = 250;
  51. //图像有很多变化的时候可以自动插入一个I帧,即最小的gop size
  52. (*enc_ctx)->keyint_min = 25; //option 可选
  53. //设置B帧的数量
  54. (*enc_ctx)->max_b_frames = 3;//option 可选
  55. (*enc_ctx)->has_b_frames = 1;//option 可选
  56. //设置参考帧的数量
  57. (*enc_ctx)->refcounted_frames = 3; //option 可选
  58. //输入的YUV格式
  59. (*enc_ctx)->pix_fmt = AV_PIX_FMT_YUV420P;
  60. //设置码率
  61. (*enc_ctx)->bit_rate = 400000; //600kbps
  62. //设置帧率
  63. AVRational time_base_t = { 1, 15 };
  64. AVRational framerate_t = { 15, 1 };
  65. (*enc_ctx)->time_base = time_base_t; //帧与帧之间的间隔是time_base
  66. (*enc_ctx)->framerate = framerate_t; //帧率, 每秒30帧
  67. ret = avcodec_open2((*enc_ctx), codec, NULL);
  68. if (ret < 0)
  69. {
  70. //printf("Counld not open codec: %s\n", av_err2str(ret));
  71. printf("Counld not open codec: %d\n", ret);
  72. exit(1);
  73. }
  74. }
  75. /**
  76. * @brief xxxx
  77. * @param[in] width
  78. * @param[in] height
  79. * @return AVFrame*
  80. */
  81. static AVFrame* create_frame(int width, int height, AVPixelFormat pix_fmt)
  82. {
  83. int ret = 0;
  84. AVFrame* frame = NULL;
  85. frame = av_frame_alloc();
  86. if (!frame)
  87. {
  88. printf("Error, No Memory!\n");
  89. goto __ERROR;
  90. }
  91. //设置参数
  92. frame->width = width;
  93. frame->height = height;
  94. frame->format = pix_fmt;
  95. //alloc inner memory
  96. ret = av_frame_get_buffer(frame, 32); //按32位对齐 【视频必须是32位对齐】
  97. if (ret < 0)
  98. {
  99. printf("Error, Failed to alloc buffer for frame!\n");
  100. goto __ERROR;
  101. }
  102. return frame;
  103. __ERROR:
  104. if (frame)
  105. {
  106. av_frame_free(&frame);
  107. }
  108. return NULL;
  109. }
  110. void encode(AVCodecContext* enc_ctx, AVFrame* frame, AVPacket* newpkt, FILE* outfile)
  111. {
  112. if (frame)
  113. printf("send frame to encoder, pts = %lld\n", frame->pts);
  114. //把AVFrame传给编码器,送原始数据给编码器进行编码
  115. int ret = avcodec_send_frame(enc_ctx, frame);
  116. if (ret < 0)
  117. {
  118. }
  119. //从编码器获取编码好的数据
  120. //从编码器获取编码是连续输出的,有可能喂frame数据给编码器的是编码器并不输出
  121. //有可能一次输出好几个编码好的数据。
  122. while (ret >= 0)
  123. {
  124. ret = avcodec_receive_packet(enc_ctx, newpkt);
  125. //编码器数据不足时,返回EAGAIN,或者到数据结尾时返回AVERROR_EOF
  126. if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
  127. return;
  128. if (ret < 0) {
  129. printf("error, failed to encode.\n");
  130. exit(1);
  131. }
  132. //outfile输出h264的编码
  133. fwrite(newpkt->data, 1, newpkt->size, outfile);
  134. av_packet_unref(newpkt);//减少newpkt的引用计数
  135. }
  136. }
  137. int main()
  138. {
  139. int base = 0;
  140. int ret = 0;
  141. AVPacket* pkt = NULL;
  142. AVInputFormat* in_format = NULL;
  143. AVFormatContext* fmt_ctx = NULL;
  144. AVDictionary* options = NULL;
  145. AVFrame* frame_yuyv422 = NULL;
  146. AVFrame* frame_yuv420 = NULL;
  147. FILE* out_file = NULL;
  148. char errors[1024] = { 0 };
  149. char device_name[256] = "video=Integrated Webcam";
  150. //图像转换参数
  151. struct SwsContext* img_convert_ctx = NULL;
  152. AVPixelFormat in_pix_fmt = AV_PIX_FMT_YUYV422;
  153. AVPixelFormat out_pix_fmt = AV_PIX_FMT_YUV420P;
  154. static int sws_flags = SWS_BICUBIC; //差值算法,双三次
  155. //create file
  156. const char* yuvout = "video.yuv"; //yuv420p
  157. const char* h264out = "out.h264"; //h264
  158. AVCodecContext* enc_ctx = NULL;
  159. FILE* yuv_outfile = fopen(yuvout, "wb+");
  160. FILE* h264_outfile = fopen(h264out, "wb+");
  161. av_register_all();
  162. avdevice_register_all();
  163. //设置采集
  164. in_format = av_find_input_format("dshow");
  165. if (in_format == NULL)
  166. {
  167. printf("av_find_input_format error\n");
  168. }
  169. av_dict_set(&options, "video_size", "640*480", 0);
  170. av_dict_set(&options, "framerate", "30", 0);
  171. av_dict_set(&options, "pixel_format", "yuyv422", 0);
  172. if ((ret = avformat_open_input(&fmt_ctx, device_name, in_format, &options)) != 0)
  173. {
  174. av_strerror(ret, errors, 1024);
  175. printf("Failed to open video device, [%s][%d]\n", errors, ret);
  176. return -1;
  177. }
  178. pkt = av_packet_alloc();
  179. av_init_packet(pkt);
  180. av_dump_format(fmt_ctx, 0, device_name, 0);
  181. //打开编码器
  182. open_encoder(V_WIDTH, V_HEIGHT, &enc_ctx);
  183. //创建avframe
  184. frame_yuyv422 = create_frame(V_WIDTH, V_HEIGHT, in_pix_fmt);
  185. frame_yuv420 = create_frame(V_WIDTH, V_HEIGHT, out_pix_fmt);
  186. //设置转换context
  187. if (img_convert_ctx == NULL)
  188. {
  189. img_convert_ctx = sws_getContext(V_WIDTH, V_HEIGHT,
  190. (AVPixelFormat)in_pix_fmt,
  191. V_WIDTH,
  192. V_HEIGHT,
  193. (AVPixelFormat)out_pix_fmt,
  194. sws_flags, NULL, NULL, NULL);
  195. if (img_convert_ctx == NULL)
  196. {
  197. fprintf(stderr, "Cannot initialize the conversion context\n");
  198. return -1;
  199. }
  200. }
  201. //创建编码输出的packet
  202. AVPacket* newpkt = av_packet_alloc();
  203. if (!newpkt)
  204. {
  205. printf("Error, Failed to alloc avpacket!\n");
  206. goto __END;
  207. }
  208. while ((ret = av_read_frame(fmt_ctx, pkt)) == 0)
  209. {
  210. #if 1 //sws_scale函数转换 yuyv422 -> yuv420p
  211. //avpicture_fill((AVPicture*)frame_yuyv422, (unsigned char*)pkt->data, (AVPixelFormat)in_pix_fmt, V_WIDTH, V_HEIGHT);
  212. av_image_fill_arrays(frame_yuyv422->data, frame_yuyv422->linesize, pkt->data, in_pix_fmt, V_WIDTH, V_HEIGHT, 4);
  213. //yuyv422 -> yuv420p
  214. sws_scale(img_convert_ctx, frame_yuyv422->data, frame_yuyv422->linesize,
  215. 0, V_HEIGHT, frame_yuv420->data, frame_yuv420->linesize);
  216. fwrite(frame_yuv420->data[0], 1, V_WIDTH * V_HEIGHT, yuv_outfile);
  217. fwrite(frame_yuv420->data[1], 1, V_WIDTH * V_HEIGHT / 4, yuv_outfile);
  218. fwrite(frame_yuv420->data[2], 1, V_WIDTH * V_HEIGHT / 4, yuv_outfile);
  219. #else //代码处理将pkt->data里的数据按yuv420p的方式存储
  220. //YYYYYYYYUVUV NV12
  221. //YYYYYYYYUUVV YUV420P
  222. //Y
  223. memcpy(frame->data[0], pkt->data, V_WIDTH * V_HEIGHT);
  224. //V_WIDTH * V_HEIGHT之后是UV
  225. for (i = 0; i < V_WIDTH * V_HEIGHT / 4; i++)
  226. {
  227. frame->data[1][i] = pkt->data[V_WIDTH * V_HEIGHT + i*2];
  228. frame->data[2][i] = pkt->data[V_WIDTH * V_HEIGHT + i*2 + 1];
  229. }
  230. fwrite(frame->data[0], 1, V_WIDTH * V_HEIGHT, yuvoutfile);
  231. fwrite(frame->data[1], 1, V_WIDTH * V_HEIGHT/4, yuvoutfile);
  232. fwrite(frame->data[2], 1, V_WIDTH * V_HEIGHT/4, yuvoutfile);
  233. #endif
  234. frame_yuv420->pts = base++; //告知编码器输入帧与帧之间的关系,否则编出来的数据会花屏
  235. encode(enc_ctx, frame_yuv420, newpkt, h264_outfile);
  236. }
  237. encode(enc_ctx, NULL, newpkt, h264_outfile);//传入NULL的frame,告诉编码器数据结束了,
  238. //让编码器输出所有的数据,防止丢帧。
  239. __END:
  240. if (frame_yuyv422)
  241. {
  242. av_frame_free(&frame_yuyv422);
  243. frame_yuyv422 = NULL;
  244. }
  245. if (frame_yuv420)
  246. {
  247. av_frame_free(&frame_yuv420);
  248. frame_yuv420 = NULL;
  249. }
  250. av_packet_free(&pkt);
  251. cout << "Hello World!\n";
  252. }