FFmpeg 264编码保存mp4文件示例

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xubuwei/article/details/83994044

这里使用FFmpeg 3版本的新接口来做编码工作。其中需要注意的是编码是有缓存延迟的,因此在最后需要重复给空帧并且不断读取解码帧来完成最后的缓存输出。
参考:
https://blog.csdn.net/hb707934728/article/details/81476756
https://blog.csdn.net/dangxw_/article/details/50974677

示例代码:

encoder_work::encoder_work()
{
	mWidth = 0;
	mHeight = 0;
	mFPS = 0;
	mYSize = 0;
	mUVSize = 0;
	mPTS = 0;
	mFmtCtx = NULL;
	mEncCtx = NULL;
	mYUVFrm = NULL;
	mEncoder = NULL;
}


int encoder_work::init(int w, int h, int fps, int bit_rate, char *outfile_name)
{
	printf("encoder work init========>\n");
	uninit();

	mWidth = w;
	mHeight = h;
	mFPS = fps;
	mYSize = w*h;
	mUVSize = mYSize / 4;
	
	//register file package like mp4 and all others
	av_register_all();
	//register all codecs
	avcodec_register_all();
	
	//1 create encoder
	mEncoder = avcodec_find_encoder(AV_CODEC_ID_H264);
	if (!mEncoder)
	{
		cout << " avcodec_find_encoder AV_CODEC_ID_H264 failed!" << endl;
		return -1;
	}
	//get encoder contex
	mEncCtx = avcodec_alloc_context3(mEncoder);
	if (!mEncCtx)
	{
		cout << " avcodec_alloc_context3 for encoder contx failed!" << endl;
		return -1;
	}
	//set encoder params
	//bit rate
	mEncCtx->bit_rate = bit_rate;
	
	mEncCtx->width = mWidth;
	mEncCtx->height = mHeight;
	mEncCtx->time_base = { 1,mFPS };
	
	//set gop size, in another way I frame gap
	mEncCtx->gop_size = 50;
	
	mEncCtx->max_b_frames = 0;
	mEncCtx->pix_fmt = AV_PIX_FMT_YUV420P;
	mEncCtx->codec_id = AV_CODEC_ID_H264;
	mEncCtx->thread_count = 4;
	mEncCtx->qmin = 10;
	mEncCtx->qmax = 51;
	mEncCtx->qcompress  = 0.6;

	//av_opt_set(mEncCtx->priv_data, "preset", "ultrafast", 0);
	av_opt_set(mEncCtx->priv_data, "tune", "zerolatency", 0);
	
	//global header info
	mEncCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
	
	//open encoder
	int ret = avcodec_open2(mEncCtx,mEncoder,NULL);
	if (ret < 0)
	{
		cout << " avcodec_open2 encoder failed!" << endl;
		return -1;
	}
	cout << "avcodec_open2 encoder success!" << endl;
	
	//2 create out context
	avformat_alloc_output_context2(&mFmtCtx, 0, 0, outfile_name);
	
	//3 add video stream
	mOutStm = avformat_new_stream(mFmtCtx,NULL);
	mOutStm->id = 0;
	mOutStm->codecpar->codec_tag = 0;
	avcodec_parameters_from_context(mOutStm->codecpar,mEncCtx);
	
	cout << "===============================================" << endl;
	av_dump_format(mFmtCtx, 0, outfile_name, 1);
	cout << "===============================================" << endl;
	
	//alloc output yuv frame
	mYUVFrm = av_frame_alloc();
	mYUVFrm->format = AV_PIX_FMT_YUV420P;
	mYUVFrm->width = mWidth;
	mYUVFrm->height = mHeight;
	//alloc frame buffer
	ret = av_frame_get_buffer(mYUVFrm,32);
	
	if (ret < 0)
	{
		av_frame_free(&mYUVFrm);
		mYUVFrm = NULL;
		cout << " av_frame_get_buffer  failed!" << endl;
		return -1;
	}
	
	//5 write mp4 head
	ret = avio_open(&mFmtCtx->pb,outfile_name,AVIO_FLAG_WRITE);
	if (ret < 0)
	{
		cout << " avio_open  failed!" << endl;
		return -1;
	}
	ret = avformat_write_header(mFmtCtx, NULL);
	if (ret < 0)
	{
		cout << " avformat_write_header  failed!" << endl;
		return -1;
	}

}

int encoder_work::uninit()
{
	printf("encoder_work uninit-----\n");
	if(mFmtCtx)
	{
		//wirte file trailer
		av_write_trailer(mFmtCtx);
		
		//close file IO
		avio_close(mFmtCtx->pb);

		//clean context
		avformat_free_context(mFmtCtx);

		mFmtCtx = NULL;
	}
	
	if(mEncCtx)
	{
		//close encoder
		avcodec_close(mEncCtx);
		
		//clean encoder contex
		avcodec_free_context(&mEncCtx);

		mEncCtx = NULL;
	}

	if(mYUVFrm)
	{
		av_frame_free(&mYUVFrm);
		mYUVFrm = NULL;
	}

	mWidth = 0;
	mHeight = 0;
	mFPS = 0;
	mYSize = 0;
	mUVSize = 0;
	mPTS = 0;

	return 0;
}

int encoder_work::process(cv::Mat &rgb)
{
	cv::Mat yuv;
	cv::cvtColor(rgb, yuv, cv::COLOR_BGR2YUV_I420);

	return process(yuv.data);
}

int encoder_work::process(unsigned char *yuvData)
{
	if(!mYUVFrm)
	{
		printf("not ready env\n");
		return -1;
	}

	memcpy(mYUVFrm->data[0], yuvData, mYSize);
	memcpy(mYUVFrm->data[1], yuvData+mYSize, mUVSize);
	memcpy(mYUVFrm->data[2], yuvData+mYSize+mUVSize, mUVSize);
	mYUVFrm->pts = mPTS;
	mPTS = mPTS + 4000;
	//mPTS = mPTS + 1000000;
	//send to encoder
	int ret = avcodec_send_frame(mEncCtx, mYUVFrm);
	if (ret != 0)
	{
		char err[64] = {0};
		av_strerror(ret, err, 64);
		printf("encoder send frame %d err:%s\n",mPTS-4000, err);
		return -1;
	}
	AVPacket pkt;
	av_init_packet(&pkt);
	//receive from encoder
	//note that frame will not be received immediately
	ret = avcodec_receive_packet(mEncCtx,&pkt);
	if (ret != 0)
	{
		printf("encoder recieve frame %d err\n",mPTS-4000);
		return -1;
	}
	//write encoded frame to file
	av_interleaved_write_frame(mFmtCtx,&pkt);
	av_free_packet(&pkt);
	return 0;
}

int encoder_work::flushOut()
{
	//as frames will be hysteresis, we need flush all remained frames
	AVPacket pkt;
	av_init_packet(&pkt);
	int ret = 0;
	while(ret >= 0)
	{
		//flush out rest frames, send NULL frame data
		avcodec_send_frame(mEncCtx, NULL);
		//receive frame from encoder
		ret = avcodec_receive_packet(mEncCtx,&pkt);
		if (ret != 0)
		{
			printf("encoder recieve frame %d err\n",mPTS-4000);
			break;
		}
		//wirte encoded frame to file
		av_interleaved_write_frame(mFmtCtx,&pkt);
		av_free_packet(&pkt);
	}
	return 0;
}

头文件定义

class encoder_work
{
public:
	encoder_work();
	~encoder_work() { uninit(); }

	int init(int w, int h, int fps, int bit_rate, char *outfile_name);
	int uninit();
	int process(unsigned char *yuvData);
	int process(cv::Mat &rgb);
	int flushOut();

private:
	int mWidth;
	int mHeight;
	int mFPS;
	int mYSize;
	int mUVSize;
	int mPTS;
	AVCodec *mEncoder;
	AVCodecContext *mEncCtx;
	AVFormatContext *mFmtCtx;
	AVStream *mOutStm;
	AVFrame *mYUVFrm;
};

涉及到的一些头文件:

#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/opt.h"

猜你喜欢

转载自blog.csdn.net/xubuwei/article/details/83994044