这一步我们可以设置一个专门的配置文件,并将参数按照某个事写入这个配置文件中,再在程序中解析这个配置文件获得编码的参数。如果参数不多的话,我们可以直接使用命令行将编码参数传入即可。
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_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();
}
三个颜色分量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的一半*
0? ctx.frame->width : ctx.frame->width / 2;// YUV420 1,2色度分量的采样个数为亮度分量0的一半
int frame_width = color_plane =
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;
}
该对象保存了编码之后的码流数据。
* 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));