313 lines
10 KiB
C++
313 lines
10 KiB
C++
#include "decklink.hpp"
|
|
#include "decklink-device.hpp"
|
|
#include "decklink-device-discovery.hpp"
|
|
|
|
#include <obs-module.h>
|
|
|
|
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<DeckLinkDevice> 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<DeckLinkDevice> 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<DeckLinkDeviceMode*> &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<DeckLinkDevice*> &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;
|
|
}
|