当前位置:   article > 正文

12 - FFmpeg 编码 H264

12 - FFmpeg 编码 H264

---------------------------- 视频编码 ---------------------------- 
ffmpeg命令
    ffmpeg -s 768*432 -pix_fmt yuv420p -i hong.yuv -vcodec libx264 -b:v 4096k -bf 0 -g 10 -r 30 out1.h264
参数介绍:
    -s 指定视频大小
    -pix_fmt 指定图形颜色空间
    -b:v 指定视频平均码率
    -bf 指定B帧数目
    -g 指定两个l帧之间的间隔
    -r 指定视频帧率

---------------------------- 编码流程 ---------------------------- 
1、查找编码器 --- avcodec_find_encoder_by_name
2、创建编码器上下文 --- avcodec_alloc_context3
3、设置编码参数 --- avcode_open2
4、打开编码器 --- av_frame_alloc
5、读取yuv数据 --- av_image_get_buffer_size
6、开始编码 --- av_image_fill_arrays
8、写入编码数据 --- avcodec_send_frame + avcodec_receive_packet

方法一:

  1. int writePacketCount = 0;
  2. int encodeVideo(AVCodecContext *encoderCtx, AVFrame *frame, AVPacket *packet, FILE *dest_fp)
  3. {
  4. int ret = avcodec_send_frame(encoderCtx, frame);
  5. if (ret < 0)
  6. {
  7. av_log(NULL, AV_LOG_ERROR, "send frame to encoder failed: %s\n", av_err2str(ret));
  8. return -1;
  9. }
  10. while (ret >= 0)
  11. {
  12. ret = avcodec_receive_packet(encoderCtx, packet);
  13. if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
  14. {
  15. av_log(NULL, AV_LOG_INFO, "[int encodeVideo] -- AVERROR(EAGAIN) || AVERROR_EOF \n");
  16. return 0;
  17. }
  18. else if (ret < 0)
  19. {
  20. av_log(NULL, AV_LOG_ERROR, "encoder frame failed:%s\n", av_err2str(ret));
  21. return -1;
  22. }
  23. fwrite(packet->data, 1, packet->size, dest_fp);
  24. writePacketCount++;
  25. av_log(NULL, AV_LOG_INFO, "writePacketCount:%d\n", writePacketCount);
  26. av_packet_unref(packet);
  27. }
  28. return 0;
  29. }
  1. int YUVencodeH264(const char *inFileName, const char *outFileName, const char *encoderName, const char *videoSize)
  2. {
  3. int ret = 0;
  4. /***************************************************************************************************/
  5. FILE *src_fp = fopen(inFileName, "rb");
  6. if (src_fp == NULL)
  7. {
  8. av_log(NULL, AV_LOG_ERROR, "open infile %s failed!\n", inFileName);
  9. ret = -1;
  10. goto end;
  11. }
  12. FILE *dest_fp = fopen(outFileName, "wb+");
  13. if (dest_fp == NULL)
  14. {
  15. av_log(NULL, AV_LOG_ERROR, "open outfile %s failed!\n", outFileName);
  16. ret = -1;
  17. goto end;
  18. }
  19. /***************************************************************************************************/
  20. int width = 0, height = 0;
  21. ret = av_parse_video_size(&width, &height, videoSize);
  22. if (ret < 0)
  23. {
  24. av_log(NULL, AV_LOG_ERROR, "parse video size failed:%s\n", av_err2str(ret));
  25. return -1;
  26. }
  27. av_log(NULL, AV_LOG_INFO, "getWidth:%d, getHeight:%d \n", width, height);
  28. enum AVPixelFormat pixFmt = AV_PIX_FMT_YUV420P;
  29. int fps = 24;
  30. AVCodec *encoder = avcodec_find_encoder_by_name(encoderName);
  31. if (encoder == NULL)
  32. {
  33. av_log(NULL, AV_LOG_ERROR, "find encoder %s failed\n", encoderName);
  34. return -1;
  35. }
  36. AVCodecContext *encoderCtx = avcodec_alloc_context3(encoder);
  37. if (encoderCtx == NULL)
  38. {
  39. av_log(NULL, AV_LOG_ERROR, "alloc encoder context!\n");
  40. return -1;
  41. }
  42. encoderCtx->codec_type = AVMEDIA_TYPE_VIDEO;
  43. encoderCtx->pix_fmt = pixFmt;
  44. encoderCtx->width = width;
  45. encoderCtx->height = height;
  46. encoderCtx->time_base = (AVRational){1, fps};
  47. encoderCtx->bit_rate = 4096000;
  48. encoderCtx->max_b_frames = 0;
  49. encoderCtx->gop_size = 10;
  50. // 打开编码器
  51. ret = avcodec_open2(encoderCtx, encoder, NULL);
  52. if (ret < 0)
  53. {
  54. av_log(NULL, AV_LOG_ERROR, "open encoder failed:%s\n", av_err2str(ret));
  55. goto end;
  56. }
  57. AVFrame *frame = av_frame_alloc();
  58. int frameSize = av_image_get_buffer_size(pixFmt, width, height, 1);
  59. uint8_t *frameBuffer = av_malloc(frameSize);
  60. av_image_fill_arrays(frame->data, frame->linesize, frameBuffer, pixFmt, width, height, 1);
  61. frame->format = pixFmt;
  62. frame->width = width;
  63. frame->height = height;
  64. int pictureSize = width * height;
  65. AVPacket packet;
  66. av_init_packet(&packet);
  67. int readFrameCount = 0;
  68. while (fread(frameBuffer, 1, pictureSize * 3 / 2, src_fp) == pictureSize * 3 / 2)
  69. {
  70. // Y 1 | U 1/4 | V 1/4
  71. frame->data[0] = frameBuffer;
  72. frame->data[1] = frameBuffer + pictureSize;
  73. frame->data[2] = frameBuffer + pictureSize + pictureSize / 4;
  74. frame->pts = readFrameCount; // 帧的展示顺序
  75. readFrameCount++;
  76. av_log(NULL, AV_LOG_INFO, "readFrameCount:%d\n", readFrameCount);
  77. encodeVideo(encoderCtx, frame, &packet, dest_fp);
  78. }
  79. encodeVideo(encoderCtx, NULL, &packet, dest_fp);
  80. end:
  81. if (encoderCtx)
  82. {
  83. avcodec_free_context(&encoderCtx);
  84. }
  85. if (src_fp)
  86. {
  87. fclose(src_fp);
  88. }
  89. if (dest_fp)
  90. {
  91. fclose(dest_fp);
  92. }
  93. if (frameBuffer)
  94. {
  95. av_freep(&frameBuffer);
  96. }
  97. return 0;
  98. }

-----------------------------------------------------------------------------------------------------------------------------------------------------

方法二:

  1. int64_t GetTime()
  2. {
  3. // 获取当前的时间戳
  4. return av_gettime_relative() / 1000; // 换算成毫秒
  5. }
  1. int EncodeVideoInterface(AVCodecContext *encoderCtx, AVFrame *frame, AVPacket *packet, FILE *outfile, uint16_t *frameCount)
  2. {
  3. if (frame)
  4. {
  5. av_log(NULL, AV_LOG_INFO, "[%s] Send frame pts %3ld frameCount:%d -- line:%d \n", __FUNCTION__, frame->pts, *frameCount, __LINE__);
  6. (*frameCount)++;
  7. }
  8. // 使用x264进行编码时,具体缓存帧是在x264源码进行,不会增加 avframe 应对 buffer 的 reference
  9. int ret = avcodec_send_frame(encoderCtx, frame);
  10. if (ret < 0)
  11. {
  12. av_log(NULL, AV_LOG_ERROR, "[%s] sending the frame to the encoder error! -- line:%d\n", __FUNCTION__, __LINE__);
  13. return -1;
  14. }
  15. while (ret >= 0)
  16. {
  17. ret = avcodec_receive_packet(encoderCtx, packet);
  18. if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
  19. {
  20. return 0;
  21. }
  22. else if (ret < 0)
  23. {
  24. av_log(NULL, AV_LOG_ERROR, "[%s] encoding audio frame error! -- line:%d\n", __FUNCTION__, __LINE__);
  25. return -1;
  26. }
  27. // 判断有没有关键帧 - 这个例子没有设置关键帧
  28. if (packet->flags)
  29. {
  30. av_log(NULL, AV_LOG_INFO, "[%s] Write packet flags:%d pts:%3ld dts:%3ld (size:%5d) -- line:%d\n", __FUNCTION__, packet->flags, packet->pts, packet->dts, packet->size, __LINE__);
  31. }
  32. if (!packet->flags)
  33. {
  34. av_log(NULL, AV_LOG_INFO, "[%s] Write packet flags:%d pts:%3ld dts:%3ld (size:%5d) -- line:%d\n", __FUNCTION__, packet->flags, packet->pts, packet->dts, packet->size, __LINE__);
  35. }
  36. fwrite(packet->data, 1, packet->size, outfile);
  37. }
  38. return 0;
  39. }
  1. int EncodeVideo(const char *yuvFileName, const char *h264FileName, const char *encoderName)
  2. {
  3. FILE *inFile = fopen(yuvFileName, "rb");
  4. FILE *outFile = fopen(h264FileName, "wb");
  5. if (inFile == NULL || outFile == NULL)
  6. {
  7. av_log(NULL, AV_LOG_ERROR, "[%s] open %s or %s file failed -- line:%d \n", __FUNCTION__, yuvFileName, h264FileName, __LINE__);
  8. goto _end;
  9. }
  10. // 查找指定的编码器
  11. AVCodec *encoder = avcodec_find_encoder_by_name(encoderName);
  12. if (encoder == NULL)
  13. {
  14. av_log(NULL, AV_LOG_ERROR, "[%s] Codec found error! -- line:%d\n", __FUNCTION__, __LINE__);
  15. goto _end;
  16. }
  17. // 创建编码器上下文
  18. AVCodecContext *codecCtx = avcodec_alloc_context3(encoder);
  19. if (codecCtx == NULL)
  20. {
  21. av_log(NULL, AV_LOG_ERROR, "[%s] Conld not allocate video codec context -- line:%d\n", __FUNCTION__, __LINE__);
  22. goto _end;
  23. }
  24. // 设置分辨率
  25. codecCtx->width = 1920;
  26. codecCtx->height = 1080;
  27. // 设置 time base
  28. codecCtx->time_base = (AVRational){1, 25};
  29. codecCtx->framerate = (AVRational){25, 1};
  30. // 设置 I 帧间隔
  31. // 如果 frame->pict_type 设置为 AV_PICTURE_TYPE_I,则忽略gop_size的设置,一直当做I帧进行编码
  32. codecCtx->gop_size = 25; // I 帧的间隔 (每秒都有一个I帧)
  33. codecCtx->max_b_frames = 0; // 如果不想包含B帧则设置为0(直播一般都设置成0)
  34. codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
  35. int ret;
  36. if (encoder->id == AV_CODEC_ID_H264)
  37. { // 相关的参数 libx264.c 的 AVOption options
  38. /** preset 预设是一系列参数的集合,这个集合能够在编码速度和压缩率之间做出一个权衡。
  39. * 一个编码速度稍慢的预设会提供更高的压缩效率(压缩效率是以文件大小来衡量的)。
  40. * 这就是说,假如你想得到一个指定大小的文件或者采用恒定比特率编码模式,你可以采用一个较慢的预设来获得更好的质量。
  41. * 同样的,对于恒定质量编码模式,你可以通过选择一个较慢的预设轻松地节省比特率。
  42. * 如果你很有耐心,通常的建议是使用最慢的预设。
  43. * 目前所有的预设按照编码速度降序排列为:
  44. * ultrafast superfast veryfast faster fast medium[default] preset slow slower veryslow
  45. * 默认为medium级别。
  46. */
  47. ret = av_opt_set(codecCtx->priv_data, "preset", "veryslow", 0);
  48. if (ret != 0)
  49. {
  50. av_log(NULL, AV_LOG_ERROR, "[%s] av_opt_set preset failed -- line:%d\n", __FUNCTION__, __LINE__);
  51. }
  52. /**
  53. * 所有的profile 包括:
  54. * 1. baseline profile: 基本画质。支持I/P 帧,只支持无交错(Progressive)和CAVLC;
  55. * 2. extended profile:进阶画质。支持I/P/B/SP/SI帧,只支持无交错(Progressive)和CAVLC;
  56. * 3. main profile:主流画质。提供I/P/B 帧,支持无交错(Progressive)和交错(Interlaced),也支持CAVLC 和CABAC 的支持
  57. * 4. high profile:高级画质。在 main Profile 的基础上增加了8x8内部预测、自定义量化、无损视频编码和更多的 YUV 格式:
  58. */
  59. ret = av_opt_set(codecCtx->priv_data, "profile", "high", 0);
  60. if (ret != 0)
  61. {
  62. av_log(NULL, AV_LOG_ERROR, "[%s] av_opt_set profile failed -- line:%d\n", __FUNCTION__, __LINE__);
  63. }
  64. /** tune
  65. * tune 是 x264 中重要性仅次于preset的选项,它是视觉优化的参数,tune可以理解为视频偏好(或者视频类型),
  66. * tune不是一个单一的参数,而是由一组参数构成 -tune 来改变参数设置。当前的 tune包括:
  67. * film:电影类型,对视频的质量非常严格时使用该选项
  68. * animation:动画片,压缩的视频是动画片时使用该选项
  69. * grain:颗粒物很重,该选项适用于颗粒感很重的视频
  70. * stillimage:静态图像,该选项主要用于静止画面比较多的视频
  71. * psnr:提高psnr,该选项编码出来的视频psnr比较高
  72. * ssim:提高ssim,该选项编码出来的视频ssim比较高
  73. * fastdecode:快速解码,该选项有利于快速解码
  74. * zerolatency:零延迟,该选项主要用于视频直播
  75. **/
  76. ret = av_opt_set(codecCtx->priv_data, "tune", "film", 0);
  77. if (ret != 0)
  78. {
  79. av_log(NULL, AV_LOG_ERROR, "[%s] av_opt_set tune failed -- line:%d\n", __FUNCTION__, __LINE__);
  80. }
  81. }
  82. // 设置编码器参数
  83. codecCtx->bit_rate = 16 * 1024 * 1024; // 极高码率
  84. codecCtx->thread_count = 8; // 开了多线程后会导致帧输出延时,需要缓存 thread_count 帧后再编程
  85. codecCtx->thread_type = FF_THREAD_FRAME;
  86. // 对于 H264 AV_CODEC_FLAG_GLOBAL_HEADER 设置则只包含I帧,此时sps pps 需要从 codec_ctx->extradata 读取
  87. // 不设置则每一帧都带 sps pps sei
  88. // 将 codecCtx 和 codec 进行绑定
  89. ret = avcodec_open2(codecCtx, encoder, NULL);
  90. if (ret < 0)
  91. {
  92. av_log(NULL, AV_LOG_ERROR, "[%s] Could not open codec -- line:%d\n", __FUNCTION__, __LINE__);
  93. goto _end;
  94. }
  95. av_log(NULL, AV_LOG_INFO, "[%s] thread_count:%d, thread_type:%d -- line:%d\n", __FUNCTION__, codecCtx->thread_count, codecCtx->thread_type, __LINE__);
  96. AVPacket *packet = av_packet_alloc();
  97. if (!packet)
  98. {
  99. av_log(NULL, AV_LOG_ERROR, "[%s] packet alloc error! -- line:%d \n", __FUNCTION__, __LINE__);
  100. goto _end;
  101. }
  102. AVFrame *frame = av_frame_alloc();
  103. if (!frame)
  104. {
  105. av_log(NULL, AV_LOG_ERROR, "[%s] Could not allocate video frame -- line:%d\n", __FUNCTION__, __LINE__);
  106. goto _end;
  107. }
  108. // 为 frame 分配 buffer
  109. frame->format = codecCtx->pix_fmt;
  110. frame->width = codecCtx->width;
  111. frame->height = codecCtx->height;
  112. // 为frame分配buffer
  113. ret = av_frame_get_buffer(frame, 0);
  114. if (ret < 0)
  115. {
  116. av_log(NULL, AV_LOG_ERROR, "[%s] Could not allocate audio data buffers -- line:%d\n", __FUNCTION__, __LINE__);
  117. goto _end;
  118. }
  119. // 计算出每一帧的数据 像素格式 * 宽 * 高
  120. int frameByteSize = av_image_get_buffer_size(frame->format, frame->width, frame->height, 1);
  121. av_log(NULL, AV_LOG_INFO, "[%s] frameByteSize: %d -- line:%d\n", __FUNCTION__, frameByteSize, __LINE__);
  122. uint8_t *yuvBuf = (uint8_t *)malloc(frameByteSize);
  123. if (!yuvBuf)
  124. {
  125. av_log(NULL, AV_LOG_ERROR, "[%s] yuvBuf malloc failed -- line:%d\n", __FUNCTION__, __LINE__);
  126. goto _end;
  127. }
  128. int64_t beginTime = GetTime();
  129. int64_t endTime = beginTime;
  130. int64_t allBeginTime = GetTime();
  131. int64_t allendTime = allBeginTime;
  132. int64_t pts = 0;
  133. av_log(NULL, AV_LOG_INFO, "\n[%s] ------------------------ start enode ------------------------ line:%d \n", __FUNCTION__, __LINE__);
  134. uint16_t frameCount = 0;
  135. while (1)
  136. {
  137. memset(yuvBuf, 0, frameByteSize);
  138. size_t readByteSize = fread(yuvBuf, 1, frameByteSize, inFile);
  139. if (readByteSize <= 0)
  140. {
  141. av_log(NULL, AV_LOG_INFO, "[%s] read file finish -- line:%d \n", __FUNCTION__, __LINE__);
  142. break;
  143. }
  144. if ((av_frame_make_writable(frame)) != 0)
  145. {
  146. av_log(NULL, AV_LOG_INFO, "[%s] Failed to make frame writable -- line:%d\n", __FUNCTION__, __LINE__);
  147. if (frame->buf && frame->buf[0])
  148. {
  149. av_log(NULL, AV_LOG_INFO, "[%s] frame buffer is not writable, ref_count = %d -- line:%d\n", __FUNCTION__, av_buffer_get_ref_count(frame->buf[0]), __LINE__);
  150. }
  151. goto _end;
  152. }
  153. int needSize = av_image_fill_arrays(frame->data, frame->linesize, yuvBuf, frame->format, frame->width, frame->height, 1);
  154. if (needSize != frameByteSize)
  155. {
  156. av_log(NULL, AV_LOG_INFO, "[%s] av_image_fill_array failed, needSize:%d, frame_bytes:%d\n", __FUNCTION__, needSize, frameByteSize);
  157. break;
  158. }
  159. pts += 40;
  160. // 设置 pts
  161. frame->pts = pts; // 使用采样率作为 pts 的单位,具体换算成秒 pts * 1 / 采样率
  162. beginTime = GetTime();
  163. ret = EncodeVideoInterface(codecCtx, frame, packet, outFile, &frameCount);
  164. if (ret < 0)
  165. {
  166. av_log(NULL, AV_LOG_ERROR, "[%s] encode failed -- line:%d\n", __FUNCTION__, __LINE__);
  167. break;
  168. }
  169. endTime = GetTime();
  170. av_log(NULL, AV_LOG_INFO, "[%s] The encoding time of this frame is: %ld ms -- line:%d\n", __FUNCTION__, endTime - beginTime, __LINE__);
  171. }
  172. /*冲刷编码器*/
  173. EncodeVideoInterface(codecCtx, NULL, packet, outFile, &frameCount);
  174. _end:
  175. if (inFile)
  176. {
  177. fclose(inFile);
  178. }
  179. if (outFile)
  180. {
  181. fclose(outFile);
  182. }
  183. if (yuvBuf)
  184. {
  185. free(yuvBuf);
  186. }
  187. if (packet)
  188. {
  189. av_packet_free(&packet);
  190. }
  191. if (frame)
  192. {
  193. av_frame_free(&frame);
  194. }
  195. if (codecCtx)
  196. {
  197. avcodec_free_context(&codecCtx);
  198. }
  199. return ret;
  200. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/921365
推荐阅读
相关标签
  

闽ICP备14008679号