ffmpeg-4.2.2:视频编码流程(yuv编码h.264)

这是基于FFMPEG的视频编码器,可以将yuv视频元数据编码成h264压缩编码数据。
主要是记录一下自己学习FFMPEG时总结的视频编码流程。
ffmpeg版本:ffmpeg-4.2.2
libx264版本:x264-snapshot-20191023-2245-stable

流程图

ffmpeg-4.2.2:视频编码流程(yuv编码h.264)简单介绍下各个函数的功能:

avcodec_find_encoder_by_name():通过编码器的名字查找编码器
avcodec_alloc_context3():初始化AVCodecContext
avcodec_open2():打开编码器
av_packet_alloc():初始化AVPacket
av_frame_alloc():初始化AVFrame
av_frame_get_buffer():为AVFrame->data等分配内存
av_frame_make_writable():检查AVFrame->data是否可写
avcodec_send_frame():编码视频:将一帧视频元数据发送给编码器
avcodec_receive_packet():编码视频:接收编码完成的AVPacket数据包


代码

[plain] view plaincopy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <libavcodec/avcodec.h>  
  5. #include <libavutil/opt.h>  
  6. #include <libavutil/imgutils.h>  
  7.   
  8. static void encode(AVCodecContext *cdc_ctx, AVFrame *frame, AVPacket *pkt, FILE *fp_out)  
  9. {  
  10. int ret = 0;  
  11.   
  12. if (frame != NULL)  
  13. printf("Send %d frame.\n", frame->pts);  
  14.   
  15. if ((ret = avcodec_send_frame(cdc_ctx, frame)) < 0)  
  16. {  
  17. fprintf(stderr, "avcodec_send_frame failed.\n");  
  18. exit(1);  
  19. }  
  20.   
  21. while ((ret = avcodec_receive_packet(cdc_ctx, pkt)) >= 0)  
  22. {  
  23. printf("Write %d packet.\n", pkt->pts);  
  24. fwrite(pkt->data, 1, pkt->size, fp_out);  
  25. av_packet_unref(pkt);  
  26. }  
  27.   
  28. if ((ret != AVERROR(EAGAIN)) && (ret != AVERROR_EOF))  
  29. {  
  30. fprintf(stderr, "avcodec_receive_packet failed.\n");  
  31. exit(1);  
  32. }  
  33. }  
  34.   
  35. void encode_video(const char *input_file, const char *output_file, const char *encoder_name)  
  36. {  
  37. int ret = 0;  
  38. int i = 0;  
  39. AVCodec *codec = NULL;  
  40. AVCodecContext *cdc_ctx = NULL;  
  41. AVPacket *pkt = NULL;  
  42. AVFrame *frame = NULL;  
  43. FILE *fp_in, *fp_out;  
  44.   
  45. if ((codec = avcodec_find_encoder_by_name(encoder_name)) == NULL)  
  46. {  
  47. fprintf(stderr, "avcodec_find_encoder_by_name failed.\n");  
  48. goto ret1;  
  49. }  
  50.   
  51. if ((cdc_ctx = avcodec_alloc_context3(codec)) == NULL)  
  52. {  
  53. fprintf(stderr, "avcodec_alloc_context3 failed.\n");  
  54. goto ret1;  
  55. }  
  56. cdc_ctx->bit_rate = 400000;  
  57. cdc_ctx->width = 352;  
  58. cdc_ctx->height = 288;  
  59. cdc_ctx->time_base = (AVRational){1,25};  
  60. cdc_ctx->framerate = (AVRational){25,1};  
  61. cdc_ctx->gop_size = 10;  
  62. cdc_ctx->max_b_frames = 1;  
  63. cdc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;  
  64. if (codec->id == AV_CODEC_ID_H264)  
  65. av_opt_set(cdc_ctx->priv_data, "preset""slow"0);  
  66.   
  67. if ((ret = avcodec_open2(cdc_ctx, codec, NULL)) < 0)  
  68. {  
  69. fprintf(stderr, "avcodec_open2 failed.\n");  
  70. goto ret2;  
  71. }  
  72.   
  73. if ((pkt = av_packet_alloc()) == NULL)  
  74. {  
  75. fprintf(stderr, "av_packet_alloc failed.\n");  
  76. goto ret3;  
  77. }  
  78.   
  79. if ((frame = av_frame_alloc()) == NULL)  
  80. {  
  81. fprintf(stderr, "av_frame_alloc failed.\n");  
  82. goto ret4;  
  83. }  
  84. frame->format = cdc_ctx->pix_fmt;  
  85. frame->width = cdc_ctx->width;  
  86. frame->height = cdc_ctx->height;  
  87.   
  88. if ((ret = av_frame_get_buffer(frame, 0)) < 0)  
  89. {  
  90. fprintf(stderr, "av_frame_get_buffer failed.\n");  
  91. goto ret5;  
  92. }  
  93.   
  94. if ((fp_in = fopen(input_file, "rb")) == NULL)  
  95. {  
  96. fprintf(stderr, "fopen %s failed.\n", input_file);  
  97. goto ret5;  
  98. }  
  99. if ((fp_out = fopen(output_file, "wb")) == NULL)  
  100. {  
  101. fprintf(stderr, "fopen %s failed.\n", output_file);  
  102. goto ret6;  
  103. }  
  104.   
  105. while (feof(fp_in) == 0)  
  106. {  
  107. int y = 0;  
  108.   
  109. if ((ret = av_frame_make_writable(frame)) < 0)  
  110. {  
  111. fprintf(stderr, "frame is not writable.\n");  
  112. goto ret7;  
  113. }  
  114.   
  115. /*y*/  
  116. for (y = 0; y < frame->height; y++)  
  117. fread(&frame->data[0][y * frame->linesize[0]], 1, frame->width, fp_in);  
  118. /*u*/  
  119. for (y = 0; y < frame->height / 2; y++)  
  120. fread(&frame->data[1][y * frame->linesize[1]], 1, frame->width / 2, fp_in);  
  121. /*v*/  
  122. for (y = 0; y < frame->height / 2; y++)  
  123. fread(&frame->data[2][y * frame->linesize[2]], 1, frame->width / 2, fp_in);  
  124.   
  125. frame->pts = i++;  
  126.   
  127. encode(cdc_ctx, frame, pkt, fp_out);  
  128. }  
  129.   
  130. /*flush buffer*/  
  131. encode(cdc_ctx, NULL, pkt, fp_out);  
  132.   
  133. fclose(fp_out);  
  134. fclose(fp_in);  
  135. av_frame_free(&frame);  
  136. av_packet_free(&pkt);  
  137. avcodec_close(cdc_ctx);  
  138. avcodec_free_context(&cdc_ctx);  
  139. return;  
  140. ret7:  
  141. fclose(fp_out);  
  142. ret6:  
  143. fclose(fp_in);  
  144. ret5:  
  145. av_frame_free(&frame);  
  146. ret4:  
  147. av_packet_free(&pkt);  
  148. ret3:  
  149. avcodec_close(cdc_ctx);  
  150. ret2:  
  151. avcodec_free_context(&cdc_ctx);  
  152. ret1:  
  153. exit(1);  
  154. }  
  155.   
  156. int main(int argc, const char *argv[])  
  157. {  
  158. if (argc < 4)  
  159. {  
  160. fprintf(stderr, "Uage:<input file> <output file> <encoder_name>\n");  
  161. exit(0);  
  162. }  
  163.   
  164. encode_video(argv[1], argv[2], argv[3]);  
  165.   
  166. return 0;  
  167. }  

注:

  1. 查找编码器也可以通过编码器ID使用以下函数查找:
    codec = avcode_find_encoder(AV_CODEC_ID_H264);
  2. 在循环读取yuv文件时,不能直接把一帧的y数据全读出来拷贝给AVFrame->data[0],因为yuv文件中y值一行的大小是分辨率的width,而data[0]中一行是AVFrame->linesize[0],AVFrame->linesize[0]略大于width,所以只能一行一行读取拷贝,u、v同理
  3. avcodec_send_frame()的第二个参数可以传入NULL,代表的是刷新包,表示流的结束
  4. flush buffer冲洗缓冲区其实就是在执行一次编码过程,但是avcodec_send_frame()第二个参数传入的是NULL,然后在循环调用avcodec_receive_packet接口时,如果编码器有缓冲AVPacket数据,就会返回它们

下载

项目主页

Github:https://github.com/newbie-plan/encode_video

ffmpeg-4.2.2:视频编码流程(yuv编码h.264)

上一篇:短信平台对接-一信通


下一篇:快速数论变换ntt