From 520e146b80eee58f8034d99e47fdc9e2e66f0bf8 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Tue, 31 Aug 2021 21:13:42 -0400 Subject: [PATCH] Added X265 video encoder feature to AVI Qt GUI. --- README | 1 + src/CMakeLists.txt | 9 +- src/drivers/Qt/AviRecord.cpp | 176 +++++++++++++++++++++++++++++++++++ src/drivers/Qt/AviRecord.h | 3 + src/drivers/Qt/avi/gwavi.cpp | 4 + 5 files changed, 192 insertions(+), 1 deletion(-) diff --git a/README b/README index 96d476d5..2282aa79 100644 --- a/README +++ b/README @@ -27,6 +27,7 @@ Table of Contents * qt5 OR qt6 - (qt version >= 5.11 recommended) * liblua5.1 (optional) - Will statically link internally if the system cannot provide this. * libx264 (optional) - H.264 video encoder for avi recording (recommended) +* libx265 (optional) - H.265 video encoder for avi recording (recommended) * minizip * zlib * openGL diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 491d989a..797bf066 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -83,6 +83,13 @@ else(WIN32) add_definitions( -D_USE_X264 ${X264_CFLAGS} ) endif() + pkg_check_modules( X265 x265) + + if ( ${X265_FOUND} ) + message( STATUS "Using System X265 Encoder Library ${X265_VERSION}" ) + add_definitions( -D_USE_X265 ${X265_CFLAGS} ) + endif() + #pkg_check_modules( GL gl) # Use built in find package instead for OpenGL # Check for OpenGL @@ -540,7 +547,7 @@ target_link_libraries( ${APP_NAME} ${OPENGL_LDFLAGS} ${SDL2_LDFLAGS} ${MINIZIP_LDFLAGS} ${ZLIB_LIBRARIES} - ${LUA_LDFLAGS} ${X264_LDFLAGS} + ${LUA_LDFLAGS} ${X264_LDFLAGS} ${X265_LDFLAGS} ${SYS_LIBS} ) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index 2b111ec0..0daaa661 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -40,6 +40,9 @@ #ifdef _USE_X264 #include "x264.h" #endif +#ifdef _USE_X265 +#include "x265.h" +#endif #include "Qt/AviRecord.h" #include "Qt/avi/gwavi.h" @@ -323,6 +326,149 @@ static int close(void) }; // End X264 namespace #endif //************************************************************************************** +#ifdef _USE_X265 + +namespace X265 +{ +static x265_param param; +static x265_picture *pic = NULL; +static x265_picture pic_out; +static x265_encoder *hdl = NULL; +static x265_nal *nal = NULL; +static unsigned int i_nal = 0; +static int i_frame = 0; + +static int init( int width, int height ) +{ + double fps; + unsigned int usec; + + /* Get default params for preset/tuning */ + //if( x265_param_default_preset( ¶m, "medium", NULL ) < 0 ) + //{ + // goto x265_init_fail; + //} + + fps = getBaseFrameRate(); + + usec = (unsigned int)((1000000.0 / fps)+0.50); + + x265_param_default( ¶m); + + /* Configure non-default params */ + param.internalCsp = X265_CSP_I420; + param.sourceWidth = width; + param.sourceHeight = height; + param.bRepeatHeaders = 1; + param.fpsNum = 1000000; + param.fpsDenom = usec; + + /* Apply profile restrictions. */ + //if( x265_param_apply_profile( ¶m, "high" ) < 0 ) + //{ + // goto x265_init_fail; + //} + + if( (pic = x265_picture_alloc()) == NULL ) + { + goto x265_init_fail; + } + x265_picture_init( ¶m, pic ); + + hdl = x265_encoder_open( ¶m ); + if ( hdl == NULL ) + { + goto x265_init_fail; + } + i_frame = 0; + + return 0; + +x265_init_fail: + return -1; +} + +static int encode_frame( unsigned char *inBuf, int width, int height ) +{ + int luma_size = width * height; + int chroma_size = luma_size / 4; + int ret = 0; + int ofs; + unsigned int flags = 0, totalPayload = 0; + + ofs = 0; + pic->planes[0] = &inBuf[ofs]; ofs += luma_size; + pic->planes[1] = &inBuf[ofs]; ofs += chroma_size; + pic->planes[2] = &inBuf[ofs]; ofs += chroma_size; + pic->stride[0] = width; + pic->stride[1] = width/2; + pic->stride[2] = width/2; + + ret = x265_encoder_encode( hdl, &nal, &i_nal, pic, &pic_out ); + + if ( ret <= 0 ) + { + return -1; + } + else if ( i_nal > 0 ) + { + flags = 0; + totalPayload = 0; + + if ( IS_X265_TYPE_I(pic_out.sliceType) ) + { + flags |= gwavi_t::IF_KEYFRAME; + } + + for (unsigned int i=0; iadd_frame( nal[0].payload, totalPayload, flags ); + } + return ret; +} + +static int close(void) +{ + int ret; + unsigned int flags = 0, totalPayload = 0; + + /* Flush delayed frames */ + while( hdl != NULL ) + { + ret = x265_encoder_encode( hdl, &nal, &i_nal, NULL, &pic_out ); + + if ( ret <= 0 ) + { + break; + } + else if ( i_nal > 0 ) + { + totalPayload = 0; + flags = 0; + + if ( IS_X265_TYPE_I(pic_out.sliceType) ) + { + flags |= gwavi_t::IF_KEYFRAME; + } + for (unsigned int i=0; iadd_frame( nal[0].payload, totalPayload, flags ); + } + } + + x265_encoder_close( hdl ); + x265_picture_free( pic ); + + return 0; +} + +}; // End X265 namespace +#endif +//************************************************************************************** // Windows VFW Interface #ifdef WIN32 namespace VFW @@ -641,6 +787,12 @@ int aviRecordOpenFile( const char *filepath ) strcpy( fourcc, "X264"); } #endif + #ifdef _USE_X265 + else if ( videoFormat == AVI_X265 ) + { + strcpy( fourcc, "H265"); + } + #endif #ifdef WIN32 else if ( videoFormat == AVI_VFW ) { @@ -864,6 +1016,11 @@ int FCEUD_AviGetFormatOpts( std::vector &formatList ) s.assign("X264 (H.264)"); break; #endif + #ifdef _USE_X265 + case AVI_X265: + s.assign("X265 (H.265)"); + break; + #endif #ifdef WIN32 case AVI_VFW: s.assign("VfW (Video for Windows)"); @@ -932,6 +1089,12 @@ void AviRecordDiskThread_t::run(void) X264::init( width, height ); } #endif +#ifdef _USE_X265 + if ( localVideoFormat == AVI_X265) + { + X265::init( width, height ); + } +#endif #ifdef WIN32 if ( localVideoFormat == AVI_VFW) { @@ -970,6 +1133,13 @@ void AviRecordDiskThread_t::run(void) X264::encode_frame( rgb24, width, height ); } #endif + #ifdef _USE_X265 + else if ( localVideoFormat == AVI_X265) + { + Convert_4byte_To_I420Frame<4>(videoOut,rgb24,numPixels,width); + X265::encode_frame( rgb24, width, height ); + } + #endif #ifdef WIN32 else if ( localVideoFormat == AVI_VFW) { @@ -1026,6 +1196,12 @@ void AviRecordDiskThread_t::run(void) X264::close(); } #endif +#ifdef _USE_X265 + if ( localVideoFormat == AVI_X265) + { + X265::close(); + } +#endif #ifdef WIN32 if ( localVideoFormat == AVI_VFW) { diff --git a/src/drivers/Qt/AviRecord.h b/src/drivers/Qt/AviRecord.h index aadf6ef3..4bd92bc7 100644 --- a/src/drivers/Qt/AviRecord.h +++ b/src/drivers/Qt/AviRecord.h @@ -18,6 +18,9 @@ enum aviEncoderList #ifdef _USE_X264 AVI_X264, #endif + #ifdef _USE_X265 + AVI_X265, + #endif #ifdef WIN32 AVI_VFW, #endif diff --git a/src/drivers/Qt/avi/gwavi.cpp b/src/drivers/Qt/avi/gwavi.cpp index 5f6acf09..9b848190 100644 --- a/src/drivers/Qt/avi/gwavi.cpp +++ b/src/drivers/Qt/avi/gwavi.cpp @@ -155,6 +155,10 @@ gwavi_t::open(const char *filename, unsigned int width, unsigned int height, { // X264 H.264 bits_per_pixel = 12; } + else if ( strcmp( fourcc, "H265" ) == 0 ) + { // X265 H.265 + bits_per_pixel = 12; + } else { // Plain RGB24 bits_per_pixel = 24;