使用ffmpeg接口将YUV编码为h.264

目录

  • 1. 第一步: 解析数据参数
  • 2. 第二步: 按要求初始化ffmpeg结构
    • 2.1. 第一步: 根据编解码器ID,获取编解码器指针
    • 2.2. 第二步: 获取AVCodecContext实例。通过编解码器AVCodec指针,获取编解码器上下文
    • 2.3. 第三步: 设置编码器参数
    • 2.4. 第四步: 根据AVCodec和AVCodecContext实例,打开编码器
    • 2.5. 第五步:配置编码的输入数据
  • 3. 第三步: 编码过程循环
    • 3.1. 算法框架
    • 3.2. 第一步: 将YUV像素值保存在AVFrame中
    • 3.3. 第二步: 编码前另外需要完成的操作时初始化AVPacket对象。
    • 3.4. 第三步:编码
    • 3.5. 完整的编码循环提就可以使用下面的代码实现:
  • 4. 第四步:写出码流数据
  • 5. 第五步: 收尾工作
    • 5.1. 输出残留在编码器中的最后一帧
    • 5.2. 释放资源

第一步: 解析数据参数

这一步我们可以设置一个专门的配置文件,并将参数按照某个事写入这个配置文件中,再在程序中解析这个配置文件获得编码的参数。如果参数不多的话,我们可以直接使用命令行将编码参数传入即可。

第二步: 按要求初始化ffmpeg结构

第一步: 根据编解码器ID,获取编解码器指针

第二步: 获取AVCodecContext实例。通过编解码器AVCodec指针,获取编解码器上下文

第三步: 设置编码器参数

ctx.c->bit_rate = io_param.nBitRate;

* resolution must be a multiple of two *
ctx.c->width = io_param.nImageWidth;
ctx.c->height = io_param.nImageHeight;

* frames per second *
AVRational rational = {1,25};
ctx.c->time_base = rational;

ctx.c->gop_size = io_param.nGOPSize;
ctx.c->max_b_frames = io_param.nMaxBFrames;
ctx.c->pix_fmt = AV_PIX_FMT_YUV420P;

av_opt_set(ctx.c->priv_data, “preset”, “slow”, 0); //自定义私有数据

第四步: 根据AVCodec和AVCodecContext实例,打开编码器

现在,AVCodec、AVCodecContext的指针都已经分配好,然后以这两个对象的指针作为参数打开编码器对象。调用的函数为avcodec_open2,声明方式为:

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

该函数的前两个参数是我们刚刚建立的两个对象,第三个参数为一个字典类型对象,用于保存函数执行过程总未能识别的AVCodecContext和另外一些私有设置选项。函数的返回值表示编码器是否打开成功,若成功返回0,失败返回一个负数。调用方式为:

if (avcodec_open2(ctx.c, ctx.codec, NULL) < 0) //根据编码器上下文打开编码器
{
fprintf(stderr, “Could not open codec\n”);
exit(1);
}

第五步:配置编码的输入数据

第三步: 编码过程循环

算法框架

while (numCoded < maxNumToCode)
{
read_yuv_data();
encode_video_frame();
write_out_h264();
}

第一步: 将YUV像素值保存在AVFrame中

三个颜色分量Y/U/V的地址分别为AVframe::data[0]、AVframe::data[1]和AVframe::data[2],图像的宽度分别为AVframe::linesize[0]、AVframe::linesize[1]和AVframe::linesize[2]。
需要注意的是,linesize中的值通常指的是stride而不是width,也就是说,像素保存区可能是带有一定宽度的无效边区的,在读取数据时需注意。
AVframe::data[0] :亮度存储空间
AVframe::data[1] :色度存储空间,采样个数为0的一半
AVframe::data[2] :色度存储空间,采样个数为0的一半

int Read_yuv_data(CodecCtx &ctx, IOParam &io_param, int color_plane)
{
int frame_height = color_plane = 0? ctx.frame->height : ctx.frame->height / 2;// *YUV420 1,2色度分量的采样个数为亮度分量0的一半*
int frame_width = color_plane =
0? ctx.frame->width : ctx.frame->width / 2;// YUV420 1,2色度分量的采样个数为亮度分量0的一半
int frame_size = frame_width * frame_height;
int frame_stride = ctx.frame->linesize[color_plane];

if (frame_width == frame_stride)
{
// 宽度和跨度相等,像素信息连续存放
fread_s(ctx.frame->data[color_plane], frame_size, 1, frame_size, io_param.pFin);
}
else
{
//宽度小于跨度,像素信息保存空间之间存在间隔
for (int row_idx = 0; row_idx < frame_height; row_idx++)
{
fread_s(ctx.frame->data[color_plane] + row_idx * frame_stride, frame_width, 1, frame_width, io_param.pFin);
}
}

return frame_size;
}

第二步: 编码前另外需要完成的操作时初始化AVPacket对象。

该对象保存了编码之后的码流数据。

  1. 对其进行初始化的操作非常简单,只需要调用av_init_packet并传入AVPacket对象的指针。
  2. AVPacket::data设为NULL,AVPacket::size赋值0.

第三步:编码

完整的编码循环提就可以使用下面的代码实现:

* encode 1 second of video *
for (frameIdx = 0; frameIdx < io_param.nTotalFrames; frameIdx++)
{
av_init_packet(&(ctx.pkt)); */初始化AVPacket实例
ctx.pkt.data = NULL; /* packet data will be allocated by the encoder
ctx.pkt.size = 0;

fflush(stdout);

Read_yuv_data(ctx, io_param, 0); //Y分量
Read_yuv_data(ctx, io_param, 1); //U分量
Read_yuv_data(ctx, io_param, 2); //V分量

ctx.frame->pts = frameIdx;

* encode the image *
ret = avcodec_encode_video2(ctx.c, &(ctx.pkt), ctx.frame, &got_output); //将AVFrame中的像素信息编码为AVPacket中的码流
if (ret < 0)
{
fprintf(stderr, “Error encoding frame\n”);
exit(1);
}

if (got\_output)  
{  
    //获得一个完整的编码帧  
    printf("Write frame %3d (size=%5d)\n", frameIdx, ctx.pkt.size);  
    fwrite(ctx.pkt.data, 1, ctx.pkt.size, io\_param.pFout);  
    av\_packet\_unref(&(ctx.pkt));  
}  

} //for (frameIdx = 0; frameIdx < io_param.nTotalFrames; frameIdx++)

第四步:写出码流数据

第五步: 收尾工作

输出残留在编码器中的最后一帧

如果我们就此结束编码器的整个运行过程,我们会发现,编码完成之后的码流对比原来的数据少了一帧。这是因为我们是根据读取原始像素数据结束来判断循环结束的,这样最后一帧还保留在编码器中尚未输出。所以在关闭整个解码过程之前,我们必须继续执行编码的操作,直到将最后一帧输出为止。执行这项操作依然调用avcodec_encode_video2函数,只是表示AVFrame的参数设为NULL即可:

* get the delayed frames *
for (got_output = 1; got_output; frameIdx++)
{
fflush(stdout);

ret = avcodec_encode_video2(ctx.c, &(ctx.pkt), NULL, &got_output); //输出编码器中剩余的码流
if (ret < 0)
{
fprintf(stderr, “Error encoding frame\n”);
exit(1);
}

if (got\_output)  
{  
    printf("Write frame %3d (size=%5d)\n", frameIdx, ctx.pkt.size);  
    fwrite(ctx.pkt.data, 1, ctx.pkt.size, io\_param.pFout);  
    av\_packet\_unref(&(ctx.pkt));  
}  

} //for (got_output = 1; got_output; frameIdx++)

释放资源

我们就可以按计划关闭编码器的各个组件,结束整个编码的流程。编码器组件的释放流程可类比建立流程,需要关闭AVCocec、释放AVCodecContext、释放AVFrame中的图像缓存和对象本身:

avcodec_close(ctx.c);
av_free(ctx.c);
av_freep(&(ctx.frame->data[0]));
av_frame_free(&(ctx.frame));

你可能感兴趣的:(视频处理,h.264,编码,ffmpeg,yuv,视频处理)