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>
|
2020-10-01 20:15:25 +00:00
|
|
|
#include <util/util_uint64.h>
|
2016-02-23 23:16:51 +00:00
|
|
|
|
|
|
|
#include <sstream>
|
2020-12-22 17:32:50 +00:00
|
|
|
#include <iomanip>
|
2019-07-27 12:47:10 +00:00
|
|
|
#include <algorithm>
|
2017-06-29 19:01:10 +00:00
|
|
|
|
2020-12-22 17:32:50 +00:00
|
|
|
#include "OBSVideoFrame.h"
|
|
|
|
|
|
|
|
#include <caption/caption.h>
|
|
|
|
#include <util/bitstream.h>
|
|
|
|
|
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_)
|
2020-12-22 17:32:50 +00:00
|
|
|
: currentFrame(),
|
|
|
|
currentPacket(),
|
|
|
|
currentCaptions(),
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-12-22 17:32:50 +00:00
|
|
|
DeckLinkDeviceInstance::~DeckLinkDeviceInstance()
|
|
|
|
{
|
|
|
|
if (convertFrame) {
|
|
|
|
delete convertFrame;
|
|
|
|
}
|
|
|
|
}
|
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 -=
|
2020-10-01 20:15:25 +00:00
|
|
|
util_mul_div64(frameCount, 1000000000ULL,
|
|
|
|
currentPacket.samples_per_sec);
|
2018-02-19 19:54:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 +
|
2020-10-01 20:15:25 +00:00
|
|
|
util_mul_div64(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(),
|
|
|
|
¤tPacket);
|
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;
|
|
|
|
|
2020-12-22 17:32:50 +00:00
|
|
|
IDeckLinkVideoFrameAncillaryPackets *packets;
|
|
|
|
|
|
|
|
if (videoFrame->QueryInterface(IID_IDeckLinkVideoFrameAncillaryPackets,
|
|
|
|
(void **)&packets) == S_OK) {
|
|
|
|
IDeckLinkAncillaryPacketIterator *iterator;
|
|
|
|
packets->GetPacketIterator(&iterator);
|
|
|
|
|
|
|
|
IDeckLinkAncillaryPacket *packet;
|
|
|
|
iterator->Next(&packet);
|
|
|
|
|
|
|
|
if (packet) {
|
|
|
|
auto did = packet->GetDID();
|
|
|
|
auto sdid = packet->GetSDID();
|
|
|
|
|
|
|
|
// Caption data
|
|
|
|
if (did == 0x61 && sdid == 0x01) {
|
|
|
|
this->HandleCaptionPacket(packet, timestamp);
|
|
|
|
}
|
|
|
|
|
|
|
|
packet->Release();
|
|
|
|
}
|
|
|
|
|
|
|
|
iterator->Release();
|
|
|
|
packets->Release();
|
|
|
|
}
|
|
|
|
|
|
|
|
IDeckLinkVideoConversion *frameConverter =
|
|
|
|
CreateVideoConversionInstance();
|
|
|
|
|
|
|
|
frameConverter->ConvertFrame(videoFrame, convertFrame);
|
|
|
|
|
2016-02-23 23:16:51 +00:00
|
|
|
void *bytes;
|
2020-12-22 17:32:50 +00:00
|
|
|
if (convertFrame->GetBytes(&bytes) != S_OK) {
|
2016-02-23 23:16:51 +00:00
|
|
|
LOG(LOG_WARNING, "Failed to get video frame data");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-09-22 21:19:10 +00:00
|
|
|
currentFrame.data[0] = (uint8_t *)bytes;
|
2020-12-22 17:32:50 +00:00
|
|
|
currentFrame.linesize[0] = (uint32_t)convertFrame->GetRowBytes();
|
|
|
|
currentFrame.width = (uint32_t)convertFrame->GetWidth();
|
|
|
|
currentFrame.height = (uint32_t)convertFrame->GetHeight();
|
2019-09-22 21:19:10 +00:00
|
|
|
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(),
|
|
|
|
¤tFrame);
|
2016-02-23 23:16:51 +00:00
|
|
|
}
|
|
|
|
|
2020-12-22 17:32:50 +00:00
|
|
|
void DeckLinkDeviceInstance::HandleCaptionPacket(
|
|
|
|
IDeckLinkAncillaryPacket *packet, const uint64_t timestamp)
|
|
|
|
{
|
|
|
|
const void *data;
|
|
|
|
uint32_t size;
|
|
|
|
packet->GetBytes(bmdAncillaryPacketFormatUInt8, &data, &size);
|
|
|
|
|
|
|
|
auto anc = (uint8_t *)data;
|
|
|
|
struct bitstream_reader reader;
|
|
|
|
bitstream_reader_init(&reader, anc, size);
|
|
|
|
|
|
|
|
// header1
|
|
|
|
bitstream_reader_r8(&reader);
|
|
|
|
// header2
|
|
|
|
bitstream_reader_r8(&reader);
|
|
|
|
|
|
|
|
// length
|
|
|
|
bitstream_reader_r8(&reader);
|
|
|
|
// frameRate
|
|
|
|
bitstream_reader_read_bits(&reader, 4);
|
|
|
|
//reserved
|
|
|
|
bitstream_reader_read_bits(&reader, 4);
|
|
|
|
|
|
|
|
auto cdp_timecode_added = bitstream_reader_read_bits(&reader, 1);
|
|
|
|
// cdp_data_block_added
|
|
|
|
bitstream_reader_read_bits(&reader, 1);
|
|
|
|
// cdp_service_info_added
|
|
|
|
bitstream_reader_read_bits(&reader, 1);
|
|
|
|
// cdp_service_info_start
|
|
|
|
bitstream_reader_read_bits(&reader, 1);
|
|
|
|
// cdp_service_info_changed
|
|
|
|
bitstream_reader_read_bits(&reader, 1);
|
|
|
|
// cdp_service_info_end
|
|
|
|
bitstream_reader_read_bits(&reader, 1);
|
|
|
|
auto cdp_contains_captions = bitstream_reader_read_bits(&reader, 1);
|
|
|
|
//reserved
|
|
|
|
bitstream_reader_read_bits(&reader, 1);
|
|
|
|
|
|
|
|
// cdp_counter
|
|
|
|
bitstream_reader_r8(&reader);
|
|
|
|
// cdp_counter2
|
|
|
|
bitstream_reader_r8(&reader);
|
|
|
|
|
|
|
|
if (cdp_timecode_added) {
|
|
|
|
// timecodeSectionID
|
|
|
|
bitstream_reader_r8(&reader);
|
|
|
|
//reserved
|
|
|
|
bitstream_reader_read_bits(&reader, 2);
|
|
|
|
bitstream_reader_read_bits(&reader, 2);
|
|
|
|
bitstream_reader_read_bits(&reader, 4);
|
|
|
|
// reserved
|
|
|
|
bitstream_reader_read_bits(&reader, 1);
|
|
|
|
bitstream_reader_read_bits(&reader, 3);
|
|
|
|
bitstream_reader_read_bits(&reader, 4);
|
|
|
|
bitstream_reader_read_bits(&reader, 1);
|
|
|
|
bitstream_reader_read_bits(&reader, 3);
|
|
|
|
bitstream_reader_read_bits(&reader, 4);
|
|
|
|
bitstream_reader_read_bits(&reader, 1);
|
|
|
|
bitstream_reader_read_bits(&reader, 1);
|
|
|
|
bitstream_reader_read_bits(&reader, 3);
|
|
|
|
bitstream_reader_read_bits(&reader, 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cdp_contains_captions) {
|
|
|
|
// cdp_data_section
|
|
|
|
bitstream_reader_r8(&reader);
|
|
|
|
|
|
|
|
//process_em_data_flag
|
|
|
|
bitstream_reader_read_bits(&reader, 1);
|
|
|
|
// process_cc_data_flag
|
|
|
|
bitstream_reader_read_bits(&reader, 1);
|
|
|
|
//additional_data_flag
|
|
|
|
bitstream_reader_read_bits(&reader, 1);
|
|
|
|
|
|
|
|
auto cc_count = bitstream_reader_read_bits(&reader, 5);
|
|
|
|
|
|
|
|
auto *outData =
|
|
|
|
(uint8_t *)bzalloc(sizeof(uint8_t) * cc_count * 3);
|
|
|
|
memcpy(outData, anc + reader.pos, cc_count * 3);
|
|
|
|
|
|
|
|
currentCaptions.data = outData;
|
|
|
|
currentCaptions.timestamp = timestamp;
|
|
|
|
currentCaptions.packets = cc_count;
|
|
|
|
|
|
|
|
obs_source_output_cea708(
|
|
|
|
static_cast<DeckLinkInput *>(decklink)->GetSource(),
|
|
|
|
¤tCaptions);
|
|
|
|
bfree(outData);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2020-12-22 17:32:50 +00:00
|
|
|
if (convertFrame) {
|
|
|
|
delete convertFrame;
|
|
|
|
}
|
|
|
|
convertFrame = new OBSVideoFrame(mode_->GetWidth(), mode_->GetHeight());
|
|
|
|
|
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",
|
2020-10-01 20:15:25 +00:00
|
|
|
activeColorSpace == VIDEO_CS_601 ? "BT.601" : "BT.709",
|
2019-09-22 21:19:10 +00:00
|
|
|
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;
|
2020-12-22 17:32:50 +00:00
|
|
|
pixelFormat = bmdFormat10BitYUV;
|
2018-02-19 19:54:37 +00:00
|
|
|
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
|
|
|
|
|
|
|
if (events & bmdVideoInputColorspaceChanged) {
|
|
|
|
switch (detectedSignalFlags) {
|
|
|
|
case bmdDetectedVideoInputRGB444:
|
|
|
|
pixelFormat = bmdFormat8BitBGRA;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
case bmdDetectedVideoInputYCbCr422:
|
2020-12-22 17:32:50 +00:00
|
|
|
pixelFormat = bmdFormat10BitYUV;
|
2018-02-19 19:54:37 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-22 17:32:50 +00:00
|
|
|
if (events & bmdVideoInputDisplayModeChanged) {
|
|
|
|
input->PauseStreams();
|
|
|
|
mode->SetMode(newMode);
|
|
|
|
displayMode = mode->GetDisplayMode();
|
2018-02-19 19:54:37 +00:00
|
|
|
|
2020-12-22 17:32:50 +00:00
|
|
|
const HRESULT videoResult = input->EnableVideoInput(
|
|
|
|
displayMode, pixelFormat,
|
|
|
|
bmdVideoInputEnableFormatDetection);
|
|
|
|
if (videoResult != S_OK) {
|
|
|
|
LOG(LOG_ERROR, "Failed to enable video input");
|
|
|
|
input->StopStreams();
|
|
|
|
FinalizeStream();
|
2016-02-23 23:16:51 +00:00
|
|
|
|
2020-12-22 17:32:50 +00:00
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
SetupVideoFormat(mode);
|
|
|
|
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;
|
|
|
|
}
|