天天看點

h264編碼基礎h264編碼

h264編碼

    h264編碼10年屹立,優秀的東西總是一直在發光,相對于RGB和YUV,将資料成幾百倍的壓縮。

    初始化包含進來的資料格式,出去的資料格式,一般我們的資料如果為BGR格式,一個像素是有三個位元組組成的,那麼一副圖像比如720P是有多大呢,12807203 = 2764800 位元組 ,一秒鐘20幀數 ,那就是 2764800*20 = 55,296,000 位元組,也就是一秒鐘52M位元組,一分鐘産生的圖像資料為 3120M位元組,也就是3G 左右,第一步為轉碼為YUV420P,這個格式為RGB的一半,經過h264 編碼壓縮,比如壓縮為每秒鐘比特率為3Mbit,一分鐘也就是180Mbit,位元組為22M多,也就是3G 和 22M byte的對比。壓縮比到了 141:1。

    除了直接用libx264 編碼,也可以用ffmpeg,實際上ffmpeg封裝了libx264 libx265,甚至像intel Gpu qsv 和 cuda 硬體編碼。這裡使用軟編碼作為示例。

ffmpeg的作者之一Bellard 貝拉是一位極其著名優秀的軟體工程師,qemu 的作者,同時也出品了quickjs,有興趣可以看看他寫的東西。

1、編碼初始化

int Encodeh264::Encoder_Init(AVCodecID codec_id, 
	int in_w, 
	int in_h, 
	int fps, AVPixelFormat src_pix_fmt, 
	int dst_w, 
	int dst_h, AVPixelFormat dst_pix_fmt 
	)
{
	//<---------------找到編碼器-------------------->

	_pCodec = avcodec_find_encoder(codec_id);
	if (!_pCodec)
	{
		printf("Codec not found\n");
		return -1;
	}

	//<---------------申請編碼器上下文-------------------->
	_pCodecCtx = avcodec_alloc_context3(_pCodec);

	if (!_pCodecCtx)
	{
		printf("Could not allocate video codec context\n");
		return -1;
	}
	//編碼指定
	_pCodecCtx->bit_rate = 200*1000; //這個需要傳進來
	_pCodecCtx->width = in_w;
	_pCodecCtx->height = in_h;
	_pCodecCtx->time_base.num = 1;
	_pCodecCtx->time_base.den = fps;
	_pCodecCtx->framerate.num = fps;
	_pCodecCtx->framerate.den = 1;
	_pCodecCtx->gop_size = fps;
	_pCodecCtx->max_b_frames = 0; //b幀指定為0
	_pCodecCtx->pix_fmt = dst_pix_fmt;
	//線程指定
	_pCodecCtx->thread_count = 2;
	av_opt_set(_pCodecCtx->priv_data, "preset", "ultrafast", 0);
	av_opt_set(_pCodecCtx->priv_data, "tune", "zerolatency", 0);
	av_opt_set(_pCodecCtx->priv_data, "preset", "ultrafast", 0);
	_pCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

	//<--------------打開編碼器-------------------->
	if (avcodec_open2(_pCodecCtx, _pCodec, NULL) < 0)
	{
		printf("Could not open codec\n");
		return -1;
	}
	//<---------------申請src資料封裝格式-------------------->
	_AVFrame = av_frame_alloc();
	if (!_AVFrame)
	{
		printf("_AVFrame Could mot allocate video frame\n");
		return -1;
	}
	_AVFrame->format = src_pix_fmt;
	_AVFrame->width  = in_w;
	_AVFrame->height = in_h;
	int ret = av_frame_get_buffer(_AVFrame, 0);

	if (ret < 0)
	{
		printf(" _AVFrame Could not allocate the video frame data\n");
		return -1;
	}
	/* make sure the frame data is writable */
	ret = av_frame_make_writable(_AVFrame);

	if (ret < 0)
	{
		printf("_AVFrame set frame  writable  fail");
		return -1;
	}
	_AVFrame->pts = 0;

	//<---------------申請YUV420P資料封裝格式-------------------->
	_YUVFrame = av_frame_alloc();

	if (!_YUVFrame)
	{
		printf("_YUVFrame Could mot allocate video frame\n");
		return -1;
	}

	_YUVFrame->format = dst_pix_fmt;
	_YUVFrame->width = dst_w;
	_YUVFrame->height = dst_h;

	ret = av_frame_get_buffer(_YUVFrame, 0);

	if (ret < 0)
	{
		printf("_YUVFrame Could not allocate the video frame data\n");
		return -1;
	}
	/* make sure the frame data is writable */
	ret = av_frame_make_writable(_YUVFrame);

	if (ret < 0)
	{
		printf("_YUVFrame set frame  writable  fail");
		return -1;
	}
	_YUVFrame->pts = 0;
	//<---------------申請編碼後資料包-------------------->
	_img_converT_ctx = sws_getContext(in_w, in_h, src_pix_fmt, dst_w, dst_h, dst_pix_fmt, SWS_BICUBIC, NULL, NULL, NULL);
	return 0;
}
           

SWS_BICUBIC 這個參數是可以修改的,大變小和小變大可以選擇不同的參數,ffmpeg這個開源庫做得非常好,算法異常優秀,這個圖像轉換可以把能把比較模糊和扭曲的圖像變得更符合正常,看起來更為自然。

av_opt_set(_pCodecCtx->priv_data, "preset", "ultrafast", 0);
	av_opt_set(_pCodecCtx->priv_data, "tune", "zerolatency", 0);
	av_opt_set(_pCodecCtx->priv_data, "preset", "ultrafast", 0);
           

這三句話是用來實時編碼用的,可以修改,具體看ffmpeg的幫助。如果存檔案,可以将編碼從ultrafast 改成 slow等在碼率不變的情況下更加清晰。圖像的編碼首先是把BGR24這種圖像轉成YUV420P,然後将YUV420P壓縮成h26x

編碼

AVPacket *Encodeh264::Encode_H264(uint8_t *data)
{
	sws_scale(_img_converT_ctx, &data, _AVFrame->linesize, 0, _AVFrame->height, _YUVFrame->data, _YUVFrame->linesize);
	int ret = avcodec_send_frame(_pCodecCtx, _YUVFrame);
	if (ret < 0)
	{
		printf("Error sending a frame for encoding :%d \n", ret);
		return NULL;
	}
	else
	{
		AVPacket *pkt = av_packet_alloc();
		ret = avcodec_receive_packet(_pCodecCtx, pkt);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
		{
			av_packet_free(&pkt);
			return NULL;
		}
		else if (ret < 0)
		{
			av_packet_free(&pkt);
			printf("Error during encoding\n");
			return NULL;
		}
		else
		{
			//編碼完成
			_AVFrame->pts++;
			_YUVFrame->pts++; /*= v_framenum++;*/
			return pkt;
		}
	}
}
           

調用

Encodeh264  v_264_obj;
	if (v_264_obj.Encoder_Init(AV_CODEC_ID_H264,
			CANVAS_WIDTH,
			CANVAS_HEIGHT, fps,
			AV_PIX_FMT_BGR24,
			CANVAS_WIDTH,
			CANVAS_HEIGHT, AV_PIX_FMT_YUV420P) != 0)
		{
			std::cout << "Error ini h264 encoder" << std::endl;
			return -1;
		}

AVPacket *pkt = v_264_obj.Encode_H264(v_srcImg.data);
           

不要忘了釋放pkt。

繼續閱讀