Decklink: add output support

master
Colin Edwards 2018-09-25 17:51:32 -05:00
parent f8e628ac36
commit 21b67cff64
23 changed files with 1102 additions and 439 deletions

View File

@ -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()
{
}

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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
plugins/decklink/const.h Normal file
View File

@ -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")

View File

@ -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"

View File

@ -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,

View File

@ -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(

View File

@ -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);
};

View File

@ -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)

View File

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

View File

@ -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;
if (device->QueryInterface(IID_IDeckLinkInput, (void **) &input) == S_OK) {
IDeckLinkDisplayModeIterator *modeIterator;
if (input->GetDisplayModeIterator(&modeIterator) == S_OK) {
IDeckLinkDisplayMode *displayMode;
long long modeId = 1;
IDeckLinkDisplayModeIterator *modeIterator;
if (input->GetDisplayModeIterator(&modeIterator) == S_OK) {
IDeckLinkDisplayMode *displayMode;
long long modeId = 1;
while (modeIterator->Next(&displayMode) == S_OK) {
if (displayMode == nullptr)
continue;
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;
}
DeckLinkDeviceMode *mode =
new DeckLinkDeviceMode(displayMode, modeId);
modes.push_back(mode);
modeIdMap[modeId] = mode;
displayMode->Release();
++modeId;
modeIterator->Release();
}
}
modeIterator->Release();
// find output modes
ComPtr<IDeckLinkOutput> output;
if (device->QueryInterface(IID_IDeckLinkOutput, (void **) &output) == S_OK) {
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)
{
return modeIdMap[id];
if (device->QueryInterface(IID_IDeckLinkOutput, (void**)output) != S_OK)
return false;
return true;
}
DeckLinkDeviceMode *DeckLinkDevice::FindInputMode(long long 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 modes;
return inputModes;
}
const std::vector<DeckLinkDeviceMode *>& DeckLinkDevice::GetOutputModes(void) const
{
return outputModes;
}
const std::string& DeckLinkDevice::GetName(void) const

View File

@ -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_)
{

View File

@ -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();
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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)

View File

@ -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

View File

@ -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"
extern struct obs_source_info create_decklink_source_info();
struct obs_source_info decklink_source_info;
#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;
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;
}

View File

@ -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)