yolobs-studio/plugins/decklink/decklink-device-instance.cpp

565 lines
14 KiB
C++
Raw Normal View History

2016-02-23 23:16:51 +00:00
#include "decklink-device-instance.hpp"
2017-06-29 19:01:10 +00:00
#include "audio-repack.hpp"
2016-02-23 23:16:51 +00:00
2019-07-27 12:47:10 +00:00
#include "DecklinkInput.hpp"
#include "DecklinkOutput.hpp"
2016-02-23 23:16:51 +00:00
#include <util/platform.h>
#include <util/threading.h>
#include <sstream>
2019-07-27 12:47:10 +00:00
#include <algorithm>
2017-06-29 19:01:10 +00:00
2016-02-23 23:16:51 +00:00
static inline enum video_format ConvertPixelFormat(BMDPixelFormat format)
{
switch (format) {
2019-09-22 21:19:10 +00:00
case bmdFormat8BitBGRA:
return VIDEO_FORMAT_BGRX;
2016-02-23 23:16:51 +00:00
default:
case bmdFormat8BitYUV:;
}
return VIDEO_FORMAT_UYVY;
}
2017-06-29 19:01:10 +00:00
static inline int ConvertChannelFormat(speaker_layout format)
{
switch (format) {
2018-02-19 19:54:37 +00:00
case SPEAKERS_2POINT1:
case SPEAKERS_4POINT0:
case SPEAKERS_4POINT1:
2017-06-29 19:01:10 +00:00
case SPEAKERS_5POINT1:
case SPEAKERS_7POINT1:
return 8;
default:
case SPEAKERS_STEREO:
return 2;
}
}
2019-09-22 21:19:10 +00:00
static inline audio_repack_mode_t ConvertRepackFormat(speaker_layout format,
bool swap)
2017-06-29 19:01:10 +00:00
{
switch (format) {
2018-02-19 19:54:37 +00:00
case SPEAKERS_2POINT1:
2019-07-27 12:47:10 +00:00
return repack_mode_8to3ch;
2018-02-19 19:54:37 +00:00
case SPEAKERS_4POINT0:
2019-07-27 12:47:10 +00:00
return repack_mode_8to4ch;
2018-02-19 19:54:37 +00:00
case SPEAKERS_4POINT1:
2019-09-22 21:19:10 +00:00
return swap ? repack_mode_8to5ch_swap : repack_mode_8to5ch;
2017-06-29 19:01:10 +00:00
case SPEAKERS_5POINT1:
2019-07-27 12:47:10 +00:00
return swap ? repack_mode_8to6ch_swap : repack_mode_8to6ch;
2017-06-29 19:01:10 +00:00
case SPEAKERS_7POINT1:
2019-09-22 21:19:10 +00:00
return swap ? repack_mode_8ch_swap : repack_mode_8ch;
2017-06-29 19:01:10 +00:00
default:
assert(false && "No repack requested");
return (audio_repack_mode_t)-1;
}
}
2019-07-27 12:47:10 +00:00
DeckLinkDeviceInstance::DeckLinkDeviceInstance(DecklinkBase *decklink_,
2019-09-22 21:19:10 +00:00
DeckLinkDevice *device_)
: currentFrame(), currentPacket(), decklink(decklink_), device(device_)
2016-02-23 23:16:51 +00:00
{
currentPacket.samples_per_sec = 48000;
2019-09-22 21:19:10 +00:00
currentPacket.speakers = SPEAKERS_STEREO;
currentPacket.format = AUDIO_FORMAT_16BIT;
2016-02-23 23:16:51 +00:00
}
2019-09-22 21:19:10 +00:00
DeckLinkDeviceInstance::~DeckLinkDeviceInstance() {}
2016-02-23 23:16:51 +00:00
void DeckLinkDeviceInstance::HandleAudioPacket(
2019-09-22 21:19:10 +00:00
IDeckLinkAudioInputPacket *audioPacket, const uint64_t timestamp)
2016-02-23 23:16:51 +00:00
{
if (audioPacket == nullptr)
return;
void *bytes;
if (audioPacket->GetBytes(&bytes) != S_OK) {
LOG(LOG_WARNING, "Failed to get audio packet data");
return;
}
2019-09-22 21:19:10 +00:00
const uint32_t frameCount =
(uint32_t)audioPacket->GetSampleFrameCount();
currentPacket.frames = frameCount;
currentPacket.timestamp = timestamp;
2017-06-29 19:01:10 +00:00
2019-09-22 21:19:10 +00:00
if (decklink && !static_cast<DeckLinkInput *>(decklink)->buffering) {
2018-02-19 19:54:37 +00:00
currentPacket.timestamp = os_gettime_ns();
currentPacket.timestamp -=
(uint64_t)frameCount * 1000000000ULL /
(uint64_t)currentPacket.samples_per_sec;
}
int maxdevicechannel = device->GetMaxChannel();
if (channelFormat != SPEAKERS_UNKNOWN &&
channelFormat != SPEAKERS_MONO &&
channelFormat != SPEAKERS_STEREO &&
2019-09-22 21:19:10 +00:00
(channelFormat != SPEAKERS_7POINT1 ||
static_cast<DeckLinkInput *>(decklink)->swap) &&
maxdevicechannel >= 8) {
2018-02-19 19:54:37 +00:00
2017-06-29 19:01:10 +00:00
if (audioRepacker->repack((uint8_t *)bytes, frameCount) < 0) {
LOG(LOG_ERROR, "Failed to convert audio packet data");
return;
}
2018-02-19 19:54:37 +00:00
currentPacket.data[0] = (*audioRepacker)->packet_buffer;
2017-06-29 19:01:10 +00:00
} else {
2018-02-19 19:54:37 +00:00
currentPacket.data[0] = (uint8_t *)bytes;
2017-06-29 19:01:10 +00:00
}
nextAudioTS = timestamp +
2019-09-22 21:19:10 +00:00
((uint64_t)frameCount * 1000000000ULL / 48000ULL) + 1;
2016-02-23 23:16:51 +00:00
2019-09-22 21:19:10 +00:00
obs_source_output_audio(
static_cast<DeckLinkInput *>(decklink)->GetSource(),
&currentPacket);
2016-02-23 23:16:51 +00:00
}
void DeckLinkDeviceInstance::HandleVideoFrame(
2019-09-22 21:19:10 +00:00
IDeckLinkVideoInputFrame *videoFrame, const uint64_t timestamp)
2016-02-23 23:16:51 +00:00
{
if (videoFrame == nullptr)
return;
void *bytes;
if (videoFrame->GetBytes(&bytes) != S_OK) {
LOG(LOG_WARNING, "Failed to get video frame data");
return;
}
2019-09-22 21:19:10 +00:00
currentFrame.data[0] = (uint8_t *)bytes;
2016-02-23 23:16:51 +00:00
currentFrame.linesize[0] = (uint32_t)videoFrame->GetRowBytes();
2019-09-22 21:19:10 +00:00
currentFrame.width = (uint32_t)videoFrame->GetWidth();
currentFrame.height = (uint32_t)videoFrame->GetHeight();
currentFrame.timestamp = timestamp;
2016-02-23 23:16:51 +00:00
2019-09-22 21:19:10 +00:00
obs_source_output_video2(
static_cast<DeckLinkInput *>(decklink)->GetSource(),
&currentFrame);
2016-02-23 23:16:51 +00:00
}
2017-06-29 19:01:10 +00:00
void DeckLinkDeviceInstance::FinalizeStream()
{
input->SetCallback(nullptr);
2018-02-19 19:54:37 +00:00
input->DisableVideoInput();
if (channelFormat != SPEAKERS_UNKNOWN)
input->DisableAudioInput();
2017-06-29 19:01:10 +00:00
2019-09-22 21:19:10 +00:00
if (audioRepacker != nullptr) {
2017-06-29 19:01:10 +00:00
delete audioRepacker;
audioRepacker = nullptr;
}
mode = nullptr;
}
2018-02-19 19:54:37 +00:00
//#define LOG_SETUP_VIDEO_FORMAT 1
void DeckLinkDeviceInstance::SetupVideoFormat(DeckLinkDeviceMode *mode_)
{
if (mode_ == nullptr)
return;
currentFrame.format = ConvertPixelFormat(pixelFormat);
2019-09-22 21:19:10 +00:00
colorSpace = static_cast<DeckLinkInput *>(decklink)->GetColorSpace();
2018-02-19 19:54:37 +00:00
if (colorSpace == VIDEO_CS_DEFAULT) {
const BMDDisplayModeFlags flags = mode_->GetDisplayModeFlags();
if (flags & bmdDisplayModeColorspaceRec709)
activeColorSpace = VIDEO_CS_709;
else if (flags & bmdDisplayModeColorspaceRec601)
activeColorSpace = VIDEO_CS_601;
else
activeColorSpace = VIDEO_CS_DEFAULT;
} else {
activeColorSpace = colorSpace;
}
2019-09-22 21:19:10 +00:00
colorRange = static_cast<DeckLinkInput *>(decklink)->GetColorRange();
2019-07-27 12:47:10 +00:00
currentFrame.range = colorRange;
2018-02-19 19:54:37 +00:00
video_format_get_parameters(activeColorSpace, colorRange,
2019-09-22 21:19:10 +00:00
currentFrame.color_matrix,
currentFrame.color_range_min,
currentFrame.color_range_max);
2018-02-19 19:54:37 +00:00
#ifdef LOG_SETUP_VIDEO_FORMAT
LOG(LOG_INFO, "Setup video format: %s, %s, %s",
2019-09-22 21:19:10 +00:00
pixelFormat == bmdFormat8BitYUV ? "YUV" : "RGB",
activeColorSpace == VIDEO_CS_709 ? "BT.709" : "BT.601",
colorRange == VIDEO_RANGE_FULL ? "full" : "limited");
2018-02-19 19:54:37 +00:00
#endif
}
2019-07-27 12:47:10 +00:00
bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_,
2019-09-22 21:19:10 +00:00
BMDVideoConnection bmdVideoConnection,
BMDAudioConnection bmdAudioConnection)
2016-02-23 23:16:51 +00:00
{
if (mode != nullptr)
return false;
if (mode_ == nullptr)
return false;
LOG(LOG_INFO, "Starting capture...");
if (!device->GetInput(&input))
return false;
2019-07-27 12:47:10 +00:00
IDeckLinkConfiguration *deckLinkConfiguration = NULL;
HRESULT result = input->QueryInterface(IID_IDeckLinkConfiguration,
2019-09-22 21:19:10 +00:00
(void **)&deckLinkConfiguration);
if (result != S_OK) {
2019-07-27 12:47:10 +00:00
LOG(LOG_ERROR,
2019-09-22 21:19:10 +00:00
"Could not obtain the IDeckLinkConfiguration interface: %08x\n",
result);
2019-07-27 12:47:10 +00:00
} else {
if (bmdVideoConnection > 0) {
result = deckLinkConfiguration->SetInt(
2019-09-22 21:19:10 +00:00
bmdDeckLinkConfigVideoInputConnection,
bmdVideoConnection);
2019-07-27 12:47:10 +00:00
if (result != S_OK) {
LOG(LOG_ERROR,
2019-09-22 21:19:10 +00:00
"Couldn't set input video port to %d\n\n",
bmdVideoConnection);
2019-07-27 12:47:10 +00:00
}
}
if (bmdAudioConnection > 0) {
result = deckLinkConfiguration->SetInt(
2019-09-22 21:19:10 +00:00
bmdDeckLinkConfigAudioInputConnection,
bmdAudioConnection);
2019-07-27 12:47:10 +00:00
if (result != S_OK) {
LOG(LOG_ERROR,
2019-09-22 21:19:10 +00:00
"Couldn't set input audio port to %d\n\n",
bmdVideoConnection);
2019-07-27 12:47:10 +00:00
}
}
}
videoConnection = bmdVideoConnection;
audioConnection = bmdAudioConnection;
2018-02-19 19:54:37 +00:00
BMDVideoInputFlags flags;
2016-02-23 23:16:51 +00:00
2018-02-19 19:54:37 +00:00
bool isauto = mode_->GetName() == "Auto";
if (isauto) {
displayMode = bmdModeNTSC;
pixelFormat = bmdFormat8BitYUV;
flags = bmdVideoInputEnableFormatDetection;
} else {
displayMode = mode_->GetDisplayMode();
2019-09-22 21:19:10 +00:00
pixelFormat =
static_cast<DeckLinkInput *>(decklink)->GetPixelFormat();
2018-02-19 19:54:37 +00:00
flags = bmdVideoInputFlagDefault;
}
2016-02-23 23:16:51 +00:00
2019-09-22 21:19:10 +00:00
const HRESULT videoResult =
input->EnableVideoInput(displayMode, pixelFormat, flags);
2016-02-23 23:16:51 +00:00
if (videoResult != S_OK) {
LOG(LOG_ERROR, "Failed to enable video input");
return false;
}
2018-02-19 19:54:37 +00:00
SetupVideoFormat(mode_);
2019-09-22 21:19:10 +00:00
channelFormat =
static_cast<DeckLinkInput *>(decklink)->GetChannelFormat();
2017-06-29 19:01:10 +00:00
currentPacket.speakers = channelFormat;
2019-09-22 21:19:10 +00:00
swap = static_cast<DeckLinkInput *>(decklink)->swap;
2017-06-29 19:01:10 +00:00
2018-02-19 19:54:37 +00:00
int maxdevicechannel = device->GetMaxChannel();
2017-06-29 19:01:10 +00:00
if (channelFormat != SPEAKERS_UNKNOWN) {
const int channel = ConvertChannelFormat(channelFormat);
const HRESULT audioResult = input->EnableAudioInput(
2019-09-22 21:19:10 +00:00
bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger,
channel);
2017-06-29 19:01:10 +00:00
if (audioResult != S_OK)
2019-09-22 21:19:10 +00:00
LOG(LOG_WARNING,
"Failed to enable audio input; continuing...");
2017-06-29 19:01:10 +00:00
2018-02-19 19:54:37 +00:00
if (channelFormat != SPEAKERS_UNKNOWN &&
channelFormat != SPEAKERS_MONO &&
channelFormat != SPEAKERS_STEREO &&
2019-09-22 21:19:10 +00:00
(channelFormat != SPEAKERS_7POINT1 || swap) &&
maxdevicechannel >= 8) {
2018-02-19 19:54:37 +00:00
2019-09-22 21:19:10 +00:00
const audio_repack_mode_t repack_mode =
ConvertRepackFormat(channelFormat, swap);
2017-06-29 19:01:10 +00:00
audioRepacker = new AudioRepacker(repack_mode);
}
}
if (input->SetCallback(this) != S_OK) {
LOG(LOG_ERROR, "Failed to set callback");
FinalizeStream();
return false;
}
2016-02-23 23:16:51 +00:00
if (input->StartStreams() != S_OK) {
LOG(LOG_ERROR, "Failed to start streams");
2017-06-29 19:01:10 +00:00
FinalizeStream();
2016-02-23 23:16:51 +00:00
return false;
}
mode = mode_;
return true;
}
bool DeckLinkDeviceInstance::StopCapture(void)
{
if (mode == nullptr || input == nullptr)
return false;
LOG(LOG_INFO, "Stopping capture of '%s'...",
2019-09-22 21:19:10 +00:00
GetDevice()->GetDisplayName().c_str());
2016-02-23 23:16:51 +00:00
input->StopStreams();
2017-06-29 19:01:10 +00:00
FinalizeStream();
2016-02-23 23:16:51 +00:00
return true;
}
2019-07-27 12:47:10 +00:00
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(
2019-09-22 21:19:10 +00:00
mode_->GetDisplayMode(), bmdVideoOutputFlagDefault);
2019-07-27 12:47:10 +00:00
if (videoResult != S_OK) {
LOG(LOG_ERROR, "Failed to enable video output");
return false;
}
const HRESULT audioResult = output->EnableAudioOutput(
2019-09-22 21:19:10 +00:00
bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2,
bmdAudioOutputStreamTimestamped);
2019-07-27 12:47:10 +00:00
if (audioResult != S_OK) {
LOG(LOG_ERROR, "Failed to enable audio output");
return false;
}
mode = mode_;
int keyerMode = device->GetKeyerMode();
IDeckLinkKeyer *deckLinkKeyer = nullptr;
if (device->GetKeyer(&deckLinkKeyer)) {
if (keyerMode) {
deckLinkKeyer->Enable(keyerMode == 1);
deckLinkKeyer->SetLevel(255);
} else {
deckLinkKeyer->Disable();
}
}
2019-09-22 21:19:10 +00:00
auto decklinkOutput = dynamic_cast<DeckLinkOutput *>(decklink);
2019-07-27 12:47:10 +00:00
if (decklinkOutput == nullptr)
return false;
int rowBytes = decklinkOutput->GetWidth() * 2;
if (decklinkOutput->keyerMode != 0) {
rowBytes = decklinkOutput->GetWidth() * 4;
}
BMDPixelFormat pixelFormat = bmdFormat8BitYUV;
if (keyerMode != 0) {
pixelFormat = bmdFormat8BitBGRA;
}
HRESULT result;
result = output->CreateVideoFrame(decklinkOutput->GetWidth(),
2019-09-22 21:19:10 +00:00
decklinkOutput->GetHeight(), rowBytes,
pixelFormat, bmdFrameFlagDefault,
&decklinkOutputFrame);
2019-07-27 12:47:10 +00:00
if (result != S_OK) {
2019-09-22 21:19:10 +00:00
blog(LOG_ERROR, "failed to make frame 0x%X", result);
2019-07-27 12:47:10 +00:00
return false;
}
return true;
}
bool DeckLinkDeviceInstance::StopOutput()
{
if (mode == nullptr || output == nullptr)
return false;
LOG(LOG_INFO, "Stopping output of '%s'...",
2019-09-22 21:19:10 +00:00
GetDevice()->GetDisplayName().c_str());
2019-07-27 12:47:10 +00:00
output->DisableVideoOutput();
output->DisableAudioOutput();
if (decklinkOutputFrame != nullptr) {
decklinkOutputFrame->Release();
decklinkOutputFrame = nullptr;
}
return true;
}
void DeckLinkDeviceInstance::DisplayVideoFrame(video_data *frame)
{
2019-09-22 21:19:10 +00:00
auto decklinkOutput = dynamic_cast<DeckLinkOutput *>(decklink);
2019-07-27 12:47:10 +00:00
if (decklinkOutput == nullptr)
return;
uint8_t *destData;
2019-09-22 21:19:10 +00:00
decklinkOutputFrame->GetBytes((void **)&destData);
2019-07-27 12:47:10 +00:00
uint8_t *outData = frame->data[0];
int rowBytes = decklinkOutput->GetWidth() * 2;
if (device->GetKeyerMode()) {
rowBytes = decklinkOutput->GetWidth() * 4;
}
2019-09-22 21:19:10 +00:00
std::copy(outData, outData + (decklinkOutput->GetHeight() * rowBytes),
destData);
2019-07-27 12:47:10 +00:00
output->DisplayVideoFrameSync(decklinkOutputFrame);
}
void DeckLinkDeviceInstance::WriteAudio(audio_data *frames)
{
uint32_t sampleFramesWritten;
2019-09-22 21:19:10 +00:00
output->WriteAudioSamplesSync(frames->data[0], frames->frames,
&sampleFramesWritten);
2019-07-27 12:47:10 +00:00
}
2016-02-23 23:16:51 +00:00
#define TIME_BASE 1000000000
HRESULT STDMETHODCALLTYPE DeckLinkDeviceInstance::VideoInputFrameArrived(
2019-09-22 21:19:10 +00:00
IDeckLinkVideoInputFrame *videoFrame,
IDeckLinkAudioInputPacket *audioPacket)
2016-02-23 23:16:51 +00:00
{
BMDTimeValue videoTS = 0;
BMDTimeValue videoDur = 0;
BMDTimeValue audioTS = 0;
2017-06-29 19:01:10 +00:00
if (videoFrame) {
2016-02-23 23:16:51 +00:00
videoFrame->GetStreamTime(&videoTS, &videoDur, TIME_BASE);
2017-06-29 19:01:10 +00:00
lastVideoTS = (uint64_t)videoTS;
}
if (audioPacket) {
BMDTimeValue newAudioTS = 0;
int64_t diff;
audioPacket->GetPacketTime(&newAudioTS, TIME_BASE);
audioTS = newAudioTS + audioOffset;
diff = (int64_t)audioTS - (int64_t)nextAudioTS;
if (diff > 10000000LL) {
audioOffset -= diff;
audioTS = newAudioTS + audioOffset;
} else if (diff < -1000000) {
audioOffset = 0;
audioTS = newAudioTS;
}
}
2016-02-23 23:16:51 +00:00
if (videoFrame && videoTS >= 0)
HandleVideoFrame(videoFrame, (uint64_t)videoTS);
if (audioPacket && audioTS >= 0)
HandleAudioPacket(audioPacket, (uint64_t)audioTS);
return S_OK;
}
HRESULT STDMETHODCALLTYPE DeckLinkDeviceInstance::VideoInputFormatChanged(
2019-09-22 21:19:10 +00:00
BMDVideoInputFormatChangedEvents events, IDeckLinkDisplayMode *newMode,
BMDDetectedVideoInputFormatFlags detectedSignalFlags)
2016-02-23 23:16:51 +00:00
{
2018-02-19 19:54:37 +00:00
input->PauseStreams();
mode->SetMode(newMode);
if (events & bmdVideoInputDisplayModeChanged) {
displayMode = mode->GetDisplayMode();
}
if (events & bmdVideoInputColorspaceChanged) {
switch (detectedSignalFlags) {
case bmdDetectedVideoInputRGB444:
pixelFormat = bmdFormat8BitBGRA;
break;
default:
case bmdDetectedVideoInputYCbCr422:
pixelFormat = bmdFormat8BitYUV;
break;
}
}
2019-09-22 21:19:10 +00:00
const HRESULT videoResult = input->EnableVideoInput(
displayMode, pixelFormat, bmdVideoInputEnableFormatDetection);
2018-02-19 19:54:37 +00:00
if (videoResult != S_OK) {
LOG(LOG_ERROR, "Failed to enable video input");
input->StopStreams();
FinalizeStream();
return E_FAIL;
}
SetupVideoFormat(mode);
2016-02-23 23:16:51 +00:00
2018-02-19 19:54:37 +00:00
input->FlushStreams();
input->StartStreams();
2016-02-23 23:16:51 +00:00
return S_OK;
}
ULONG STDMETHODCALLTYPE DeckLinkDeviceInstance::AddRef(void)
{
return os_atomic_inc_long(&refCount);
}
HRESULT STDMETHODCALLTYPE DeckLinkDeviceInstance::QueryInterface(REFIID iid,
2019-09-22 21:19:10 +00:00
LPVOID *ppv)
2016-02-23 23:16:51 +00:00
{
HRESULT result = E_NOINTERFACE;
*ppv = nullptr;
CFUUIDBytes unknown = CFUUIDGetUUIDBytes(IUnknownUUID);
if (memcmp(&iid, &unknown, sizeof(REFIID)) == 0) {
*ppv = this;
AddRef();
result = S_OK;
} else if (memcmp(&iid, &IID_IDeckLinkNotificationCallback,
2019-09-22 21:19:10 +00:00
sizeof(REFIID)) == 0) {
2016-02-23 23:16:51 +00:00
*ppv = (IDeckLinkNotificationCallback *)this;
AddRef();
result = S_OK;
}
return result;
}
ULONG STDMETHODCALLTYPE DeckLinkDeviceInstance::Release(void)
{
const long newRefCount = os_atomic_dec_long(&refCount);
if (newRefCount == 0) {
delete this;
return 0;
}
return newRefCount;
}