编码流程
-
查找编码器
-
创建编码器上下文
-
设置编码参数
编码的一些信息,
-
编码器与编码器上下文绑定在一起
-
创建输出编码后的文件
-
创建(获取)AVFrame
这个是原始的帧数据。
-
创建AVPacket
编码后的是视频帧是保存在数据包中的。一个数据包包括多个数据帧。
-
生成(获取)视频内容。
-
进行编码。
对视频内容进行编码。
-
得到编码后数据写入到输出文件
流程图如下:
实现代码
自定义填充yuv数据,生成编码格式为h264的编码文件
#include <stdio.h>
#include <libavutil/log.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <SDL.h>
#include <stdbool.h>
static int encode(AVCodecContext* ctx, AVFrame* frame, AVPacket* pkt, FILE* out) {
int ret = -1;
// 要编码的数据传给编码器
ret = avcodec_send_frame(ctx, frame);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to send frame to encoder!\n");
goto _END;
}
while (ret >= 0) {
// 从编码器中获取编码好的数据
ret = avcodec_receive_packet(ctx, pkt);
// 如果编码出错了或者编码的缓冲区没有数据
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return 0;
}
else if (ret < 0) {
return -1; //退出tkyc
}
// 1.将什么数据, 每次写的大小, 编码后包的字节数
fwrite(pkt->data, 1, pkt->size, out);
av_packet_unref(pkt);
}
_END:
return 0;
}
/*
* 对视频内容进行编码
* 本次视频内容是自己生成的yuv数据
*/
void encode_vedio_new() {
int ret = -1;
FILE* f = NULL;
char* dst = NULL;
char* codecName = NULL;
const AVCodec* codec = NULL;
AVCodecContext* ctx = NULL;
AVFrame* frame = NULL;
AVPacket* pkt = NULL;
av_log_set_level(AV_LOG_DEBUG);
//1. 输入参数
dst = "f:\\test_data\\encode_vedio.h264";
codecName = "libx264";
//2. 查找编码器
codec = avcodec_find_encoder_by_name(codecName);
if (!codec) {
av_log(NULL, AV_LOG_ERROR, "don't find Codec: %s", codecName);
goto _ERROR;
}
//3. 创建编码器上下文
ctx = avcodec_alloc_context3(codec);
if (!ctx) {
av_log(NULL, AV_LOG_ERROR, "NO MEMRORY\n");
goto _ERROR;
}
//4. 设置编码器参数
ctx->width = 640; // 视频宽高
ctx->height = 480;
ctx->bit_rate = 500000; // 视频码率
ctx->time_base = (AVRational){ 1, 25 }; // 时间基
ctx->framerate = (AVRational){ 25, 1 }; // 视频帧率
ctx->gop_size = 10; // gop表示一组帧的大小,10代表每10帧代表1组
ctx->max_b_frames = 1; // 一组帧中最大可以设置多少个B帧
ctx->pix_fmt = AV_PIX_FMT_YUV420P; // 视频像素格式
// 是h264的话,可以设置一些私有的
if (codec->id == AV_CODEC_ID_H264) {
av_opt_set(ctx->priv_data, "preset", "slow", 0);
}
//5. 编码器与编码器上下文绑定到一起
ret = avcodec_open2(ctx, codec, NULL);
if (ret < 0) {
av_log(ctx, AV_LOG_ERROR, "Don't open codec: %s \n", av_err2str(ret));
goto _ERROR;
}
//6. 创建输出文件
f = fopen(dst, "wb");
if (!f) {
av_log(NULL, AV_LOG_ERROR, "Don't open file:%s", dst);
goto _ERROR;
}
//7. 创建AVFrame
frame = av_frame_alloc();
if (!frame) {
av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");
goto _ERROR;
}
// 为frame 的data分配空间的时候,必须告诉它的宽高和格式,这样ffmpeg才知道为这个帧分配多大的空间。
frame->width = ctx->width;
frame->height = ctx->height;
frame->format = ctx->pix_fmt;
ret = av_frame_get_buffer(frame, 0); // 用于为AVFrame结构体分配内存空间
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Could not allocate the video frame \n");
goto _ERROR;
}
//8. 创建AVPacket
pkt = av_packet_alloc();
if (!pkt) {
av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");
goto _ERROR;
}
//9. 生成视频内容 这个循环中,每一次循环,产生一帧数据
for (int i = 0; i < 25; i++) {
ret = av_frame_make_writable(frame);
if (ret < 0) {
break;
}
/*
未编码的每一帧都是一张图像。x是横轴,y是纵轴
frame->data[0]表示Y分量
frame->data[1]表示U分量
frame->data[2]表示V分量
linesize表示行的大小
下面就是自定义生成图像帧数据
*/
//Y分量
for (int y = 0; y < ctx->height; y++) {
for (int x = 0; x < ctx->width; x++) {
frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
}
}
//UV分量
for (int y = 0; y < ctx->height / 2; y++) {
for (int x = 0; x < ctx->width / 2; x++) {
frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;
frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;
}
}
frame->pts = i;
//10. 编码
ret = encode(ctx, frame, pkt, f);
if (ret == -1) {
goto _ERROR;
}
}
//10. 编码 再次调用编码方法,这是因为编码器的缓冲区中可能还残存剩余的数据,所以要让她刷新出来,否则发编码后的数据会少帧。
// 我们传给编码器一个为null的frame,告诉编码器,现在没有数据,需要把缓冲区中的数据全部给刷新出来。
encode(ctx, NULL, pkt, f);
_ERROR:
//ctx
if (ctx) {
avcodec_free_context(&ctx);
}
//avframe
if (frame) {
av_frame_free(&frame);
}
//avpacket
if (pkt) {
av_packet_free(&pkt);
}
//dst
if (f) {
fclose(f);
}
}