MS-RDPECAM: VAAPI h264 encoding

This commit is contained in:
oleg0421
2025-01-14 04:32:12 -08:00
parent 6b577b167e
commit 7b4a903f66
7 changed files with 209 additions and 49 deletions

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);

View File

@@ -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);

View File

@@ -62,6 +62,7 @@ extern "C"
UINT32 FrameRate;
UINT32 QP;
UINT32 UsageType;
BOOL hwAccel;
UINT32 NumberOfThreads;
UINT32 iStride[3];

View File

@@ -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");

View File

@@ -648,6 +648,7 @@ static BOOL openh264_init(H264_CONTEXT* h264)
}
}
h264->hwAccel = FALSE; /* not supported */
return TRUE;
EXCEPTION:
openh264_uninit(h264);