1.FFmpeg进行视频编解码所需要的结构
.AVCodec :AVCodec结构保存了一个编解码器的实例,实现实际的编码功能。通常我们在程序中定义一个指向AVCodec 结构的指针指向该实例。
.AVCodecContext :AVCodecContext表示AVCodec所代表的上下文信息,保存了AVCodec所需要的一些参数。对于实现 编码功能,我们可以在这个结构体中设置我们指定的编码参数。通常也是定义一个指针指向AVCodecContext.
.AVFrame :AVFrame结构保存编码之前的像素数据,并作为编码器的输入数据。其在程序中也是一个指针的形式
.AVPacket :AVPacket表示码流包结构,包含编码之后的码流数据。该结构可以不定一指针,以一个对象的形式定义。
2.FFmpeg编码的主要步骤
1.解析输入参赛
2.按要求初始化需要的FFmpeg结构
3.编码过程循环
4.写出码流数据
5.收尾工作
3.源代码
#include "stdio.h"
#include "Error.h"
extern "C"
{
#include "libavutil/imgutils.h"
#include "libavutil/samplefmt.h"
#include "libavformat/avformat.h"
#include "libavutil/opt.h"
}
const char *inputFileName = NULL;//要打开的yuv文件名
const char *outputFileName = NULL;//编码后的输出的文件名
int frameWidth = 0, frameHeight = 0;//视频的宽和高
int bitRate = 0;//视频的比特率
int frameToEncode = 0;//需要转换编码的帧数
FILE *pFin = NULL,*pFout = NULL; //打开输入和输出的文件描述符
AVCodec *codec = NULL; //代表一个编解码器
AVCodecContext *codecCtx = NULL; //编解码器上下文
AVFrame *frame = NULL; //待编码的像素数据
AVPacket pkt; //保存编码之后的码流
int read_yuv_data(int color);
int parse_input_paramaters(int argc, char **argv)
{
inputFileName = argv[1]; //输入文件名称in.yuv
outputFileName = argv[2]; //输出文件名称out.h264
frameWidth = atoi(argv[3]); //视频的宽
frameHeight = atoi(argv[4]);//视频的高
bitRate = atoi(argv[5]); //比特率
frameToEncode = atoi(argv[6]);//要编码的帧数
fopen_s(&pFin, inputFileName, "rb+");//打开输入文件
if (!pFin)
{
printf("fopen_s inputFile failed\n");
return FILE_ERROR_OPEN_FAILED;
}
fopen_s(&pFout, outputFileName, "wb+");//打开输出文件
if (!pFout)
{
printf("fopen_s outputFile failed\n");
return FILE_ERROR_OPEN_FAILED;
}
return HPR_OK;
}
int main(int argc, char **argv)
{
int got_packet;//是否已经完成编码出一个完整的数据包
printf("Hello world \n");
/*解析输入参数*/
if (HPR_OK == parse_input_paramaters(argc,argv))
{
printf("inputFileName:%s\n outputFileName:%s\n frameWidth:%d\n frameHeight:%d\n bitRate:%d\n frameToEncode:%d\n",inputFileName,outputFileName,frameWidth,frameHeight,bitRate,frameToEncode);
}
else
{
printf("parse_input_paramaters error\n");
return HPR_ERROR;
}
/*注册编解码器*/
avcodec_register_all();
/*查找AVCodec编码器*/
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec)
{
return FF_ERROR_INIT_FAILED;
}
/*分配AVCodecContext实例*/
codecCtx = avcodec_alloc_context3(codec);
if (!codecCtx)
{
return FF_ERROR_INIT_FAILED;
}
/*设置编码器参数*/
codecCtx->width = frameWidth; //视频帧宽
codecCtx->height = frameHeight;//视频帧高
codecCtx->bit_rate = bitRate;//比特率
AVRational r = { 1, 25 }; //每秒25帧
codecCtx->time_base = r;
codecCtx->gop_size = 12;//GOP即Group of picture(图像组),指两个I帧之间的距离
codecCtx->max_b_frames = 1;//两个非B帧之间的最大B帧数目
codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;//像素格式
//priv_data 属于每个编码器特有的设置域,用av_opt_set 设置
//preset 与编码速度和质量相关
av_opt_set(codecCtx->priv_data, "preset", "slow", 0);
/*打开编码器*/
if(avcodec_open2(codecCtx, codec, NULL) < 0)
{
return FF_ERROR_INIT_FAILED;
}
/*分配AVFrame以及像素存储空间*/
frame = av_frame_alloc();//分配的AVFrame结构体空间
if (!frame)
{
return FF_ERROR_INIT_FAILED;
}
frame->width = codecCtx->width;//像素的宽
frame->height = codecCtx->height;//像素的高
frame->format = codecCtx->pix_fmt;//像素的格式
//分配实际存储像素的存储空间
// frame->linesize:frame->data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽。
if (av_image_alloc(frame->data, frame->linesize, frame->width, frame->height, (AVPixelFormat)frame->format, 32) < 0)
{
return FF_ERROR_INIT_FAILED;
}
/*读取像素数据到AVFrame*/
for (int frameIdx = 0; frameIdx < frameToEncode;frameIdx++)
{
//初始化AVPacket
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
//读取像素数据(YUV三个分量)到AVFrame
read_yuv_data(0);
read_yuv_data(1);
read_yuv_data(2);
frame->pts = frameIdx;//显示时间戳,这一帧要显示的时间
/*编码*/
//AVFrame的像素数据编码为AVPacket的码流数据
//got_packet:是否已经完成编码出一个完整的数据包
if(avcodec_encode_video2(codecCtx, &pkt, frame, &got_packet) < 0)
{
return FF_ERROR_ENCODING_FAILED;
}
if (got_packet)
{
printf("write packet of frame %d,size = %d\n", frameIdx, pkt.size);
//写入out.h264文件
fwrite(pkt.data, 1, pkt.size,pFout);
//释放pkt
av_packet_unref(&pkt);
}
}
/*编码器可能还保留着没有输出的数据*/
for (got_packet = 1; got_packet;)
{
//frame传入NULL,不从frame里面读取数据,编码自己内部缓存的数据
if (avcodec_encode_video2(codecCtx, &pkt, NULL, &got_packet) < 0)
{
return FF_ERROR_ENCODING_FAILED;
}
if (got_packet)
{
printf("write cached packet size = %d\n",pkt.size);
//写入out.h264文件
fwrite(pkt.data, 1, pkt.size, pFout);
//释放pkt
av_packet_unref(&pkt);
}
}
/*收尾工作*/
//关闭输入输出文件
fclose(pFin);
fclose(pFout);
avcodec_close(codecCtx);//关闭AVCodecConetxt
av_free(codecCtx);//释放codecCtx
av_freep(&frame->data[0]);//释放frame保存像素数据的地址空间
av_frame_free(&frame);//释放frame
return 0;
}
int read_yuv_data(int color)
{
//color = 0; Y分量
//color = 1; U分量
//color = 2; V分量
//色度分量的采样分辨率是亮度分量的1/2,宽和高分别为1/2
int color_height = color == 0 ? frameHeight : frameHeight / 2;
int color_width = color == 0 ? frameWidth : frameWidth / 2;
int color_size = color_height*color_width;//图片大小
int color_stride = frame->linesize[color];//缓存的宽度:为像素的宽度加上外边框
//像素分量在AVFrame中连续存放的,内存边缘没有填充的数据
if (color_width == color_stride)
{
fread_s(frame->data[color], color_size, 1, color_size, pFin);
}
else
{
//一行一行读取
for (int row_idx = 0; row_idx < color_height; row_idx++)
{
fread_s(frame->data[color] + row_idx*color_stride, color_width, 1, color_width, pFin);
}
}
return color_size;
}