From 41c2f5e13b95160c453b3755dcaaba93201139a2 Mon Sep 17 00:00:00 2001 From: mntone Date: Fri, 5 May 2017 21:55:06 +0900 Subject: [PATCH] decklink: Add feature to detect resolution/format Closes jp9000/obs-studio#879 --- plugins/decklink/data/locale/en-US.ini | 6 + plugins/decklink/decklink-device-instance.cpp | 103 +++++++++++++++--- plugins/decklink/decklink-device-instance.hpp | 7 ++ plugins/decklink/decklink-device-mode.cpp | 19 ++++ plugins/decklink/decklink-device-mode.hpp | 5 + plugins/decklink/decklink-device.cpp | 18 ++- plugins/decklink/decklink.cpp | 2 + plugins/decklink/decklink.hpp | 12 ++ plugins/decklink/platform.hpp | 3 + plugins/decklink/plugin-main.cpp | 63 +++++++++++ 10 files changed, 220 insertions(+), 18 deletions(-) diff --git a/plugins/decklink/data/locale/en-US.ini b/plugins/decklink/data/locale/en-US.ini index 202070d5f..5605932f9 100644 --- a/plugins/decklink/data/locale/en-US.ini +++ b/plugins/decklink/data/locale/en-US.ini @@ -3,6 +3,12 @@ Device="Device" Mode="Mode" Buffering="Use Buffering" PixelFormat="Pixel Format" +ColorSpace="YUV Color Space" +ColorSpace.Default="Default" +ColorRange="YUV Color Range" +ColorRange.Default="Default" +ColorRange.Partial="Partial" +ColorRange.Full="Full" ChannelFormat="Channel" ChannelFormat.None="None" ChannelFormat.2_0ch="2ch" diff --git a/plugins/decklink/decklink-device-instance.cpp b/plugins/decklink/decklink-device-instance.cpp index 557713aaa..9e95e68d0 100644 --- a/plugins/decklink/decklink-device-instance.cpp +++ b/plugins/decklink/decklink-device-instance.cpp @@ -118,16 +118,15 @@ void DeckLinkDeviceInstance::HandleVideoFrame( currentFrame.height = (uint32_t)videoFrame->GetHeight(); currentFrame.timestamp = timestamp; - video_format_get_parameters(VIDEO_CS_601, VIDEO_RANGE_PARTIAL, - currentFrame.color_matrix, currentFrame.color_range_min, - currentFrame.color_range_max); - obs_source_output_video(decklink->GetSource(), ¤tFrame); } void DeckLinkDeviceInstance::FinalizeStream() { input->SetCallback(nullptr); + input->DisableVideoInput(); + if (channelFormat != SPEAKERS_UNKNOWN) + input->DisableAudioInput(); if (audioRepacker != nullptr) { @@ -138,6 +137,43 @@ void DeckLinkDeviceInstance::FinalizeStream() mode = nullptr; } +//#define LOG_SETUP_VIDEO_FORMAT 1 + +void DeckLinkDeviceInstance::SetupVideoFormat(DeckLinkDeviceMode *mode_) +{ + if (mode_ == nullptr) + return; + + currentFrame.format = ConvertPixelFormat(pixelFormat); + + colorSpace = decklink->GetColorSpace(); + if (colorSpace == VIDEO_CS_DEFAULT) { + const BMDDisplayModeFlags flags = mode_->GetDisplayModeFlags(); + if (flags & bmdDisplayModeColorspaceRec709) + activeColorSpace = VIDEO_CS_709; + else if (flags & bmdDisplayModeColorspaceRec601) + activeColorSpace = VIDEO_CS_601; + else + activeColorSpace = VIDEO_CS_DEFAULT; + } else { + activeColorSpace = colorSpace; + } + + colorRange = decklink->GetColorRange(); + currentFrame.full_range = colorRange == VIDEO_RANGE_FULL; + + video_format_get_parameters(activeColorSpace, colorRange, + currentFrame.color_matrix, currentFrame.color_range_min, + currentFrame.color_range_max); + +#ifdef LOG_SETUP_VIDEO_FORMAT + LOG(LOG_INFO, "Setup video format: %s, %s, %s", + pixelFormat == bmdFormat8BitYUV ? "YUV" : "RGB", + activeColorSpace == VIDEO_CS_709 ? "BT.709" : "BT.601", + colorRange == VIDEO_RANGE_FULL ? "full" : "limited"); +#endif +} + bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_) { if (mode != nullptr) @@ -150,19 +186,28 @@ bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_) if (!device->GetInput(&input)) return false; - pixelFormat = decklink->GetPixelFormat(); - currentFrame.format = ConvertPixelFormat(pixelFormat); + BMDVideoInputFlags flags; - const BMDDisplayMode displayMode = mode_->GetDisplayMode(); + bool isauto = mode_->GetName() == "Auto"; + if (isauto) { + displayMode = bmdModeNTSC; + pixelFormat = bmdFormat8BitYUV; + flags = bmdVideoInputEnableFormatDetection; + } else { + displayMode = mode_->GetDisplayMode(); + pixelFormat = decklink->GetPixelFormat(); + flags = bmdVideoInputFlagDefault; + } const HRESULT videoResult = input->EnableVideoInput(displayMode, - pixelFormat, bmdVideoInputFlagDefault); - + pixelFormat, flags); if (videoResult != S_OK) { LOG(LOG_ERROR, "Failed to enable video input"); return false; } + SetupVideoFormat(mode_); + channelFormat = decklink->GetChannelFormat(); currentPacket.speakers = channelFormat; @@ -171,7 +216,6 @@ bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_) const HRESULT audioResult = input->EnableAudioInput( bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, channel); - if (audioResult != S_OK) LOG(LOG_WARNING, "Failed to enable audio input; continuing..."); @@ -257,12 +301,41 @@ HRESULT STDMETHODCALLTYPE DeckLinkDeviceInstance::VideoInputFormatChanged( IDeckLinkDisplayMode *newMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags) { - UNUSED_PARAMETER(events); - UNUSED_PARAMETER(newMode); - UNUSED_PARAMETER(detectedSignalFlags); + input->PauseStreams(); - // There is no implementation for automatic format detection, so this - // method goes unused. + mode->SetMode(newMode); + + if (events & bmdVideoInputDisplayModeChanged) { + displayMode = mode->GetDisplayMode(); + } + + if (events & bmdVideoInputColorspaceChanged) { + switch (detectedSignalFlags) { + case bmdDetectedVideoInputRGB444: + pixelFormat = bmdFormat8BitBGRA; + break; + + default: + case bmdDetectedVideoInputYCbCr422: + pixelFormat = bmdFormat8BitYUV; + break; + } + } + + const HRESULT videoResult = input->EnableVideoInput(displayMode, + pixelFormat, bmdVideoInputEnableFormatDetection); + if (videoResult != S_OK) { + LOG(LOG_ERROR, "Failed to enable video input"); + input->StopStreams(); + FinalizeStream(); + + return E_FAIL; + } + + SetupVideoFormat(mode); + + input->FlushStreams(); + input->StartStreams(); return S_OK; } diff --git a/plugins/decklink/decklink-device-instance.hpp b/plugins/decklink/decklink-device-instance.hpp index c0af8f5b0..ffbed0782 100644 --- a/plugins/decklink/decklink-device-instance.hpp +++ b/plugins/decklink/decklink-device-instance.hpp @@ -11,7 +11,11 @@ protected: DeckLink *decklink = nullptr; DeckLinkDevice *device = nullptr; DeckLinkDeviceMode *mode = nullptr; + BMDDisplayMode displayMode = bmdModeNTSC; BMDPixelFormat pixelFormat = bmdFormat8BitYUV; + video_colorspace colorSpace = VIDEO_CS_DEFAULT; + video_colorspace activeColorSpace = VIDEO_CS_DEFAULT; + video_range_type colorRange = VIDEO_RANGE_DEFAULT; ComPtr input; volatile long refCount = 1; int64_t audioOffset = 0; @@ -21,6 +25,7 @@ protected: speaker_layout channelFormat = SPEAKERS_STEREO; void FinalizeStream(); + void SetupVideoFormat(DeckLinkDeviceMode *mode_); void HandleAudioPacket(IDeckLinkAudioInputPacket *audioPacket, const uint64_t timestamp); @@ -38,6 +43,8 @@ public: } inline BMDPixelFormat GetActivePixelFormat() const {return pixelFormat;} + inline video_colorspace GetActiveColorSpace() const {return colorSpace;} + inline video_range_type GetActiveColorRange() const {return colorRange;} inline speaker_layout GetActiveChannelFormat() const {return channelFormat;} inline DeckLinkDeviceMode *GetMode() const {return mode;} diff --git a/plugins/decklink/decklink-device-mode.cpp b/plugins/decklink/decklink-device-mode.cpp index 8f53ac749..48d6eac5d 100644 --- a/plugins/decklink/decklink-device-mode.cpp +++ b/plugins/decklink/decklink-device-mode.cpp @@ -32,6 +32,14 @@ BMDDisplayMode DeckLinkDeviceMode::GetDisplayMode(void) const return bmdModeUnknown; } +BMDDisplayModeFlags DeckLinkDeviceMode::GetDisplayModeFlags(void) const +{ + if (mode != nullptr) + return mode->GetFlags(); + + return (BMDDisplayModeFlags)0; +} + long long DeckLinkDeviceMode::GetId(void) const { return id; @@ -41,3 +49,14 @@ const std::string& DeckLinkDeviceMode::GetName(void) const { return name; } + +void DeckLinkDeviceMode::SetMode(IDeckLinkDisplayMode *mode_) +{ + IDeckLinkDisplayMode *old = mode; + if (old != nullptr) + old->Release(); + + mode = mode_; + if (mode != nullptr) + mode->AddRef(); +} diff --git a/plugins/decklink/decklink-device-mode.hpp b/plugins/decklink/decklink-device-mode.hpp index 36bbc2ff6..8ff642da7 100644 --- a/plugins/decklink/decklink-device-mode.hpp +++ b/plugins/decklink/decklink-device-mode.hpp @@ -4,6 +4,8 @@ #include +#define MODE_ID_AUTO -1 + class DeckLinkDeviceMode { protected: long long id; @@ -16,6 +18,9 @@ public: virtual ~DeckLinkDeviceMode(void); BMDDisplayMode GetDisplayMode(void) const; + BMDDisplayModeFlags GetDisplayModeFlags(void) const; long long GetId(void) const; const std::string& GetName(void) const; + + void SetMode(IDeckLinkDisplayMode *mode); }; diff --git a/plugins/decklink/decklink-device.cpp b/plugins/decklink/decklink-device.cpp index 3203247d4..d173053af 100644 --- a/plugins/decklink/decklink-device.cpp +++ b/plugins/decklink/decklink-device.cpp @@ -29,6 +29,21 @@ ULONG DeckLinkDevice::Release() bool DeckLinkDevice::Init() { + ComPtr attributes; + const HRESULT result = device->QueryInterface(IID_IDeckLinkAttributes, + (void **)&attributes); + + if (result == S_OK) { + decklink_bool_t detectable = false; + if (attributes->GetFlag(BMDDeckLinkSupportsInputFormatDetection, + &detectable) == S_OK && !!detectable) { + DeckLinkDeviceMode *mode = + new DeckLinkDeviceMode("Auto", MODE_ID_AUTO); + modes.push_back(mode); + modeIdMap[MODE_ID_AUTO] = mode; + } + } + ComPtr input; if (device->QueryInterface(IID_IDeckLinkInput, (void**)&input) != S_OK) return false; @@ -66,9 +81,6 @@ bool DeckLinkDevice::Init() hash = displayName; - ComPtr attributes; - const HRESULT result = device->QueryInterface(IID_IDeckLinkAttributes, - (void **)&attributes); if (result != S_OK) return true; diff --git a/plugins/decklink/decklink.cpp b/plugins/decklink/decklink.cpp index c20ff32d5..ea0ec9632 100644 --- a/plugins/decklink/decklink.cpp +++ b/plugins/decklink/decklink.cpp @@ -66,6 +66,8 @@ bool DeckLink::Activate(DeckLinkDevice *device, long long modeId) return false; if (instance->GetActiveModeId() == modeId && instance->GetActivePixelFormat() == pixelFormat && + instance->GetActiveColorSpace() == colorSpace && + instance->GetActiveColorRange() == colorRange && instance->GetActiveChannelFormat() == channelFormat) return false; } diff --git a/plugins/decklink/decklink.hpp b/plugins/decklink/decklink.hpp index 217637a4a..10d3b11f9 100644 --- a/plugins/decklink/decklink.hpp +++ b/plugins/decklink/decklink.hpp @@ -22,6 +22,8 @@ protected: volatile long activateRefs = 0; std::recursive_mutex deviceMutex; BMDPixelFormat pixelFormat = bmdFormat8BitYUV; + video_colorspace colorSpace = VIDEO_CS_DEFAULT; + video_range_type colorRange = VIDEO_RANGE_DEFAULT; speaker_layout channelFormat = SPEAKERS_STEREO; void SaveSettings(); @@ -42,6 +44,16 @@ public: { pixelFormat = format; } + inline video_colorspace GetColorSpace() const {return colorSpace;} + inline void SetColorSpace(video_colorspace format) + { + colorSpace = format; + } + inline video_range_type GetColorRange() const {return colorRange;} + inline void SetColorRange(video_range_type format) + { + colorRange = format; + } inline speaker_layout GetChannelFormat() const {return channelFormat;} inline void SetChannelFormat(speaker_layout format) { diff --git a/plugins/decklink/platform.hpp b/plugins/decklink/platform.hpp index 0be43f458..b5f76002b 100644 --- a/plugins/decklink/platform.hpp +++ b/plugins/decklink/platform.hpp @@ -2,6 +2,7 @@ #if defined(_WIN32) #include +typedef BOOL decklink_bool_t; typedef BSTR decklink_string_t; IDeckLinkDiscovery *CreateDeckLinkDiscoveryInstance(void); #define IUnknownUUID IID_IUnknown @@ -10,9 +11,11 @@ typedef REFIID CFUUIDBytes; #elif defined(__APPLE__) #include "mac/decklink-sdk/DeckLinkAPI.h" #include +typedef bool decklink_bool_t; typedef CFStringRef decklink_string_t; #elif defined(__linux__) #include "linux/decklink-sdk/DeckLinkAPI.h" +typedef bool decklink_bool_t; typedef const char *decklink_string_t; #endif diff --git a/plugins/decklink/plugin-main.cpp b/plugins/decklink/plugin-main.cpp index 465eda02c..0cefc1979 100644 --- a/plugins/decklink/plugin-main.cpp +++ b/plugins/decklink/plugin-main.cpp @@ -13,11 +13,19 @@ OBS_MODULE_USE_DEFAULT_LOCALE("decklink", "en-US") #define MODE_NAME "mode_name" #define CHANNEL_FORMAT "channel_format" #define PIXEL_FORMAT "pixel_format" +#define COLOR_SPACE "color_space" +#define COLOR_RANGE "color_range" #define BUFFERING "buffering" #define TEXT_DEVICE obs_module_text("Device") #define TEXT_MODE obs_module_text("Mode") #define TEXT_PIXEL_FORMAT obs_module_text("PixelFormat") +#define TEXT_COLOR_SPACE obs_module_text("ColorSpace") +#define TEXT_COLOR_SPACE_DEFAULT obs_module_text("ColorSpace.Default") +#define TEXT_COLOR_RANGE obs_module_text("ColorRange") +#define TEXT_COLOR_RANGE_DEFAULT obs_module_text("ColorRange.Default") +#define TEXT_COLOR_RANGE_PARTIAL obs_module_text("ColorRange.Partial") +#define TEXT_COLOR_RANGE_FULL obs_module_text("ColorRange.Full") #define TEXT_CHANNEL_FORMAT obs_module_text("ChannelFormat") #define TEXT_CHANNEL_FORMAT_NONE obs_module_text("ChannelFormat.None") #define TEXT_CHANNEL_FORMAT_2_0CH obs_module_text("ChannelFormat.2_0ch") @@ -58,6 +66,10 @@ static void decklink_update(void *data, obs_data_t *settings) long long id = obs_data_get_int(settings, MODE_ID); BMDPixelFormat pixelFormat = (BMDPixelFormat)obs_data_get_int(settings, PIXEL_FORMAT); + video_colorspace colorSpace = (video_colorspace)obs_data_get_int(settings, + COLOR_SPACE); + video_range_type colorRange = (video_range_type)obs_data_get_int(settings, + COLOR_RANGE); speaker_layout channelFormat = (speaker_layout)obs_data_get_int(settings, CHANNEL_FORMAT); @@ -68,6 +80,8 @@ static void decklink_update(void *data, obs_data_t *settings) device.Set(deviceEnum->FindByHash(hash)); decklink->SetPixelFormat(pixelFormat); + decklink->SetColorSpace(colorSpace); + decklink->SetColorRange(colorRange); decklink->SetChannelFormat(channelFormat); decklink->Activate(device, id); } @@ -76,6 +90,8 @@ static void decklink_get_defaults(obs_data_t *settings) { obs_data_set_default_bool(settings, BUFFERING, true); obs_data_set_default_int(settings, PIXEL_FORMAT, bmdFormat8BitYUV); + obs_data_set_default_int(settings, COLOR_SPACE, VIDEO_CS_DEFAULT); + obs_data_set_default_int(settings, COLOR_RANGE, VIDEO_RANGE_DEFAULT); obs_data_set_default_int(settings, CHANNEL_FORMAT, SPEAKERS_STEREO); } @@ -162,6 +178,38 @@ static void fill_out_devices(obs_property_t *list) deviceEnum->Unlock(); } +static bool color_format_changed(obs_properties_t *props, + obs_property_t *list, obs_data_t *settings); + +static bool mode_id_changed(obs_properties_t *props, + obs_property_t *list, obs_data_t *settings) +{ + long long id = obs_data_get_int(settings, MODE_ID); + + list = obs_properties_get(props, PIXEL_FORMAT); + obs_property_set_visible(list, id != MODE_ID_AUTO); + + return color_format_changed(props, nullptr, settings); +} + +static bool color_format_changed(obs_properties_t *props, + obs_property_t *list, obs_data_t *settings) +{ + long long id = obs_data_get_int(settings, MODE_ID); + BMDPixelFormat pixelFormat = (BMDPixelFormat)obs_data_get_int(settings, + PIXEL_FORMAT); + + list = obs_properties_get(props, COLOR_SPACE); + obs_property_set_visible(list, + id != MODE_ID_AUTO && pixelFormat == bmdFormat8BitYUV); + + list = obs_properties_get(props, COLOR_RANGE); + obs_property_set_visible(list, + id == MODE_ID_AUTO || pixelFormat == bmdFormat8BitYUV); + + return true; +} + static obs_properties_t *decklink_get_properties(void *data) { obs_properties_t *props = obs_properties_create(); @@ -174,13 +222,28 @@ static obs_properties_t *decklink_get_properties(void *data) list = obs_properties_add_list(props, MODE_ID, TEXT_MODE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_modified_callback(list, mode_id_changed); list = obs_properties_add_list(props, PIXEL_FORMAT, TEXT_PIXEL_FORMAT, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_modified_callback(list, color_format_changed); + obs_property_list_add_int(list, "8-bit YUV", bmdFormat8BitYUV); obs_property_list_add_int(list, "8-bit BGRA", bmdFormat8BitBGRA); + list = obs_properties_add_list(props, COLOR_SPACE, TEXT_COLOR_SPACE, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(list, TEXT_COLOR_SPACE_DEFAULT, VIDEO_CS_DEFAULT); + obs_property_list_add_int(list, "BT.601", VIDEO_CS_601); + obs_property_list_add_int(list, "BT.709", VIDEO_CS_709); + + list = obs_properties_add_list(props, COLOR_RANGE, TEXT_COLOR_RANGE, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(list, TEXT_COLOR_RANGE_DEFAULT, VIDEO_RANGE_DEFAULT); + obs_property_list_add_int(list, TEXT_COLOR_RANGE_PARTIAL, VIDEO_RANGE_PARTIAL); + obs_property_list_add_int(list, TEXT_COLOR_RANGE_FULL, VIDEO_RANGE_FULL); + list = obs_properties_add_list(props, CHANNEL_FORMAT, TEXT_CHANNEL_FORMAT, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);