From 0510d673d34cde46b7509bbe3e1bea6df7cd74c3 Mon Sep 17 00:00:00 2001 From: Rodney Date: Sun, 24 Dec 2023 10:16:00 +0100 Subject: [PATCH] obs-qsv11: Add ROI support --- plugins/obs-qsv11/QSV_Encoder.cpp | 16 ++++++ plugins/obs-qsv11/QSV_Encoder.h | 2 + plugins/obs-qsv11/QSV_Encoder_Internal.cpp | 43 +++++++++++++++- plugins/obs-qsv11/QSV_Encoder_Internal.h | 7 +++ plugins/obs-qsv11/obs-qsv11.c | 57 +++++++++++++++++++--- 5 files changed, 117 insertions(+), 8 deletions(-) diff --git a/plugins/obs-qsv11/QSV_Encoder.cpp b/plugins/obs-qsv11/QSV_Encoder.cpp index 387fd56d8..544cc7482 100644 --- a/plugins/obs-qsv11/QSV_Encoder.cpp +++ b/plugins/obs-qsv11/QSV_Encoder.cpp @@ -206,6 +206,22 @@ int qsv_encoder_headers(qsv_t *pContext, uint8_t **pSPS, uint8_t **pPPS, return 0; } +void qsv_encoder_add_roi(qsv_t *pContext, const obs_encoder_roi *roi) +{ + QSV_Encoder_Internal *pEncoder = (QSV_Encoder_Internal *)pContext; + + /* QP value is range 0..51 */ + // ToDo figure out if this is different for AV1 + mfxI16 delta = (mfxI16)(-51.0f * roi->priority); + pEncoder->AddROI(roi->left, roi->top, roi->right, roi->bottom, delta); +} + +void qsv_encoder_clear_roi(qsv_t *pContext) +{ + QSV_Encoder_Internal *pEncoder = (QSV_Encoder_Internal *)pContext; + pEncoder->ClearROI(); +} + int qsv_encoder_encode(qsv_t *pContext, uint64_t ts, uint8_t *pDataY, uint8_t *pDataUV, uint32_t strideY, uint32_t strideUV, mfxBitstream **pBS) diff --git a/plugins/obs-qsv11/QSV_Encoder.h b/plugins/obs-qsv11/QSV_Encoder.h index 742d8c1d0..59dc239bd 100644 --- a/plugins/obs-qsv11/QSV_Encoder.h +++ b/plugins/obs-qsv11/QSV_Encoder.h @@ -160,6 +160,8 @@ int qsv_encoder_reconfig(qsv_t *, qsv_param_t *); void qsv_encoder_version(unsigned short *major, unsigned short *minor); qsv_t *qsv_encoder_open(qsv_param_t *, enum qsv_codec codec); bool qsv_encoder_is_dgpu(qsv_t *); +void qsv_encoder_add_roi(qsv_t *, const struct obs_encoder_roi *roi); +void qsv_encoder_clear_roi(qsv_t *pContext); int qsv_encoder_encode(qsv_t *, uint64_t, uint8_t *, uint8_t *, uint32_t, uint32_t, mfxBitstream **pBS); int qsv_encoder_encode_tex(qsv_t *, uint64_t, uint32_t, uint64_t, uint64_t *, diff --git a/plugins/obs-qsv11/QSV_Encoder_Internal.cpp b/plugins/obs-qsv11/QSV_Encoder_Internal.cpp index 9cf157c06..8b97f1a52 100644 --- a/plugins/obs-qsv11/QSV_Encoder_Internal.cpp +++ b/plugins/obs-qsv11/QSV_Encoder_Internal.cpp @@ -442,6 +442,9 @@ mfxStatus QSV_Encoder_Internal::InitParams(qsv_param_t *pParams, } } + memset(&m_ctrl, 0, sizeof(m_ctrl)); + memset(&m_roi, 0, sizeof(m_roi)); + return sts; } @@ -776,7 +779,7 @@ mfxStatus QSV_Encoder_Internal::Encode(uint64_t ts, uint8_t *pDataY, for (;;) { // Encode a frame asynchronously (returns immediately) - sts = m_pmfxENC->EncodeFrameAsync(NULL, pSurface, + sts = m_pmfxENC->EncodeFrameAsync(&m_ctrl, pSurface, &m_pTaskPool[nTaskIdx].mfxBS, &m_pTaskPool[nTaskIdx].syncp); @@ -841,7 +844,7 @@ mfxStatus QSV_Encoder_Internal::Encode_tex(uint64_t ts, uint32_t tex_handle, for (;;) { // Encode a frame asynchronously (returns immediately) - sts = m_pmfxENC->EncodeFrameAsync(NULL, pSurface, + sts = m_pmfxENC->EncodeFrameAsync(&m_ctrl, pSurface, &m_pTaskPool[nTaskIdx].mfxBS, &m_pTaskPool[nTaskIdx].syncp); @@ -939,3 +942,39 @@ mfxStatus QSV_Encoder_Internal::Reset(qsv_param_t *pParams, return sts; } + +void QSV_Encoder_Internal::AddROI(mfxU32 left, mfxU32 top, mfxU32 right, + mfxU32 bottom, mfxI16 delta) +{ + if (m_roi.NumROI == 256) { + warn("Maximum number of ROIs hit, ignoring additional ROI!"); + return; + } + + m_roi.Header.BufferId = MFX_EXTBUFF_ENCODER_ROI; + m_roi.Header.BufferSz = sizeof(mfxExtEncoderROI); + m_roi.ROIMode = MFX_ROI_MODE_QP_DELTA; + /* The SDK will automatically align the values to block sizes so we + * don't have to do any maths here. */ + m_roi.ROI[m_roi.NumROI].Left = left; + m_roi.ROI[m_roi.NumROI].Top = top; + m_roi.ROI[m_roi.NumROI].Right = right; + m_roi.ROI[m_roi.NumROI].Bottom = bottom; + m_roi.ROI[m_roi.NumROI].DeltaQP = delta; + m_roi.NumROI++; + + /* Right now ROI is the only thing we add so this is fine */ + if (m_extbuf.empty()) + m_extbuf.push_back((mfxExtBuffer *)&m_roi); + + m_ctrl.ExtParam = m_extbuf.data(); + m_ctrl.NumExtParam = (mfxU16)m_extbuf.size(); +} + +void QSV_Encoder_Internal::ClearROI() +{ + m_roi.NumROI = 0; + m_ctrl.ExtParam = nullptr; + m_ctrl.NumExtParam = 0; + m_extbuf.clear(); +} diff --git a/plugins/obs-qsv11/QSV_Encoder_Internal.h b/plugins/obs-qsv11/QSV_Encoder_Internal.h index 6c480b336..d96ac11dc 100644 --- a/plugins/obs-qsv11/QSV_Encoder_Internal.h +++ b/plugins/obs-qsv11/QSV_Encoder_Internal.h @@ -81,6 +81,9 @@ public: mfxStatus Reset(qsv_param_t *pParams, enum qsv_codec codec); mfxStatus ReconfigureEncoder(); bool UpdateParams(qsv_param_t *pParams); + void AddROI(mfxU32 left, mfxU32 top, mfxU32 right, mfxU32 bottom, + mfxI16 delta); + void ClearROI(); bool IsDGPU() const { return m_isDGPU; } @@ -135,4 +138,8 @@ private: static mfxU16 g_numEncodersOpen; static mfxHDL g_DX_Handle; // we only want one handle for all instances to use; + + mfxEncodeCtrl m_ctrl; + mfxExtEncoderROI m_roi; + std::vector m_extbuf; }; diff --git a/plugins/obs-qsv11/obs-qsv11.c b/plugins/obs-qsv11/obs-qsv11.c index 0885a3a49..e00defef7 100644 --- a/plugins/obs-qsv11/obs-qsv11.c +++ b/plugins/obs-qsv11/obs-qsv11.c @@ -99,6 +99,8 @@ struct obs_qsv { size_t sei_size; os_performance_token_t *performance_token; + + uint32_t roi_increment; }; /* ------------------------------------------------------------------------- */ @@ -1377,6 +1379,37 @@ static void parse_packet_hevc(struct obs_qsv *obsqsv, g_bFirst = false; } +static void roi_cb(void *param, struct obs_encoder_roi *roi) +{ + struct darray *da = param; + darray_push_back(sizeof(struct obs_encoder_roi), da, roi); +} + +static void obs_qsv_setup_rois(struct obs_qsv *obsqsv) +{ + const uint32_t increment = + obs_encoder_get_roi_increment(obsqsv->encoder); + if (obsqsv->roi_increment == increment) + return; + + qsv_encoder_clear_roi(obsqsv->context); + /* Because we pass-through the ROIs more or less directly we need to + * pass them in reverse order, so make a temporary copy and then use + * that instead. */ + DARRAY(struct obs_encoder_roi) rois; + da_init(rois); + + obs_encoder_enum_roi(obsqsv->encoder, roi_cb, &rois); + + size_t idx = rois.num; + while (idx) + qsv_encoder_add_roi(obsqsv->context, &rois.array[--idx]); + + da_free(rois); + + obsqsv->roi_increment = increment; +} + static bool obs_qsv_encode(void *data, struct encoder_frame *frame, struct encoder_packet *packet, bool *received_packet) { @@ -1396,6 +1429,9 @@ static bool obs_qsv_encode(void *data, struct encoder_frame *frame, mfxU64 qsvPTS = ts_obs_to_mfx(frame->pts, voi); + if (obs_encoder_has_roi(obsqsv->encoder)) + obs_qsv_setup_rois(obsqsv); + // FIXME: remove null check from the top of this function // if we actually do expect null frames to complete output. if (frame) @@ -1452,6 +1488,9 @@ static bool obs_qsv_encode_tex(void *data, uint32_t handle, int64_t pts, mfxU64 qsvPTS = ts_obs_to_mfx(pts, voi); + if (obs_encoder_has_roi(obsqsv->encoder)) + obs_qsv_setup_rois(obsqsv); + ret = qsv_encoder_encode_tex(obsqsv->context, qsvPTS, handle, lock_key, next_key, &pBS); @@ -1516,7 +1555,8 @@ struct obs_encoder_info obs_qsv_encoder_tex_v2 = { .get_name = obs_qsv_getname, .create = obs_qsv_create_tex_h264_v2, .destroy = obs_qsv_destroy, - .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_PASS_TEXTURE, + .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_PASS_TEXTURE | + OBS_ENCODER_CAP_ROI, .encode_texture = obs_qsv_encode_tex, .update = obs_qsv_update, .get_properties = obs_qsv_props_h264_v2, @@ -1540,7 +1580,8 @@ struct obs_encoder_info obs_qsv_encoder_v2 = { .get_extra_data = obs_qsv_extra_data, .get_sei_data = obs_qsv_sei, .get_video_info = obs_qsv_video_info, - .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_INTERNAL, + .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_INTERNAL | + OBS_ENCODER_CAP_ROI, }; struct obs_encoder_info obs_qsv_av1_encoder_tex = { @@ -1550,7 +1591,8 @@ struct obs_encoder_info obs_qsv_av1_encoder_tex = { .get_name = obs_qsv_getname_av1, .create = obs_qsv_create_tex_av1, .destroy = obs_qsv_destroy, - .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_PASS_TEXTURE, + .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_PASS_TEXTURE | + OBS_ENCODER_CAP_ROI, .encode_texture = obs_qsv_encode_tex, .update = obs_qsv_update, .get_properties = obs_qsv_props_av1, @@ -1572,7 +1614,8 @@ struct obs_encoder_info obs_qsv_av1_encoder = { .get_defaults = obs_qsv_defaults_av1, .get_extra_data = obs_qsv_extra_data, .get_video_info = obs_qsv_video_plus_hdr_info, - .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_INTERNAL, + .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_INTERNAL | + OBS_ENCODER_CAP_ROI, }; struct obs_encoder_info obs_qsv_hevc_encoder_tex = { @@ -1582,7 +1625,8 @@ struct obs_encoder_info obs_qsv_hevc_encoder_tex = { .get_name = obs_qsv_getname_hevc, .create = obs_qsv_create_tex_hevc, .destroy = obs_qsv_destroy, - .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_PASS_TEXTURE, + .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_PASS_TEXTURE | + OBS_ENCODER_CAP_ROI, .encode_texture = obs_qsv_encode_tex, .update = obs_qsv_update, .get_properties = obs_qsv_props_hevc, @@ -1604,5 +1648,6 @@ struct obs_encoder_info obs_qsv_hevc_encoder = { .get_defaults = obs_qsv_defaults_hevc, .get_extra_data = obs_qsv_extra_data, .get_video_info = obs_qsv_video_plus_hdr_info, - .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_INTERNAL, + .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_INTERNAL | + OBS_ENCODER_CAP_ROI, };