1
0
Эх сурвалжийг харах

Decklink: add output support

Colin Edwards 7 жил өмнө
parent
commit
21b67cff64

+ 20 - 0
plugins/decklink/DecklinkBase.cpp

@@ -0,0 +1,20 @@
+#include "DecklinkBase.h"
+
+DecklinkBase::DecklinkBase(DeckLinkDeviceDiscovery *discovery_)
+		: discovery(discovery_)
+{
+}
+
+DeckLinkDevice *DecklinkBase::GetDevice() const
+{
+	return instance ? instance->GetDevice() : nullptr;
+}
+
+bool DecklinkBase::Activate(DeckLinkDevice*, long long)
+{
+	return false;
+}
+
+void DecklinkBase::Deactivate()
+{
+}

+ 33 - 0
plugins/decklink/DecklinkBase.h

@@ -0,0 +1,33 @@
+#pragma once
+
+#include <map>
+#include <vector>
+#include <mutex>
+
+#include <obs-module.h>
+
+#include "platform.hpp"
+
+#include "decklink-device-discovery.hpp"
+#include "decklink-device-instance.hpp"
+#include "decklink-device-mode.hpp"
+
+class DecklinkBase {
+
+protected:
+	DecklinkBase(DeckLinkDeviceDiscovery *discovery_);
+	ComPtr<DeckLinkDeviceInstance>       instance;
+	DeckLinkDeviceDiscovery              *discovery;
+	std::recursive_mutex                 deviceMutex;
+	volatile long                        activateRefs = 0;
+	BMDPixelFormat                       pixelFormat = bmdFormat8BitYUV;
+	video_colorspace                     colorSpace = VIDEO_CS_DEFAULT;
+	video_range_type                     colorRange = VIDEO_RANGE_DEFAULT;
+	speaker_layout                       channelFormat = SPEAKERS_STEREO;
+
+public:
+	virtual bool Activate(DeckLinkDevice *device, long long modeId);
+	virtual void Deactivate();
+
+	DeckLinkDevice *GetDevice() const;
+};

+ 20 - 22
plugins/decklink/decklink.cpp → plugins/decklink/DecklinkInput.cpp

@@ -1,30 +1,23 @@
-#include "decklink.hpp"
-#include "decklink-device-discovery.hpp"
-#include "decklink-device-instance.hpp"
-#include "decklink-device-mode.hpp"
+#include "DecklinkInput.hpp"
 
 #include <util/threading.h>
 
-DeckLink::DeckLink(obs_source_t *source, DeckLinkDeviceDiscovery *discovery_) :
-	discovery(discovery_), source(source)
+DeckLinkInput::DeckLinkInput(obs_source_t *source, DeckLinkDeviceDiscovery *discovery_)
+		: DecklinkBase(discovery_),
+		  source(source)
 {
-	discovery->AddCallback(DeckLink::DevicesChanged, this);
+	discovery->AddCallback(DeckLinkInput::DevicesChanged, this);
 }
 
-DeckLink::~DeckLink(void)
+DeckLinkInput::~DeckLinkInput(void)
 {
-	discovery->RemoveCallback(DeckLink::DevicesChanged, this);
+	discovery->RemoveCallback(DeckLinkInput::DevicesChanged, this);
 	Deactivate();
 }
 
-DeckLinkDevice *DeckLink::GetDevice() const
+void DeckLinkInput::DevicesChanged(void *param, DeckLinkDevice *device, bool added)
 {
-	return instance ? instance->GetDevice() : nullptr;
-}
-
-void DeckLink::DevicesChanged(void *param, DeckLinkDevice *device, bool added)
-{
-	DeckLink *decklink = reinterpret_cast<DeckLink*>(param);
+	DeckLinkInput *decklink = reinterpret_cast<DeckLinkInput*>(param);
 	std::lock_guard<std::recursive_mutex> lock(decklink->deviceMutex);
 
 	obs_source_update_properties(decklink->source);
@@ -54,7 +47,7 @@ void DeckLink::DevicesChanged(void *param, DeckLinkDevice *device, bool added)
 	}
 }
 
-bool DeckLink::Activate(DeckLinkDevice *device, long long modeId)
+bool DeckLinkInput::Activate(DeckLinkDevice *device, long long modeId)
 {
 	std::lock_guard<std::recursive_mutex> lock(deviceMutex);
 	DeckLinkDevice *curDevice = GetDevice();
@@ -82,7 +75,12 @@ bool DeckLink::Activate(DeckLinkDevice *device, long long modeId)
 	if (instance == nullptr)
 		return false;
 
-	DeckLinkDeviceMode *mode = GetDevice()->FindMode(modeId);
+	if (GetDevice() == nullptr) {
+		LOG(LOG_ERROR, "Tried to activate an input with nullptr device.");
+		return false;
+	}
+
+	DeckLinkDeviceMode *mode = GetDevice()->FindInputMode(modeId);
 	if (mode == nullptr) {
 		instance = nullptr;
 		return false;
@@ -100,7 +98,7 @@ bool DeckLink::Activate(DeckLinkDevice *device, long long modeId)
 	return true;
 }
 
-void DeckLink::Deactivate(void)
+void DeckLinkInput::Deactivate(void)
 {
 	std::lock_guard<std::recursive_mutex> lock(deviceMutex);
 	if (instance)
@@ -111,12 +109,12 @@ void DeckLink::Deactivate(void)
 	os_atomic_dec_long(&activateRefs);
 }
 
-bool DeckLink::Capturing(void)
+bool DeckLinkInput::Capturing(void)
 {
 	return isCapturing;
 }
 
-void DeckLink::SaveSettings()
+void DeckLinkInput::SaveSettings()
 {
 	if (!instance)
 		return;
@@ -136,7 +134,7 @@ void DeckLink::SaveSettings()
 	obs_data_release(settings);
 }
 
-obs_source_t *DeckLink::GetSource(void) const
+obs_source_t *DeckLinkInput::GetSource(void) const
 {
 	return source;
 }

+ 4 - 25
plugins/decklink/decklink.hpp → plugins/decklink/DecklinkInput.hpp

@@ -1,40 +1,19 @@
 #pragma once
 
-#include "platform.hpp"
+#include "DecklinkBase.h"
 
-#include <obs-module.h>
-
-#include <map>
-#include <vector>
-#include <mutex>
-
-class DeckLinkDeviceDiscovery;
-class DeckLinkDeviceInstance;
-class DeckLinkDevice;
-class DeckLinkDeviceMode;
-
-class DeckLink {
+class DeckLinkInput : public DecklinkBase {
 protected:
-	ComPtr<DeckLinkDeviceInstance>        instance;
-	DeckLinkDeviceDiscovery               *discovery;
 	bool                                  isCapturing = false;
 	obs_source_t                          *source;
-	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();
 	static void DevicesChanged(void *param, DeckLinkDevice *device,
 			bool added);
 
 public:
-	DeckLink(obs_source_t *source, DeckLinkDeviceDiscovery *discovery);
-	virtual ~DeckLink(void);
-
-	DeckLinkDevice *GetDevice() const;
+	DeckLinkInput(obs_source_t *source, DeckLinkDeviceDiscovery *discovery);
+	virtual ~DeckLinkInput(void);
 
 	long long GetActiveModeId(void) const;
 	obs_source_t *GetSource(void) const;

+ 110 - 0
plugins/decklink/DecklinkOutput.cpp

@@ -0,0 +1,110 @@
+#include "DecklinkOutput.hpp"
+
+#include <util/threading.h>
+
+DeckLinkOutput::DeckLinkOutput(obs_output_t *output, DeckLinkDeviceDiscovery *discovery_)
+		: DecklinkBase(discovery_),
+		  output(output)
+{
+	discovery->AddCallback(DeckLinkOutput::DevicesChanged, this);
+}
+
+DeckLinkOutput::~DeckLinkOutput(void)
+{
+	discovery->RemoveCallback(DeckLinkOutput::DevicesChanged, this);
+	Deactivate();
+}
+
+void DeckLinkOutput::DevicesChanged(void *param, DeckLinkDevice *device, bool)
+{
+	auto *decklink = reinterpret_cast<DeckLinkOutput*>(param);
+	std::lock_guard<std::recursive_mutex> lock(decklink->deviceMutex);
+
+	blog(LOG_DEBUG, "%s", device->GetHash().c_str());
+}
+
+bool DeckLinkOutput::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) {
+		if (!isActive)
+			return false;
+
+		if (instance->GetActiveModeId() == modeId &&
+			instance->GetActivePixelFormat() == pixelFormat &&
+			instance->GetActiveColorSpace() == colorSpace &&
+			instance->GetActiveColorRange() == colorRange &&
+			instance->GetActiveChannelFormat() == channelFormat)
+			return false;
+	}
+
+	if (isActive)
+		instance->StopOutput();
+
+	if (!same)
+		instance.Set(new DeckLinkDeviceInstance(this, device));
+
+	if (instance == nullptr)
+		return false;
+
+	DeckLinkDeviceMode *mode = GetDevice()->FindOutputMode(modeId);
+	if (mode == nullptr) {
+		instance = nullptr;
+		return false;
+	}
+
+
+	if (!instance->StartOutput(mode)) {
+		instance = nullptr;
+		return false;
+	}
+
+	os_atomic_inc_long(&activateRefs);
+	return true;
+}
+
+void DeckLinkOutput::Deactivate(void)
+{
+	std::lock_guard<std::recursive_mutex> lock(deviceMutex);
+	if (instance)
+		instance->StopOutput();
+
+	instance = nullptr;
+
+	os_atomic_dec_long(&activateRefs);
+}
+
+obs_output_t *DeckLinkOutput::GetOutput(void) const
+{
+	return output;
+}
+
+void DeckLinkOutput::DisplayVideoFrame(video_data *frame)
+{
+	instance->DisplayVideoFrame(frame);
+}
+
+void DeckLinkOutput::WriteAudio(audio_data *frames)
+{
+	instance->WriteAudio(frames);
+}
+
+void DeckLinkOutput::SetSize(int width, int height)
+{
+	this->width = width;
+	this->height = height;
+}
+
+int DeckLinkOutput::GetWidth()
+{
+	return width;
+}
+
+int DeckLinkOutput::GetHeight()
+{
+	return height;
+}

+ 33 - 0
plugins/decklink/DecklinkOutput.hpp

@@ -0,0 +1,33 @@
+#pragma once
+
+#include "DecklinkBase.h"
+
+#include "../../libobs/media-io/video-scaler.h"
+
+class DeckLinkOutput : public DecklinkBase {
+protected:
+	obs_output_t *output;
+	int width;
+	int height;
+
+	static void DevicesChanged(void *param, DeckLinkDevice *device, bool added);
+
+public:
+	const char *deviceHash;
+	long long modeID;
+	uint64_t start_timestamp;
+	uint32_t audio_samplerate;
+	size_t audio_planes;
+	size_t audio_size;
+
+	DeckLinkOutput(obs_output_t *output, DeckLinkDeviceDiscovery *discovery);
+	virtual ~DeckLinkOutput(void);
+	obs_output_t *GetOutput(void) const;
+	bool Activate(DeckLinkDevice *device, long long modeId) override;
+	void Deactivate() override;
+	void DisplayVideoFrame(video_data *pData);
+	void WriteAudio(audio_data *frames);
+	void SetSize(int width, int height);
+	int GetWidth();
+	int GetHeight();
+};

+ 32 - 0
plugins/decklink/const.h

@@ -0,0 +1,32 @@
+#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 DEACTIVATE_WNS  "deactivate_when_not_showing"
+#define AUTO_START      "auto_start"
+
+#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")
+#define TEXT_DWNS                   obs_module_text("DeactivateWhenNotShowing")
+#define TEXT_AUTO_START             obs_module_text("AutoStart")

+ 1 - 0
plugins/decklink/data/locale/en-US.ini

@@ -18,3 +18,4 @@ ChannelFormat.4_1ch="4.1ch"
 ChannelFormat.5_1ch="5.1ch"
 ChannelFormat.7_1ch="7.1ch"
 DeactivateWhenNotShowing="Deactivate when not showing"
+AutoStart="Auto start on launch"

+ 3 - 2
plugins/decklink/decklink-device-discovery.hpp

@@ -1,10 +1,11 @@
 #pragma once
 
+#include <obs-module.h>
+#include "platform.hpp"
+
 #include <vector>
 #include <mutex>
 
-#include "decklink.hpp"
-
 class DeckLinkDevice;
 
 typedef void (*DeviceChangeCallback)(void *param, DeckLinkDevice *device,

+ 107 - 11
plugins/decklink/decklink-device-instance.cpp

@@ -1,13 +1,14 @@
 #include "decklink-device-instance.hpp"
 #include "audio-repack.hpp"
 
+#include "DecklinkInput.hpp"
+#include "DecklinkOutput.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__)
+#include <algorithm>
 
 #ifdef _WIN32
 #define IS_WIN 1
@@ -62,7 +63,7 @@ static inline audio_repack_mode_t ConvertRepackFormat(speaker_layout format)
 	}
 }
 
-DeckLinkDeviceInstance::DeckLinkDeviceInstance(DeckLink *decklink_,
+DeckLinkDeviceInstance::DeckLinkDeviceInstance(DecklinkBase *decklink_,
 		DeckLinkDevice *device_) :
 	currentFrame(), currentPacket(), decklink(decklink_), device(device_)
 {
@@ -92,7 +93,7 @@ void DeckLinkDeviceInstance::HandleAudioPacket(
 	currentPacket.frames      = frameCount;
 	currentPacket.timestamp   = timestamp;
 
-	if (decklink && !decklink->buffering) {
+	if (decklink && !static_cast<DeckLinkInput*>(decklink)->buffering) {
 		currentPacket.timestamp = os_gettime_ns();
 		currentPacket.timestamp -=
 			(uint64_t)frameCount * 1000000000ULL /
@@ -120,7 +121,7 @@ void DeckLinkDeviceInstance::HandleAudioPacket(
 	nextAudioTS = timestamp +
 		((uint64_t)frameCount * 1000000000ULL / 48000ULL) + 1;
 
-	obs_source_output_audio(decklink->GetSource(), &currentPacket);
+	obs_source_output_audio(static_cast<DeckLinkInput*>(decklink)->GetSource(), &currentPacket);
 }
 
 void DeckLinkDeviceInstance::HandleVideoFrame(
@@ -141,7 +142,7 @@ void DeckLinkDeviceInstance::HandleVideoFrame(
 	currentFrame.height      = (uint32_t)videoFrame->GetHeight();
 	currentFrame.timestamp   = timestamp;
 
-	obs_source_output_video(decklink->GetSource(), &currentFrame);
+	obs_source_output_video(static_cast<DeckLinkInput*>(decklink)->GetSource(), &currentFrame);
 }
 
 void DeckLinkDeviceInstance::FinalizeStream()
@@ -169,7 +170,7 @@ void DeckLinkDeviceInstance::SetupVideoFormat(DeckLinkDeviceMode *mode_)
 
 	currentFrame.format = ConvertPixelFormat(pixelFormat);
 
-	colorSpace = decklink->GetColorSpace();
+	colorSpace = static_cast<DeckLinkInput*>(decklink)->GetColorSpace();
 	if (colorSpace == VIDEO_CS_DEFAULT) {
 		const BMDDisplayModeFlags flags = mode_->GetDisplayModeFlags();
 		if (flags & bmdDisplayModeColorspaceRec709)
@@ -182,7 +183,7 @@ void DeckLinkDeviceInstance::SetupVideoFormat(DeckLinkDeviceMode *mode_)
 		activeColorSpace = colorSpace;
 	}
 
-	colorRange = decklink->GetColorRange();
+	colorRange = static_cast<DeckLinkInput*>(decklink)->GetColorRange();
 	currentFrame.full_range = colorRange == VIDEO_RANGE_FULL;
 
 	video_format_get_parameters(activeColorSpace, colorRange,
@@ -218,7 +219,7 @@ bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_)
 		flags = bmdVideoInputEnableFormatDetection;
 	} else {
 		displayMode = mode_->GetDisplayMode();
-		pixelFormat = decklink->GetPixelFormat();
+		pixelFormat = static_cast<DeckLinkInput*>(decklink)->GetPixelFormat();
 		flags = bmdVideoInputFlagDefault;
 	}
 
@@ -231,7 +232,7 @@ bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_)
 
 	SetupVideoFormat(mode_);
 
-	channelFormat = decklink->GetChannelFormat();
+	channelFormat = static_cast<DeckLinkInput*>(decklink)->GetChannelFormat();
 	currentPacket.speakers = channelFormat;
 
 	int maxdevicechannel = device->GetMaxChannel();
@@ -288,6 +289,101 @@ bool DeckLinkDeviceInstance::StopCapture(void)
 	return true;
 }
 
+bool DeckLinkDeviceInstance::StartOutput(DeckLinkDeviceMode *mode_)
+{
+	if (mode != nullptr)
+		return false;
+	if (mode_ == nullptr)
+		return false;
+
+	LOG(LOG_INFO, "Starting output...");
+
+	if (!device->GetOutput(&output))
+		return false;
+
+	const HRESULT videoResult = output->EnableVideoOutput(
+			mode_->GetDisplayMode(),
+			bmdVideoOutputFlagDefault);
+	if (videoResult != S_OK) {
+		LOG(LOG_ERROR, "Failed to enable video output");
+		return false;
+	}
+
+	const HRESULT audioResult = output->EnableAudioOutput(
+			bmdAudioSampleRate48kHz,
+			bmdAudioSampleType16bitInteger,
+			2,
+			bmdAudioOutputStreamTimestamped);
+	if (audioResult != S_OK) {
+		LOG(LOG_ERROR, "Failed to enable audio output");
+		return false;
+	}
+
+	mode = mode_;
+
+	auto decklinkOutput = dynamic_cast<DeckLinkOutput*>(decklink);
+	if (decklinkOutput == nullptr)
+		return false;
+
+	HRESULT result;
+	result = output->CreateVideoFrame(decklinkOutput->GetWidth(),
+			decklinkOutput->GetHeight(),
+			decklinkOutput->GetWidth() * 2,
+			bmdFormat8BitYUV,
+			bmdFrameFlagDefault,
+			&decklinkOutputFrame);
+	if (result != S_OK) {
+		blog(LOG_ERROR ,"failed to make frame 0x%X", result);
+		return false;
+	}
+
+	return true;
+}
+
+bool DeckLinkDeviceInstance::StopOutput()
+{
+	if (mode == nullptr || output == nullptr)
+		return false;
+
+	LOG(LOG_INFO, "Stopping output of '%s'...",
+			GetDevice()->GetDisplayName().c_str());
+
+	output->DisableVideoOutput();
+	output->DisableAudioOutput();
+
+	if (decklinkOutputFrame != nullptr) {
+		decklinkOutputFrame->Release();
+		decklinkOutputFrame = nullptr;
+	}
+
+	return true;
+}
+
+void DeckLinkDeviceInstance::DisplayVideoFrame(video_data *frame)
+{
+	auto decklinkOutput = dynamic_cast<DeckLinkOutput*>(decklink);
+	if (decklinkOutput == nullptr)
+		return;
+
+	uint8_t *destData;
+	decklinkOutputFrame->GetBytes((void**)&destData);
+
+	uint8_t *outData = frame->data[0];
+
+	std::copy(outData, outData + (decklinkOutput->GetWidth() *
+			decklinkOutput->GetHeight() * 2), destData);
+
+	output->DisplayVideoFrameSync(decklinkOutputFrame);
+}
+
+void DeckLinkDeviceInstance::WriteAudio(audio_data *frames)
+{
+	uint32_t sampleFramesWritten;
+	output->WriteAudioSamplesSync(frames->data[0],
+			frames->frames,
+			&sampleFramesWritten);
+}
+
 #define TIME_BASE 1000000000
 
 HRESULT STDMETHODCALLTYPE DeckLinkDeviceInstance::VideoInputFrameArrived(

+ 16 - 2
plugins/decklink/decklink-device-instance.hpp

@@ -1,14 +1,19 @@
 #pragma once
 
+#define LOG(level, message, ...) blog(level, "%s: " message, "decklink", ##__VA_ARGS__)
+
+#include <obs-module.h>
 #include "decklink-device.hpp"
+#include "../../libobs/media-io/video-scaler.h"
 
 class AudioRepacker;
+class DecklinkBase;
 
 class DeckLinkDeviceInstance : public IDeckLinkInputCallback {
 protected:
 	struct obs_source_frame currentFrame;
 	struct obs_source_audio currentPacket;
-	DeckLink                *decklink = nullptr;
+	DecklinkBase            *decklink = nullptr;
 	DeckLinkDevice          *device = nullptr;
 	DeckLinkDeviceMode      *mode = nullptr;
 	BMDDisplayMode          displayMode = bmdModeNTSC;
@@ -17,6 +22,7 @@ protected:
 	video_colorspace        activeColorSpace = VIDEO_CS_DEFAULT;
 	video_range_type        colorRange = VIDEO_RANGE_DEFAULT;
 	ComPtr<IDeckLinkInput>  input;
+	ComPtr<IDeckLinkOutput> output;
 	volatile long           refCount = 1;
 	int64_t                 audioOffset = 0;
 	uint64_t                nextAudioTS = 0;
@@ -24,6 +30,8 @@ protected:
 	AudioRepacker           *audioRepacker = nullptr;
 	speaker_layout          channelFormat = SPEAKERS_STEREO;
 
+	IDeckLinkMutableVideoFrame *decklinkOutputFrame;
+
 	void FinalizeStream();
 	void SetupVideoFormat(DeckLinkDeviceMode *mode_);
 
@@ -33,7 +41,7 @@ protected:
 			const uint64_t timestamp);
 
 public:
-	DeckLinkDeviceInstance(DeckLink *decklink, DeckLinkDevice *device);
+	DeckLinkDeviceInstance(DecklinkBase *decklink, DeckLinkDevice *device);
 	virtual ~DeckLinkDeviceInstance();
 
 	inline DeckLinkDevice *GetDevice() const {return device;}
@@ -52,6 +60,9 @@ public:
 	bool StartCapture(DeckLinkDeviceMode *mode);
 	bool StopCapture(void);
 
+	bool StartOutput(DeckLinkDeviceMode *mode_);
+	bool StopOutput(void);
+
 	HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(
 			IDeckLinkVideoInputFrame *videoFrame,
 			IDeckLinkAudioInputPacket *audioPacket);
@@ -63,4 +74,7 @@ public:
 	ULONG STDMETHODCALLTYPE AddRef(void);
 	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv);
 	ULONG STDMETHODCALLTYPE Release(void);
+
+	void DisplayVideoFrame(video_data *frame);
+	void WriteAudio(audio_data *frames);
 };

+ 16 - 0
plugins/decklink/decklink-device-mode.cpp

@@ -32,6 +32,22 @@ BMDDisplayMode DeckLinkDeviceMode::GetDisplayMode(void) const
 	return bmdModeUnknown;
 }
 
+int DeckLinkDeviceMode::GetWidth()
+{
+	if (mode != nullptr)
+		return mode->GetWidth();
+
+	return 0;
+}
+
+int DeckLinkDeviceMode::GetHeight()
+{
+	if (mode != nullptr)
+		return mode->GetHeight();
+
+	return 0;
+}
+
 BMDDisplayModeFlags DeckLinkDeviceMode::GetDisplayModeFlags(void) const
 {
 	if (mode != nullptr)

+ 3 - 0
plugins/decklink/decklink-device-mode.hpp

@@ -23,4 +23,7 @@ public:
 	const std::string& GetName(void) const;
 
 	void SetMode(IDeckLinkDisplayMode *mode);
+
+	int GetWidth();
+	int GetHeight();
 };

+ 75 - 28
plugins/decklink/decklink-device.cpp

@@ -10,7 +10,10 @@ DeckLinkDevice::DeckLinkDevice(IDeckLink *device_) : device(device_)
 
 DeckLinkDevice::~DeckLinkDevice(void)
 {
-	for (DeckLinkDeviceMode *mode : modes)
+	for (DeckLinkDeviceMode *mode : inputModes)
+		delete mode;
+
+	for (DeckLinkDeviceMode *mode : outputModes)
 		delete mode;
 }
 
@@ -37,35 +40,61 @@ bool DeckLinkDevice::Init()
 		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;
+			DeckLinkDeviceMode *mode = new DeckLinkDeviceMode(
+					"Auto",
+					MODE_ID_AUTO);
+			inputModes.push_back(mode);
+			inputModeIdMap[MODE_ID_AUTO] = mode;
 		}
 	}
 
+	// Find input modes
 	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;
+	if (device->QueryInterface(IID_IDeckLinkInput, (void **) &input) == S_OK) {
+		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);
+				inputModes.push_back(mode);
+				inputModeIdMap[modeId] = mode;
+				displayMode->Release();
+				++modeId;
+			}
+
+			modeIterator->Release();
 		}
+	}
+
+	// find output modes
+	ComPtr<IDeckLinkOutput> output;
+	if (device->QueryInterface(IID_IDeckLinkOutput, (void **) &output) == S_OK) {
 
-		modeIterator->Release();
+		IDeckLinkDisplayModeIterator *modeIterator;
+		if (output->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);
+				outputModes.push_back(mode);
+				outputModeIdMap[modeId] = mode;
+				displayMode->Release();
+				++modeId;
+			}
+
+			modeIterator->Release();
+		}
 	}
 
 	decklink_string_t decklinkModelName;
@@ -115,9 +144,22 @@ bool DeckLinkDevice::GetInput(IDeckLinkInput **input)
 	return true;
 }
 
-DeckLinkDeviceMode *DeckLinkDevice::FindMode(long long id)
+bool DeckLinkDevice::GetOutput(IDeckLinkOutput **output)
+{
+	if (device->QueryInterface(IID_IDeckLinkOutput, (void**)output) != S_OK)
+		return false;
+
+	return true;
+}
+
+DeckLinkDeviceMode *DeckLinkDevice::FindInputMode(long long id)
 {
-	return modeIdMap[id];
+	return inputModeIdMap[id];
+}
+
+DeckLinkDeviceMode *DeckLinkDevice::FindOutputMode(long long id)
+{
+	return outputModeIdMap[id];
 }
 
 const std::string& DeckLinkDevice::GetDisplayName(void)
@@ -130,9 +172,14 @@ const std::string& DeckLinkDevice::GetHash(void) const
 	return hash;
 }
 
-const std::vector<DeckLinkDeviceMode *>& DeckLinkDevice::GetModes(void) const
+const std::vector<DeckLinkDeviceMode *>& DeckLinkDevice::GetInputModes(void) const
+{
+	return inputModes;
+}
+
+const std::vector<DeckLinkDeviceMode *>& DeckLinkDevice::GetOutputModes(void) const
 {
-	return modes;
+	return outputModes;
 }
 
 const std::string& DeckLinkDevice::GetName(void) const

+ 9 - 5
plugins/decklink/decklink-device.hpp

@@ -1,6 +1,5 @@
 #pragma once
 
-#include "decklink.hpp"
 #include "decklink-device-mode.hpp"
 
 #include <map>
@@ -9,8 +8,10 @@
 
 class DeckLinkDevice {
 	ComPtr<IDeckLink>                         device;
-	std::map<long long, DeckLinkDeviceMode *> modeIdMap;
-	std::vector<DeckLinkDeviceMode *>         modes;
+	std::map<long long, DeckLinkDeviceMode *> inputModeIdMap;
+	std::vector<DeckLinkDeviceMode *>         inputModes;
+	std::map<long long, DeckLinkDeviceMode *> outputModeIdMap;
+	std::vector<DeckLinkDeviceMode *>         outputModes;
 	std::string                               name;
 	std::string                               displayName;
 	std::string                               hash;
@@ -26,14 +27,17 @@ public:
 
 	bool Init();
 
-	DeckLinkDeviceMode *FindMode(long long id);
+	DeckLinkDeviceMode *FindInputMode(long long id);
+	DeckLinkDeviceMode *FindOutputMode(long long id);
 	const std::string& GetDisplayName(void);
 	const std::string& GetHash(void) const;
-	const std::vector<DeckLinkDeviceMode *>& GetModes(void) const;
+	const std::vector<DeckLinkDeviceMode *>& GetInputModes(void) const;
+	const std::vector<DeckLinkDeviceMode *>& GetOutputModes(void) const;
 	const std::string& GetName(void) const;
 	int32_t GetMaxChannel(void) const;
 
 	bool GetInput(IDeckLinkInput **input);
+	bool GetOutput(IDeckLinkOutput **output);
 
 	inline bool IsDevice(IDeckLink *device_)
 	{

+ 17 - 0
plugins/decklink/decklink-devices.cpp

@@ -0,0 +1,17 @@
+#include "decklink-devices.hpp"
+
+DeckLinkDeviceDiscovery *deviceEnum = nullptr;
+
+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();
+}

+ 8 - 0
plugins/decklink/decklink-devices.hpp

@@ -0,0 +1,8 @@
+#pragma once
+
+#include "decklink-device.hpp"
+#include "decklink-device-discovery.hpp"
+
+extern DeckLinkDeviceDiscovery *deviceEnum;
+
+void fill_out_devices(obs_property_t *list);

+ 239 - 0
plugins/decklink/decklink-output.cpp

@@ -0,0 +1,239 @@
+#include <obs-module.h>
+#include <obs-avc.h>
+
+#include "const.h"
+
+#include "DecklinkOutput.hpp"
+#include "decklink-device.hpp"
+#include "decklink-device-discovery.hpp"
+#include "decklink-devices.hpp"
+
+#include "../../libobs/media-io/video-scaler.h"
+
+static void decklink_output_destroy(void *data)
+{
+	auto *decklink = (DeckLinkOutput *)data;
+	delete decklink;
+}
+
+static void *decklink_output_create(obs_data_t *settings, obs_output_t *output)
+{
+	auto *decklinkOutput = new DeckLinkOutput(output, deviceEnum);
+
+	decklinkOutput->deviceHash = obs_data_get_string(settings, DEVICE_HASH);
+	decklinkOutput->modeID = obs_data_get_int(settings, MODE_ID);
+
+	return decklinkOutput;
+}
+
+static void decklink_output_update(void *data, obs_data_t *settings)
+{
+	auto *decklink = (DeckLinkOutput *)data;
+
+	decklink->deviceHash = obs_data_get_string(settings, DEVICE_HASH);
+	decklink->modeID = obs_data_get_int(settings, MODE_ID);
+}
+
+static bool decklink_output_start(void *data)
+{
+	auto *decklink = (DeckLinkOutput *)data;
+	struct obs_audio_info aoi;
+
+	if (!obs_get_audio_info(&aoi)) {
+		blog(LOG_WARNING, "No active audio");
+		return false;
+	}
+
+	decklink->audio_samplerate = aoi.samples_per_sec;
+	decklink->audio_planes = 2;
+	decklink->audio_size = get_audio_size(AUDIO_FORMAT_16BIT, aoi.speakers, 1);
+
+	decklink->start_timestamp = 0;
+
+	ComPtr<DeckLinkDevice> device;
+
+	device.Set(deviceEnum->FindByHash(decklink->deviceHash));
+
+	DeckLinkDeviceMode *mode = device->FindOutputMode(decklink->modeID);
+
+	decklink->SetSize(mode->GetWidth(), mode->GetHeight());
+
+	struct video_scale_info to = {};
+	to.format = VIDEO_FORMAT_UYVY;
+	to.width = mode->GetWidth();
+	to.height =  mode->GetHeight();
+
+	obs_output_set_video_conversion(decklink->GetOutput(), &to);
+
+	decklink->Activate(device, decklink->modeID);
+
+	struct audio_convert_info conversion = {};
+	conversion.format = AUDIO_FORMAT_16BIT;
+	conversion.speakers = SPEAKERS_STEREO;
+	conversion.samples_per_sec = 48000;  // Only format the decklink supports
+
+	obs_output_set_audio_conversion(decklink->GetOutput(), &conversion);
+
+	if (!obs_output_begin_data_capture(decklink->GetOutput(), 0))
+		return false;
+
+	return true;
+}
+
+static void decklink_output_stop(void *data, uint64_t)
+{
+	auto *decklink = (DeckLinkOutput *)data;
+
+	obs_output_end_data_capture(decklink->GetOutput());
+
+	ComPtr<DeckLinkDevice> device;
+
+	device.Set(deviceEnum->FindByHash(decklink->deviceHash));
+
+	decklink->Deactivate();
+}
+
+static void decklink_output_raw_video(void *data, struct video_data *frame)
+{
+	auto *decklink = (DeckLinkOutput *)data;
+
+	if (!decklink->start_timestamp)
+		decklink->start_timestamp = frame->timestamp;
+
+	decklink->DisplayVideoFrame(frame);
+}
+
+static bool prepare_audio(DeckLinkOutput *decklink,
+		const struct audio_data *frame,
+		struct audio_data *output)
+{
+	*output = *frame;
+
+	if (frame->timestamp < decklink->start_timestamp) {
+		uint64_t duration = (uint64_t)frame->frames * 1000000000 /
+				(uint64_t)decklink->audio_samplerate;
+		uint64_t end_ts = frame->timestamp + duration;
+		uint64_t cutoff;
+
+		if (end_ts <= decklink->start_timestamp)
+			return false;
+
+		cutoff = decklink->start_timestamp - frame->timestamp;
+		output->timestamp += cutoff;
+
+		cutoff *= (uint64_t)decklink->audio_samplerate / 1000000000;
+
+		for (size_t i = 0; i < decklink->audio_planes; i++)
+			output->data[i] += decklink->audio_size *
+					(uint32_t)cutoff;
+
+		output->frames -= (uint32_t)cutoff;
+	}
+
+	return true;
+}
+
+static void decklink_output_raw_audio(void *data, struct audio_data *frames)
+{
+	auto *decklink = (DeckLinkOutput *)data;
+	struct audio_data in;
+
+	if (!decklink->start_timestamp)
+		return;
+
+	if (!prepare_audio(decklink, frames, &in))
+		return;
+
+	decklink->WriteAudio(&in);
+}
+
+static bool decklink_output_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_list_clear(modeList);
+
+	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->GetOutputModes();
+
+		for (DeckLinkDeviceMode *mode : modes) {
+			obs_property_list_add_int(modeList,
+					mode->GetName().c_str(),
+					mode->GetId());
+		}
+	}
+
+	return true;
+}
+
+static obs_properties_t *decklink_output_properties(void *unused)
+{
+	UNUSED_PARAMETER(unused);
+	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_output_device_changed);
+
+	fill_out_devices(list);
+
+	obs_properties_add_list(props,
+			MODE_ID, TEXT_MODE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
+
+	obs_properties_add_bool(props, AUTO_START, TEXT_AUTO_START);
+
+	return props;
+}
+
+static const char *decklink_output_get_name(void*)
+{
+	return obs_module_text("BlackmagicDevice");
+}
+
+struct obs_output_info create_decklink_output_info()
+{
+	struct obs_output_info decklink_output_info = {};
+
+	decklink_output_info.id             = "decklink_output";
+	decklink_output_info.flags          = OBS_OUTPUT_AV;
+	decklink_output_info.get_name       = decklink_output_get_name;
+	decklink_output_info.create         = decklink_output_create;
+	decklink_output_info.destroy        = decklink_output_destroy;
+	decklink_output_info.start          = decklink_output_start;
+	decklink_output_info.stop           = decklink_output_stop;
+	decklink_output_info.get_properties = decklink_output_properties;
+	decklink_output_info.raw_video      = decklink_output_raw_video;
+	decklink_output_info.raw_audio      = decklink_output_raw_audio;
+	decklink_output_info.update         = decklink_output_update;
+
+	return decklink_output_info;
+}

+ 288 - 0
plugins/decklink/decklink-source.cpp

@@ -0,0 +1,288 @@
+#include <obs-module.h>
+
+#include "const.h"
+
+#include "DecklinkInput.hpp"
+#include "decklink-device.hpp"
+#include "decklink-device-discovery.hpp"
+#include "decklink-devices.hpp"
+
+static void decklink_enable_buffering(DeckLinkInput *decklink, bool enabled)
+{
+	obs_source_t *source = decklink->GetSource();
+	obs_source_set_async_unbuffered(source, !enabled);
+	decklink->buffering = enabled;
+}
+
+static void decklink_deactivate_when_not_showing(DeckLinkInput *decklink, bool dwns)
+{
+	decklink->dwns = dwns;
+}
+
+static void *decklink_create(obs_data_t *settings, obs_source_t *source)
+{
+	DeckLinkInput *decklink = new DeckLinkInput(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)
+{
+	DeckLinkInput *decklink = (DeckLinkInput *)data;
+	delete decklink;
+}
+
+static void decklink_update(void *data, obs_data_t *settings)
+{
+	DeckLinkInput *decklink = (DeckLinkInput *)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));
+
+	decklink_deactivate_when_not_showing(decklink,
+			obs_data_get_bool(settings, DEACTIVATE_WNS));
+
+	ComPtr<DeckLinkDevice> device;
+	device.Set(deviceEnum->FindByHash(hash));
+
+	decklink->SetPixelFormat(pixelFormat);
+	decklink->SetColorSpace(colorSpace);
+	decklink->SetColorRange(colorRange);
+	decklink->SetChannelFormat(channelFormat);
+	decklink->Activate(device, id);
+	decklink->hash = std::string(hash);
+}
+
+static void decklink_show(void *data)
+{
+	DeckLinkInput *decklink = (DeckLinkInput *)data;
+	obs_source_t *source = decklink->GetSource();
+	bool showing = obs_source_showing(source);
+	if (decklink->dwns && showing && !decklink->Capturing()) {
+		ComPtr<DeckLinkDevice> device;
+		device.Set(deviceEnum->FindByHash(decklink->hash.c_str()));
+		decklink->Activate(device, decklink->id);
+	}
+}
+static void decklink_hide(void *data)
+{
+	DeckLinkInput *decklink = (DeckLinkInput *)data;
+	obs_source_t *source = decklink->GetSource();
+	bool showing = obs_source_showing(source);
+	if (decklink->dwns && showing)
+		decklink->Deactivate();
+}
+
+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->GetInputModes();
+
+		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 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);
+
+	obs_properties_add_bool(props, DEACTIVATE_WNS, TEXT_DWNS);
+
+	UNUSED_PARAMETER(data);
+	return props;
+}
+
+
+struct obs_source_info create_decklink_source_info()
+{
+	struct obs_source_info decklink_source_info = {};
+	decklink_source_info.id             = "decklink-input";
+	decklink_source_info.type           = OBS_SOURCE_TYPE_INPUT;
+	decklink_source_info.output_flags   = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE;
+	decklink_source_info.create         = decklink_create;
+	decklink_source_info.destroy        = decklink_destroy;
+	decklink_source_info.get_defaults   = decklink_get_defaults;
+	decklink_source_info.get_name       = decklink_get_name;
+	decklink_source_info.get_properties = decklink_get_properties;
+	decklink_source_info.update         = decklink_update;
+	decklink_source_info.show           = decklink_show;
+	decklink_source_info.hide           = decklink_hide;
+
+	return decklink_source_info;
+}

+ 17 - 5
plugins/decklink/linux/CMakeLists.txt

@@ -21,8 +21,12 @@ set(linux-decklink-sdk_SOURCES
 	)
 
 set(linux-decklink_HEADERS
+	../decklink-devices.hpp
+	../const.h
+	../DecklinkOutput.hpp
 	../platform.hpp
-	../decklink.hpp
+	../DecklinkInput.hpp
+	../DecklinkBase.h
 	../decklink-device-instance.hpp
 	../decklink-device-discovery.hpp
 	../decklink-device.hpp
@@ -33,21 +37,29 @@ set(linux-decklink_HEADERS
 
 set(linux-decklink_SOURCES
 	../plugin-main.cpp
-	../decklink.cpp
+	../decklink-devices.cpp
+	../decklink-source.cpp
+	../decklink-output.cpp
+	../DecklinkOutput.cpp
+	../DecklinkInput.cpp
+	../DecklinkBase.cpp
 	../decklink-device-instance.cpp
 	../decklink-device-discovery.cpp
 	../decklink-device.cpp
 	../decklink-device-mode.cpp
 	../audio-repack.c
-	platform.cpp)
+	platform.cpp
+	)
 
 add_library(linux-decklink MODULE
 	${linux-decklink_SOURCES}
 	${linux-decklink_HEADERS}
 	${linux-decklink-sdk_HEADERS}
-	${linux-decklink-sdk_SOURCES})
+	${linux-decklink-sdk_SOURCES}
+	)
 
 target_link_libraries(linux-decklink
-	libobs)
+	libobs
+	)
 
 install_obs_plugin_with_data(linux-decklink ../data)

+ 26 - 8
plugins/decklink/mac/CMakeLists.txt

@@ -5,9 +5,9 @@ if(DISABLE_DECKLINK)
 	return()
 endif()
 
-find_library(COREFOUNDATION CoreFoundation)
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}")
 
-include_directories(${COREFOUNDATION})
+find_library(COREFOUNDATION CoreFoundation)
 
 set(mac-decklink-sdk_HEADERS
 	decklink-sdk/DeckLinkAPI.h
@@ -17,16 +17,19 @@ set(mac-decklink-sdk_HEADERS
 	decklink-sdk/DeckLinkAPIModes.h
 	decklink-sdk/DeckLinkAPIStreaming.h
 	decklink-sdk/DeckLinkAPITypes.h
-	decklink-sdk/DeckLinkAPIVersion.h
-	)
+	decklink-sdk/DeckLinkAPIVersion.h)
 
 set(mac-decklink-sdk_SOURCES
 	decklink-sdk/DeckLinkAPIDispatch.cpp
 	)
 
 set(mac-decklink_HEADERS
+	../decklink-devices.hpp
+	../const.h
+	../DecklinkOutput.hpp
 	../platform.hpp
-	../decklink.hpp
+	../DecklinkInput.hpp
+	../DecklinkBase.h
 	../decklink-device-instance.hpp
 	../decklink-device-discovery.hpp
 	../decklink-device.hpp
@@ -37,19 +40,34 @@ set(mac-decklink_HEADERS
 
 set(mac-decklink_SOURCES
 	../plugin-main.cpp
-	../decklink.cpp
+	../decklink-devices.cpp
+	../decklink-output.cpp
+	../decklink-source.cpp
+	../DecklinkOutput.cpp
+	../DecklinkInput.cpp
+	../DecklinkBase.cpp
 	../decklink-device-instance.cpp
 	../decklink-device-discovery.cpp
 	../decklink-device.cpp
 	../decklink-device-mode.cpp
 	../audio-repack.c
-	platform.cpp)
+	platform.cpp
+	)
+
+list(APPEND decklink_HEADERS ${decklink_UI_HEADERS})
+
+include_directories(
+	${COREFOUNDATION}
+	"${CMAKE_SOURCE_DIR}/UI/obs-frontend-api")
+
+list(APPEND mac-decklink_HEADERS ${decklink_UI_HEADERS})
 
 add_library(mac-decklink MODULE
 	${mac-decklink_SOURCES}
 	${mac-decklink_HEADERS}
 	${mac-decklink-sdk_HEADERS}
-	${mac-decklink-sdk_SOURCES})
+	${mac-decklink-sdk_SOURCES}
+	)
 
 target_link_libraries(mac-decklink
 	libobs

+ 9 - 328
plugins/decklink/plugin-main.cpp

@@ -1,323 +1,14 @@
-#include "decklink.hpp"
-#include "decklink-device.hpp"
-#include "decklink-device-discovery.hpp"
-
 #include <obs-module.h>
+#include "decklink-devices.hpp"
 
 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 DEACTIVATE_WNS  "deactivate_when_not_showing"
-
-#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")
-#define TEXT_DWNS                       obs_module_text("DeactivateWhenNotShowing")
-
-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_deactivate_when_not_showing(DeckLink *decklink, bool dwns)
-{
-	decklink->dwns = dwns;
-}
-
-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));
-
-	decklink_deactivate_when_not_showing(decklink,
-			obs_data_get_bool(settings, DEACTIVATE_WNS));
-
-	ComPtr<DeckLinkDevice> device;
-	device.Set(deviceEnum->FindByHash(hash));
-
-	decklink->SetPixelFormat(pixelFormat);
-	decklink->SetColorSpace(colorSpace);
-	decklink->SetColorRange(colorRange);
-	decklink->SetChannelFormat(channelFormat);
-	decklink->Activate(device, id);
-	decklink->hash = std::string(hash);
-}
-
-static void decklink_show(void *data)
-{
-	DeckLink *decklink = (DeckLink *)data;
-	obs_source_t *source = decklink->GetSource();
-	bool showing = obs_source_showing(source);
-	if (decklink->dwns && showing && !decklink->Capturing()) {
-		ComPtr<DeckLinkDevice> device;
-		device.Set(deviceEnum->FindByHash(decklink->hash.c_str()));
-
-		decklink->Activate(device, decklink->id);
-	}
-}
-
-static void decklink_hide(void *data)
-{
-	DeckLink *decklink = (DeckLink *)data;
-	obs_source_t *source = decklink->GetSource();
-	bool showing = obs_source_showing(source);
-	if (decklink->dwns && showing)
-		decklink->Deactivate();
-
-}
-
-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;
+extern struct obs_source_info create_decklink_source_info();
+struct obs_source_info decklink_source_info;
 
-	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);
-
-	obs_properties_add_bool(props, DEACTIVATE_WNS, TEXT_DWNS);
-
-	UNUSED_PARAMETER(data);
-	return props;
-}
+extern struct obs_output_info create_decklink_output_info();
+struct obs_output_info decklink_output_info;
 
 bool obs_module_load(void)
 {
@@ -325,21 +16,11 @@ bool obs_module_load(void)
 	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;
-	info.show           = decklink_show;
-	info.hide           = decklink_hide;
+	decklink_source_info = create_decklink_source_info();
+	obs_register_source(&decklink_source_info);
 
-	obs_register_source(&info);
+	decklink_output_info = create_decklink_output_info();
+	obs_register_output(&decklink_output_info);
 
 	return true;
 }

+ 19 - 6
plugins/decklink/win/CMakeLists.txt

@@ -16,8 +16,12 @@ set(win-decklink-sdk_HEADERS
 	)
 
 set(win-decklink_HEADERS
+	../decklink-devices.hpp
+	../DecklinkOutput.hpp
+	../const.h
 	../platform.hpp
-	../decklink.hpp
+	../DecklinkInput.hpp
+	../DecklinkBase.h
 	../decklink-device-instance.hpp
 	../decklink-device-discovery.hpp
 	../decklink-device.hpp
@@ -28,25 +32,34 @@ set(win-decklink_HEADERS
 
 set(win-decklink_SOURCES
 	../plugin-main.cpp
-	../decklink.cpp
+	../decklink-devices.cpp
+	../DecklinkOutput.cpp
+	../decklink-source.cpp
+	../decklink-output.cpp
+	../DecklinkInput.cpp
+	../DecklinkBase.cpp
 	../decklink-device-instance.cpp
 	../decklink-device-discovery.cpp
 	../decklink-device.cpp
 	../decklink-device-mode.cpp
 	../audio-repack.c
-	platform.cpp)
+	platform.cpp
+	)
 
 add_idl_files(win-decklink-sdk_GENERATED_FILES
-	${win-decklink-sdk_IDLS})
+	${win-decklink-sdk_IDLS}
+	)
 
 include_directories(
-	${CMAKE_CURRENT_BINARY_DIR})
+	${CMAKE_CURRENT_BINARY_DIR}
+)
 
 add_library(win-decklink MODULE
 	${win-decklink_SOURCES}
 	${win-decklink_HEADERS}
 	${win-decklink-sdk_HEADERS}
-	${win-decklink-sdk_GENERATED_FILES})
+	${win-decklink-sdk_GENERATED_FILES}
+	)
 
 target_link_libraries(win-decklink
 	libobs)