diff --git a/plugins/decklink/data/locale/en-US.ini b/plugins/decklink/data/locale/en-US.ini new file mode 100644 index 000000000..50ace69d8 --- /dev/null +++ b/plugins/decklink/data/locale/en-US.ini @@ -0,0 +1,4 @@ +BlackmagicDevice="Blackmagic Device" +Device="Device" +Mode="Mode" +Buffering="Use Buffering" diff --git a/plugins/decklink/decklink-device-discovery.cpp b/plugins/decklink/decklink-device-discovery.cpp new file mode 100644 index 000000000..73e7918dd --- /dev/null +++ b/plugins/decklink/decklink-device-discovery.cpp @@ -0,0 +1,131 @@ +#include "decklink-device-discovery.hpp" +#include "decklink-device.hpp" + +#include + +DeckLinkDeviceDiscovery::DeckLinkDeviceDiscovery() +{ + discovery = CreateDeckLinkDiscoveryInstance(); + if (discovery == nullptr) + blog(LOG_ERROR, "Failed to create IDeckLinkDiscovery"); +} + +DeckLinkDeviceDiscovery::~DeckLinkDeviceDiscovery(void) +{ + if (discovery != nullptr) { + if (initialized) + discovery->UninstallDeviceNotifications(); + for (DeckLinkDevice *device : devices) + device->Release(); + } +} + +bool DeckLinkDeviceDiscovery::Init(void) +{ + HRESULT result = E_FAIL; + + if (initialized) + return false; + + if (discovery != nullptr) + result = discovery->InstallDeviceNotifications(this); + + initialized = result == S_OK; + if (initialized) + blog(LOG_INFO, "Failed to start search for DeckLink devices"); + + return initialized; +} + +DeckLinkDevice *DeckLinkDeviceDiscovery::FindByHash(const char *hash) +{ + DeckLinkDevice *ret = nullptr; + + deviceMutex.lock(); + for (DeckLinkDevice *device : devices) { + if (device->GetHash().compare(hash) == 0) { + ret = device; + ret->AddRef(); + break; + } + } + deviceMutex.unlock(); + + return ret; +} + +HRESULT STDMETHODCALLTYPE DeckLinkDeviceDiscovery::DeckLinkDeviceArrived( + IDeckLink *device) +{ + DeckLinkDevice *newDev = new DeckLinkDevice(device); + if (!newDev->Init()) { + delete newDev; + return S_OK; + } + + std::lock_guard lock(deviceMutex); + + devices.push_back(newDev); + + for (DeviceChangeInfo &cb : callbacks) + cb.callback(cb.param, newDev, true); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE DeckLinkDeviceDiscovery::DeckLinkDeviceRemoved( + IDeckLink *device) +{ + std::lock_guard lock(deviceMutex); + + for (size_t i = 0; i < devices.size(); i++) { + if (devices[i]->IsDevice(device)) { + + for (DeviceChangeInfo &cb : callbacks) + cb.callback(cb.param, devices[i], false); + + devices[i]->Release(); + devices.erase(devices.begin() + i); + break; + } + } + return S_OK; +} + +ULONG STDMETHODCALLTYPE DeckLinkDeviceDiscovery::AddRef(void) +{ + return os_atomic_inc_long(&refCount); +} + +HRESULT STDMETHODCALLTYPE DeckLinkDeviceDiscovery::QueryInterface(REFIID iid, + LPVOID *ppv) +{ + HRESULT result = E_NOINTERFACE; + + *ppv = nullptr; + + CFUUIDBytes unknown = CFUUIDGetUUIDBytes(IUnknownUUID); + if (memcmp(&iid, &unknown, sizeof(REFIID)) == 0) { + *ppv = this; + AddRef(); + result = S_OK; + } else if (memcmp(&iid, &IID_IDeckLinkDeviceNotificationCallback, + sizeof(REFIID)) == 0) { + *ppv = (IDeckLinkDeviceNotificationCallback *)this; + AddRef(); + result = S_OK; + } + + return result; +} + +ULONG STDMETHODCALLTYPE DeckLinkDeviceDiscovery::Release(void) +{ + const long newRefCount = os_atomic_dec_long(&refCount); + if (newRefCount == 0) { + delete this; + return 0; + } + + return newRefCount; +} diff --git a/plugins/decklink/decklink-device-discovery.hpp b/plugins/decklink/decklink-device-discovery.hpp new file mode 100644 index 000000000..3bb6e3cd8 --- /dev/null +++ b/plugins/decklink/decklink-device-discovery.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include +#include + +#include "decklink.hpp" + +class DeckLinkDevice; + +typedef void (*DeviceChangeCallback)(void *param, DeckLinkDevice *device, + bool added); + +struct DeviceChangeInfo { + DeviceChangeCallback callback; + void *param; +}; + +class DeckLinkDeviceDiscovery : public IDeckLinkDeviceNotificationCallback { +protected: + ComPtr discovery; + long refCount = 1; + bool initialized = false; + + std::recursive_mutex deviceMutex; + std::vector devices; + std::vector callbacks; + +public: + DeckLinkDeviceDiscovery(); + virtual ~DeckLinkDeviceDiscovery(void); + + bool Init(); + + HRESULT STDMETHODCALLTYPE DeckLinkDeviceArrived(IDeckLink *device); + HRESULT STDMETHODCALLTYPE DeckLinkDeviceRemoved(IDeckLink *device); + + inline void AddCallback(DeviceChangeCallback callback, void *param) + { + std::lock_guard lock(deviceMutex); + DeviceChangeInfo info; + + info.callback = callback; + info.param = param; + + for (DeviceChangeInfo &curCB : callbacks) { + if (curCB.callback == callback && + curCB.param == param) + return; + } + + callbacks.push_back(info); + } + + inline void RemoveCallback(DeviceChangeCallback callback, void *param) + { + std::lock_guard lock(deviceMutex); + + for (size_t i = 0; i < callbacks.size(); i++) { + DeviceChangeInfo &curCB = callbacks[i]; + + if (curCB.callback == callback && + curCB.param == param) { + callbacks.erase(callbacks.begin() + i); + return; + } + } + } + + DeckLinkDevice *FindByHash(const char *hash); + + inline void Lock() {deviceMutex.lock();} + inline void Unlock() {deviceMutex.unlock();} + inline const std::vector &GetDevices() const + { + return devices; + } + + ULONG STDMETHODCALLTYPE AddRef(void); + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv); + ULONG STDMETHODCALLTYPE Release(void); +}; diff --git a/plugins/decklink/decklink-device-instance.cpp b/plugins/decklink/decklink-device-instance.cpp new file mode 100644 index 000000000..f551793a2 --- /dev/null +++ b/plugins/decklink/decklink-device-instance.cpp @@ -0,0 +1,192 @@ +#include "decklink-device-instance.hpp" + +#include +#include + +#include + +#define LOG(level, message, ...) blog(level, "%s: " message, \ + obs_source_get_name(this->decklink->GetSource()), ##__VA_ARGS__) + +DeckLinkDeviceInstance::DeckLinkDeviceInstance(DeckLink *decklink_, + DeckLinkDevice *device_) : + currentFrame(), currentPacket(), decklink(decklink_), device(device_) +{ + currentFrame.format = VIDEO_FORMAT_UYVY; + + currentPacket.samples_per_sec = 48000; + currentPacket.speakers = SPEAKERS_STEREO; + currentPacket.format = AUDIO_FORMAT_16BIT; +} + +void DeckLinkDeviceInstance::HandleAudioPacket( + IDeckLinkAudioInputPacket *audioPacket, + const uint64_t timestamp) +{ + if (audioPacket == nullptr) + return; + + void *bytes; + if (audioPacket->GetBytes(&bytes) != S_OK) { + LOG(LOG_WARNING, "Failed to get audio packet data"); + return; + } + + currentPacket.data[0] = (uint8_t *)bytes; + currentPacket.frames = (uint32_t)audioPacket->GetSampleFrameCount(); + currentPacket.timestamp = timestamp; + + obs_source_output_audio(decklink->GetSource(), ¤tPacket); +} + +void DeckLinkDeviceInstance::HandleVideoFrame( + IDeckLinkVideoInputFrame *videoFrame, const uint64_t timestamp) +{ + if (videoFrame == nullptr) + return; + + void *bytes; + if (videoFrame->GetBytes(&bytes) != S_OK) { + LOG(LOG_WARNING, "Failed to get video frame data"); + return; + } + + currentFrame.data[0] = (uint8_t *)bytes; + currentFrame.linesize[0] = (uint32_t)videoFrame->GetRowBytes(); + currentFrame.width = (uint32_t)videoFrame->GetWidth(); + 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); +} + +bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_) +{ + if (mode != nullptr) + return false; + if (mode_ == nullptr) + return false; + + LOG(LOG_INFO, "Starting capture..."); + + if (!device->GetInput(&input)) + return false; + + input->SetCallback(this); + + const BMDDisplayMode displayMode = mode_->GetDisplayMode(); + const HRESULT videoResult = input->EnableVideoInput(displayMode, + bmdFormat8BitYUV, bmdVideoInputFlagDefault); + + if (videoResult != S_OK) { + LOG(LOG_ERROR, "Failed to enable video input"); + input->SetCallback(nullptr); + return false; + } + + const HRESULT audioResult = input->EnableAudioInput( + bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, + 2); + + if (audioResult != S_OK) + LOG(LOG_WARNING, "Failed to enable audio input; continuing..."); + + if (input->StartStreams() != S_OK) { + LOG(LOG_ERROR, "Failed to start streams"); + input->SetCallback(nullptr); + input->DisableVideoInput(); + input->DisableAudioInput(); + return false; + } + + mode = mode_; + + return true; +} + +bool DeckLinkDeviceInstance::StopCapture(void) +{ + if (mode == nullptr || input == nullptr) + return false; + + LOG(LOG_INFO, "Stopping capture of '%s'...", + GetDevice()->GetDisplayName().c_str()); + + input->StopStreams(); + input->SetCallback(nullptr); + input->DisableVideoInput(); + input->DisableAudioInput(); + + mode = nullptr; + + return true; +} + +HRESULT STDMETHODCALLTYPE DeckLinkDeviceInstance::VideoInputFrameArrived( + IDeckLinkVideoInputFrame *videoFrame, + IDeckLinkAudioInputPacket *audioPacket) +{ + const uint64_t timestamp = os_gettime_ns(); + + HandleVideoFrame(videoFrame, timestamp); + HandleAudioPacket(audioPacket, timestamp); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE DeckLinkDeviceInstance::VideoInputFormatChanged( + BMDVideoInputFormatChangedEvents events, + IDeckLinkDisplayMode *newMode, + BMDDetectedVideoInputFormatFlags detectedSignalFlags) +{ + UNUSED_PARAMETER(events); + UNUSED_PARAMETER(newMode); + UNUSED_PARAMETER(detectedSignalFlags); + + // There is no implementation for automatic format detection, so this + // method goes unused. + + return S_OK; +} + +ULONG STDMETHODCALLTYPE DeckLinkDeviceInstance::AddRef(void) +{ + return os_atomic_inc_long(&refCount); +} + +HRESULT STDMETHODCALLTYPE DeckLinkDeviceInstance::QueryInterface(REFIID iid, + LPVOID *ppv) +{ + HRESULT result = E_NOINTERFACE; + + *ppv = nullptr; + + CFUUIDBytes unknown = CFUUIDGetUUIDBytes(IUnknownUUID); + if (memcmp(&iid, &unknown, sizeof(REFIID)) == 0) { + *ppv = this; + AddRef(); + result = S_OK; + } else if (memcmp(&iid, &IID_IDeckLinkNotificationCallback, + sizeof(REFIID)) == 0) { + *ppv = (IDeckLinkNotificationCallback *)this; + AddRef(); + result = S_OK; + } + + return result; +} + +ULONG STDMETHODCALLTYPE DeckLinkDeviceInstance::Release(void) +{ + const long newRefCount = os_atomic_dec_long(&refCount); + if (newRefCount == 0) { + delete this; + return 0; + } + + return newRefCount; +} diff --git a/plugins/decklink/decklink-device-instance.hpp b/plugins/decklink/decklink-device-instance.hpp new file mode 100644 index 000000000..254e0bc90 --- /dev/null +++ b/plugins/decklink/decklink-device-instance.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "decklink-device.hpp" + +class DeckLinkDeviceInstance : public IDeckLinkInputCallback { +protected: + struct obs_source_frame currentFrame; + struct obs_source_audio currentPacket; + DeckLink *decklink = nullptr; + DeckLinkDevice *device = nullptr; + DeckLinkDeviceMode *mode = nullptr; + ComPtr input; + volatile long refCount = 1; + + void HandleAudioPacket(IDeckLinkAudioInputPacket *audioPacket, + const uint64_t timestamp); + void HandleVideoFrame(IDeckLinkVideoInputFrame *videoFrame, + const uint64_t timestamp); + +public: + DeckLinkDeviceInstance(DeckLink *decklink, DeckLinkDevice *device); + + inline DeckLinkDevice *GetDevice() const {return device;} + inline long long GetActiveModeId() const + { + return mode ? mode->GetId() : 0; + } + + inline DeckLinkDeviceMode *GetMode() const {return mode;} + + bool StartCapture(DeckLinkDeviceMode *mode); + bool StopCapture(void); + + HRESULT STDMETHODCALLTYPE VideoInputFrameArrived( + IDeckLinkVideoInputFrame *videoFrame, + IDeckLinkAudioInputPacket *audioPacket); + HRESULT STDMETHODCALLTYPE VideoInputFormatChanged( + BMDVideoInputFormatChangedEvents events, + IDeckLinkDisplayMode *newMode, + BMDDetectedVideoInputFormatFlags detectedSignalFlags); + + ULONG STDMETHODCALLTYPE AddRef(void); + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv); + ULONG STDMETHODCALLTYPE Release(void); +}; diff --git a/plugins/decklink/decklink-device-mode.cpp b/plugins/decklink/decklink-device-mode.cpp new file mode 100644 index 000000000..8f53ac749 --- /dev/null +++ b/plugins/decklink/decklink-device-mode.cpp @@ -0,0 +1,43 @@ +#include "decklink-device-mode.hpp" + +DeckLinkDeviceMode::DeckLinkDeviceMode(IDeckLinkDisplayMode *mode, + long long id) : id(id), mode(mode) +{ + if (mode == nullptr) + return; + + mode->AddRef(); + + decklink_string_t decklinkStringName; + if (mode->GetName(&decklinkStringName) == S_OK) + DeckLinkStringToStdString(decklinkStringName, name); +} + +DeckLinkDeviceMode::DeckLinkDeviceMode(const std::string& name, long long id) : + id(id), mode(nullptr), name(name) +{ +} + +DeckLinkDeviceMode::~DeckLinkDeviceMode(void) +{ + if (mode != nullptr) + mode->Release(); +} + +BMDDisplayMode DeckLinkDeviceMode::GetDisplayMode(void) const +{ + if (mode != nullptr) + return mode->GetDisplayMode(); + + return bmdModeUnknown; +} + +long long DeckLinkDeviceMode::GetId(void) const +{ + return id; +} + +const std::string& DeckLinkDeviceMode::GetName(void) const +{ + return name; +} diff --git a/plugins/decklink/decklink-device-mode.hpp b/plugins/decklink/decklink-device-mode.hpp new file mode 100644 index 000000000..36bbc2ff6 --- /dev/null +++ b/plugins/decklink/decklink-device-mode.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "platform.hpp" + +#include + +class DeckLinkDeviceMode { +protected: + long long id; + IDeckLinkDisplayMode *mode; + std::string name; + +public: + DeckLinkDeviceMode(IDeckLinkDisplayMode *mode, long long id); + DeckLinkDeviceMode(const std::string& name, long long id); + virtual ~DeckLinkDeviceMode(void); + + BMDDisplayMode GetDisplayMode(void) const; + long long GetId(void) const; + const std::string& GetName(void) const; +}; diff --git a/plugins/decklink/decklink-device.cpp b/plugins/decklink/decklink-device.cpp new file mode 100644 index 000000000..899c0f972 --- /dev/null +++ b/plugins/decklink/decklink-device.cpp @@ -0,0 +1,115 @@ +#include + +#include "decklink-device.hpp" + +#include + +DeckLinkDevice::DeckLinkDevice(IDeckLink *device_) : device(device_) +{ +} + +DeckLinkDevice::~DeckLinkDevice(void) +{ + for (DeckLinkDeviceMode *mode : modes) + delete mode; +} + +ULONG DeckLinkDevice::AddRef() +{ + return os_atomic_inc_long(&refCount); +} + +ULONG DeckLinkDevice::Release() +{ + long ret = os_atomic_dec_long(&refCount); + if (ret == 0) + delete this; + return ret; +} + +bool DeckLinkDevice::Init() +{ + ComPtr input; + if (device->QueryInterface(IID_IDeckLinkInput, (void**)&input) != S_OK) + return false; + + IDeckLinkDisplayModeIterator *modeIterator; + if (input->GetDisplayModeIterator(&modeIterator) == S_OK) { + IDeckLinkDisplayMode *displayMode; + long long modeId = 1; + + while (modeIterator->Next(&displayMode) == S_OK) { + if (displayMode == nullptr) + continue; + + DeckLinkDeviceMode *mode = + new DeckLinkDeviceMode(displayMode, modeId); + modes.push_back(mode); + modeIdMap[modeId] = mode; + displayMode->Release(); + ++modeId; + } + + modeIterator->Release(); + } + + decklink_string_t decklinkModelName; + decklink_string_t decklinkDisplayName; + + if (device->GetModelName(&decklinkModelName) != S_OK) + return false; + DeckLinkStringToStdString(decklinkModelName, name); + + if (device->GetDisplayName(&decklinkDisplayName) != S_OK) + return false; + DeckLinkStringToStdString(decklinkDisplayName, displayName); + + hash = displayName; + + ComPtr attributes; + const HRESULT result = device->QueryInterface(IID_IDeckLinkAttributes, + (void **)&attributes); + if (result != S_OK) + return true; + + int64_t value; + if (attributes->GetInt(BMDDeckLinkPersistentID, &value) != S_OK) + return true; + + std::ostringstream os; + os << value << "_" << name; + hash = os.str(); + return true; +} + +bool DeckLinkDevice::GetInput(IDeckLinkInput **input) +{ + if (device->QueryInterface(IID_IDeckLinkInput, (void**)input) != S_OK) + return false; + return true; +} + +DeckLinkDeviceMode *DeckLinkDevice::FindMode(long long id) +{ + return modeIdMap[id]; +} + +const std::string& DeckLinkDevice::GetDisplayName(void) +{ + return displayName; +} + +const std::string& DeckLinkDevice::GetHash(void) const +{ + return hash; +} + +const std::vector& DeckLinkDevice::GetModes(void) const +{ + return modes; +} + +const std::string& DeckLinkDevice::GetName(void) const +{ + return name; +} diff --git a/plugins/decklink/decklink-device.hpp b/plugins/decklink/decklink-device.hpp new file mode 100644 index 000000000..bf1a79be4 --- /dev/null +++ b/plugins/decklink/decklink-device.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "decklink.hpp" +#include "decklink-device-mode.hpp" + +#include +#include +#include + +class DeckLinkDevice { + ComPtr device; + std::map modeIdMap; + std::vector modes; + std::string name; + std::string displayName; + std::string hash; + volatile long refCount = 1; + +public: + DeckLinkDevice(IDeckLink *device); + ~DeckLinkDevice(void); + + ULONG AddRef(void); + ULONG Release(void); + + bool Init(); + + DeckLinkDeviceMode *FindMode(long long id); + const std::string& GetDisplayName(void); + const std::string& GetHash(void) const; + const std::vector& GetModes(void) const; + const std::string& GetName(void) const; + + bool GetInput(IDeckLinkInput **input); + + inline bool IsDevice(IDeckLink *device_) + { + return device_ == device; + } +}; diff --git a/plugins/decklink/decklink.cpp b/plugins/decklink/decklink.cpp new file mode 100644 index 000000000..d9fc8a11f --- /dev/null +++ b/plugins/decklink/decklink.cpp @@ -0,0 +1,125 @@ +#include "decklink.hpp" +#include "decklink-device-discovery.hpp" +#include "decklink-device-instance.hpp" +#include "decklink-device-mode.hpp" + +#include + +DeckLink::DeckLink(obs_source_t *source, DeckLinkDeviceDiscovery *discovery_) : + discovery(discovery_), source(source) +{ + discovery->AddCallback(DeckLink::DevicesChanged, this); +} + +DeckLink::~DeckLink(void) +{ + discovery->RemoveCallback(DeckLink::DevicesChanged, this); + Deactivate(); +} + +DeckLinkDevice *DeckLink::GetDevice() const +{ + return instance ? instance->GetDevice() : nullptr; +} + +void DeckLink::DevicesChanged(void *param, DeckLinkDevice *device, bool added) +{ + DeckLink *decklink = reinterpret_cast(param); + std::lock_guard lock(decklink->deviceMutex); + + obs_source_update_properties(decklink->source); + + if (added && !decklink->instance) { + const char *hash; + long long mode; + obs_data_t *settings; + + settings = obs_source_get_settings(decklink->source); + hash = obs_data_get_string(settings, "device_hash"); + mode = obs_data_get_int(settings, "mode_id"); + obs_data_release(settings); + + if (device->GetHash().compare(hash) == 0) { + if (!decklink->activateRefs) + return; + if (decklink->Activate(device, mode)) + os_atomic_dec_long(&decklink->activateRefs); + } + + } else if (!added && decklink->instance) { + if (decklink->instance->GetDevice() == device) { + os_atomic_inc_long(&decklink->activateRefs); + decklink->Deactivate(); + } + } +} + +bool DeckLink::Activate(DeckLinkDevice *device, long long modeId) +{ + std::lock_guard lock(deviceMutex); + DeckLinkDevice *curDevice = GetDevice(); + const bool same = device == curDevice; + const bool isActive = instance != nullptr; + + if (same && (!isActive || instance->GetActiveModeId() == modeId)) + return false; + + if (isActive) + instance->StopCapture(); + + if (!same) + instance.Set(new DeckLinkDeviceInstance(this, device)); + + if (instance == nullptr) + return false; + + DeckLinkDeviceMode *mode = GetDevice()->FindMode(modeId); + if (mode == nullptr) { + instance = nullptr; + return false; + } + + if (!instance->StartCapture(mode)) { + instance = nullptr; + return false; + } + + os_atomic_inc_long(&activateRefs); + SaveSettings(); + return true; +} + +void DeckLink::Deactivate(void) +{ + std::lock_guard lock(deviceMutex); + if (instance) + instance->StopCapture(); + instance = nullptr; + + os_atomic_dec_long(&activateRefs); +} + +void DeckLink::SaveSettings() +{ + if (!instance) + return; + + DeckLinkDevice *device = instance->GetDevice(); + DeckLinkDeviceMode *mode = instance->GetMode(); + + obs_data_t *settings = obs_source_get_settings(source); + + obs_data_set_string(settings, "device_hash", + device->GetHash().c_str()); + obs_data_set_string(settings, "device_name", + device->GetDisplayName().c_str()); + obs_data_set_int(settings, "mode_id", instance->GetActiveModeId()); + obs_data_set_string(settings, "mode_name", mode->GetName().c_str()); + + obs_data_release(settings); +} + +obs_source_t *DeckLink::GetSource(void) const +{ + return source; +} diff --git a/plugins/decklink/decklink.hpp b/plugins/decklink/decklink.hpp new file mode 100644 index 000000000..9b68f67bc --- /dev/null +++ b/plugins/decklink/decklink.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "platform.hpp" + +#include + +#include +#include +#include + +class DeckLinkDeviceDiscovery; +class DeckLinkDeviceInstance; +class DeckLinkDevice; +class DeckLinkDeviceMode; + +class DeckLink { +protected: + ComPtr instance; + DeckLinkDeviceDiscovery *discovery; + bool isCapturing = false; + obs_source_t *source; + volatile long activateRefs = 0; + std::recursive_mutex deviceMutex; + + void SaveSettings(); + static void DevicesChanged(void *param, DeckLinkDevice *device, + bool added); + +public: + DeckLink(obs_source_t *source, DeckLinkDeviceDiscovery *discovery); + virtual ~DeckLink(void); + + DeckLinkDevice *GetDevice() const; + + long long GetActiveModeId(void) const; + obs_source_t *GetSource(void) const; + + bool Activate(DeckLinkDevice *device, long long modeId); + void Deactivate(); +}; diff --git a/plugins/decklink/platform.hpp b/plugins/decklink/platform.hpp new file mode 100644 index 000000000..178612ed1 --- /dev/null +++ b/plugins/decklink/platform.hpp @@ -0,0 +1,16 @@ +#pragma once + +#if defined(_WIN32) +// TODO: Windows support +#elif defined(__APPLE__) +// TODO: Mac support +#elif defined(__linux__) +// TODO: Linux support +#endif + +#include +#include + +#include + +bool DeckLinkStringToStdString(decklink_string_t input, std::string& output); diff --git a/plugins/decklink/plugin-main.cpp b/plugins/decklink/plugin-main.cpp new file mode 100644 index 000000000..86253e838 --- /dev/null +++ b/plugins/decklink/plugin-main.cpp @@ -0,0 +1,174 @@ +#include "decklink.hpp" +#include "decklink-device.hpp" +#include "decklink-device-discovery.hpp" + +#include + +OBS_DECLARE_MODULE() +OBS_MODULE_USE_DEFAULT_LOCALE("decklink", "en-US") + +static DeckLinkDeviceDiscovery *deviceEnum = nullptr; + +static void decklink_enable_buffering(DeckLink *decklink, bool enabled) +{ + obs_source_t *source = decklink->GetSource(); + uint32_t flags = obs_source_get_flags(source); + if (enabled) + flags &= ~OBS_SOURCE_FLAG_UNBUFFERED; + else + flags |= OBS_SOURCE_FLAG_UNBUFFERED; + obs_source_set_flags(source, flags); +} + +static void *decklink_create(obs_data_t *settings, obs_source_t *source) +{ + DeckLink *decklink = new DeckLink(source, deviceEnum); + + 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"); + + decklink_enable_buffering(decklink, + obs_data_get_bool(settings, "buffering")); + + ComPtr device; + device.Set(deviceEnum->FindByHash(hash)); + + decklink->Activate(device, id); +} + +static void decklink_get_defaults(obs_data_t *settings) +{ + obs_data_set_default_bool(settings, "buffering", true); +} + +static const char *decklink_get_name() +{ + 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); + } + + list = obs_properties_get(props, "mode_id"); + + obs_property_list_clear(list); + + ComPtr device; + device.Set(deviceEnum->FindByHash(hash)); + + if (!device) { + obs_property_list_add_int(list, mode, modeId); + obs_property_list_item_disable(list, 0, true); + } else { + const std::vector &modes = + device->GetModes(); + + for (DeckLinkDeviceMode *mode : modes) { + obs_property_list_add_int(list, + mode->GetName().c_str(), + mode->GetId()); + } + } + + 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 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", + obs_module_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", + obs_module_text("Mode"), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + + obs_properties_add_bool(props, "buffering", + obs_module_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; + 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; +}