diff --git a/channels/rdpecam/client/encoding.c b/channels/rdpecam/client/encoding.c index b252156dc..bcc84b2d9 100644 --- a/channels/rdpecam/client/encoding.c +++ b/channels/rdpecam/client/encoding.c @@ -130,8 +130,12 @@ static BOOL ecam_init_sws_context(CameraDeviceStream* stream, enum AVPixelFormat const int width = (int)stream->currMediaType.Width; const int height = (int)stream->currMediaType.Height; - stream->sws = sws_getContext(width, height, pixFormat, width, height, AV_PIX_FMT_YUV420P, 0, - NULL, NULL, NULL); + const enum AVPixelFormat outPixFormat = + h264_context_get_option(stream->h264, H264_CONTEXT_OPTION_HW_ACCEL) ? AV_PIX_FMT_NV12 + : AV_PIX_FMT_YUV420P; + + stream->sws = + sws_getContext(width, height, pixFormat, width, height, outPixFormat, 0, NULL, NULL, NULL); if (!stream->sws) { WLog_ERR(TAG, "sws_getContext failed"); @@ -152,8 +156,8 @@ static BOOL ecam_encoder_compress_h264(CameraDeviceStream* stream, const BYTE* s UINT32 dstSize = 0; BYTE* srcSlice[4] = { 0 }; int srcLineSizes[4] = { 0 }; - BYTE* yuv420pData[3] = { 0 }; - UINT32 yuv420pStride[3] = { 0 }; + BYTE* yuvData[3] = { 0 }; + UINT32 yuvLineSizes[3] = { 0 }; prim_size_t size = { stream->currMediaType.Width, stream->currMediaType.Height }; CAM_MEDIA_FORMAT inputFormat = streamInputFormat(stream); enum AVPixelFormat pixFormat = AV_PIX_FMT_NONE; @@ -205,21 +209,20 @@ static BOOL ecam_encoder_compress_h264(CameraDeviceStream* stream, const BYTE* s } } - /* get buffers for YUV420P */ - if (h264_get_yuv_buffer(stream->h264, WINPR_ASSERTING_INT_CAST(uint32_t, srcLineSizes[0]), - size.width, size.height, yuv420pData, yuv420pStride) < 0) + /* get buffers for YUV420P or NV12 */ + if (h264_get_yuv_buffer(stream->h264, 0, size.width, size.height, yuvData, yuvLineSizes) < 0) return FALSE; - /* convert from source format to YUV420P */ + /* convert from source format to YUV420P or NV12 */ if (!ecam_init_sws_context(stream, pixFormat)) return FALSE; const BYTE* cSrcSlice[4] = { srcSlice[0], srcSlice[1], srcSlice[2], srcSlice[3] }; - if (sws_scale(stream->sws, cSrcSlice, srcLineSizes, 0, (int)size.height, yuv420pData, - (int*)yuv420pStride) <= 0) + if (sws_scale(stream->sws, cSrcSlice, srcLineSizes, 0, (int)size.height, yuvData, + (int*)yuvLineSizes) <= 0) return FALSE; - /* encode from YUV420P to H264 */ + /* encode from YUV420P or NV12 to H264 */ if (h264_compress(stream->h264, ppDstData, &dstSize) < 0) return FALSE; @@ -337,10 +340,6 @@ static BOOL ecam_encoder_context_init_h264(CameraDeviceStream* stream) return FALSE; } - if (!h264_context_reset(stream->h264, stream->currMediaType.Width, - stream->currMediaType.Height)) - goto fail; - if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_USAGETYPE, H264_CAMERA_VIDEO_REAL_TIME)) goto fail; @@ -354,13 +353,30 @@ static BOOL ecam_encoder_context_init_h264(CameraDeviceStream* stream) ecam_encoder_h264_get_max_bitrate(stream))) goto fail; + /* Using CQP mode for rate control. It produces more comparable quality + * between VAAPI and software encoding than VBR mode + */ if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_RATECONTROL, - H264_RATECONTROL_VBR)) + H264_RATECONTROL_CQP)) goto fail; - if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_QP, 0)) + /* Using 26 as CQP value. Lower values will produce better quality but + * higher bitrate; higher values - lower bitrate but degraded quality + */ + if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_QP, 26)) goto fail; + /* Requesting hardware acceleration before calling h264_context_reset */ + if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_HW_ACCEL, TRUE)) + goto fail; + + if (!h264_context_reset(stream->h264, stream->currMediaType.Width, + stream->currMediaType.Height)) + { + WLog_ERR(TAG, "h264_context_reset failed"); + goto fail; + } + #if defined(WITH_INPUT_FORMAT_MJPG) if (streamInputFormat(stream) == CAM_MEDIA_FORMAT_MJPG && !ecam_init_mjpeg_decoder(stream)) goto fail; diff --git a/cmake/ConfigOptions.cmake b/cmake/ConfigOptions.cmake index 761c2eb3e..cf94704e1 100644 --- a/cmake/ConfigOptions.cmake +++ b/cmake/ConfigOptions.cmake @@ -161,6 +161,10 @@ option(WITH_FFMPEG "Enable FFMPEG for audio/video encoding/decoding" ON) cmake_dependent_option(WITH_DSP_FFMPEG "Use FFMPEG for audio encoding/decoding" ON "WITH_FFMPEG" OFF) cmake_dependent_option(WITH_VIDEO_FFMPEG "Use FFMPEG for video encoding/decoding" ON "WITH_FFMPEG" OFF) cmake_dependent_option(WITH_VAAPI "Use FFMPEG VAAPI" OFF "WITH_VIDEO_FFMPEG" OFF) +cmake_dependent_option(WITH_VAAPI_H264_ENCODING "Use FFMPEG VAAPI hardware H264 encoding" ON "WITH_VIDEO_FFMPEG" OFF) +if(WITH_VAAPI_H264_ENCODING) + add_definitions("-DWITH_VAAPI_H264_ENCODING") +endif() option(USE_VERSION_FROM_GIT_TAG "Extract FreeRDP version from git tag." ON) diff --git a/include/freerdp/codec/h264.h b/include/freerdp/codec/h264.h index add267849..77fcc0c6f 100644 --- a/include/freerdp/codec/h264.h +++ b/include/freerdp/codec/h264.h @@ -61,6 +61,8 @@ extern "C" H264_CONTEXT_OPTION_FRAMERATE, H264_CONTEXT_OPTION_QP, H264_CONTEXT_OPTION_USAGETYPE, /** @since version 3.6.0 */ + H264_CONTEXT_OPTION_HW_ACCEL, /** set to request hw accel, get to check if hw accel is on, + @since version 3.11.0 */ } H264_CONTEXT_OPTION; FREERDP_API void free_h264_metablock(RDPGFX_H264_METABLOCK* meta); diff --git a/libfreerdp/codec/h264.c b/libfreerdp/codec/h264.c index 46790ea4c..0cae3cf26 100644 --- a/libfreerdp/codec/h264.c +++ b/libfreerdp/codec/h264.c @@ -37,7 +37,7 @@ static BOOL avc444_ensure_buffer(H264_CONTEXT* h264, DWORD nDstHeight); -BOOL avc420_ensure_buffer(H264_CONTEXT* h264, UINT32 stride, UINT32 width, UINT32 height) +BOOL yuv_ensure_buffer(H264_CONTEXT* h264, UINT32 stride, UINT32 width, UINT32 height) { BOOL isNull = FALSE; UINT32 pheight = height; @@ -54,7 +54,9 @@ BOOL avc420_ensure_buffer(H264_CONTEXT* h264, UINT32 stride, UINT32 width, UINT3 if (pheight % 16 != 0) pheight += 16 - pheight % 16; - for (size_t x = 0; x < 3; x++) + const size_t nPlanes = h264->hwAccel ? 2 : 3; + + for (size_t x = 0; x < nPlanes; x++) { if (!h264->pYUVData[x] || !h264->pOldYUVData[x]) isNull = TRUE; @@ -68,13 +70,23 @@ BOOL avc420_ensure_buffer(H264_CONTEXT* h264, UINT32 stride, UINT32 width, UINT3 if (isNull || (width != h264->width) || (height != h264->height) || (stride != h264->iStride[0])) { - h264->iStride[0] = stride; - h264->iStride[1] = (stride + 1) / 2; - h264->iStride[2] = (stride + 1) / 2; + if (h264->hwAccel) /* NV12 */ + { + h264->iStride[0] = stride; + h264->iStride[1] = stride; + h264->iStride[2] = 0; + } + else /* I420 */ + { + h264->iStride[0] = stride; + h264->iStride[1] = (stride + 1) / 2; + h264->iStride[2] = (stride + 1) / 2; + } + h264->width = width; h264->height = height; - for (size_t x = 0; x < 3; x++) + for (size_t x = 0; x < nPlanes; x++) { BYTE* tmp1 = winpr_aligned_recalloc(h264->pYUVData[x], h264->iStride[x], pheight, 16); BYTE* tmp2 = @@ -91,6 +103,11 @@ BOOL avc420_ensure_buffer(H264_CONTEXT* h264, UINT32 stride, UINT32 width, UINT3 return TRUE; } +BOOL avc420_ensure_buffer(H264_CONTEXT* h264, UINT32 stride, UINT32 width, UINT32 height) +{ + return yuv_ensure_buffer(h264, stride, width, height); +} + INT32 avc420_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstData, DWORD DstFormat, UINT32 nDstStep, UINT32 nDstWidth, UINT32 nDstHeight, const RECTANGLE_16* regionRects, UINT32 numRegionRects) @@ -238,7 +255,7 @@ INT32 h264_get_yuv_buffer(H264_CONTEXT* h264, UINT32 nSrcStride, UINT32 nSrcWidt if (!h264 || !h264->Compressor || !h264->subsystem || !h264->subsystem->Compress) return -1; - if (!avc420_ensure_buffer(h264, nSrcStride, nSrcWidth, nSrcHeight)) + if (!yuv_ensure_buffer(h264, nSrcStride, nSrcWidth, nSrcHeight)) return -1; for (size_t x = 0; x < 3; x++) @@ -708,7 +725,6 @@ H264_CONTEXT* h264_context_new(BOOL Compressor) h264->Compressor = Compressor; if (Compressor) - { /* Default compressor settings, may be changed by caller */ h264->BitRate = 1000000; @@ -786,6 +802,9 @@ BOOL h264_context_set_option(H264_CONTEXT* h264, H264_CONTEXT_OPTION option, UIN case H264_CONTEXT_OPTION_USAGETYPE: h264->UsageType = value; return TRUE; + case H264_CONTEXT_OPTION_HW_ACCEL: + h264->hwAccel = value ? TRUE : FALSE; + return TRUE; default: WLog_Print(h264->log, WLOG_WARN, "Unknown H264_CONTEXT_OPTION[0x%08" PRIx32 "]", option); @@ -808,6 +827,8 @@ UINT32 h264_context_get_option(H264_CONTEXT* h264, H264_CONTEXT_OPTION option) return h264->QP; case H264_CONTEXT_OPTION_USAGETYPE: return h264->UsageType; + case H264_CONTEXT_OPTION_HW_ACCEL: + return h264->hwAccel; default: WLog_Print(h264->log, WLOG_WARN, "Unknown H264_CONTEXT_OPTION[0x%08" PRIx32 "]", option); diff --git a/libfreerdp/codec/h264.h b/libfreerdp/codec/h264.h index 9c0463c7d..4b62b9030 100644 --- a/libfreerdp/codec/h264.h +++ b/libfreerdp/codec/h264.h @@ -62,6 +62,7 @@ extern "C" UINT32 FrameRate; UINT32 QP; UINT32 UsageType; + BOOL hwAccel; UINT32 NumberOfThreads; UINT32 iStride[3]; diff --git a/libfreerdp/codec/h264_ffmpeg.c b/libfreerdp/codec/h264_ffmpeg.c index d762b4292..a58c2b5e3 100644 --- a/libfreerdp/codec/h264_ffmpeg.c +++ b/libfreerdp/codec/h264_ffmpeg.c @@ -65,7 +65,7 @@ static inline char* error_string(char* errbuf, size_t errbuf_size, int errnum) #define av_err2str(errnum) error_string((char[64]){ 0 }, 64, errnum) #endif -#ifdef WITH_VAAPI +#if defined(WITH_VAAPI) || defined(WITH_VAAPI_H264_ENCODING) #define VAAPI_DEVICE "/dev/dri/renderD128" #endif @@ -81,7 +81,7 @@ typedef struct AVPacket bufferpacket; #endif AVPacket* packet; -#ifdef WITH_VAAPI +#if defined(WITH_VAAPI) || defined(WITH_VAAPI_H264_ENCODING) AVBufferRef* hwctx; AVFrame* hwVideoFrame; enum AVPixelFormat hw_pix_fmt; @@ -91,7 +91,7 @@ typedef struct #endif } H264_CONTEXT_LIBAVCODEC; -static void libavcodec_destroy_encoder(H264_CONTEXT* WINPR_RESTRICT h264) +static void libavcodec_destroy_encoder_context(H264_CONTEXT* WINPR_RESTRICT h264) { H264_CONTEXT_LIBAVCODEC* sys = NULL; @@ -110,11 +110,47 @@ static void libavcodec_destroy_encoder(H264_CONTEXT* WINPR_RESTRICT h264) #endif } - sys->codecEncoder = NULL; sys->codecEncoderContext = NULL; } -static BOOL libavcodec_create_encoder(H264_CONTEXT* WINPR_RESTRICT h264) +#ifdef WITH_VAAPI_H264_ENCODING +static int set_hw_frames_ctx(H264_CONTEXT* WINPR_RESTRICT h264) +{ + H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData; + AVBufferRef* hw_frames_ref = NULL; + AVHWFramesContext* frames_ctx = NULL; + int err = 0; + + if (!(hw_frames_ref = av_hwframe_ctx_alloc(sys->hwctx))) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to create VAAPI frame context"); + return -1; + } + frames_ctx = (AVHWFramesContext*)(hw_frames_ref->data); + frames_ctx->format = AV_PIX_FMT_VAAPI; + frames_ctx->sw_format = AV_PIX_FMT_NV12; + frames_ctx->width = sys->codecEncoderContext->width; + frames_ctx->height = sys->codecEncoderContext->height; + frames_ctx->initial_pool_size = 20; + if ((err = av_hwframe_ctx_init(hw_frames_ref)) < 0) + { + WLog_Print(h264->log, WLOG_ERROR, + "Failed to initialize VAAPI frame context." + "Error code: %s", + av_err2str(err)); + av_buffer_unref(&hw_frames_ref); + return err; + } + sys->codecEncoderContext->hw_frames_ctx = av_buffer_ref(hw_frames_ref); + if (!sys->codecEncoderContext->hw_frames_ctx) + err = AVERROR(ENOMEM); + + av_buffer_unref(&hw_frames_ref); + return err; +} +#endif + +static BOOL libavcodec_create_encoder_context(H264_CONTEXT* WINPR_RESTRICT h264) { BOOL recreate = FALSE; H264_CONTEXT_LIBAVCODEC* sys = NULL; @@ -126,9 +162,10 @@ static BOOL libavcodec_create_encoder(H264_CONTEXT* WINPR_RESTRICT h264) return FALSE; sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData; - if (!sys) + if (!sys || !sys->codecEncoder) return FALSE; - recreate = !sys->codecEncoder || !sys->codecEncoderContext; + + recreate = !sys->codecEncoderContext; if (sys->codecEncoderContext) { @@ -140,11 +177,7 @@ static BOOL libavcodec_create_encoder(H264_CONTEXT* WINPR_RESTRICT h264) if (!recreate) return TRUE; - libavcodec_destroy_encoder(h264); - sys->codecEncoder = avcodec_find_encoder(AV_CODEC_ID_H264); - - if (!sys->codecEncoder) - goto EXCEPTION; + libavcodec_destroy_encoder_context(h264); sys->codecEncoderContext = avcodec_alloc_context3(sys->codecEncoder); @@ -158,7 +191,11 @@ static BOOL libavcodec_create_encoder(H264_CONTEXT* WINPR_RESTRICT h264) break; case H264_RATECONTROL_CQP: - /* TODO: sys->codecEncoderContext-> = h264->QP; */ + if (av_opt_set_int(sys->codecEncoderContext, "qp", h264->QP, AV_OPT_SEARCH_CHILDREN) < + 0) + { + WLog_Print(h264->log, WLOG_ERROR, "av_opt_set_int failed"); + } break; default: @@ -174,17 +211,33 @@ static BOOL libavcodec_create_encoder(H264_CONTEXT* WINPR_RESTRICT h264) #endif sys->codecEncoderContext->time_base = (AVRational){ 1, WINPR_ASSERTING_INT_CAST(int, h264->FrameRate) }; - av_opt_set(sys->codecEncoderContext, "preset", "medium", AV_OPT_SEARCH_CHILDREN); av_opt_set(sys->codecEncoderContext, "tune", "zerolatency", AV_OPT_SEARCH_CHILDREN); + sys->codecEncoderContext->flags |= AV_CODEC_FLAG_LOOP_FILTER; - sys->codecEncoderContext->pix_fmt = AV_PIX_FMT_YUV420P; + +#ifdef WITH_VAAPI_H264_ENCODING + if (sys->hwctx) + { + av_opt_set(sys->codecEncoderContext, "preset", "veryslow", AV_OPT_SEARCH_CHILDREN); + + sys->codecEncoderContext->pix_fmt = AV_PIX_FMT_VAAPI; + /* set hw_frames_ctx for encoder's AVCodecContext */ + if (set_hw_frames_ctx(h264) < 0) + goto EXCEPTION; + } + else +#endif + { + av_opt_set(sys->codecEncoderContext, "preset", "medium", AV_OPT_SEARCH_CHILDREN); + sys->codecEncoderContext->pix_fmt = AV_PIX_FMT_YUV420P; + } if (avcodec_open2(sys->codecEncoderContext, sys->codecEncoder, NULL) < 0) goto EXCEPTION; return TRUE; EXCEPTION: - libavcodec_destroy_encoder(h264); + libavcodec_destroy_encoder_context(h264); return FALSE; } @@ -346,7 +399,7 @@ static int libavcodec_compress(H264_CONTEXT* WINPR_RESTRICT h264, H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData; WINPR_ASSERT(sys); - if (!libavcodec_create_encoder(h264)) + if (!libavcodec_create_encoder_context(h264)) return -1; #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100) @@ -369,7 +422,7 @@ static int libavcodec_compress(H264_CONTEXT* WINPR_RESTRICT h264, WINPR_ASSERT(sys->videoFrame); WINPR_ASSERT(sys->codecEncoderContext); - sys->videoFrame->format = sys->codecEncoderContext->pix_fmt; + sys->videoFrame->format = AV_PIX_FMT_YUV420P; sys->videoFrame->width = sys->codecEncoderContext->width; sys->videoFrame->height = sys->codecEncoderContext->height; #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 48, 100) @@ -391,9 +444,37 @@ static int libavcodec_compress(H264_CONTEXT* WINPR_RESTRICT h264, sys->videoFrame->linesize[1] = (int)pStride[1]; sys->videoFrame->linesize[2] = (int)pStride[2]; sys->videoFrame->pts++; + +#ifdef WITH_VAAPI_H264_ENCODING + if (sys->hwctx) + { + av_frame_unref(sys->hwVideoFrame); + if ((status = av_hwframe_get_buffer(sys->codecEncoderContext->hw_frames_ctx, + sys->hwVideoFrame, 0)) < 0 || + !sys->hwVideoFrame->hw_frames_ctx) + { + WLog_Print(h264->log, WLOG_ERROR, "av_hwframe_get_buffer failed (%s [%d])", + av_err2str(status), status); + goto fail; + } + sys->videoFrame->format = AV_PIX_FMT_NV12; + if ((status = av_hwframe_transfer_data(sys->hwVideoFrame, sys->videoFrame, 0)) < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "av_hwframe_transfer_data failed (%s [%d])", + av_err2str(status), status); + goto fail; + } + } +#endif + /* avcodec_encode_video2 is deprecated with libavcodec 57.48.101 */ #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101) +#ifdef WITH_VAAPI_H264_ENCODING + status = avcodec_send_frame(sys->codecEncoderContext, + sys->hwctx ? sys->hwVideoFrame : sys->videoFrame); +#else status = avcodec_send_frame(sys->codecEncoderContext, sys->videoFrame); +#endif if (status < 0) { @@ -486,8 +567,7 @@ static void libavcodec_uninit(H264_CONTEXT* h264) #endif } -#ifdef WITH_VAAPI - +#if defined(WITH_VAAPI) || defined(WITH_VAAPI_H264_ENCODING) if (sys->hwVideoFrame) { #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 18, 102) @@ -506,6 +586,7 @@ static void libavcodec_uninit(H264_CONTEXT* h264) av_buffer_unref(&sys->hw_frames_ctx); #endif + #endif if (sys->codecParser) @@ -521,7 +602,7 @@ static void libavcodec_uninit(H264_CONTEXT* h264) #endif } - libavcodec_destroy_encoder(h264); + libavcodec_destroy_encoder_context(h264); free(sys); h264->pSystemData = NULL; } @@ -666,10 +747,45 @@ static BOOL libavcodec_init(H264_CONTEXT* h264) goto EXCEPTION; } } + else + { +#ifdef WITH_VAAPI_H264_ENCODING + if (h264->hwAccel) /* user requested hw accel */ + { + sys->codecEncoder = avcodec_find_encoder_by_name("h264_vaapi"); + if (!sys->codecEncoder) + { + WLog_Print(h264->log, WLOG_ERROR, "H264 VAAPI encoder not found"); + } + else if (av_hwdevice_ctx_create(&sys->hwctx, AV_HWDEVICE_TYPE_VAAPI, VAAPI_DEVICE, NULL, + 0) < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "av_hwdevice_ctx_create failed"); + sys->codecEncoder = NULL; + sys->hwctx = NULL; + } + else + { + WLog_Print(h264->log, WLOG_INFO, "Using VAAPI for accelerated H264 encoding"); + } + } +#endif + if (!sys->codecEncoder) + { + sys->codecEncoder = avcodec_find_encoder(AV_CODEC_ID_H264); + h264->hwAccel = FALSE; /* not supported */ + } + + if (!sys->codecEncoder) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to initialize H264 encoder"); + goto EXCEPTION; + } + } #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 18, 102) sys->videoFrame = av_frame_alloc(); -#ifdef WITH_VAAPI +#if defined(WITH_VAAPI) || defined(WITH_VAAPI_H264_ENCODING) sys->hwVideoFrame = av_frame_alloc(); #endif #else @@ -682,8 +798,7 @@ static BOOL libavcodec_init(H264_CONTEXT* h264) goto EXCEPTION; } -#ifdef WITH_VAAPI - +#if defined(WITH_VAAPI) || defined(WITH_VAAPI_H264_ENCODING) if (!sys->hwVideoFrame) { WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate libav hw frame"); diff --git a/libfreerdp/codec/h264_openh264.c b/libfreerdp/codec/h264_openh264.c index b64b89690..09ef51ffb 100644 --- a/libfreerdp/codec/h264_openh264.c +++ b/libfreerdp/codec/h264_openh264.c @@ -648,6 +648,7 @@ static BOOL openh264_init(H264_CONTEXT* h264) } } + h264->hwAccel = FALSE; /* not supported */ return TRUE; EXCEPTION: openh264_uninit(h264);