Decklink: add output support
This commit is contained in:
parent
f8e628ac36
commit
21b67cff64
20
plugins/decklink/DecklinkBase.cpp
Normal file
20
plugins/decklink/DecklinkBase.cpp
Normal 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()
|
||||
{
|
||||
}
|
33
plugins/decklink/DecklinkBase.h
Normal file
33
plugins/decklink/DecklinkBase.h
Normal 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;
|
||||
};
|
@ -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;
|
||||
}
|
@ -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
plugins/decklink/DecklinkOutput.cpp
Normal file
110
plugins/decklink/DecklinkOutput.cpp
Normal 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;
|
||||
}
|
33
plugins/decklink/DecklinkOutput.hpp
Normal file
33
plugins/decklink/DecklinkOutput.hpp
Normal 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
32
plugins/decklink/const.h
Normal 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")
|
@ -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"
|
||||
|
@ -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,
|
||||
|
@ -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(), ¤tPacket);
|
||||
obs_source_output_audio(static_cast<DeckLinkInput*>(decklink)->GetSource(), ¤tPacket);
|
||||
}
|
||||
|
||||
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(), ¤tFrame);
|
||||
obs_source_output_video(static_cast<DeckLinkInput*>(decklink)->GetSource(), ¤tFrame);
|
||||
}
|
||||
|
||||
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(
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -23,4 +23,7 @@ public:
|
||||
const std::string& GetName(void) const;
|
||||
|
||||
void SetMode(IDeckLinkDisplayMode *mode);
|
||||
|
||||
int GetWidth();
|
||||
int GetHeight();
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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
plugins/decklink/decklink-devices.cpp
Normal file
17
plugins/decklink/decklink-devices.cpp
Normal 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();
|
||||
}
|
8
plugins/decklink/decklink-devices.hpp
Normal file
8
plugins/decklink/decklink-devices.hpp
Normal 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);
|
239
plugins/decklink/decklink-output.cpp
Normal file
239
plugins/decklink/decklink-output.cpp
Normal 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;
|
||||
}
|
288
plugins/decklink/decklink-source.cpp
Normal file
288
plugins/decklink/decklink-source.cpp
Normal 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;
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user