#include #include #include #include #include "mf-h264-encoder.hpp" #include "mf-encoder-descriptor.hpp" #include using namespace MF; struct MFH264_Encoder { obs_encoder_t *encoder; std::shared_ptr descriptor; std::unique_ptr h264Encoder; uint32_t width; uint32_t height; uint32_t framerateNum; uint32_t framerateDen; uint32_t keyint; bool advanced; uint32_t bitrate; uint32_t maxBitrate; bool useMaxBitrate; uint32_t bufferSize; bool useBufferSize; H264Profile profile; H264RateControl rateControl; H264QP qp; uint32_t minQp; uint32_t maxQp; bool lowLatency; uint32_t bFrames; const char *profiler_encode = nullptr; }; #define MFTEXT(x) obs_module_text("MF.H264." x) #define TEXT_ADVANCED MFTEXT("Advanced") #define TEXT_LOW_LAT MFTEXT("LowLatency") #define TEXT_B_FRAMES MFTEXT("BFrames") #define TEXT_BITRATE MFTEXT("Bitrate") #define TEXT_CUSTOM_BUF MFTEXT("CustomBufsize") #define TEXT_BUF_SIZE MFTEXT("BufferSize") #define TEXT_USE_MAX_BITRATE MFTEXT("CustomMaxBitrate") #define TEXT_MAX_BITRATE MFTEXT("MaxBitrate") #define TEXT_KEYINT_SEC MFTEXT("KeyframeIntervalSec") #define TEXT_RATE_CONTROL MFTEXT("RateControl") #define TEXT_MIN_QP MFTEXT("MinQP") #define TEXT_MAX_QP MFTEXT("MaxQP") #define TEXT_QPI MFTEXT("QPI") #define TEXT_QPP MFTEXT("QPP") #define TEXT_QPB MFTEXT("QPB") #define TEXT_PROFILE MFTEXT("Profile") #define TEXT_CBR MFTEXT("CBR") #define TEXT_VBR MFTEXT("VBR") #define TEXT_CQP MFTEXT("CQP") #define MFP(x) "mf_h264_" ## x #define MFP_USE_ADVANCED MFP("use_advanced") #define MFP_USE_LOWLAT MFP("use_low_latency") #define MFP_B_FRAMES MFP("b_frames") #define MFP_BITRATE MFP("bitrate") #define MFP_USE_BUF_SIZE MFP("use_buf_size") #define MFP_BUF_SIZE MFP("buf_size") #define MFP_USE_MAX_BITRATE MFP("use_max_bitrate") #define MFP_MAX_BITRATE MFP("max_bitrate") #define MFP_KEY_INT MFP("key_int") #define MFP_RATE_CONTROL MFP("rate_control") #define MFP_MIN_QP MFP("min_qp") #define MFP_MAX_QP MFP("max_qp") #define MFP_QP_I MFP("qp_i") #define MFP_QP_P MFP("qp_p") #define MFP_QP_B MFP("qp_b") #define MFP_PROFILE MFP("profile") struct TypeData { std::shared_ptr descriptor; inline TypeData(std::shared_ptr descriptor_) : descriptor(descriptor_) {} }; static const char *MFH264_GetName(void *type_data) { TypeData &typeData = *reinterpret_cast(type_data); return obs_module_text(typeData.descriptor->Name()); } static void set_visible(obs_properties_t *ppts, const char *name, bool visible) { obs_property_t *p = obs_properties_get(ppts, name); obs_property_set_visible(p, visible); } static bool use_bufsize_modified(obs_properties_t *ppts, obs_property_t *p, obs_data_t *settings) { UNUSED_PARAMETER(p); bool use_bufsize = obs_data_get_bool(settings, MFP_USE_BUF_SIZE); set_visible(ppts, MFP_BUF_SIZE, use_bufsize); return true; } static bool use_max_bitrate_modified(obs_properties_t *ppts, obs_property_t *p, obs_data_t *settings) { UNUSED_PARAMETER(p); bool advanced = obs_data_get_bool(settings, MFP_USE_ADVANCED); bool use_max_bitrate = obs_data_get_bool(settings, MFP_USE_MAX_BITRATE); set_visible(ppts, MFP_MAX_BITRATE, advanced && use_max_bitrate); return true; } static bool use_advanced_modified(obs_properties_t *ppts, obs_property_t *p, obs_data_t *settings) { UNUSED_PARAMETER(p); bool advanced = obs_data_get_bool(settings, MFP_USE_ADVANCED); set_visible(ppts, MFP_MIN_QP, advanced); set_visible(ppts, MFP_MAX_QP, advanced); set_visible(ppts, MFP_USE_LOWLAT, advanced); set_visible(ppts, MFP_B_FRAMES, advanced); H264RateControl rateControl = (H264RateControl)obs_data_get_int( settings, MFP_RATE_CONTROL); if (rateControl == H264RateControlCBR || rateControl == H264RateControlVBR) { set_visible(ppts, MFP_USE_MAX_BITRATE, advanced); use_max_bitrate_modified(ppts, NULL, settings); } return true; } static bool rate_control_modified(obs_properties_t *ppts, obs_property_t *p, obs_data_t *settings) { UNUSED_PARAMETER(p); H264RateControl rateControl = (H264RateControl)obs_data_get_int( settings, MFP_RATE_CONTROL); bool advanced = obs_data_get_bool(settings, MFP_USE_ADVANCED); set_visible(ppts, MFP_BITRATE, false); set_visible(ppts, MFP_USE_BUF_SIZE, false); set_visible(ppts, MFP_BUF_SIZE, false); set_visible(ppts, MFP_USE_MAX_BITRATE, false); set_visible(ppts, MFP_MAX_BITRATE, false); set_visible(ppts, MFP_QP_I, false); set_visible(ppts, MFP_QP_P, false); set_visible(ppts, MFP_QP_B, false); switch (rateControl) { case H264RateControlCBR: use_bufsize_modified(ppts, NULL, settings); use_max_bitrate_modified(ppts, NULL, settings); set_visible(ppts, MFP_BITRATE, true); set_visible(ppts, MFP_USE_BUF_SIZE, true); set_visible(ppts, MFP_USE_MAX_BITRATE, advanced); break; case H264RateControlVBR: use_bufsize_modified(ppts, NULL, settings); use_max_bitrate_modified(ppts, NULL, settings); set_visible(ppts, MFP_BITRATE, true); set_visible(ppts, MFP_USE_BUF_SIZE, true); set_visible(ppts, MFP_USE_MAX_BITRATE, advanced); break; case H264RateControlCQP: set_visible(ppts, MFP_QP_I, true); set_visible(ppts, MFP_QP_P, true); set_visible(ppts, MFP_QP_B, true); break; default: break; } return true; } static obs_properties_t *MFH264_GetProperties(void *) { obs_properties_t *props = obs_properties_create(); obs_property_t *p; obs_property_t *list = obs_properties_add_list(props, MFP_PROFILE, TEXT_PROFILE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_list_add_int(list, "baseline", H264ProfileBaseline); obs_property_list_add_int(list, "main", H264ProfileMain); obs_property_list_add_int(list, "high", H264ProfileHigh); obs_properties_add_int(props, MFP_KEY_INT, TEXT_KEYINT_SEC, 0, 20, 1); list = obs_properties_add_list(props, MFP_RATE_CONTROL, TEXT_RATE_CONTROL, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_list_add_int(list, TEXT_CBR, H264RateControlCBR); obs_property_list_add_int(list, TEXT_VBR, H264RateControlVBR); obs_property_list_add_int(list, TEXT_CQP, H264RateControlCQP); obs_property_set_modified_callback(list, rate_control_modified); obs_properties_add_int(props, MFP_BITRATE, TEXT_BITRATE, 50, 10000000, 1); p = obs_properties_add_bool(props, MFP_USE_BUF_SIZE, TEXT_CUSTOM_BUF); obs_property_set_modified_callback(p, use_bufsize_modified); obs_properties_add_int(props, MFP_BUF_SIZE, TEXT_BUF_SIZE, 0, 10000000, 1); obs_properties_add_int(props, MFP_QP_I, TEXT_QPI, 0, 51, 1); obs_properties_add_int(props, MFP_QP_P, TEXT_QPP, 0, 51, 1); obs_properties_add_int(props, MFP_QP_B, TEXT_QPB, 0, 51, 1); p = obs_properties_add_bool(props, MFP_USE_ADVANCED, TEXT_ADVANCED); obs_property_set_modified_callback(p, use_advanced_modified); p = obs_properties_add_bool(props, MFP_USE_MAX_BITRATE, TEXT_USE_MAX_BITRATE); obs_property_set_modified_callback(p, use_max_bitrate_modified); obs_properties_add_int(props, MFP_MAX_BITRATE, TEXT_MAX_BITRATE, 50, 10000000, 1); obs_properties_add_bool(props, MFP_USE_LOWLAT, TEXT_LOW_LAT); obs_properties_add_int(props, MFP_B_FRAMES, TEXT_B_FRAMES, 0, 16, 1); obs_properties_add_int(props, MFP_MIN_QP, TEXT_MIN_QP, 1, 51, 1); obs_properties_add_int(props, MFP_MAX_QP, TEXT_MAX_QP, 1, 51, 1); return props; } static void MFH264_GetDefaults(obs_data_t *settings) { #define PROP_DEF(x, y, z) obs_data_set_default_ ## x(settings, y, z) PROP_DEF(int, MFP_BITRATE, 2500); PROP_DEF(bool, MFP_USE_LOWLAT, true); PROP_DEF(int, MFP_B_FRAMES, 2); PROP_DEF(bool, MFP_USE_BUF_SIZE, false); PROP_DEF(int, MFP_BUF_SIZE, 2500); PROP_DEF(bool, MFP_USE_MAX_BITRATE, false); PROP_DEF(int, MFP_MAX_BITRATE, 2500); PROP_DEF(int, MFP_KEY_INT, 2); PROP_DEF(int, MFP_RATE_CONTROL, H264RateControlCBR); PROP_DEF(int, MFP_PROFILE, H264ProfileMain); PROP_DEF(int, MFP_MIN_QP, 1); PROP_DEF(int, MFP_MAX_QP, 51); PROP_DEF(int, MFP_QP_I, 26); PROP_DEF(int, MFP_QP_B, 26); PROP_DEF(int, MFP_QP_P, 26); PROP_DEF(bool, MFP_USE_ADVANCED, false); #undef DEF } static void UpdateParams(MFH264_Encoder *enc, obs_data_t *settings) { video_t *video = obs_encoder_video(enc->encoder); const struct video_output_info *voi = video_output_get_info(video); TypeData &typeData = *reinterpret_cast( obs_encoder_get_type_data(enc->encoder)); enc->width = (uint32_t)obs_encoder_get_width(enc->encoder); enc->height = (uint32_t)obs_encoder_get_height(enc->encoder); enc->framerateNum = voi->fps_num; enc->framerateDen = voi->fps_den; enc->descriptor = typeData.descriptor; #define PROP_GET(x, y, z) (z)obs_data_get_ ## x(settings, y) enc->profile = PROP_GET(int, MFP_PROFILE, H264Profile); enc->rateControl = PROP_GET(int, MFP_RATE_CONTROL, H264RateControl); enc->keyint = PROP_GET(int, MFP_KEY_INT, uint32_t); enc->bitrate = PROP_GET(int, MFP_BITRATE, uint32_t); enc->useBufferSize = PROP_GET(bool, MFP_USE_BUF_SIZE, bool); enc->bufferSize = PROP_GET(int, MFP_BUF_SIZE, uint32_t); enc->useMaxBitrate = PROP_GET(bool, MFP_USE_MAX_BITRATE, uint32_t); enc->maxBitrate = PROP_GET(int, MFP_MAX_BITRATE, uint32_t); enc->minQp = PROP_GET(int, MFP_MIN_QP, uint16_t); enc->maxQp = PROP_GET(int, MFP_MAX_QP, uint16_t); enc->qp.defaultQp = PROP_GET(int, MFP_QP_I, uint16_t); enc->qp.i = PROP_GET(int, MFP_QP_I, uint16_t); enc->qp.p = PROP_GET(int, MFP_QP_P, uint16_t); enc->qp.b = PROP_GET(int, MFP_QP_B, uint16_t); enc->lowLatency = PROP_GET(bool, MFP_USE_LOWLAT, bool); enc->bFrames = PROP_GET(int, MFP_B_FRAMES, uint32_t); enc->advanced = PROP_GET(bool, MFP_USE_ADVANCED, bool); #undef PROP_GET } #undef MFTEXT #undef MFP static bool ApplyCBR(MFH264_Encoder *enc) { enc->h264Encoder->SetBitrate(enc->bitrate); if (enc->useMaxBitrate) enc->h264Encoder->SetMaxBitrate(enc->maxBitrate); else enc->h264Encoder->SetMaxBitrate(enc->bitrate); if (enc->useBufferSize) enc->h264Encoder->SetBufferSize(enc->bufferSize); return true; } static bool ApplyCVBR(MFH264_Encoder *enc) { enc->h264Encoder->SetBitrate(enc->bitrate); if (enc->advanced && enc->useMaxBitrate) enc->h264Encoder->SetMaxBitrate(enc->maxBitrate); else enc->h264Encoder->SetMaxBitrate(enc->bitrate); if (enc->useBufferSize) enc->h264Encoder->SetBufferSize(enc->bufferSize); return true; } static bool ApplyVBR(MFH264_Encoder *enc) { enc->h264Encoder->SetBitrate(enc->bitrate); if (enc->useBufferSize) enc->h264Encoder->SetBufferSize(enc->bufferSize); return true; } static bool ApplyCQP(MFH264_Encoder *enc) { enc->h264Encoder->SetQP(enc->qp); return true; } static void *MFH264_Create(obs_data_t *settings, obs_encoder_t *encoder) { ProfileScope("MFH264_Create"); std::unique_ptr enc(new MFH264_Encoder()); enc->encoder = encoder; UpdateParams(enc.get(), settings); ProfileScope(enc->descriptor->Name()); enc->h264Encoder.reset(new H264Encoder(encoder, enc->descriptor, enc->width, enc->height, enc->framerateNum, enc->framerateDen, enc->profile, enc->bitrate)); auto applySettings = [&]() { enc.get()->h264Encoder->SetRateControl(enc->rateControl); enc.get()->h264Encoder->SetKeyframeInterval(enc->keyint); enc.get()->h264Encoder->SetEntropyEncoding( H264EntropyEncodingCABAC); if (enc->advanced) { enc.get()->h264Encoder->SetLowLatency(enc->lowLatency); enc.get()->h264Encoder->SetBFrameCount(enc->bFrames); enc.get()->h264Encoder->SetMinQP(enc->minQp); enc.get()->h264Encoder->SetMaxQP(enc->maxQp); } if (enc->rateControl == H264RateControlVBR && enc->advanced && enc->useMaxBitrate) enc->rateControl = H264RateControlConstrainedVBR; switch (enc->rateControl) { case H264RateControlCBR: return ApplyCBR(enc.get()); case H264RateControlConstrainedVBR: return ApplyCVBR(enc.get()); case H264RateControlVBR: return ApplyVBR(enc.get()); case H264RateControlCQP: return ApplyCQP(enc.get()); default: return false; } }; if (!enc->h264Encoder->Initialize(applySettings)) return nullptr; return enc.release(); } static void MFH264_Destroy(void *data) { MFH264_Encoder *enc = static_cast(data); delete enc; } static bool MFH264_Encode(void *data, struct encoder_frame *frame, struct encoder_packet *packet, bool *received_packet) { MFH264_Encoder *enc = static_cast(data); Status status; if (!enc->profiler_encode) enc->profiler_encode = profile_store_name( obs_get_profiler_name_store(), "MFH264_Encode(%s)", enc->descriptor->Name()); ProfileScope(enc->profiler_encode); *received_packet = false; if (!enc->h264Encoder->ProcessInput(frame->data, frame->linesize, frame->pts, &status)) return false; UINT8 *outputData; UINT32 outputDataLength; UINT64 outputPts; UINT64 outputDts; bool keyframe; if (!enc->h264Encoder->ProcessOutput(&outputData, &outputDataLength, &outputPts, &outputDts, &keyframe, &status)) return false; // Needs more input, not a failure case if (status == NEED_MORE_INPUT) return true; packet->type = OBS_ENCODER_VIDEO; packet->pts = outputPts; packet->dts = outputPts; packet->data = outputData; packet->size = outputDataLength; packet->keyframe = keyframe; *received_packet = true; return true; } static bool MFH264_GetExtraData(void *data, uint8_t **extra_data, size_t *size) { MFH264_Encoder *enc = static_cast(data); uint8_t *extraData; UINT32 extraDataLength; if (!enc->h264Encoder->ExtraData(&extraData, &extraDataLength)) return false; *extra_data = extraData; *size = extraDataLength; return true; } static bool MFH264_GetSEIData(void *data, uint8_t **sei_data, size_t *size) { UNUSED_PARAMETER(data); UNUSED_PARAMETER(sei_data); UNUSED_PARAMETER(size); return false; } static void MFH264_GetVideoInfo(void *, struct video_scale_info *info) { info->format = VIDEO_FORMAT_NV12; } static bool MFH264_Update(void *data, obs_data_t *settings) { MFH264_Encoder *enc = static_cast(data); UpdateParams(enc, settings); enc->h264Encoder->SetBitrate(enc->bitrate); enc->h264Encoder->SetQP(enc->qp); return true; } static bool CanSpawnEncoder(std::shared_ptr descriptor) { HRESULT hr; ComPtr transform; hr = CoCreateInstance(descriptor->Guid(), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&transform)); return hr == S_OK; } void RegisterMFH264Encoders() { obs_encoder_info info = { 0 }; info.type = OBS_ENCODER_VIDEO; info.get_name = MFH264_GetName; info.create = MFH264_Create; info.destroy = MFH264_Destroy; info.encode = MFH264_Encode; info.update = MFH264_Update; info.get_properties = MFH264_GetProperties; info.get_defaults = MFH264_GetDefaults; info.get_extra_data = MFH264_GetExtraData; info.get_sei_data = MFH264_GetSEIData; info.get_video_info = MFH264_GetVideoInfo; info.codec = "h264"; auto encoders = EncoderDescriptor::Enumerate(); for (auto e : encoders) { /* ignore the software encoder due to the fact that we already * have an objectively superior software encoder available */ if (e->Type() == EncoderType::H264_SOFTWARE) continue; /* certain encoders such as quicksync will be "available" but * not usable with certain processors */ if (!CanSpawnEncoder(e)) continue; info.caps = OBS_ENCODER_CAP_DEPRECATED; info.id = e->Id(); info.type_data = new TypeData(e); info.free_type_data = [] (void *type_data) { delete reinterpret_cast(type_data); }; obs_register_encoder(&info); } }