فهرست منبع

decklink: Add HDR playback support

jpark37 2 سال پیش
والد
کامیت
bb12fe9db5

+ 15 - 10
UI/frontend-plugins/decklink-output-ui/decklink-ui-main.cpp

@@ -116,9 +116,6 @@ void output_start()
 
 
 			context.stage_index = 0;
 			context.stage_index = 0;
 
 
-			const video_output_info *mainVOI =
-				video_output_get_info(obs_get_video());
-
 			video_output_info vi = {0};
 			video_output_info vi = {0};
 			vi.format = VIDEO_FORMAT_BGRA;
 			vi.format = VIDEO_FORMAT_BGRA;
 			vi.width = width;
 			vi.width = width;
@@ -126,7 +123,7 @@ void output_start()
 			vi.fps_den = context.ovi.fps_den;
 			vi.fps_den = context.ovi.fps_den;
 			vi.fps_num = context.ovi.fps_num;
 			vi.fps_num = context.ovi.fps_num;
 			vi.cache_size = 16;
 			vi.cache_size = 16;
-			vi.colorspace = mainVOI->colorspace;
+			vi.colorspace = VIDEO_CS_DEFAULT;
 			vi.range = VIDEO_RANGE_FULL;
 			vi.range = VIDEO_RANGE_FULL;
 			vi.name = "decklink_output";
 			vi.name = "decklink_output";
 
 
@@ -253,9 +250,6 @@ void preview_output_start()
 
 
 			context.stage_index = 0;
 			context.stage_index = 0;
 
 
-			const video_output_info *mainVOI =
-				video_output_get_info(obs_get_video());
-
 			video_output_info vi = {0};
 			video_output_info vi = {0};
 			vi.format = VIDEO_FORMAT_BGRA;
 			vi.format = VIDEO_FORMAT_BGRA;
 			vi.width = width;
 			vi.width = width;
@@ -263,7 +257,7 @@ void preview_output_start()
 			vi.fps_den = context.ovi.fps_den;
 			vi.fps_den = context.ovi.fps_den;
 			vi.fps_num = context.ovi.fps_num;
 			vi.fps_num = context.ovi.fps_num;
 			vi.cache_size = 16;
 			vi.cache_size = 16;
-			vi.colorspace = mainVOI->colorspace;
+			vi.colorspace = VIDEO_CS_DEFAULT;
 			vi.range = VIDEO_RANGE_FULL;
 			vi.range = VIDEO_RANGE_FULL;
 			vi.name = "decklink_preview_output";
 			vi.name = "decklink_preview_output";
 
 
@@ -386,13 +380,24 @@ static void decklink_ui_render(void *param)
 		return;
 		return;
 
 
 	const bool previous = gs_framebuffer_srgb_enabled();
 	const bool previous = gs_framebuffer_srgb_enabled();
-	gs_enable_framebuffer_srgb(true);
+	const bool source_hdr = (context.ovi.colorspace == VIDEO_CS_2100_PQ) ||
+				(context.ovi.colorspace == VIDEO_CS_2100_HLG);
+	const bool target_hdr = source_hdr &&
+				(conversion->colorspace == VIDEO_CS_2100_PQ);
+	gs_enable_framebuffer_srgb(!target_hdr);
 	gs_enable_blending(false);
 	gs_enable_blending(false);
 
 
 	gs_effect_t *const effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
 	gs_effect_t *const effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
 	gs_effect_set_texture_srgb(gs_effect_get_param_by_name(effect, "image"),
 	gs_effect_set_texture_srgb(gs_effect_get_param_by_name(effect, "image"),
 				   tex);
 				   tex);
-	while (gs_effect_loop(effect, "DrawAlphaDivide")) {
+	const char *const tech_name =
+		target_hdr ? "DrawAlphaDivideR10L"
+			   : (source_hdr ? "DrawAlphaDivideTonemap"
+					 : "DrawAlphaDivide");
+	while (gs_effect_loop(effect, tech_name)) {
+		gs_effect_set_float(gs_effect_get_param_by_name(effect,
+								"multiplier"),
+				    obs_get_video_sdr_white_level() / 10000.f);
 		gs_draw_sprite(tex, 0, 0, 0);
 		gs_draw_sprite(tex, 0, 0, 0);
 	}
 	}
 
 

+ 1 - 0
plugins/decklink/DecklinkOutput.hpp

@@ -21,6 +21,7 @@ public:
 	size_t audio_planes;
 	size_t audio_planes;
 	size_t audio_size;
 	size_t audio_size;
 	int keyerMode;
 	int keyerMode;
+	bool force_sdr;
 
 
 	DeckLinkOutput(obs_output_t *output,
 	DeckLinkOutput(obs_output_t *output,
 		       DeckLinkDeviceDiscovery *discovery);
 		       DeckLinkDeviceDiscovery *discovery);

+ 149 - 0
plugins/decklink/OBSVideoFrame.cpp

@@ -89,3 +89,152 @@ HRESULT OBSVideoFrame::GetBytes(void **buffer)
 	*buffer = this->data;
 	*buffer = this->data;
 	return S_OK;
 	return S_OK;
 }
 }
+
+#define CompareREFIID(iid1, iid2) (memcmp(&iid1, &iid2, sizeof(REFIID)) == 0)
+
+HDRVideoFrame::HDRVideoFrame(IDeckLinkMutableVideoFrame *frame)
+	: m_videoFrame(frame),
+	  m_refCount(1)
+{
+}
+
+HRESULT HDRVideoFrame::QueryInterface(REFIID iid, LPVOID *ppv)
+{
+	if (ppv == nullptr)
+		return E_INVALIDARG;
+
+	CFUUIDBytes unknown = CFUUIDGetUUIDBytes(IUnknownUUID);
+	if (CompareREFIID(iid, unknown))
+		*ppv = static_cast<IDeckLinkVideoFrame *>(this);
+	else if (CompareREFIID(iid, IID_IDeckLinkVideoFrame))
+		*ppv = static_cast<IDeckLinkVideoFrame *>(this);
+	else if (CompareREFIID(iid, IID_IDeckLinkVideoFrameMetadataExtensions))
+		*ppv = static_cast<IDeckLinkVideoFrameMetadataExtensions *>(
+			this);
+	else {
+		*ppv = nullptr;
+		return E_NOINTERFACE;
+	}
+
+	AddRef();
+	return S_OK;
+}
+
+ULONG HDRVideoFrame::AddRef(void)
+{
+	return ++m_refCount;
+}
+
+ULONG HDRVideoFrame::Release(void)
+{
+	ULONG newRefValue = --m_refCount;
+
+	if (newRefValue == 0)
+		delete this;
+
+	return newRefValue;
+}
+
+HRESULT HDRVideoFrame::GetInt(BMDDeckLinkFrameMetadataID metadataID,
+			      int64_t *value)
+{
+	HRESULT result = S_OK;
+
+	switch (metadataID) {
+	case bmdDeckLinkFrameMetadataHDRElectroOpticalTransferFunc:
+		*value = 2;
+		break;
+
+	case bmdDeckLinkFrameMetadataColorspace:
+		// Colorspace is fixed for this sample
+		*value = bmdColorspaceRec2020;
+		break;
+
+	default:
+		result = E_INVALIDARG;
+	}
+
+	return result;
+}
+
+HRESULT HDRVideoFrame::GetFloat(BMDDeckLinkFrameMetadataID metadataID,
+				double *value)
+{
+	HRESULT result = S_OK;
+
+	switch (metadataID) {
+	case bmdDeckLinkFrameMetadataHDRDisplayPrimariesRedX:
+		*value = 0.708;
+		break;
+
+	case bmdDeckLinkFrameMetadataHDRDisplayPrimariesRedY:
+		*value = 0.292;
+		break;
+
+	case bmdDeckLinkFrameMetadataHDRDisplayPrimariesGreenX:
+		*value = 0.17;
+		break;
+
+	case bmdDeckLinkFrameMetadataHDRDisplayPrimariesGreenY:
+		*value = 0.797;
+		break;
+
+	case bmdDeckLinkFrameMetadataHDRDisplayPrimariesBlueX:
+		*value = 0.131;
+		break;
+
+	case bmdDeckLinkFrameMetadataHDRDisplayPrimariesBlueY:
+		*value = 0.046;
+		break;
+
+	case bmdDeckLinkFrameMetadataHDRWhitePointX:
+		*value = 0.3127;
+		break;
+
+	case bmdDeckLinkFrameMetadataHDRWhitePointY:
+		*value = 0.329;
+		break;
+
+	case bmdDeckLinkFrameMetadataHDRMaxDisplayMasteringLuminance:
+		*value = obs_get_video_hdr_nominal_peak_level();
+		break;
+
+	case bmdDeckLinkFrameMetadataHDRMinDisplayMasteringLuminance:
+		*value = 0.00001;
+		break;
+
+	case bmdDeckLinkFrameMetadataHDRMaximumContentLightLevel:
+		*value = obs_get_video_hdr_nominal_peak_level();
+		break;
+
+	case bmdDeckLinkFrameMetadataHDRMaximumFrameAverageLightLevel:
+		*value = obs_get_video_hdr_nominal_peak_level();
+		break;
+
+	default:
+		result = E_INVALIDARG;
+	}
+
+	return result;
+}
+
+HRESULT HDRVideoFrame::GetFlag(BMDDeckLinkFrameMetadataID, decklink_bool_t *)
+{
+	// Not expecting GetFlag
+	return E_INVALIDARG;
+}
+
+HRESULT HDRVideoFrame::GetString(BMDDeckLinkFrameMetadataID,
+				 decklink_string_t *)
+{
+	// Not expecting GetString
+	return E_INVALIDARG;
+}
+
+HRESULT HDRVideoFrame::GetBytes(BMDDeckLinkFrameMetadataID, void *,
+				uint32_t *bufferSize)
+{
+	*bufferSize = 0;
+	// Not expecting GetBytes
+	return E_INVALIDARG;
+}

+ 68 - 0
plugins/decklink/OBSVideoFrame.h

@@ -2,6 +2,7 @@
 
 
 #include "platform.hpp"
 #include "platform.hpp"
 #include "obs.hpp"
 #include "obs.hpp"
+#include <atomic>
 
 
 class OBSVideoFrame : public IDeckLinkMutableVideoFrame {
 class OBSVideoFrame : public IDeckLinkMutableVideoFrame {
 private:
 private:
@@ -75,3 +76,70 @@ public:
 	virtual ULONG STDMETHODCALLTYPE AddRef() override { return 1; }
 	virtual ULONG STDMETHODCALLTYPE AddRef() override { return 1; }
 	virtual ULONG STDMETHODCALLTYPE Release() override { return 1; }
 	virtual ULONG STDMETHODCALLTYPE Release() override { return 1; }
 };
 };
+
+class HDRVideoFrame : public IDeckLinkVideoFrame,
+		      public IDeckLinkVideoFrameMetadataExtensions {
+public:
+	HDRVideoFrame(IDeckLinkMutableVideoFrame *frame);
+	virtual ~HDRVideoFrame() {}
+
+	// IUnknown interface
+	virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid,
+							 LPVOID *ppv);
+	virtual ULONG STDMETHODCALLTYPE AddRef(void);
+	virtual ULONG STDMETHODCALLTYPE Release(void);
+
+	// IDeckLinkVideoFrame interface
+	virtual long STDMETHODCALLTYPE GetWidth(void)
+	{
+		return m_videoFrame->GetWidth();
+	}
+	virtual long STDMETHODCALLTYPE GetHeight(void)
+	{
+		return m_videoFrame->GetHeight();
+	}
+	virtual long STDMETHODCALLTYPE GetRowBytes(void)
+	{
+		return m_videoFrame->GetRowBytes();
+	}
+	virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat(void)
+	{
+		return m_videoFrame->GetPixelFormat();
+	}
+	virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags(void)
+	{
+		return m_videoFrame->GetFlags() | bmdFrameContainsHDRMetadata;
+	}
+	virtual HRESULT STDMETHODCALLTYPE GetBytes(void **buffer)
+	{
+		return m_videoFrame->GetBytes(buffer);
+	}
+	virtual HRESULT STDMETHODCALLTYPE
+	GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode **timecode)
+	{
+		return m_videoFrame->GetTimecode(format, timecode);
+	}
+	virtual HRESULT STDMETHODCALLTYPE
+	GetAncillaryData(IDeckLinkVideoFrameAncillary **ancillary)
+	{
+		return m_videoFrame->GetAncillaryData(ancillary);
+	}
+
+	// IDeckLinkVideoFrameMetadataExtensions interface
+	virtual HRESULT STDMETHODCALLTYPE
+	GetInt(BMDDeckLinkFrameMetadataID metadataID, int64_t *value);
+	virtual HRESULT STDMETHODCALLTYPE
+	GetFloat(BMDDeckLinkFrameMetadataID metadataID, double *value);
+	virtual HRESULT STDMETHODCALLTYPE
+	GetFlag(BMDDeckLinkFrameMetadataID metadataID, decklink_bool_t *value);
+	virtual HRESULT STDMETHODCALLTYPE
+	GetString(BMDDeckLinkFrameMetadataID metadataID,
+		  decklink_string_t *value);
+	virtual HRESULT STDMETHODCALLTYPE
+	GetBytes(BMDDeckLinkFrameMetadataID metadataID, void *buffer,
+		 uint32_t *bufferSize);
+
+private:
+	ComPtr<IDeckLinkMutableVideoFrame> m_videoFrame;
+	std::atomic<ULONG> m_refCount;
+};

+ 2 - 0
plugins/decklink/const.h

@@ -11,6 +11,7 @@
 #define BUFFERING "buffering"
 #define BUFFERING "buffering"
 #define DEACTIVATE_WNS "deactivate_when_not_showing"
 #define DEACTIVATE_WNS "deactivate_when_not_showing"
 #define AUTO_START "auto_start"
 #define AUTO_START "auto_start"
+#define FORCE_SDR "force_sdr"
 #define KEYER "keyer"
 #define KEYER "keyer"
 #define SWAP "swap"
 #define SWAP "swap"
 #define ALLOW_10_BIT "allow_10_bit"
 #define ALLOW_10_BIT "allow_10_bit"
@@ -37,6 +38,7 @@
 #define TEXT_BUFFERING obs_module_text("Buffering")
 #define TEXT_BUFFERING obs_module_text("Buffering")
 #define TEXT_DWNS obs_module_text("DeactivateWhenNotShowing")
 #define TEXT_DWNS obs_module_text("DeactivateWhenNotShowing")
 #define TEXT_AUTO_START obs_module_text("AutoStart")
 #define TEXT_AUTO_START obs_module_text("AutoStart")
+#define TEXT_FORCE_SDR obs_module_text("ForceSDR")
 #define TEXT_ENABLE_KEYER obs_module_text("Keyer")
 #define TEXT_ENABLE_KEYER obs_module_text("Keyer")
 #define TEXT_SWAP obs_module_text("SwapFC-LFE")
 #define TEXT_SWAP obs_module_text("SwapFC-LFE")
 #define TEXT_SWAP_TOOLTIP obs_module_text("SwapFC-LFE.Tooltip")
 #define TEXT_SWAP_TOOLTIP obs_module_text("SwapFC-LFE.Tooltip")

+ 2 - 1
plugins/decklink/data/locale/en-US.ini

@@ -19,8 +19,9 @@ ChannelFormat.5_1ch="5.1ch"
 ChannelFormat.7_1ch="7.1ch"
 ChannelFormat.7_1ch="7.1ch"
 DeactivateWhenNotShowing="Deactivate when not showing"
 DeactivateWhenNotShowing="Deactivate when not showing"
 AutoStart="Auto start on launch"
 AutoStart="Auto start on launch"
+ForceSDR="Force SDR"
 SwapFC-LFE="Swap FC and LFE"
 SwapFC-LFE="Swap FC and LFE"
 SwapFC-LFE.Tooltip="Swap Front Center Channel and LFE Channel"
 SwapFC-LFE.Tooltip="Swap Front Center Channel and LFE Channel"
 VideoConnection="Video Connection"
 VideoConnection="Video Connection"
 AudioConnection="Audio Connection"
 AudioConnection="Audio Connection"
-Allow10Bit="Allow 10 Bit (Required for SDI captions, may cause performance overhead)"
+Allow10Bit="Allow 10 Bit (Required for SDI captions, may cause performance overhead)"

+ 21 - 2
plugins/decklink/decklink-device-instance.cpp

@@ -626,13 +626,24 @@ bool DeckLinkDeviceInstance::StartOutput(DeckLinkDeviceMode *mode_)
 	}
 	}
 	activeBlob = nullptr;
 	activeBlob = nullptr;
 
 
+	struct obs_video_info ovi;
+	const enum video_colorspace colorspace =
+		obs_get_video_info(&ovi) ? ovi.colorspace : VIDEO_CS_DEFAULT;
+	const bool source_hdr = (colorspace == VIDEO_CS_2100_PQ) ||
+				(colorspace == VIDEO_CS_2100_HLG);
+	const bool enable_hdr =
+		source_hdr &&
+		(obs_output_get_video_conversion(decklinkOutput->GetOutput())
+			 ->colorspace == VIDEO_CS_2100_PQ);
+	BMDPixelFormat pixelFormat = enable_hdr ? bmdFormat10BitRGBXLE
+						: bmdFormat8BitBGRA;
 	const int64_t minimumPrerollFrames =
 	const int64_t minimumPrerollFrames =
 		std::max(device->GetMinimumPrerollFrames(), INT64_C(3));
 		std::max(device->GetMinimumPrerollFrames(), INT64_C(3));
 	for (int64_t i = 0; i < minimumPrerollFrames; ++i) {
 	for (int64_t i = 0; i < minimumPrerollFrames; ++i) {
 		ComPtr<IDeckLinkMutableVideoFrame> decklinkOutputFrame;
 		ComPtr<IDeckLinkMutableVideoFrame> decklinkOutputFrame;
 		HRESULT result = output_->CreateVideoFrame(
 		HRESULT result = output_->CreateVideoFrame(
 			decklinkOutput->GetWidth(), decklinkOutput->GetHeight(),
 			decklinkOutput->GetWidth(), decklinkOutput->GetHeight(),
-			rowSize, bmdFormat8BitBGRA, bmdFrameFlagDefault,
+			rowSize, pixelFormat, bmdFrameFlagDefault,
 			&decklinkOutputFrame);
 			&decklinkOutputFrame);
 		if (result != S_OK) {
 		if (result != S_OK) {
 			blog(LOG_ERROR, "failed to create video frame 0x%X",
 			blog(LOG_ERROR, "failed to create video frame 0x%X",
@@ -640,7 +651,15 @@ bool DeckLinkDeviceInstance::StartOutput(DeckLinkDeviceMode *mode_)
 			return false;
 			return false;
 		}
 		}
 
 
-		result = output_->ScheduleVideoFrame(decklinkOutputFrame,
+		IDeckLinkVideoFrame *theFrame = decklinkOutputFrame.Get();
+		ComPtr<HDRVideoFrame> decklinkOutputHDRFrame;
+		if (enable_hdr) {
+			*decklinkOutputHDRFrame.Assign() =
+				new HDRVideoFrame(decklinkOutputFrame);
+			theFrame = decklinkOutputHDRFrame.Get();
+		}
+
+		result = output_->ScheduleVideoFrame(theFrame,
 						     i * frameDuration,
 						     i * frameDuration,
 						     frameDuration,
 						     frameDuration,
 						     frameTimescale);
 						     frameTimescale);

+ 8 - 0
plugins/decklink/decklink-device.cpp

@@ -110,6 +110,9 @@ bool DeckLinkDevice::Init()
 	attributes->GetFlag(BMDDeckLinkSupportsInternalKeying,
 	attributes->GetFlag(BMDDeckLinkSupportsInternalKeying,
 			    &supportsInternalKeyer);
 			    &supportsInternalKeyer);
 
 
+	attributes->GetFlag(BMDDeckLinkSupportsHDRMetadata,
+			    &supportsHDRMetadata);
+
 	// Sub Device Counts
 	// Sub Device Counts
 	attributes->GetInt(BMDDeckLinkSubDeviceIndex, &subDeviceIndex);
 	attributes->GetInt(BMDDeckLinkSubDeviceIndex, &subDeviceIndex);
 	attributes->GetInt(BMDDeckLinkNumberOfSubDevices, &numSubDevices);
 	attributes->GetInt(BMDDeckLinkNumberOfSubDevices, &numSubDevices);
@@ -250,6 +253,11 @@ bool DeckLinkDevice::GetSupportsInternalKeyer(void) const
 	return supportsInternalKeyer;
 	return supportsInternalKeyer;
 }
 }
 
 
+bool DeckLinkDevice::GetSupportsHDRMetadata(void) const
+{
+	return supportsHDRMetadata;
+}
+
 int64_t DeckLinkDevice::GetSubDeviceCount()
 int64_t DeckLinkDevice::GetSubDeviceCount()
 {
 {
 	return numSubDevices;
 	return numSubDevices;

+ 2 - 0
plugins/decklink/decklink-device.hpp

@@ -19,6 +19,7 @@ class DeckLinkDevice {
 	int32_t maxChannel = 0;
 	int32_t maxChannel = 0;
 	decklink_bool_t supportsExternalKeyer = false;
 	decklink_bool_t supportsExternalKeyer = false;
 	decklink_bool_t supportsInternalKeyer = false;
 	decklink_bool_t supportsInternalKeyer = false;
+	decklink_bool_t supportsHDRMetadata = false;
 	int64_t subDeviceIndex = 0;
 	int64_t subDeviceIndex = 0;
 	int64_t numSubDevices = 0;
 	int64_t numSubDevices = 0;
 	int64_t minimumPrerollFrames = 3;
 	int64_t minimumPrerollFrames = 3;
@@ -48,6 +49,7 @@ public:
 	int64_t GetAudioInputConnections();
 	int64_t GetAudioInputConnections();
 	bool GetSupportsExternalKeyer(void) const;
 	bool GetSupportsExternalKeyer(void) const;
 	bool GetSupportsInternalKeyer(void) const;
 	bool GetSupportsInternalKeyer(void) const;
+	bool GetSupportsHDRMetadata(void) const;
 	int64_t GetSubDeviceCount();
 	int64_t GetSubDeviceCount();
 	int64_t GetSubDeviceIndex();
 	int64_t GetSubDeviceIndex();
 	int64_t GetMinimumPrerollFrames();
 	int64_t GetMinimumPrerollFrames();

+ 7 - 3
plugins/decklink/decklink-output.cpp

@@ -24,6 +24,7 @@ static void *decklink_output_create(obs_data_t *settings, obs_output_t *output)
 	decklinkOutput->deviceHash = obs_data_get_string(settings, DEVICE_HASH);
 	decklinkOutput->deviceHash = obs_data_get_string(settings, DEVICE_HASH);
 	decklinkOutput->modeID = obs_data_get_int(settings, MODE_ID);
 	decklinkOutput->modeID = obs_data_get_int(settings, MODE_ID);
 	decklinkOutput->keyerMode = (int)obs_data_get_int(settings, KEYER);
 	decklinkOutput->keyerMode = (int)obs_data_get_int(settings, KEYER);
+	decklinkOutput->force_sdr = obs_data_get_bool(settings, FORCE_SDR);
 
 
 	ComPtr<DeckLinkDevice> device;
 	ComPtr<DeckLinkDevice> device;
 	device.Set(deviceEnum->FindByHash(decklinkOutput->deviceHash));
 	device.Set(deviceEnum->FindByHash(decklinkOutput->deviceHash));
@@ -36,9 +37,10 @@ static void *decklink_output_create(obs_data_t *settings, obs_output_t *output)
 		to.width = mode->GetWidth();
 		to.width = mode->GetWidth();
 		to.height = mode->GetHeight();
 		to.height = mode->GetHeight();
 		to.range = VIDEO_RANGE_FULL;
 		to.range = VIDEO_RANGE_FULL;
-		struct obs_video_info ovi;
-		to.colorspace = obs_get_video_info(&ovi) ? ovi.colorspace
-							 : VIDEO_CS_DEFAULT;
+		to.colorspace = (device->GetSupportsHDRMetadata() &&
+				 !decklinkOutput->force_sdr)
+					? VIDEO_CS_2100_PQ
+					: VIDEO_CS_709;
 
 
 		obs_output_set_video_conversion(output, &to);
 		obs_output_set_video_conversion(output, &to);
 	}
 	}
@@ -53,6 +55,7 @@ static void decklink_output_update(void *data, obs_data_t *settings)
 	decklink->deviceHash = obs_data_get_string(settings, DEVICE_HASH);
 	decklink->deviceHash = obs_data_get_string(settings, DEVICE_HASH);
 	decklink->modeID = obs_data_get_int(settings, MODE_ID);
 	decklink->modeID = obs_data_get_int(settings, MODE_ID);
 	decklink->keyerMode = (int)obs_data_get_int(settings, KEYER);
 	decklink->keyerMode = (int)obs_data_get_int(settings, KEYER);
+	decklink->force_sdr = obs_data_get_bool(settings, FORCE_SDR);
 }
 }
 
 
 static bool decklink_output_start(void *data)
 static bool decklink_output_start(void *data)
@@ -272,6 +275,7 @@ static obs_properties_t *decklink_output_properties(void *unused)
 				OBS_COMBO_FORMAT_INT);
 				OBS_COMBO_FORMAT_INT);
 
 
 	obs_properties_add_bool(props, AUTO_START, TEXT_AUTO_START);
 	obs_properties_add_bool(props, AUTO_START, TEXT_AUTO_START);
+	obs_properties_add_bool(props, FORCE_SDR, TEXT_FORCE_SDR);
 
 
 	obs_properties_add_list(props, KEYER, TEXT_ENABLE_KEYER,
 	obs_properties_add_list(props, KEYER, TEXT_ENABLE_KEYER,
 				OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
 				OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);