最簡單的基於FFmpeg的視頻編碼器-更新版(YUV編碼爲HEVC(H.265))

=====================================================
git

最簡單的基於FFmpeg的視頻編碼器文章列表:
github

最簡單的基於FFMPEG的視頻編碼器(YUV編碼爲H.264)
ide

最簡單的基於FFmpeg的視頻編碼器-更新版(YUV編碼爲HEVC(H.265))
函數

最簡單的基於FFmpeg的編碼器-純淨版(不包含libavformat)
post

=====================================================學習


前一陣子作過一個基於FFmpeg的視頻編碼器的例子:測試

最簡單的基於FFMPEG的視頻編碼器(YUV編碼爲H.264)
在該例子中,能夠將YUV像素數據(YUV420P)編碼爲H.264碼流。由於現在FFmpeg已經實現了對libx265的支持,所以對上述編碼H.264的例子進行了升級,使之變成編碼H.265(HEVC)的例子。

比較早的FFmpeg的類庫(大約幾個月之前的版本,我這裏編譯時間是2014.05.06)對H.265的編碼支持有問題。開始調試的時候,覺得是本身的代碼有問題,幾經修改也沒有找到解決方法。最終發現是類庫自己的問題,更換新版本的類庫(我這裏編譯時間是2014.09.16)後問題解決。ui


流程

下面附上一張FFmpeg編碼視頻的流程圖。經過該流程,不只能夠編碼H.264/H.265的碼流,並且能夠編碼MPEG4/MPEG2/VP9/VP8等多種碼流。實際上使用FFmpeg編碼視頻的方式都是同樣的。圖中藍色背景的函數是實際輸出數據的函數。淺綠色的函數是視頻編碼的函數。編碼


 


簡單介紹一下流程中各個函數的意義(上一篇YUV編碼爲H.264的文章中已經寫過一遍,這裏複製粘貼一下):
av_register_all():註冊FFmpeg全部編解碼器。
avformat_alloc_output_context2():初始化輸出碼流的AVFormatContext。
avio_open():打開輸出文件。
av_new_stream():建立輸出碼流的AVStream。
avcodec_find_encoder():查找編碼器。
avcodec_open2():打開編碼器。
avformat_write_header():寫文件頭(對於某些沒有文件頭的封裝格式,不須要此函數。好比說MPEG2TS)。
avcodec_encode_video2():編碼一幀視頻。即將AVFrame(存儲YUV像素數據)編碼爲AVPacket(存儲H.264等格式的碼流數據)。
av_write_frame():將編碼後的視頻碼流寫入文件。
flush_encoder():輸入的像素數據讀取完成後調用此函數。用於輸出編碼器中剩餘的AVPacket。
av_write_trailer():寫文件尾(對於某些沒有文件頭的封裝格式,不須要此函數。好比說MPEG2TS)。


代碼

下面直接貼上代碼
/**
 * 最簡單的基於FFmpeg的視頻編碼器
 * Simplest FFmpeg Video Encoder
 * 
 * 雷霄驊 Lei Xiaohua
 * leixiaohua1020@126.com
 * 中國傳媒大學/數字電視技術
 * Communication University of China / Digital TV Technology
 * http://blog.csdn.net/leixiaohua1020
 * 
 * 本程序實現了YUV像素數據編碼爲視頻碼流(HEVC(H.265),H264,MPEG2,VP8等等)。
 * 是最簡單的FFmpeg視頻編碼方面的教程。
 * 經過學習本例子能夠了解FFmpeg的編碼流程。
 * This software encode YUV420P data to HEVC(H.265) bitstream (or
 * H.264, MPEG2, VP8 etc.).
 * It's the simplest video encoding software based on FFmpeg. 
 * Suitable for beginner of FFmpeg 
 */

#include <stdio.h>

extern "C"
{
#include "libavutil\opt.h"
#include "libavcodec\avcodec.h"
#include "libavformat\avformat.h"
#include "libswscale\swscale.h"
};


int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index)
{
	int ret;
	int got_frame;
	AVPacket enc_pkt;
	if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
		CODEC_CAP_DELAY))
		return 0;
	while (1) {
		printf("Flushing stream #%u encoder\n", stream_index);
		//ret = encode_write_frame(NULL, stream_index, &got_frame);
		enc_pkt.data = NULL;
		enc_pkt.size = 0;
		av_init_packet(&enc_pkt);
		ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
			NULL, &got_frame);
		av_frame_free(NULL);
		if (ret < 0)
			break;
		if (!got_frame){
			ret=0;
			break;
		}
		printf("Succeed to encode 1 frame! 編碼成功1幀!\n");
		/* mux encoded frame */
		ret = av_write_frame(fmt_ctx, &enc_pkt);
		if (ret < 0)
			break;
	}
	return ret;
}

int main(int argc, char* argv[])
{
	AVFormatContext* pFormatCtx;
	AVOutputFormat* fmt;
	AVStream* video_st;
	AVCodecContext* pCodecCtx;
	AVCodec* pCodec;

	uint8_t* picture_buf;
	AVFrame* picture;
	int size;

	//FILE *in_file = fopen("src01_480x272.yuv", "rb");	//Input YUV data 視頻YUV源文件 
	FILE *in_file = fopen("ds_480x272.yuv", "rb");	//Input YUV data 視頻YUV源文件 
	int in_w=480,in_h=272;//寬高	
	//Frames to encode
	int framenum=100;
	//const char* out_file = "src01.h264";	//Output Filepath 輸出文件路徑
	//const char* out_file = "src01.ts";
	//const char* out_file = "src01.hevc";
	const char* out_file = "ds.hevc";

	av_register_all();
	//Method1 方法1.組合使用幾個函數
	pFormatCtx = avformat_alloc_context();
	//Guess Format 猜格式
	fmt = av_guess_format(NULL, out_file, NULL);
	pFormatCtx->oformat = fmt;
	
	//Method 2 方法2.更加自動化一些
	//avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);
	//fmt = pFormatCtx->oformat;


	//Output Format 注意輸出路徑
	if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0)
	{
		printf("Failed to open output file! 輸出文件打開失敗");
		return -1;
	}

	video_st = avformat_new_stream(pFormatCtx, 0);
	video_st->time_base.num = 1; 
	video_st->time_base.den = 25;  

	if (video_st==NULL)
	{
		return -1;
	}
	//Param that must set
	pCodecCtx = video_st->codec;
	//pCodecCtx->codec_id =AV_CODEC_ID_HEVC;
	pCodecCtx->codec_id = fmt->video_codec;
	pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
	pCodecCtx->pix_fmt = PIX_FMT_YUV420P;
	pCodecCtx->width = in_w;  
	pCodecCtx->height = in_h;
	pCodecCtx->time_base.num = 1;  
	pCodecCtx->time_base.den = 25;  
	pCodecCtx->bit_rate = 400000;  
	pCodecCtx->gop_size=250;
	//H264
	//pCodecCtx->me_range = 16;
	//pCodecCtx->max_qdiff = 4;
	//pCodecCtx->qcompress = 0.6;
	pCodecCtx->qmin = 10;
	pCodecCtx->qmax = 51;

	//Optional Param
	pCodecCtx->max_b_frames=3;

	// Set Option
	AVDictionary *param = 0;
	//H.264
	if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {
		av_dict_set(?m, "preset", "slow", 0);
		av_dict_set(?m, "tune", "zerolatency", 0);
	}
	//H.265
	if(pCodecCtx->codec_id == AV_CODEC_ID_H265){
		av_dict_set(?m, "x265-params", "qp=20", 0);
		av_dict_set(?m, "preset", "ultrafast", 0);
		av_dict_set(?m, "tune", "zero-latency", 0);
	}

	//Dump Information 輸出格式信息
	av_dump_format(pFormatCtx, 0, out_file, 1);

	pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
	if (!pCodec){
		printf("Can not find encoder! 沒有找到合適的編碼器!\n");
		return -1;
	}
	if (avcodec_open2(pCodecCtx, pCodec,?m) < 0){
		printf("Failed to open encoder! 編碼器打開失敗!\n");
		return -1;
	}
	


	picture = avcodec_alloc_frame();
	size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
	picture_buf = (uint8_t *)av_malloc(size);
	avpicture_fill((AVPicture *)picture, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);

	//Write File Header 寫文件頭
	avformat_write_header(pFormatCtx,NULL);

	AVPacket pkt;
	int y_size = pCodecCtx->width * pCodecCtx->height;
	av_new_packet(&pkt,y_size*3);

	for (int i=0; i<framenum; i++){
		//Read YUV 讀入YUV
		if (fread(picture_buf, 1, y_size*3/2, in_file) < 0){
			printf("Failed to read YUV data! 文件讀取錯誤\n");
			return -1;
		}else if(feof(in_file)){
			break;
		}
		picture->data[0] = picture_buf;  // 亮度Y
		picture->data[1] = picture_buf+ y_size;  // U 
		picture->data[2] = picture_buf+ y_size*5/4; // V
		//PTS
		picture->pts=i;
		int got_picture=0;
		//Encode 編碼
		int ret = avcodec_encode_video2(pCodecCtx, &pkt,picture, &got_picture);
		if(ret < 0){
			printf("Failed to encode! 編碼錯誤!\n");
			return -1;
		}
		if (got_picture==1){
			printf("Succeed to encode 1 frame! 編碼成功1幀!\n");
			pkt.stream_index = video_st->index;
			ret = av_write_frame(pFormatCtx, &pkt);
			av_free_packet(&pkt);
		}
	}
	//Flush Encoder
	int ret = flush_encoder(pFormatCtx,0);
	if (ret < 0) {
		printf("Flushing encoder failed\n");
		return -1;
	}

	//Write file trailer 寫文件尾
	av_write_trailer(pFormatCtx);

	//Clean 清理
	if (video_st){
		avcodec_close(video_st->codec);
		av_free(picture);
		av_free(picture_buf);
	}
	avio_close(pFormatCtx->pb);
	avformat_free_context(pFormatCtx);

	fclose(in_file);

	return 0;
}


結果

軟件運行截圖(受限於文件體積,原始YUV幀數只有100幀):spa


此次換了個有趣點的YUV序列。以前老是看YUV標準測試序列都已經看煩了,此次換個電視劇裏的序列相對更加生動一些。YUV序列以下圖所示。

 
編碼後的HEVC(H.265)碼流:
 

下載


Simplest ffmpeg video encoder


項目主頁

SourceForge:https://sourceforge.net/projects/simplestffmpegvideoencoder/

Github:https://github.com/leixiaohua1020/simplest_ffmpeg_video_encoder

開源中國:http://git.oschina.net/leixiaohua1020/simplest_ffmpeg_video_encoder


CSDN下載地址:http://download.csdn.net/detail/leixiaohua1020/8001515


本程序實現了YUV像素數據編碼爲視頻碼流(H.265,H264,MPEG2,VP8等等)。是最簡單的FFmpeg視頻編碼方面的教程。

它包含如下兩個子項目:

simplest_ffmpeg_video_encoder:最簡單的基於FFmpeg的視頻編碼器。使用libavcodec和libavformat編碼而且封裝視頻。
simplest_ffmpeg_video_encoder_pure:最簡單的基於FFmpeg的視頻編碼器-純淨版。僅使用libavcodec編碼視頻,不使用libavformat。


更新-1.1 (2015.1.03)=========================================

增長了《最簡單的基於FFmpeg的編碼器-純淨版(不包含libavformat)》中的simplest_ffmpeg_video_encoder_pure工程。

CSDN下載地址:http://download.csdn.net/detail/leixiaohua1020/8322003


更新-1.2 (2015.2.13)=========================================

此次考慮到了跨平臺的要求,調整了源代碼。通過此次調整以後,源代碼能夠在如下平臺編譯經過:

VC++:打開sln文件便可編譯,無需配置。

cl.exe:打開compile_cl.bat便可命令行下使用cl.exe進行編譯,注意可能須要按照VC的安裝路徑調整腳本里面的參數。編譯命令以下。

::VS2010 Environment
call "D:\Program Files\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"
::include
@set INCLUDE=include;%INCLUDE%
::lib
@set LIB=lib;%LIB%
::compile and link
cl simplest_ffmpeg_video_encoder.cpp /link avcodec.lib avformat.lib avutil.lib ^
avdevice.lib avfilter.lib postproc.lib swresample.lib swscale.lib /OPT:NOREF

MinGW:MinGW命令行下運行compile_mingw.sh便可使用MinGW的g++進行編譯。編譯命令以下。

g++ simplest_ffmpeg_video_encoder.cpp -g -o simplest_ffmpeg_video_encoder.exe \
-I /usr/local/include -L /usr/local/lib \
-lavformat -lavcodec -lavutil

GCC:Linux或者MacOS命令行下運行compile_gcc.sh便可使用GCC進行編譯。編譯命令以下。

gcc simplest_ffmpeg_video_encoder.cpp -g -o simplest_ffmpeg_video_encoder.out \
-I /usr/local/include -L /usr/local/lib -lavformat -lavcodec -lavutil

PS:相關的編譯命令已經保存到了工程文件夾中


CSDN下載地址:http://download.csdn.net/detail/leixiaohua1020/8444967

SourceForge上已經更新。