ffmpeg-4.2.2:视频编码流程(yuv编码h.264)
2020-05-06 04:21
2871 查看
这是基于FFMPEG的视频编码器,可以将yuv视频元数据编码成h264压缩编码数据。
主要是记录一下自己学习FFMPEG时总结的视频编码流程。
ffmpeg版本:ffmpeg-4.2.2
libx264版本:x264-snapshot-20191023-2245-stable
流程图
简单介绍下各个函数的功能:
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数据包
代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <libavcodec/avcodec.h> #include <libavutil/opt.h> #include <libavutil/imgutils.h> static void encode(AVCodecContext *cdc_ctx, AVFrame *frame, AVPacket *pkt, FILE *fp_out) { int ret = 0; if (frame != NULL) printf("Send %d frame.\n", frame->pts); if ((ret = avcodec_send_frame(cdc_ctx, frame)) < 0) { fprintf(stderr, "avcodec_send_frame failed.\n"); exit(1); } while ((ret = avcodec_receive_packet(cdc_ctx, pkt)) >= 0) { printf("Write %d packet.\n", pkt->pts); fwrite(pkt->data, 1, pkt->size, fp_out); av_packet_unref(pkt); } if ((ret != AVERROR(EAGAIN)) && (ret != AVERROR_EOF)) { fprintf(stderr, "avcodec_receive_packet failed.\n"); exit(1); } } void encode_video(const char *input_file, const char *output_file, const char *encoder_name) { int ret = 0; int i = 0; AVCodec *codec = NULL; AVCodecContext *cdc_ctx = NULL; AVPacket *pkt = NULL; AVFrame *frame = NULL; FILE *fp_in, *fp_out; if ((codec = avcodec_find_encoder_by_name(encoder_name)) == NULL) { fprintf(stderr, "avcodec_find_encoder_by_name failed.\n"); goto ret1; } if ((cdc_ctx = avcodec_alloc_context3(codec)) == NULL) { fprintf(stderr, "avcodec_alloc_context3 failed.\n"); goto ret1; } cdc_ctx->bit_rate = 400000; cdc_ctx->width = 352; cdc_ctx->height = 288; cdc_ctx->time_base = (AVRational){1,25}; cdc_ctx->framerate = (AVRational){25,1}; cdc_ctx->gop_size = 10; cdc_ctx->max_b_frames = 1; cdc_ctx->pix_fmt = AV_PIX_FMT_YUV420P; if (codec->id == AV_CODEC_ID_H264) av_opt_set(cdc_ctx->priv_data, "preset", "slow", 0); if ((ret = avcodec_open2(cdc_ctx, codec, NULL)) < 0) { fprintf(stderr, "avcodec_open2 failed.\n"); goto ret2; } if ((pkt = av_packet_alloc()) == NULL) { fprintf(stderr, "av_packet_alloc failed.\n"); goto ret3; } if ((frame = av_frame_alloc()) == NULL) { fprintf(stderr, "av_frame_alloc failed.\n"); goto ret4; } frame->format = cdc_ctx->pix_fmt; frame->width = cdc_ctx->width; frame->height = cdc_ctx->height; if ((ret = av_frame_get_buffer(frame, 0)) < 0) { fprintf(stderr, "av_frame_get_buffer failed.\n"); goto ret5; } if ((fp_in = fopen(input_file, "rb")) == NULL) { fprintf(stderr, "fopen %s failed.\n", input_file); goto ret5; } if ((fp_out = fopen(output_file, "wb")) == NULL) { fprintf(stderr, "fopen %s failed.\n", output_file); goto ret6; } while (feof(fp_in) == 0) { int y = 0; if ((ret = av_frame_make_writable(frame)) < 0) { fprintf(stderr, "frame is not writable.\n"); goto ret7; } /*y*/ for (y = 0; y < frame->height; y++) fread(&frame->data[0][y * frame->linesize[0]], 1, frame->width, fp_in); /*u*/ for (y = 0; y < frame->height / 2; y++) fread(&frame->data[1][y * frame->linesize[1]], 1, frame->width / 2, fp_in); /*v*/ for (y = 0; y < frame->height / 2; y++) fread(&frame->data[2][y * frame->linesize[2]], 1, frame->width / 2, fp_in); frame->pts = i++; encode(cdc_ctx, frame, pkt, fp_out); } /*flush buffer*/ encode(cdc_ctx, NULL, pkt, fp_out); fclose(fp_out); fclose(fp_in); av_frame_free(&frame); av_packet_free(&pkt); avcodec_close(cdc_ctx); avcodec_free_context(&cdc_ctx); return; ret7: fclose(fp_out); ret6: fclose(fp_in); ret5: av_frame_free(&frame); ret4: av_packet_free(&pkt); ret3: avcodec_close(cdc_ctx); ret2: avcodec_free_context(&cdc_ctx); ret1: exit(1); } int main(int argc, const char *argv[]) { if (argc < 4) { fprintf(stderr, "Uage:<input file> <output file> <encoder_name>\n"); exit(0); } encode_video(argv[1], argv[2], argv[3]); return 0; }
注:
- 查找编码器也可以通过编码器ID使用以下函数查找:
codec = avcode_find_encoder(AV_CODEC_ID_H264); - 在循环读取yuv文件时,不能直接把一帧的y数据全读出来拷贝给AVFrame->data[0],因为yuv文件中y值一行的大小是分辨率的width,而data[0]中一行是AVFrame->linesize[0],AVFrame->linesize[0]略大于width,所以只能一行一行读取拷贝,u、v同理
- avcodec_send_frame()的第二个参数可以传入NULL,代表的是刷新包,表示流的结束
- flush buffer冲洗缓冲区其实就是在执行一次编码过程,但是avcodec_send_frame()第二个参数传入的是NULL,然后在循环调用avcodec_receive_packet接口时,如果编码器有缓冲AVPacket数据,就会返回它们
下载
项目主页
Github:https://github.com/newbie-plan/encode_video
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/28/cefa2925f8eb3ae12571d20427f4963e.png)
相关文章推荐
- ffmpeg-4.2.2:视频解码流程(h.264解码成yuv)
- 最简单的基于FFMPEG的视频编码器(YUV编码为H.264)
- 【FFMpeg视频开发与应用基础】二、调用FFmpeg SDK对YUV视频序列进行编码
- 图像视频编码和FFmpeg(2)-----YUV格式介绍和应用
- 最简单的基于FFMPEG的视频编码器(YUV编码为H.264)
- FFmpeg SDK开发课程笔记(一):调用FFmpeg SDK对YUV视频序列进行编码
- 最简单的视频编码器:基于libx264(编码YUV为H.264)
- 最简单的基于FFMPEG的视频编码器(YUV编码为H.264)
- 使用FFMPEG将YUV编码为H.264
- 最简单的视频编码器:基于libx264(编码YUV为H.264)
- 使用FFmpeg类库实现YUV视频序列编码为视频
- FFmpeg,H.264,Directshow,和opencv及视频编码与封装格式
- ffmpeg学习八:软件生成yuv420p视频并将其编码为H264格式
- 【FFmpeg学习笔记005】 libx264和FFmpeg不同方式(YUV编码为H.264)
- 图像视频编码和FFmpeg(2)-----YUV格式介绍和应用
- FFmpeg浅尝辄止(二)——YUV视频序列编码为视频
- 使用FFmpeg类库实现YUV视频序列编码为视频
- 使用FFmpeg类库实现YUV视频序列编码为视频
- 使用ffmpeg将BMP图片编码为x264视频文件,将H264视频保存为BMP图片,yuv视频文件保存为图片的代码
- FFMPEG学习----分离视频里的H.264与YUV数据