使用FFmpeg库实现视频编码

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

你可能感兴趣的:(FFmpeg)