*/
#include "../StdAfx.h"
-
+
#include "decklink_consumer.h"
#include "../util/util.h"
+#include "../decklink.h"
#include "../decklink_api.h"
#include <core/frame/frame.h>
+#include <core/frame/audio_channel_layout.h>
#include <core/mixer/audio/audio_mixer.h>
#include <core/consumer/frame_consumer.h>
#include <core/diagnostics/call_context.h>
#include <common/diagnostics/graph.h>
#include <common/except.h>
#include <common/memshfl.h>
+#include <common/memcpy.h>
+#include <common/no_init_proxy.h>
#include <common/array.h>
#include <common/future.h>
#include <common/cache_aligned_vector.h>
#include <common/timer.h>
#include <common/param.h>
+#include <common/software_version.h>
#include <tbb/concurrent_queue.h>
#include <boost/lexical_cast.hpp>
#include <boost/circular_buffer.hpp>
#include <boost/property_tree/ptree.hpp>
+#include <boost/thread/mutex.hpp>
+
+#include <future>
+
+namespace caspar { namespace decklink {
-namespace caspar { namespace decklink {
-
struct configuration
{
enum class keyer_t
internal_keyer,
external_keyer,
external_separate_device_keyer,
- default_keyer
+ default_keyer = external_keyer
};
enum class latency_t
{
low_latency,
normal_latency,
- default_latency
+ default_latency = normal_latency
};
- int device_index = 1;
- int key_device_idx = 0;
- bool embedded_audio = true;
- keyer_t keyer = keyer_t::default_keyer;
- latency_t latency = latency_t::default_latency;
- bool key_only = false;
- int base_buffer_depth = 3;
-
+ int device_index = 1;
+ int key_device_idx = 0;
+ bool embedded_audio = false;
+ keyer_t keyer = keyer_t::default_keyer;
+ latency_t latency = latency_t::default_latency;
+ bool key_only = false;
+ int base_buffer_depth = 3;
+ core::audio_channel_layout out_channel_layout = core::audio_channel_layout::invalid();
+
int buffer_depth() const
{
- return base_buffer_depth + (latency == latency_t::low_latency ? 0 : 1) + (embedded_audio ? 1 : 0);
+ return base_buffer_depth + (latency == latency_t::low_latency ? 0 : 1);
}
int key_device_index() const
{
return key_device_idx == 0 ? device_index + 1 : key_device_idx;
}
+
+ core::audio_channel_layout get_adjusted_layout(const core::audio_channel_layout& in_layout) const
+ {
+ auto adjusted = out_channel_layout == core::audio_channel_layout::invalid() ? in_layout : out_channel_layout;
+
+ if (adjusted.num_channels == 1) // Duplicate mono-signal into both left and right.
+ {
+ adjusted.num_channels = 2;
+ adjusted.channel_order.push_back(adjusted.channel_order.at(0)); // Usually FC -> FC FC
+ }
+ else if (adjusted.num_channels == 2)
+ {
+ adjusted.num_channels = 2;
+ }
+ else if (adjusted.num_channels <= 8)
+ {
+ adjusted.num_channels = 8;
+ }
+ else // Over 8 always pad to 16 or drop >16
+ {
+ adjusted.num_channels = 16;
+ }
+
+ return adjusted;
+ }
};
+template <typename Configuration>
void set_latency(
- const com_iface_ptr<IDeckLinkConfiguration>& config,
+ const com_iface_ptr<Configuration>& config,
configuration::latency_t latency,
const std::wstring& print)
{
configuration::keyer_t keyer,
const std::wstring& print)
{
- if (keyer == configuration::keyer_t::internal_keyer
- || keyer == configuration::keyer_t::external_separate_device_keyer)
+ if (keyer == configuration::keyer_t::internal_keyer)
{
BOOL value = true;
if (SUCCEEDED(attributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &value)) && !value)
- CASPAR_LOG(error) << print << L" Failed to enable internal keyer.";
+ CASPAR_LOG(error) << print << L" Failed to enable internal keyer.";
else if (FAILED(decklink_keyer->Enable(FALSE)))
- CASPAR_LOG(error) << print << L" Failed to enable internal keyer.";
+ CASPAR_LOG(error) << print << L" Failed to enable internal keyer.";
else if (FAILED(decklink_keyer->SetLevel(255)))
CASPAR_LOG(error) << print << L" Failed to set key-level to max.";
else
- CASPAR_LOG(info) << print << L" Enabled internal keyer.";
+ CASPAR_LOG(info) << print << L" Enabled internal keyer.";
}
else if (keyer == configuration::keyer_t::external_keyer)
{
BOOL value = true;
if (SUCCEEDED(attributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &value)) && !value)
- CASPAR_LOG(error) << print << L" Failed to enable external keyer.";
- else if (FAILED(decklink_keyer->Enable(TRUE)))
- CASPAR_LOG(error) << print << L" Failed to enable external keyer.";
+ CASPAR_LOG(error) << print << L" Failed to enable external keyer.";
+ else if (FAILED(decklink_keyer->Enable(TRUE)))
+ CASPAR_LOG(error) << print << L" Failed to enable external keyer.";
else if (FAILED(decklink_keyer->SetLevel(255)))
CASPAR_LOG(error) << print << L" Failed to set key-level to max.";
else
- CASPAR_LOG(info) << print << L" Enabled external keyer.";
+ CASPAR_LOG(info) << print << L" Enabled external keyer.";
}
}
class decklink_frame : public IDeckLinkVideoFrame
{
- tbb::atomic<int> ref_count_;
- core::const_frame frame_;
- const core::video_format_desc format_desc_;
+ tbb::atomic<int> ref_count_;
+ core::const_frame frame_;
+ const core::video_format_desc format_desc_;
- const bool key_only_;
- cache_aligned_vector<uint8_t> data_;
+ const bool key_only_;
+ bool needs_to_copy_;
+ cache_aligned_vector<no_init_proxy<uint8_t>> data_;
public:
- decklink_frame(core::const_frame frame, const core::video_format_desc& format_desc, bool key_only)
+ decklink_frame(core::const_frame frame, const core::video_format_desc& format_desc, bool key_only, bool will_attempt_dma)
: frame_(frame)
, format_desc_(format_desc)
, key_only_(key_only)
{
ref_count_ = 0;
+
+ bool dma_transfer_from_gl_buffer_impossible;
+
+#if !defined(_MSC_VER)
+ // On Linux Decklink cannot DMA transfer from memory returned by glMapBuffer (at least on nvidia)
+ dma_transfer_from_gl_buffer_impossible = true;
+#else
+ // On Windows it is possible.
+ dma_transfer_from_gl_buffer_impossible = false;
+#endif
+
+ needs_to_copy_ = will_attempt_dma && dma_transfer_from_gl_buffer_impossible;
}
-
+
// IUnknown
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)
{
return E_NOINTERFACE;
}
-
+
virtual ULONG STDMETHODCALLTYPE AddRef()
{
return ++ref_count_;
virtual ULONG STDMETHODCALLTYPE Release()
{
if(--ref_count_ == 0)
+ {
delete this;
+
+ return 0;
+ }
+
return ref_count_;
}
virtual long STDMETHODCALLTYPE GetRowBytes() {return static_cast<long>(format_desc_.width*4);}
virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat() {return bmdFormat8BitBGRA;}
virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags() {return bmdFrameFlagDefault;}
-
+
virtual HRESULT STDMETHODCALLTYPE GetBytes(void** buffer)
{
try
{
if(static_cast<int>(frame_.image_data().size()) != format_desc_.size)
{
- data_.resize(format_desc_.size, 0);
+ data_.resize(format_desc_.size);
*buffer = data_.data();
}
else if(key_only_)
*buffer = data_.data();
}
else
+ {
*buffer = const_cast<uint8_t*>(frame_.image_data().begin());
+
+ if (needs_to_copy_)
+ {
+ data_.resize(frame_.image_data().size());
+ fast_memcpy(data_.data(), *buffer, frame_.image_data().size());
+ *buffer = data_.data();
+ }
+ }
}
catch(...)
{
return S_OK;
}
-
+
virtual HRESULT STDMETHODCALLTYPE GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode) {return S_FALSE;}
virtual HRESULT STDMETHODCALLTYPE GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary) {return S_FALSE;}
- // decklink_frame
+ // decklink_frame
const core::audio_buffer& audio_data()
{
}
};
+template <typename Configuration>
struct key_video_context : public IDeckLinkVideoOutputCallback, boost::noncopyable
{
- const configuration config_;
- com_ptr<IDeckLink> decklink_ = get_device(config_.key_device_index());
- com_iface_ptr<IDeckLinkOutput> output_ = iface_cast<IDeckLinkOutput>(decklink_);
- com_iface_ptr<IDeckLinkKeyer> keyer_ = iface_cast<IDeckLinkKeyer>(decklink_);
- com_iface_ptr<IDeckLinkAttributes> attributes_ = iface_cast<IDeckLinkAttributes>(decklink_);
- com_iface_ptr<IDeckLinkConfiguration> configuration_ = iface_cast<IDeckLinkConfiguration>(decklink_);
- tbb::atomic<int64_t> current_presentation_delay_;
- tbb::atomic<int64_t> scheduled_frames_completed_;
+ const configuration config_;
+ com_ptr<IDeckLink> decklink_ = get_device(config_.key_device_index());
+ com_iface_ptr<IDeckLinkOutput> output_ = iface_cast<IDeckLinkOutput>(decklink_);
+ com_iface_ptr<IDeckLinkKeyer> keyer_ = iface_cast<IDeckLinkKeyer>(decklink_, true);
+ com_iface_ptr<IDeckLinkAttributes> attributes_ = iface_cast<IDeckLinkAttributes>(decklink_);
+ com_iface_ptr<Configuration> configuration_ = iface_cast<Configuration>(decklink_);
+ tbb::atomic<int64_t> current_presentation_delay_;
+ tbb::atomic<int64_t> scheduled_frames_completed_;
key_video_context(const configuration& config, const std::wstring& print)
: config_(config)
- , output_(decklink_)
- , keyer_(decklink_)
- , attributes_(decklink_)
- , configuration_(decklink_)
{
current_presentation_delay_ = 0;
scheduled_frames_completed_ = 0;
if (FAILED(output_->SetScheduledFrameCompletionCallback(this)))
CASPAR_THROW_EXCEPTION(caspar_exception()
- << msg_info(u8(print) + " Failed to set key playback completion callback.")
+ << msg_info(print + L" Failed to set key playback completion callback.")
<< boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
}
void enable_video(BMDDisplayMode display_mode, const Print& print)
{
if (FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable key video output."));
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Could not enable key video output."));
if (FAILED(output_->SetScheduledFrameCompletionCallback(this)))
CASPAR_THROW_EXCEPTION(caspar_exception()
- << msg_info(u8(print()) + " Failed to set key playback completion callback.")
+ << msg_info(print() + L" Failed to set key playback completion callback.")
<< boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
}
}
}
- STDMETHOD(QueryInterface(REFIID, LPVOID*)) { return E_NOINTERFACE; }
- STDMETHOD_(ULONG, AddRef()) { return 1; }
- STDMETHOD_(ULONG, Release()) { return 1; }
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*) {return E_NOINTERFACE;}
+ virtual ULONG STDMETHODCALLTYPE AddRef() {return 1;}
+ virtual ULONG STDMETHODCALLTYPE Release() {return 1;}
- STDMETHOD(ScheduledPlaybackHasStopped())
+ virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped()
{
return S_OK;
}
- STDMETHOD(ScheduledFrameCompleted(
+ virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted(
IDeckLinkVideoFrame* completed_frame,
- BMDOutputFrameCompletionResult result))
+ BMDOutputFrameCompletionResult result)
{
auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);
current_presentation_delay_ = dframe->get_age_millis();
}
};
-struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
-{
+template <typename Configuration>
+struct decklink_consumer : public IDeckLinkVideoOutputCallback, boost::noncopyable
+{
const int channel_index_;
const configuration config_;
com_ptr<IDeckLink> decklink_ = get_device(config_.device_index);
com_iface_ptr<IDeckLinkOutput> output_ = iface_cast<IDeckLinkOutput>(decklink_);
- com_iface_ptr<IDeckLinkConfiguration> configuration_ = iface_cast<IDeckLinkConfiguration>(decklink_);
- com_iface_ptr<IDeckLinkKeyer> keyer_ = iface_cast<IDeckLinkKeyer>(decklink_);
+ com_iface_ptr<Configuration> configuration_ = iface_cast<Configuration>(decklink_);
+ com_iface_ptr<IDeckLinkKeyer> keyer_ = iface_cast<IDeckLinkKeyer>(decklink_, true);
com_iface_ptr<IDeckLinkAttributes> attributes_ = iface_cast<IDeckLinkAttributes>(decklink_);
tbb::spin_mutex exception_mutex_;
std::exception_ptr exception_;
tbb::atomic<bool> is_running_;
-
+
const std::wstring model_name_ = get_model_name(decklink_);
+ bool will_attempt_dma_;
const core::video_format_desc format_desc_;
+ const core::audio_channel_layout in_channel_layout_;
+ const core::audio_channel_layout out_channel_layout_ = config_.get_adjusted_layout(in_channel_layout_);
+ core::audio_channel_remapper channel_remapper_ { in_channel_layout_, out_channel_layout_ };
const int buffer_size_ = config_.buffer_depth(); // Minimum buffer-size 3.
long long video_scheduled_ = 0;
long long audio_scheduled_ = 0;
int preroll_count_ = 0;
-
+
boost::circular_buffer<std::vector<int32_t>> audio_container_ { buffer_size_ + 1 };
- tbb::concurrent_bounded_queue<core::const_frame> video_frame_buffer_;
- tbb::concurrent_bounded_queue<core::const_frame> audio_frame_buffer_;
-
+ tbb::concurrent_bounded_queue<core::const_frame> frame_buffer_;
+
spl::shared_ptr<diagnostics::graph> graph_;
- tbb::atomic<int64_t> current_presentation_delay_;
caspar::timer tick_timer_;
- retry_task<bool> send_completion_;
+ boost::mutex send_completion_mutex_;
+ std::packaged_task<bool ()> send_completion_;
+ reference_signal_detector reference_signal_detector_ { output_ };
+ tbb::atomic<int64_t> current_presentation_delay_;
tbb::atomic<int64_t> scheduled_frames_completed_;
- std::unique_ptr<key_video_context> key_context_;
+ std::unique_ptr<key_video_context<Configuration>> key_context_;
public:
- decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index)
+ decklink_consumer(
+ const configuration& config,
+ const core::video_format_desc& format_desc,
+ const core::audio_channel_layout& in_channel_layout,
+ int channel_index)
: channel_index_(channel_index)
, config_(config)
, format_desc_(format_desc)
+ , in_channel_layout_(in_channel_layout)
{
is_running_ = true;
current_presentation_delay_ = 0;
scheduled_frames_completed_ = 0;
-
- video_frame_buffer_.set_capacity(1);
- // Blackmagic calls RenderAudioSamples() 50 times per second
- // regardless of video mode so we sometimes need to give them
- // samples from 2 frames in order to keep up
- audio_frame_buffer_.set_capacity((format_desc.fps > 50.0) ? 2 : 1);
+ frame_buffer_.set_capacity(1);
if (config.keyer == configuration::keyer_t::external_separate_device_keyer)
- key_context_.reset(new key_video_context(config, print()));
+ key_context_.reset(new key_video_context<Configuration>(config, print()));
- graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
+ graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
graph_->set_text(print());
diagnostics::register_graph(graph_);
-
- enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
-
+
+ enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault, will_attempt_dma_));
+
if(config.embedded_audio)
enable_audio();
-
- set_latency(configuration_, config.latency, print());
+
+ set_latency(configuration_, config.latency, print());
set_keyer(attributes_, keyer_, config.keyer, print());
- if(config.embedded_audio)
- output_->BeginAudioPreroll();
-
- for(int n = 0; n < buffer_size_; ++n)
+ if(config.embedded_audio)
+ output_->BeginAudioPreroll();
+
+ for (int n = 0; n < buffer_size_; ++n)
+ {
+ if (config.embedded_audio)
+ schedule_next_audio(core::mutable_audio_buffer(format_desc_.audio_cadence[n % format_desc_.audio_cadence.size()] * out_channel_layout_.num_channels, 0));
+
schedule_next_video(core::const_frame::empty());
+ }
- if(!config.embedded_audio)
- start_playback();
+ if (config.embedded_audio)
+ {
+ // Preroll one extra frame worth of audio
+ schedule_next_audio(core::mutable_audio_buffer(format_desc_.audio_cadence[buffer_size_ % format_desc_.audio_cadence.size()] * out_channel_layout_.num_channels, 0));
+ output_->EndAudioPreroll();
+ }
+
+ start_playback();
}
~decklink_consumer()
- {
+ {
is_running_ = false;
- video_frame_buffer_.try_push(core::const_frame::empty());
- audio_frame_buffer_.try_push(core::const_frame::empty());
+ frame_buffer_.try_push(core::const_frame::empty());
- if(output_ != nullptr)
+ if(output_ != nullptr)
{
output_->StopScheduledPlayback(0, nullptr, 0);
if(config_.embedded_audio)
output_->DisableVideoOutput();
}
}
-
+
void enable_audio()
{
- if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable audio output."));
-
- if(FAILED(output_->SetAudioCallback(this)))
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not set audio callback."));
+ if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, out_channel_layout_.num_channels, bmdAudioOutputStreamTimestamped)))
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Could not enable audio output."));
CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
}
void enable_video(BMDDisplayMode display_mode)
{
- if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable fill video output."));
-
+ if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Could not enable fill video output."));
+
if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
- CASPAR_THROW_EXCEPTION(caspar_exception()
- << msg_info(u8(print()) + " Failed to set fill playback completion callback.")
+ CASPAR_THROW_EXCEPTION(caspar_exception()
+ << msg_info(print() + L" Failed to set fill playback completion callback.")
<< boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
if (key_context_)
void start_playback()
{
- if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Failed to schedule fill playback."));
+ if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to schedule fill playback."));
if (key_context_ && FAILED(key_context_->output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Failed to schedule key playback."));
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to schedule key playback."));
}
-
+
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*) {return E_NOINTERFACE;}
virtual ULONG STDMETHODCALLTYPE AddRef() {return 1;}
virtual ULONG STDMETHODCALLTYPE Release() {return 1;}
-
+
virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped()
{
is_running_ = false;
{
if(!is_running_)
return E_FAIL;
-
+
try
{
+ auto tick_time = tick_timer_.elapsed()*format_desc_.fps * 0.5;
+ graph_->set_value("tick-time", tick_time);
+ tick_timer_.restart();
+
+ reference_signal_detector_.detect_change([this]() { return print(); });
+
auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);
current_presentation_delay_ = dframe->get_age_millis();
++scheduled_frames_completed_;
if(result == bmdOutputFrameDisplayedLate)
{
- graph_->set_tag("late-frame");
+ graph_->set_tag(diagnostics::tag_severity::WARNING, "late-frame");
video_scheduled_ += format_desc_.duration;
- audio_scheduled_ += dframe->audio_data().size() / format_desc_.audio_channels;
- //++video_scheduled_;
- //audio_scheduled_ += format_desc_.audio_cadence[0];
- //++audio_scheduled_;
+ audio_scheduled_ += dframe->audio_data().size() / in_channel_layout_.num_channels;
}
else if(result == bmdOutputFrameDropped)
- graph_->set_tag("dropped-frame");
+ graph_->set_tag(diagnostics::tag_severity::WARNING, "dropped-frame");
else if(result == bmdOutputFrameFlushed)
- graph_->set_tag("flushed-frame");
+ graph_->set_tag(diagnostics::tag_severity::WARNING, "flushed-frame");
- auto frame = core::const_frame::empty();
- video_frame_buffer_.pop(frame);
- send_completion_.try_completion();
- schedule_next_video(frame);
-
UINT32 buffered;
output_->GetBufferedVideoFrameCount(&buffered);
- graph_->set_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);
- }
- catch(...)
- {
- lock(exception_mutex_, [&]
- {
- exception_ = std::current_exception();
- });
- return E_FAIL;
- }
+ graph_->set_value("buffered-video", static_cast<double>(buffered) / (config_.buffer_depth()));
- return S_OK;
- }
-
- virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples(BOOL preroll)
- {
- if(!is_running_)
- return E_FAIL;
-
- try
- {
- if(preroll)
+ if (config_.embedded_audio)
{
- if(++preroll_count_ >= buffer_size_)
- {
- output_->EndAudioPreroll();
- start_playback();
- }
- else
- {
- schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()] * format_desc_.audio_channels, 0));
- }
+ output_->GetBufferedAudioSampleFrameCount(&buffered);
+ graph_->set_value("buffered-audio", static_cast<double>(buffered) / (format_desc_.audio_cadence[0] * config_.buffer_depth()));
}
- else
+
+ auto frame = core::const_frame::empty();
+
+ frame_buffer_.pop(frame);
+
{
- auto frame = core::const_frame::empty();
+ boost::lock_guard<boost::mutex> lock(send_completion_mutex_);
- while(audio_frame_buffer_.try_pop(frame))
+ if (send_completion_.valid())
{
- send_completion_.try_completion();
- schedule_next_audio(frame.audio_data());
+ send_completion_();
+ send_completion_ = std::packaged_task<bool()>();
}
}
- UINT32 buffered;
- output_->GetBufferedAudioSampleFrameCount(&buffered);
- graph_->set_value("buffered-audio", static_cast<double>(buffered) / (format_desc_.audio_cadence[0] * format_desc_.audio_channels * 2));
+ if (config_.embedded_audio)
+ schedule_next_audio(channel_remapper_.mix_and_rearrange(frame.audio_data()));
+
+ schedule_next_video(frame);
}
catch(...)
{
- tbb::spin_mutex::scoped_lock lock(exception_mutex_);
- exception_ = std::current_exception();
+ lock(exception_mutex_, [&]
+ {
+ exception_ = std::current_exception();
+ });
return E_FAIL;
}
template<typename T>
void schedule_next_audio(const T& audio_data)
{
- auto sample_frame_count = static_cast<int>(audio_data.size()/format_desc_.audio_channels);
+ auto sample_frame_count = static_cast<int>(audio_data.size()/out_channel_layout_.num_channels);
audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));
audio_scheduled_ += sample_frame_count;
}
-
+
void schedule_next_video(core::const_frame frame)
{
if (key_context_)
{
- auto key_frame = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, true));
- if (FAILED(key_context_->output_->ScheduleVideoFrame(key_frame, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
+ auto key_frame = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, true, will_attempt_dma_));
+ if (FAILED(key_context_->output_->ScheduleVideoFrame(get_raw(key_frame), video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
CASPAR_LOG(error) << print() << L" Failed to schedule key video.";
}
- auto fill_frame = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, config_.key_only));
+ auto fill_frame = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, config_.key_only, will_attempt_dma_));
if (FAILED(output_->ScheduleVideoFrame(get_raw(fill_frame), video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
CASPAR_LOG(error) << print() << L" Failed to schedule fill video.";
video_scheduled_ += format_desc_.duration;
-
- graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
- tick_timer_.restart();
}
std::future<bool> send(core::const_frame frame)
});
if(exception != nullptr)
- std::rethrow_exception(exception);
+ std::rethrow_exception(exception);
if(!is_running_)
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Is not running."));
-
- bool audio_ready = !config_.embedded_audio;
- bool video_ready = false;
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Is not running."));
- auto enqueue_task = [audio_ready, video_ready, frame, this]() mutable -> boost::optional<bool>
- {
- if (!audio_ready)
- audio_ready = audio_frame_buffer_.try_push(frame);
+ if (frame_buffer_.try_push(frame))
+ return make_ready_future(true);
- if (!video_ready)
- video_ready = video_frame_buffer_.try_push(frame);
+ boost::lock_guard<boost::mutex> lock(send_completion_mutex_);
- if (audio_ready && video_ready)
- return true;
- else
- return boost::optional<bool>();
- };
-
- if (enqueue_task())
- return make_ready_future(true);
+ send_completion_ = std::packaged_task<bool ()>([frame, this] () mutable -> bool
+ {
+ frame_buffer_.push(frame);
- send_completion_.set_task(enqueue_task);
+ return true;
+ });
return send_completion_.get_future();
}
-
+
std::wstring print() const
{
if (config_.keyer == configuration::keyer_t::external_separate_device_keyer)
}
};
+template <typename Configuration>
struct decklink_consumer_proxy : public core::frame_consumer
{
- core::monitor::subject monitor_subject_;
- const configuration config_;
- std::unique_ptr<decklink_consumer> consumer_;
- core::video_format_desc format_desc_;
- executor executor_;
+ core::monitor::subject monitor_subject_;
+ const configuration config_;
+ std::unique_ptr<decklink_consumer<Configuration>> consumer_;
+ core::video_format_desc format_desc_;
+ executor executor_;
public:
decklink_consumer_proxy(const configuration& config)
}
// frame_consumer
-
- void initialize(const core::video_format_desc& format_desc, int channel_index) override
+
+ void initialize(const core::video_format_desc& format_desc, const core::audio_channel_layout& channel_layout, int channel_index) override
{
format_desc_ = format_desc;
executor_.invoke([=]
{
consumer_.reset();
- consumer_.reset(new decklink_consumer(config_, format_desc, channel_index));
+ consumer_.reset(new decklink_consumer<Configuration>(config_, format_desc, channel_layout, channel_index));
});
}
-
+
std::future<bool> send(core::const_frame frame) override
{
return consumer_->send(frame);
}
-
+
std::wstring print() const override
{
return consumer_ ? consumer_->print() : L"[decklink_consumer]";
- }
+ }
std::wstring name() const override
{
{
return monitor_subject_;
}
-};
+};
+
+const software_version<3>& get_driver_version()
+{
+ static software_version<3> version(u8(get_version()));
+
+ return version;
+}
+
+const software_version<3> get_new_configuration_api_version()
+{
+ static software_version<3> NEW_CONFIGURATION_API("10.2");
+
+ return NEW_CONFIGURATION_API;
+}
void describe_consumer(core::help_sink& sink, const core::help_repository& repo)
{
L"{[keyer:INTERNAL_KEY,EXTERNAL_KEY,EXTERNAL_SEPARATE_DEVICE_KEY]} "
L"{[low_latency:LOW_LATENCY]} "
L"{[embedded_audio:EMBEDDED_AUDIO]} "
- L"{[key_only:KEY_ONLY]}");
+ L"{[key_only:KEY_ONLY]} "
+ L"{CHANNEL_LAYOUT [channel_layout:string]}");
sink.para()->text(L"Sends video on an SDI output using Blackmagic Decklink video cards.");
sink.definitions()
->item(L"device_index", L"The Blackmagic video card to use (See Blackmagic control panel for card order). Default is 1.")
->item(L"key_only",
L" will extract only the alpha channel from the "
L"channel. This is useful when you have two SDI video cards, and neither has native support "
- L"for separate fill/key output");
+ L"for separate fill/key output")
+ ->item(L"channel_layout", L"If specified, overrides the audio channel layout used by the channel.");
sink.para()->text(L"Examples:");
sink.example(L">> ADD 1 DECKLINK", L"for using the default device_index of 1.");
sink.example(L">> ADD 1 DECKLINK 2", L"uses device_index 2.");
}
spl::shared_ptr<core::frame_consumer> create_consumer(
- const std::vector<std::wstring>& params, core::interaction_sink*)
+ const std::vector<std::wstring>& params, core::interaction_sink*, std::vector<spl::shared_ptr<core::video_channel>> channels)
{
if (params.size() < 1 || !boost::iequals(params.at(0), L"DECKLINK"))
return core::frame_consumer::empty();
-
+
configuration config;
-
+
if (params.size() > 1)
config.device_index = boost::lexical_cast<int>(params.at(1));
-
+
if (contains_param(L"INTERNAL_KEY", params))
config.keyer = configuration::keyer_t::internal_keyer;
else if (contains_param(L"EXTERNAL_KEY", params))
config.embedded_audio = contains_param(L"EMBEDDED_AUDIO", params);
config.key_only = contains_param(L"KEY_ONLY", params);
- return spl::make_shared<decklink_consumer_proxy>(config);
+ auto channel_layout = get_param(L"CHANNEL_LAYOUT", params);
+
+ if (!channel_layout.empty())
+ {
+ auto found_layout = core::audio_channel_layout_repository::get_default()->get_layout(channel_layout);
+
+ if (!found_layout)
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Channel layout " + channel_layout + L" not found."));
+
+ config.out_channel_layout = *found_layout;
+ }
+
+ bool old_configuration_api = get_driver_version() < get_new_configuration_api_version();
+
+ if (old_configuration_api)
+ return spl::make_shared<decklink_consumer_proxy<IDeckLinkConfiguration_v10_2>>(config);
+ else
+ return spl::make_shared<decklink_consumer_proxy<IDeckLinkConfiguration>>(config);
}
spl::shared_ptr<core::frame_consumer> create_preconfigured_consumer(
- const boost::property_tree::wptree& ptree, core::interaction_sink*)
+ const boost::property_tree::wptree& ptree, core::interaction_sink*, std::vector<spl::shared_ptr<core::video_channel>> channels)
{
configuration config;
else if (keyer == L"external_separate_device")
config.keyer = configuration::keyer_t::external_separate_device_keyer;
- auto latency = ptree.get(L"latency", L"normal");
+ auto latency = ptree.get(L"latency", L"default");
if(latency == L"low")
config.latency = configuration::latency_t::low_latency;
else if(latency == L"normal")
config.latency = configuration::latency_t::normal_latency;
+ auto channel_layout = ptree.get_optional<std::wstring>(L"channel-layout");
+
+ if (channel_layout)
+ {
+ CASPAR_SCOPED_CONTEXT_MSG("/channel-layout")
+
+ auto found_layout = core::audio_channel_layout_repository::get_default()->get_layout(*channel_layout);
+
+ if (!found_layout)
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Channel layout " + *channel_layout + L" not found."));
+
+ config.out_channel_layout = *found_layout;
+ }
+
config.key_only = ptree.get(L"key-only", config.key_only);
config.device_index = ptree.get(L"device", config.device_index);
config.key_device_idx = ptree.get(L"key-device", config.key_device_idx);
config.embedded_audio = ptree.get(L"embedded-audio", config.embedded_audio);
config.base_buffer_depth = ptree.get(L"buffer-depth", config.base_buffer_depth);
- return spl::make_shared<decklink_consumer_proxy>(config);
+ bool old_configuration_api = get_driver_version() < get_new_configuration_api_version();
+
+ if (old_configuration_api)
+ return spl::make_shared<decklink_consumer_proxy<IDeckLinkConfiguration_v10_2>>(config);
+ else
+ return spl::make_shared<decklink_consumer_proxy<IDeckLinkConfiguration>>(config);
}
}}
-----------------------------------------------------------------------------
-Thanks for your inquiry. The minimum number of frames that you can preroll
-for scheduled playback is three frames for video and four frames for audio.
+Thanks for your inquiry. The minimum number of frames that you can preroll
+for scheduled playback is three frames for video and four frames for audio.
As you mentioned if you preroll less frames then playback will not start or
-playback will be very sporadic. From our experience with Media Express, we
-recommended that at least seven frames are prerolled for smooth playback.
+playback will be very sporadic. From our experience with Media Express, we
+recommended that at least seven frames are prerolled for smooth playback.
Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
There can be around 3 frames worth of latency on scheduled output.
When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
-reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
-method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
-guarantee that the provided frame will be output as soon the previous
+reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
+method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
+guarantee that the provided frame will be output as soon the previous
frame output has been completed.
################################################################################
*/
-----------------------------------------------------------------------------
-Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
-and providing a pointer to your video buffer when GetBytes() is called.
-This may help to keep copying to a minimum. Please ensure that the pixel
-format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
+Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
+and providing a pointer to your video buffer when GetBytes() is called.
+This may help to keep copying to a minimum. Please ensure that the pixel
+format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
have to colourspace convert which may result in additional copying.
################################################################################
*/