利用FFmpge进行视频压缩(从图像到H264视频流)

    对于FFmpeg相信做视频或图像处理这一块的都不会陌生,在网上也能找到很多相关的代码,但由于版本不同等原因,往往找到的代码都是需要自行修改才可以用,为此本人希望能尽绵薄之力,将开发包和自行编写的代码都放出来,如果初学者想要可以直接运行的代码做参考的话,可以下载我放出的FFmpeg开发包进行配置(配置的教程地址如下:点击打开链接),然后参考我写的编解码代码来进行程序的开发。

    下面贴出的是我自己封装的FFmpeg视频压缩代码,如有更好的建议请告诉我,转载请注明出处。

    首先我们设计一个视频压缩相关的类,定义如下

 

class Ffmpeg_Encoder
{
public:
	AVFrame *m_pYUVFrame;	//帧对象
	AVCodec *pCodecH264;	//编码器
	AVCodecContext *c;		//编码器数据结构对象
	uint8_t *yuv_buff;      //yuv图像数据区
	uint8_t *rgb_buff;		//rgb图像数据区
	SwsContext *scxt;		//图像格式转换对象
	uint8_t *outbuf;		//编码出来视频数据缓存
	int outbuf_size;        //编码输出数据去大小
	int nDataLen;			//rgb图像数据区长度
	int width;				//输出视频宽度
	int height;				//输出视频高度
	AVPacket pkt;			//数据包对象
	int imgwidthlen;		//图像宽度占用空间
public:
	void Ffmpeg_Encoder_Init();//初始化
	void Ffmpeg_Encoder_Setpara(AVCodecID mycodeid, int vwidth, int vheight);//设置参数,第一个参数为编码器,第二个参数为压缩出来的视频的宽度,第三个视频则为其高度
	void Ffmpeg_Encoder_Encode(FILE *file, uint8_t *data);//编码并写入数据到文件
	void Ffmpeg_Encoder_Close();//关闭
};

 

 

    对类中声明的四个函数进行定义

 

void Ffmpeg_Encoder::Ffmpeg_Encoder_Init()
{
	av_register_all();
	avcodec_register_all();
	m_pYUVFrame = new AVFrame[1];//YUV帧数据赋值  
	c = NULL;//解码器指针对象赋初值
}

 

void Ffmpeg_Encoder::Ffmpeg_Encoder_Setpara(AVCodecID mycodeid, int vwidth, int vheight)
{
	pCodecH264 = avcodec_find_encoder(mycodeid);//查找h264编码器
	if (!pCodecH264)
	{
		fprintf(stderr, "h264 codec not found\n");
		exit(1);
	}
	width = vwidth;
	height = vheight;

	c = avcodec_alloc_context3(pCodecH264);//函数用于分配一个AVCodecContext并设置默认值,如果失败返回NULL,并可用av_free()进行释放
	c->bit_rate = 1024*1024; //码率,越高视频质量越好
	c->width = vwidth;//设置编码视频宽度 
	c->height = vheight; //设置编码视频高度
	c->time_base.den = 25;//设置帧率,num为分子和den为分母,如果是1/25则表示25帧/s
	c->time_base.num = 1;
	c->gop_size = 10; //设置GOP大小,该值表示每10帧会插入一个I帧
	c->max_b_frames = 1;//设置B帧最大数,该值表示在两个非B帧之间,所允许插入的B帧的最大帧数
	c->pix_fmt = AV_PIX_FMT_YUV420P;//设置像素格式

	imgwidthlen = c->width * 3;
	av_opt_set(c->priv_data, "tune", "zerolatency", 0);//设置编码器的延时,解决前面的几十帧不出数据的情况

	if (avcodec_open2(c, pCodecH264, NULL) < 0)//打开编码器
		return;

	nDataLen = vwidth*vheight * 3;//计算图像rgb数据区长度

	yuv_buff = new uint8_t[nDataLen/2];//初始化数据区,为yuv图像帧准备填充缓存
	rgb_buff = new uint8_t[nDataLen];//初始化数据区,为rgb图像帧准备填充缓存
	outbuf_size = 100000;初始化编码输出数据区
	outbuf = new uint8_t[outbuf_size];

	scxt = sws_getContext(c->width, c->height, AV_PIX_FMT_BGR24, c->width, c->height, AV_PIX_FMT_YUV420P, SWS_POINT, NULL, NULL, NULL);//初始化格式转换函数
	av_image_fill_arrays(m_pYUVFrame->data, m_pYUVFrame->linesize, yuv_buff, AV_PIX_FMT_YUV420P, width, height, 1);
}
 
void Ffmpeg_Encoder::Ffmpeg_Encoder_Encode(FILE *file, uint8_t *data)
{
	av_init_packet(&pkt);
	memcpy(rgb_buff, data, nDataLen);//拷贝图像数据到rgb图像帧缓存中准备处理   
	sws_scale(scxt, &rgb_buff, &imgwidthlen, 0, c->height, m_pYUVFrame->data, m_pYUVFrame->linesize);// 将RGB转化为YUV  
	
	int myoutputlen = 0;
	int returnvalue = avcodec_encode_video2(c, &pkt, m_pYUVFrame, &myoutputlen);
	if (returnvalue == 0)
	{
		fwrite(pkt.data, 1, pkt.size, file);
	}
}
 
void Ffmpeg_Encoder::Ffmpeg_Encoder_Close()
{
	delete[]m_pYUVFrame;
	delete[]rgb_buff;
	delete[]yuv_buff;
	delete[]outbuf;
	sws_freeContext(scxt);
	avcodec_close(c);//关闭编码器
	av_free(c);
}
 

    最后我们只需要在主函数对这几个函数进行调用就可以了,由于本人为了方便直接用OpencCV来打开图像并取得图像的数据区,所以如果希望直接运行本人后面所发的工程的话还需要自行去配置OpenCV,不过这个在网上实在是说烂了,随便找找就能配置出来。如果不希望用OpenCV来打开图像的话,本人在代码中也注明了应该修改的位置,可自行想办法得到图像数据区并放入视频压缩函数即可。

void main()
{
	Ffmpeg_Encoder ffmpegobj;
	//图象编码
	FILE *f = NULL;
	char * filename = "myData.h264";
	fopen_s(&f, filename, "wb");//打开文件存储编码完成数据

	IplImage* img = NULL;//OpenCV图像数据结构指针  
	
	int picturecount = 1;
	bool firstencode = true;
	/**此部分用的是OpenCV读入图像对象并取得图像的数据区,也可以用别的方法获得图像数据区**/
	img = cvLoadImage("1.png", 1);//打开图像
	while (picturecount != 60)
	{
		if (firstencode)
		{
			firstencode = false;
			ffmpegobj.Ffmpeg_Encoder_Init();//初始化编码器
			ffmpegobj.Ffmpeg_Encoder_Setpara(AV_CODEC_ID_H264, img->width, img->height);//设置编码器参数
		}
		ffmpegobj.Ffmpeg_Encoder_Encode(f, (uchar*)img->imageData);//编码
		picturecount++;
	}
	fclose(f);
	ffmpegobj.Ffmpeg_Encoder_Close();
	cvReleaseImage(&img);//释放图像数据结构指针对像所指内容 
}

 

 

 

    OK,到此用FFmpeg进行视频压缩的博客就告一段落了,下面是整个工程的下载地址:点击打开链接,不过如果想要运行起来配置OpenCV还是需要自行动手的哈哈。

    另外在做项目的过程中在编码这块还遇到一个很奇怪的问题,就是在编码调用到avcodec_encode_video2特别是工程也调用到解码函数的时候,在VS里面点击运行是没有问题的,但一运行release文件夹下面编译出来的exe文件,程序偶尔会报内存错误,错误的位置在avcodec_encode_video2这个函数里面。这个让人百思不得其解,后来想想或者VS在运行的时候兼容性比较好,所以没有这个问题,是故如下图对exe文件的兼容性进行设置

利用FFmpge进行视频压缩(从图像到H264视频流)_第1张图片
    这个问题就解决了,是故怀疑ffmpeg库的avcodec_encode_video2这个函数所在的dll
对于windows或者是windows64位系统的兼容性不大好。
  


    最新版本的FFmpeg(4.0.2)由于修改了很多接口,导致原来的函数有些不能用了,因此本人结合官方提供的sample,修改出一个编码图像为264视频的程序,代码如下:

#include 
#include 
#include 

#include //OpenCV包含头文件  
#include  
#include   

using namespace cv;

#ifndef INT64_C 
#define INT64_C(c) (c ## LL) 
#define UINT64_C(c) (c ## ULL) 
#endif 

#ifdef __cplusplus 
extern "C" {
#endif 
	/*Include ffmpeg header file*/
#include  
#include  
#include  

#include   
#include      
#include    
#include 

#ifdef __cplusplus 
}
#endif 

void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,FILE *outfile)
{
	int ret;

	/* send the frame to the encoder */
	if (frame)
		printf("Send frame %3\n", frame->pts);

	ret = avcodec_send_frame(enc_ctx, frame);
	if (ret < 0) {
		fprintf(stderr, "Error sending a frame for encoding\n");
		exit(1);
	}

	while (ret >= 0) {
		ret = avcodec_receive_packet(enc_ctx, pkt);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			return;
		else if (ret < 0) {
			fprintf(stderr, "Error during encoding\n");
			exit(1);
		}

		printf("Write packet %3(size=%5d)\n", pkt->pts, pkt->size);
		fwrite(pkt->data, 1, pkt->size, outfile);
		av_packet_unref(pkt);
	}
}

int main(int argc, char **argv)
{
	Mat img = imread("1.jpg");
	char filename[100]="1.264";
	char *codec_name;
	const AVCodec *codec;
	AVCodecContext *c = NULL;
	int i, ret, x, y;
	FILE *f;
	AVFrame *frame;
	AVPacket *pkt;
	uint8_t endcode[] = { 0, 0, 1, 0xb7 };

	/* find the mpeg1video encoder */
	codec = avcodec_find_encoder(AV_CODEC_ID_H264);

	c = avcodec_alloc_context3(codec);
	if (!c) {
		fprintf(stderr, "Could not allocate video codec context\n");
		exit(1);
	}

	pkt = av_packet_alloc();
	if (!pkt)
		exit(1);

	/* put sample parameters */
	c->bit_rate = 1024*1024;
	/* resolution must be a multiple of two */
	c->width = img.cols;
	c->height = img.rows;
	/* frames per second */
	c->time_base.den=25;
	c->time_base.num = 1;
	c->framerate.den = 1;
	c->framerate.num = 25;


	/* emit one intra frame every ten frames
	 * check frame pict_type before passing frame
	 * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I
	 * then gop_size is ignored and the output of encoder
	 * will always be I frame irrespective to gop_size
	 */
	c->gop_size = 10;
	c->max_b_frames = 1;
	c->pix_fmt = AV_PIX_FMT_YUV420P;

	if (codec->id == AV_CODEC_ID_H264)
		av_opt_set(c->priv_data, "preset", "slow", 0);

	/* open it */
	ret = avcodec_open2(c, codec, NULL);
	if (ret < 0) {
		fprintf(stderr, "Could not open codec");
		exit(1);
	}

	fopen_s(&f,filename, "wb");
	if (!f) {
		fprintf(stderr, "Could not open %s\n", filename);
		exit(1);
	}

	frame = av_frame_alloc();
	if (!frame) {
		fprintf(stderr, "Could not allocate video frame\n");
		exit(1);
	}
	frame->format = c->pix_fmt;
	frame->width = c->width;
	frame->height = c->height;

	ret = av_frame_get_buffer(frame, 32);
	if (ret < 0) {
		fprintf(stderr, "Could not allocate the video frame data\n");
		exit(1);
	}

	//初始化格式转换器
	struct SwsContext *sws_ctx;
	enum AVPixelFormat src_pix_fmt = AV_PIX_FMT_BGR24;
	enum AVPixelFormat dst_pix_fmt = AV_PIX_FMT_YUV420P;
	sws_ctx=sws_getContext(c->width, c->height, src_pix_fmt,
		c->width, c->height, dst_pix_fmt,
		SWS_POINT, NULL, NULL, NULL);

	/* encode 1 second of video */
	for (i = 0; i < 25; i++) {
		fflush(stdout);

		/* make sure the frame data is writable */
		ret = av_frame_make_writable(frame);
		if (ret < 0)
			exit(1);

		//RGB转YUV
		int imgwidthlen = img.step;
		uint8_t *imgdata = new uint8_t[img.rows*img.step];
		memcpy(imgdata,img.data, img.rows*img.step);
		//imshow("123",img);
		//waitKey();
		sws_scale(sws_ctx, &imgdata,
			&imgwidthlen, 0, c->height, frame->data, frame->linesize);

		frame->pts = i;

		/* encode the image */
		encode(c, frame, pkt, f);
	}

	/* flush the encoder */
	encode(c, NULL, pkt, f);

	/* add sequence end code to have a real MPEG file */
	fwrite(endcode, 1, sizeof(endcode), f);
	fclose(f);

	avcodec_free_context(&c);
	av_frame_free(&frame);
	av_packet_free(&pkt);

	img.release();
	return 0;
}

    当然也可以像上面那样把整块编码代码封装成类,以供调用,代码如下:

#ifndef INT64_C 
#define INT64_C(c) (c ## LL) 
#define UINT64_C(c) (c ## ULL) 
#endif 

#ifdef __cplusplus 
extern "C" 
{
#endif 
	/*Include ffmpeg header file*/
#include  
#include  
#include  

#include   
#include      
#include    
#include 

#ifdef __cplusplus 
}
#endif 

class Ffmpeg_Encoder
{
public:
	AVCodec *codec;//编码器
	AVCodecContext *c = NULL;//编码数据结构对象
	AVFrame *frame;
	AVPacket *pkt;//数据包
	int ret;//返回值
	struct SwsContext *sws_ctx;//格式转换器
	enum AVPixelFormat src_pix_fmt;//格式对象
	enum AVPixelFormat dst_pix_fmt;//格式对象
	int imgwidthlen;//行宽度
	int pts = 0;//时间戳对象
public:
	void Ffmpeg_Encoder_Init(int vWidth, int vHeight)//设置参数,第一个参数为编码器,第二个参数为压缩出来的视频的宽度,第三个视频则为其高度
	{
		codec = avcodec_find_encoder(AV_CODEC_ID_H264);//查找编码器
		c = avcodec_alloc_context3(codec);//用编码器初始化编码数据结构对象
		if (!c) 
		{
			printf("Could not allocate video codec context\n");
			exit(1);
		}
		pkt = av_packet_alloc();//初始化包
		if (!pkt)
			exit(1);
		//设置编码属性
		c->bit_rate = 1024 * 1024;//码率
		c->width = vWidth;//视频宽
		c->height = vHeight;//视频高
		c->time_base.den = 25;//视频每秒帧数
		c->time_base.num = 1;
		c->framerate.den = 1;//视频帧速
		c->framerate.num = 25;
		c->gop_size = 10;设置GOP大小,该值表示每10帧会插入一个I帧
		c->max_b_frames = 1;//设置B帧最大数,该值表示在两个非B帧之间,所允许插入的B帧的最大帧数
		c->pix_fmt = AV_PIX_FMT_YUV420P;//设置像素格式
		if (codec->id == AV_CODEC_ID_H264)//设置编码器为非延时格式
			av_opt_set(c->priv_data, "tune", "zerolatency", 0);
		ret = avcodec_open2(c, codec, NULL);//打开编码器
		if (ret < 0) 
		{
			printf( "Could not open codec");
			exit(1);
		}
		//设置待编码帧对象
		frame = av_frame_alloc();
		if (!frame) 
		{
			printf("Could not allocate video frame\n");
			exit(1);
		}
		frame->format = c->pix_fmt;
		frame->width = c->width;
		frame->height = c->height;
		ret = av_frame_get_buffer(frame, 32);
		if (ret < 0) 
		{
			printf("Could not allocate the video frame data\n");
			exit(1);
		}
		//初始化格式对象
		src_pix_fmt = AV_PIX_FMT_BGR24;
		dst_pix_fmt = AV_PIX_FMT_YUV420P;
		//初始化格式转换器
		sws_ctx = sws_getContext(c->width, c->height, src_pix_fmt,c->width, c->height, dst_pix_fmt,SWS_POINT, NULL, NULL, NULL);
		//初始化行宽度
		imgwidthlen = vWidth * 3;
	}

	void Ffmpeg_Encoder_Encode(FILE *file, uint8_t *data)//编码并写入数据到文件
	{
		ret = av_frame_make_writable(frame);//检测帧对象是否可读
		if (ret < 0)
			exit(1);
		sws_scale(sws_ctx, &data,&imgwidthlen, 0, c->height, frame->data, frame->linesize);//像素格式转换
		frame->pts = pts++;
		ret = avcodec_send_frame(c, frame);
		if (ret < 0) 
		{
			printf("Error sending a frame for encoding\n");
			exit(1);
		}
		while (ret >= 0) 
		{
			ret = avcodec_receive_packet(c, pkt);
			if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
				return;
			else if (ret < 0) 
			{
				printf("Error during encoding\n");
				exit(1);
			}
			printf("Write packet %3(size=%5d)\n", pkt->pts, pkt->size);
			fwrite(pkt->data, 1, pkt->size, file);
			av_packet_unref(pkt);
		}
	}
	void Ffmpeg_Encoder_Close()//关闭
	{
		avcodec_free_context(&c);
		av_frame_free(&frame);
		av_packet_free(&pkt);
	}
};

调用代码如下:

#include "ffmpegEncode.hpp"
#include //OpenCV包含头文件  
#include  
#include   

using namespace cv;
void main()
{
	Ffmpeg_Encoder ffmpegobj;
	//图象编码
	FILE *f = NULL;
	char filename[100] = "1.h264";
	fopen_s(&f, filename, "wb");//打开文件存储编码完成数据

	int picturecount = 1;
	bool firstencode = true;
	/**此部分用的是OpenCV读入图像对象并取得图像的数据区,也可以用别的方法获得图像数据区**/
	Mat img = imread("1.jpg", 1);//打开图像
	ffmpegobj.Ffmpeg_Encoder_Init(img.cols, img.rows);//初始化编码器

	while (picturecount != 600)
	{
		ffmpegobj.Ffmpeg_Encoder_Encode(f, img.data);//编码
		picturecount++;
	}
	fclose(f);
	ffmpegobj.Ffmpeg_Encoder_Close();
	img.release();
}

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(FFmpeg学习)