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

第一步: 解析数据参数

这一步咱们能够设置一个专门的配置文件,并将参数按照某个事写入这个配置文件中,再在程序中解析这个配置文件得到编码的参数。若是参数很少的话,咱们能够直接使用命令行将编码参数传入便可。 web

第二步: 按要求初始化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; ide

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

第四步: 根据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);
} 3d

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

第三步: 编码过程循环

算法框架

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));