#include "decklink.hpp" #include "decklink-device.hpp" #include "decklink-device-discovery.hpp" #include OBS_DECLARE_MODULE() OBS_MODULE_USE_DEFAULT_LOCALE("decklink", "en-US") #define DEVICE_HASH "device_hash" #define DEVICE_NAME "device_name" #define MODE_ID "mode_id" #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") #define TEXT_CHANNEL_FORMAT_2_1CH obs_module_text("ChannelFormat.2_1ch") #define TEXT_CHANNEL_FORMAT_4_0CH obs_module_text("ChannelFormat.4_0ch") #define TEXT_CHANNEL_FORMAT_4_1CH obs_module_text("ChannelFormat.4_1ch") #define TEXT_CHANNEL_FORMAT_5_1CH obs_module_text("ChannelFormat.5_1ch") #define TEXT_CHANNEL_FORMAT_7_1CH obs_module_text("ChannelFormat.7_1ch") #define TEXT_BUFFERING obs_module_text("Buffering") static DeckLinkDeviceDiscovery *deviceEnum = nullptr; static void decklink_enable_buffering(DeckLink *decklink, bool enabled) { obs_source_t *source = decklink->GetSource(); obs_source_set_async_unbuffered(source, !enabled); decklink->buffering = enabled; } static void *decklink_create(obs_data_t *settings, obs_source_t *source) { DeckLink *decklink = new DeckLink(source, deviceEnum); obs_source_set_async_decoupled(source, true); decklink_enable_buffering(decklink, obs_data_get_bool(settings, BUFFERING)); obs_source_update(source, settings); return decklink; } static void decklink_destroy(void *data) { DeckLink *decklink = (DeckLink *)data; delete decklink; } static void decklink_update(void *data, obs_data_t *settings) { DeckLink *decklink = (DeckLink *)data; const char *hash = obs_data_get_string(settings, DEVICE_HASH); 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); int chFmtInt = (int)obs_data_get_int(settings, CHANNEL_FORMAT); if (chFmtInt == 7) { chFmtInt = SPEAKERS_5POINT1; } else if (chFmtInt < SPEAKERS_UNKNOWN || chFmtInt > SPEAKERS_7POINT1) { chFmtInt = 2; } speaker_layout channelFormat = (speaker_layout)chFmtInt; decklink_enable_buffering(decklink, obs_data_get_bool(settings, BUFFERING)); ComPtr device; device.Set(deviceEnum->FindByHash(hash)); decklink->SetPixelFormat(pixelFormat); decklink->SetColorSpace(colorSpace); decklink->SetColorRange(colorRange); decklink->SetChannelFormat(channelFormat); decklink->Activate(device, id); } static void decklink_get_defaults(obs_data_t *settings) { obs_data_set_default_bool(settings, BUFFERING, false); 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); } static const char *decklink_get_name(void*) { return obs_module_text("BlackmagicDevice"); } static bool decklink_device_changed(obs_properties_t *props, obs_property_t *list, obs_data_t *settings) { const char *name = obs_data_get_string(settings, DEVICE_NAME); const char *hash = obs_data_get_string(settings, DEVICE_HASH); const char *mode = obs_data_get_string(settings, MODE_NAME); long long modeId = obs_data_get_int(settings, MODE_ID); size_t itemCount = obs_property_list_item_count(list); bool itemFound = false; for (size_t i = 0; i < itemCount; i++) { const char *curHash = obs_property_list_item_string(list, i); if (strcmp(hash, curHash) == 0) { itemFound = true; break; } } if (!itemFound) { obs_property_list_insert_string(list, 0, name, hash); obs_property_list_item_disable(list, 0, true); } obs_property_t *modeList = obs_properties_get(props, MODE_ID); obs_property_t *channelList = obs_properties_get(props, CHANNEL_FORMAT); obs_property_list_clear(modeList); obs_property_list_clear(channelList); obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_NONE, SPEAKERS_UNKNOWN); obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_2_0CH, SPEAKERS_STEREO); ComPtr device; device.Set(deviceEnum->FindByHash(hash)); if (!device) { obs_property_list_add_int(modeList, mode, modeId); obs_property_list_item_disable(modeList, 0, true); } else { const std::vector &modes = device->GetModes(); for (DeckLinkDeviceMode *mode : modes) { obs_property_list_add_int(modeList, mode->GetName().c_str(), mode->GetId()); } if (device->GetMaxChannel() >= 8) { obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_2_1CH, SPEAKERS_2POINT1); obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_4_0CH, SPEAKERS_4POINT0); obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_4_1CH, SPEAKERS_4POINT1); obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_5_1CH, SPEAKERS_5POINT1); obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_7_1CH, SPEAKERS_7POINT1); } } return true; } static void fill_out_devices(obs_property_t *list) { deviceEnum->Lock(); const std::vector &devices = deviceEnum->GetDevices(); for (DeckLinkDevice *device : devices) { obs_property_list_add_string(list, device->GetDisplayName().c_str(), device->GetHash().c_str()); } 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(); obs_property_t *list = obs_properties_add_list(props, DEVICE_HASH, TEXT_DEVICE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); obs_property_set_modified_callback(list, decklink_device_changed); fill_out_devices(list); 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); obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_NONE, SPEAKERS_UNKNOWN); obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_2_0CH, SPEAKERS_STEREO); obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_2_1CH, SPEAKERS_2POINT1); obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_4_0CH, SPEAKERS_4POINT0); obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_4_1CH, SPEAKERS_4POINT1); obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_5_1CH, SPEAKERS_5POINT1); obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_7_1CH, SPEAKERS_7POINT1); obs_properties_add_bool(props, BUFFERING, TEXT_BUFFERING); UNUSED_PARAMETER(data); return props; } bool obs_module_load(void) { deviceEnum = new DeckLinkDeviceDiscovery(); if (!deviceEnum->Init()) return true; struct obs_source_info info = {}; info.id = "decklink-input"; info.type = OBS_SOURCE_TYPE_INPUT; info.output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE; info.create = decklink_create; info.destroy = decklink_destroy; info.get_defaults = decklink_get_defaults; info.get_name = decklink_get_name; info.get_properties = decklink_get_properties; info.update = decklink_update; obs_register_source(&info); return true; } void obs_module_unload(void) { delete deviceEnum; }