使用FFmpeg库实现视频编码

本文介绍使用FFmpeg进行视频编码的基本流程,包括所需结构、主要步骤及示例代码。涉及AVCodec、AVCodecContext等关键组件,展示了如何通过C语言实现从YUV文件到H.264视频流的编码过程。

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是一个开源的跨平台音视频处理工具,它提供了丰富的功能和接口,可以用于视频编码、解码、转码等操作。下面是使用FFmpeg进行视频编码的一般步骤: 1. 安装FFmpeg:首先需要下载并安装FFmpeg,可以从官方网站或者其他可靠的渠道获取。 2. 引入FFmpeg:在你的项目中引入FFmpeg,可以通过链接静态或者动态的方式进行。 3. 初始化FFmpeg:在使用FFmpeg之前,需要进行初始化操作。可以调用`av_register_all()`函数来注册所有的编解码器。 4. 打开输入文件:使用`avformat_open_input()`函数打开输入文件,并获取相关的输入流信息。 5. 查找视频流:通过遍历输入流信息,找到视频流对应的索引。 6. 解码视频帧:使用`avcodec_decode_video2()`函数解码视频帧,得到原始的视频帧数据。 7. 配置编码器:创建输出编码器上下文,并设置相关参数,如编码格式、分辨率、比特率等。 8. 打开输出文件:使用`avformat_alloc_output_context2()`函数创建输出文件上下文,并打开输出文件。 9. 写入头部信息:使用`avformat_write_header()`函数写入输出文件的头部信息。 10. 编码视频帧:将解码得到的原始视频帧数据进行编码,得到压缩后的视频帧数据。 11. 写入视频帧:使用`av_interleaved_write_frame()`函数将编码后的视频帧数据写入输出文件。 12. 写入尾部信息:使用`av_write_trailer()`函数写入输出文件的尾部信息。 13. 释放资源:释放所有的上下文和资源,包括输入文件、输出文件、编码器等。 这是一个简单的视频编码的流程,具体的实现可能会根据你的需求和具体情况有所不同。你可以参考FFmpeg的官方文档和示例代码来更详细地了解和学习如何使用FFmpeg进行视频编码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值