decklink: Add Blackmagic DeckLink capture plugin
This is a cross-platform Blackmagic device capture plugin that makes use of the manufacturer provided DeckLink SDK.
This commit is contained in:
parent
001bfb4a6d
commit
4225fa8eee
4
plugins/decklink/data/locale/en-US.ini
Normal file
4
plugins/decklink/data/locale/en-US.ini
Normal file
@ -0,0 +1,4 @@
|
||||
BlackmagicDevice="Blackmagic Device"
|
||||
Device="Device"
|
||||
Mode="Mode"
|
||||
Buffering="Use Buffering"
|
131
plugins/decklink/decklink-device-discovery.cpp
Normal file
131
plugins/decklink/decklink-device-discovery.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
#include "decklink-device-discovery.hpp"
|
||||
#include "decklink-device.hpp"
|
||||
|
||||
#include <util/threading.h>
|
||||
|
||||
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<std::recursive_mutex> 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<std::recursive_mutex> 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;
|
||||
}
|
81
plugins/decklink/decklink-device-discovery.hpp
Normal file
81
plugins/decklink/decklink-device-discovery.hpp
Normal file
@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
#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<IDeckLinkDiscovery> discovery;
|
||||
long refCount = 1;
|
||||
bool initialized = false;
|
||||
|
||||
std::recursive_mutex deviceMutex;
|
||||
std::vector<DeckLinkDevice*> devices;
|
||||
std::vector<DeviceChangeInfo> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<DeckLinkDevice*> &GetDevices() const
|
||||
{
|
||||
return devices;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE AddRef(void);
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv);
|
||||
ULONG STDMETHODCALLTYPE Release(void);
|
||||
};
|
192
plugins/decklink/decklink-device-instance.cpp
Normal file
192
plugins/decklink/decklink-device-instance.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
#include "decklink-device-instance.hpp"
|
||||
|
||||
#include <util/platform.h>
|
||||
#include <util/threading.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#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;
|
||||
}
|
45
plugins/decklink/decklink-device-instance.hpp
Normal file
45
plugins/decklink/decklink-device-instance.hpp
Normal file
@ -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<IDeckLinkInput> 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);
|
||||
};
|
43
plugins/decklink/decklink-device-mode.cpp
Normal file
43
plugins/decklink/decklink-device-mode.cpp
Normal file
@ -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;
|
||||
}
|
21
plugins/decklink/decklink-device-mode.hpp
Normal file
21
plugins/decklink/decklink-device-mode.hpp
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "platform.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
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;
|
||||
};
|
115
plugins/decklink/decklink-device.cpp
Normal file
115
plugins/decklink/decklink-device.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
#include <sstream>
|
||||
|
||||
#include "decklink-device.hpp"
|
||||
|
||||
#include <util/threading.h>
|
||||
|
||||
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<IDeckLinkInput> 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<IDeckLinkAttributes> 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<DeckLinkDeviceMode *>& DeckLinkDevice::GetModes(void) const
|
||||
{
|
||||
return modes;
|
||||
}
|
||||
|
||||
const std::string& DeckLinkDevice::GetName(void) const
|
||||
{
|
||||
return name;
|
||||
}
|
40
plugins/decklink/decklink-device.hpp
Normal file
40
plugins/decklink/decklink-device.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "decklink.hpp"
|
||||
#include "decklink-device-mode.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class DeckLinkDevice {
|
||||
ComPtr<IDeckLink> device;
|
||||
std::map<long long, DeckLinkDeviceMode *> modeIdMap;
|
||||
std::vector<DeckLinkDeviceMode *> 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<DeckLinkDeviceMode *>& GetModes(void) const;
|
||||
const std::string& GetName(void) const;
|
||||
|
||||
bool GetInput(IDeckLinkInput **input);
|
||||
|
||||
inline bool IsDevice(IDeckLink *device_)
|
||||
{
|
||||
return device_ == device;
|
||||
}
|
||||
};
|
125
plugins/decklink/decklink.cpp
Normal file
125
plugins/decklink/decklink.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
#include "decklink.hpp"
|
||||
#include "decklink-device-discovery.hpp"
|
||||
#include "decklink-device-instance.hpp"
|
||||
#include "decklink-device-mode.hpp"
|
||||
|
||||
#include <util/threading.h>
|
||||
|
||||
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<DeckLink*>(param);
|
||||
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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;
|
||||
}
|
40
plugins/decklink/decklink.hpp
Normal file
40
plugins/decklink/decklink.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "platform.hpp"
|
||||
|
||||
#include <obs-module.h>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
class DeckLinkDeviceDiscovery;
|
||||
class DeckLinkDeviceInstance;
|
||||
class DeckLinkDevice;
|
||||
class DeckLinkDeviceMode;
|
||||
|
||||
class DeckLink {
|
||||
protected:
|
||||
ComPtr<DeckLinkDeviceInstance> 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();
|
||||
};
|
16
plugins/decklink/platform.hpp
Normal file
16
plugins/decklink/platform.hpp
Normal file
@ -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 <util/windows/HRError.hpp>
|
||||
#include <util/windows/ComPtr.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
bool DeckLinkStringToStdString(decklink_string_t input, std::string& output);
|
174
plugins/decklink/plugin-main.cpp
Normal file
174
plugins/decklink/plugin-main.cpp
Normal file
@ -0,0 +1,174 @@
|
||||
#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")
|
||||
|
||||
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<DeckLinkDevice> 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<DeckLinkDevice> 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<DeckLinkDeviceMode*> &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<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 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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user