Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/modules/decklink/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ set(SOURCES
consumer/config.h
consumer/monitor.cpp
consumer/monitor.h
consumer/vanc.cpp
consumer/vanc.h
consumer/vanc_scte104_strategy.cpp
consumer/vanc_op47_strategy.cpp

producer/decklink_producer.cpp
producer/decklink_producer.h

util/util.h
util/base64.hpp

decklink.cpp
decklink.h
Expand Down
20 changes: 20 additions & 0 deletions src/modules/decklink/consumer/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ port_configuration parse_output_config(const boost::property_tree::wptree& ptre
return port_config;
}

vanc_configuration parse_vanc_config(const boost::property_tree::wptree& vanc_tree)
{
vanc_configuration vanc_config;

vanc_config.enable = true;
vanc_config.op47_line = vanc_tree.get(L"op47-line", vanc_config.op47_line);
vanc_config.op47_line_field2 = vanc_tree.get(L"op47-line-field2", vanc_config.op47_line_field2);
vanc_config.enable_op47 = vanc_config.op47_line > 0;
vanc_config.scte104_line = vanc_tree.get(L"scte104-line", vanc_config.scte104_line);
vanc_config.enable_scte104 = vanc_config.scte104_line > 0;
vanc_config.op47_dummy_header = vanc_tree.get(L"op47-dummy-header", L"");

return vanc_config;
};

core::color_space get_color_space(const std::wstring& str)
{
auto color_space_str = boost::to_lower_copy(str);
Expand Down Expand Up @@ -144,6 +159,11 @@ configuration parse_xml_config(const boost::property_tree::wptree& ptree,
config.hdr_meta.max_cll = hdr_metadata->get(L"max-cll", config.hdr_meta.max_cll);
}

auto vanc = ptree.get_child_optional(L"vanc");
if (vanc) {
config.vanc = parse_vanc_config(vanc.get());
}

return config;
}

Expand Down
13 changes: 13 additions & 0 deletions src/modules/decklink/consumer/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ struct port_configuration
}
};

struct vanc_configuration
{
bool enable = false;
bool enable_op47 = false;
bool enable_scte104 = false;
uint8_t op47_line = 0;
uint8_t op47_line_field2 = 0;
uint8_t scte104_line = 0;
std::wstring op47_dummy_header;
};

struct hdr_meta_configuration
{
float min_dml = 0.005f;
Expand Down Expand Up @@ -102,6 +113,8 @@ struct configuration
core::color_space color_space = core::color_space::bt709;
hdr_meta_configuration hdr_meta;

vanc_configuration vanc;

[[nodiscard]] int buffer_depth() const
{
return base_buffer_depth + (latency == latency_t::low_latency ? 0 : 1) +
Expand Down
120 changes: 99 additions & 21 deletions src/modules/decklink/consumer/decklink_consumer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "decklink_consumer.h"
#include "frame.h"
#include "monitor.h"
#include "vanc.h"

#include "../util/util.h"

Expand Down Expand Up @@ -99,8 +100,13 @@ com_ptr<IDeckLinkDisplayMode> get_display_mode(const com_iface_ptr<IDeckLinkOutp
BOOL supported = false;

auto displayMode = mode->GetDisplayMode();
if (FAILED(device->DoesSupportVideoMode(
bmdVideoConnectionUnspecified, displayMode, pix_fmt, flag, &actualMode, &supported)))
if (FAILED(device->DoesSupportVideoMode(bmdVideoConnectionUnspecified,
displayMode,
pix_fmt,
bmdNoVideoOutputConversion,
flag,
&actualMode,
&supported)))
CASPAR_THROW_EXCEPTION(caspar_exception()
<< msg_info(L"Could not determine whether device supports requested video format: " +
get_mode_name(mode)));
Expand Down Expand Up @@ -218,21 +224,23 @@ class decklink_frame
: public IDeckLinkVideoFrame
, public IDeckLinkVideoFrameMetadataExtensions
{
core::video_format_desc format_desc_;
std::shared_ptr<void> data_;
std::atomic<int> ref_count_{0};
int nb_samples_;
const bool hdr_;
core::color_space color_space_;
hdr_meta_configuration hdr_metadata_;
BMDFrameFlags flags_;
BMDPixelFormat pix_fmt_;
core::video_format_desc format_desc_;
std::shared_ptr<void> data_;
std::atomic<int> ref_count_{0};
int nb_samples_;
const bool hdr_;
core::color_space color_space_;
hdr_meta_configuration hdr_metadata_;
BMDFrameFlags flags_;
BMDPixelFormat pix_fmt_;
com_ptr<IDeckLinkVideoFrameAncillaryPackets> vanc_;

public:
decklink_frame(std::shared_ptr<void> data,
core::video_format_desc format_desc,
int nb_samples,
bool hdr,
bool vanc,
core::color_space color_space,
const hdr_meta_configuration& hdr_metadata)
: format_desc_(std::move(format_desc))
Expand All @@ -243,6 +251,7 @@ class decklink_frame
, hdr_metadata_(hdr_metadata)
, flags_(hdr ? bmdFrameFlagDefault | bmdFrameContainsHDRMetadata : bmdFrameFlagDefault)
, pix_fmt_(get_pixel_format(hdr))
, vanc_(vanc ? create_ancillary_packets() : nullptr)
{
}

Expand All @@ -264,6 +273,11 @@ class decklink_frame
} else if (hdr_ && std::memcmp(&iid, &IID_IDeckLinkVideoFrameMetadataExtensions, sizeof(REFIID)) == 0) {
*ppv = static_cast<IDeckLinkVideoFrameMetadataExtensions*>(this);
AddRef();
} else if (vanc_ && std::memcmp(&iid, &IID_IDeckLinkVideoFrameAncillaryPackets, sizeof(REFIID)) == 0) {
auto raw = get_raw(vanc_);
raw->AddRef();
*ppv = raw;

} else {
*ppv = nullptr;
return E_NOINTERFACE;
Expand Down Expand Up @@ -406,13 +420,13 @@ class decklink_frame
return E_INVALIDARG;
}

/*
HRESULT STDMETHODCALLTYPE GetBytes(BMDDeckLinkFrameMetadataID metadataID, void* buffer, uint32_t* bufferSize)
HRESULT STDMETHODCALLTYPE GetBytes(BMDDeckLinkFrameMetadataID metadataID,
void* buffer,
uint32_t* bufferSize) override
{
*bufferSize = 0;
return E_INVALIDARG;
}
*/
};

struct decklink_secondary_port final : public IDeckLinkVideoOutputCallback
Expand Down Expand Up @@ -555,6 +569,7 @@ struct decklink_secondary_port final : public IDeckLinkVideoOutputCallback
decklink_format_desc_,
nb_samples,
config_.hdr,
false,
core::color_space::bt709,
config_.hdr_meta));
if (FAILED(output_->ScheduleVideoFrame(get_raw(packed_frame),
Expand Down Expand Up @@ -625,7 +640,8 @@ struct decklink_consumer final : public IDeckLinkVideoOutputCallback
bmdSupportedVideoModeDefault,
config_.hdr);

std::atomic<bool> abort_request_{false};
std::atomic<bool> abort_request_{false};
std::shared_ptr<decklink_vanc> vanc_;

public:
decklink_consumer(const configuration& config, core::video_format_desc channel_format_desc, int channel_index)
Expand Down Expand Up @@ -680,6 +696,19 @@ struct decklink_consumer final : public IDeckLinkVideoOutputCallback
device_sync_group_));
}

if (config.vanc.enable) {
BOOL flag = TRUE;
attributes_->GetFlag(BMDDeckLinkVANCRequires10BitYUVVideoFrames, &flag);
if (flag) {
CASPAR_LOG(warning) << print()
<< L" DeckLink hardware only supports VANC when the active picture and ancillary "
L"data are both 10-bit YUV pixel format.";
} else {
CASPAR_LOG(info) << print() << L" DeckLink hardware supports VANC.";
vanc_ = create_vanc(config.vanc);
}
}

enable_video();

if (config.embedded_audio) {
Expand Down Expand Up @@ -800,9 +829,15 @@ struct decklink_consumer final : public IDeckLinkVideoOutputCallback

void enable_video()
{
if (FAILED(output_->EnableVideoOutput(mode_->GetDisplayMode(),
device_sync_group_ > 0 ? bmdVideoOutputSynchronizeToPlaybackGroup
: bmdVideoOutputFlagDefault))) {
BMDVideoOutputFlags output_flags = bmdVideoOutputFlagDefault;
if (device_sync_group_ > 0) {
output_flags = static_cast<BMDVideoOutputFlags>(output_flags | bmdVideoOutputSynchronizeToPlaybackGroup);
}
if (vanc_) {
output_flags = static_cast<BMDVideoOutputFlags>(output_flags | bmdVideoOutputVANC);
}

if (FAILED(output_->EnableVideoOutput(mode_->GetDisplayMode(), output_flags))) {
CASPAR_THROW_EXCEPTION(caspar_exception()
<< msg_info(print() + L" Could not enable primary video output."));
}
Expand Down Expand Up @@ -998,8 +1033,34 @@ struct decklink_consumer final : public IDeckLinkVideoOutputCallback
BMDTimeValue display_time,
core::color_space color_space)
{
auto fill_frame = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(
std::move(image_data), decklink_format_desc_, nb_samples, config_.hdr, color_space, config_.hdr_meta));
auto fill_frame = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(std::move(image_data),
decklink_format_desc_,
nb_samples,
config_.hdr,
config_.vanc.enable,
color_space,
config_.hdr_meta));

if (vanc_ && vanc_->has_data()) {
auto ancillary_packets = iface_cast<IDeckLinkVideoFrameAncillaryPackets>(fill_frame);
auto packets = vanc_->pop_packets();
for (auto& packet : packets) {
if (FAILED(ancillary_packets->AttachPacket(get_raw(packet)))) {
CASPAR_LOG(error) << print() << L" Failed to add ancillary packet.";
}
}

bool isInterlaced = mode_->GetFieldDominance() != bmdProgressiveFrame;
if (isInterlaced) {
auto field2_packets = vanc_->pop_packets(true);
for (auto& packet : field2_packets) {
if (FAILED(ancillary_packets->AttachPacket(get_raw(packet)))) {
CASPAR_LOG(error) << print() << L" Failed to add ancillary packet.";
}
}
}
}

if (FAILED(output_->ScheduleVideoFrame(
get_raw(fill_frame), display_time, decklink_format_desc_.duration, decklink_format_desc_.time_scale))) {
CASPAR_LOG(error) << print() << L" Failed to schedule primary video.";
Expand Down Expand Up @@ -1028,6 +1089,21 @@ struct decklink_consumer final : public IDeckLinkVideoOutputCallback
return !abort_request_;
}

bool call(const std::vector<std::wstring>& params)
{
try {
bool result = vanc_->try_push_data(params);
if (!result) {
CASPAR_LOG(warning) << print() << L" Unknown command: " << (params.empty() ? L"N/A" : params[0]);
}

return result;
} catch (...) {
CASPAR_LOG(warning) << print() << L" Failed to apply: " << (params.empty() ? L"N/A" : params[0]);
}
return false;
}

[[nodiscard]] std::wstring print() const
{
std::wstringstream buffer;
Expand Down Expand Up @@ -1067,7 +1143,9 @@ struct decklink_consumer_proxy : public core::frame_consumer
});
}

void initialize(const core::video_format_desc& format_desc, const core::channel_info& channel_info, int port_index) override
void initialize(const core::video_format_desc& format_desc,
const core::channel_info& channel_info,
int port_index) override
{
format_desc_ = format_desc;
executor_.invoke([=] {
Expand Down
Loading