#include "decklink_consumer.h"
#include "../util/util.h"
+#include "../decklink.h"
#include "../decklink_api.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/circular_buffer.hpp>
#include <boost/property_tree/ptree.hpp>
+#include <future>
+
namespace caspar { namespace decklink {
struct configuration
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
}
};
+template <typename Configuration>
void set_latency(
- const com_iface_ptr<IDeckLinkConfiguration_v10_2>& config,
+ const com_iface_ptr<Configuration>& config,
configuration::latency_t latency,
const std::wstring& print)
{
}
};
+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_v10_2> configuration_ = iface_cast<IDeckLinkConfiguration_v10_2>(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)
}
};
-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_v10_2> configuration_ = iface_cast<IDeckLinkConfiguration_v10_2>(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_;
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_;
caspar::timer tick_timer_;
- retry_task<bool> send_completion_;
+ 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(
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("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
set_latency(configuration_, config.latency, print());
set_keyer(attributes_, keyer_, config.keyer, print());
- if(config.embedded_audio)
+ if(config.embedded_audio)
output_->BeginAudioPreroll();
- for(int n = 0; n < buffer_size_; ++n)
+ 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)
+ {
+ // 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();
+ }
- if(!config.embedded_audio)
- start_playback();
+ 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(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."));
- if(FAILED(output_->SetAudioCallback(this)))
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Could not set audio callback."));
-
CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
}
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_;
{
graph_->set_tag(diagnostics::tag_severity::WARNING, "late-frame");
video_scheduled_ += format_desc_.duration;
- audio_scheduled_ += dframe->audio_data().size() / out_channel_layout_.num_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(diagnostics::tag_severity::WARNING, "dropped-frame");
output_->GetBufferedVideoFrameCount(&buffered);
graph_->set_value("buffered-video", static_cast<double>(buffered) / (config_.buffer_depth()));
- auto frame = core::const_frame::empty();
- video_frame_buffer_.pop(frame);
- send_completion_.try_completion();
- schedule_next_video(frame);
- }
- catch(...)
- {
- lock(exception_mutex_, [&]
+ if (config_.embedded_audio)
{
- exception_ = std::current_exception();
- });
- return E_FAIL;
- }
+ output_->GetBufferedAudioSampleFrameCount(&buffered);
+ graph_->set_value("buffered-audio", static_cast<double>(buffered) / (format_desc_.audio_cadence[0] * config_.buffer_depth()));
+ }
- return S_OK;
- }
-
- virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples(BOOL preroll)
- {
- if(!is_running_)
- return E_FAIL;
-
- try
- {
- if(preroll)
+ auto frame = core::const_frame::empty();
+
+ frame_buffer_.pop(frame);
+
+ if (send_completion_.valid())
{
- if(++preroll_count_ >= buffer_size_)
- {
- output_->EndAudioPreroll();
- start_playback();
- }
- else
- {
- schedule_next_audio(core::mutable_audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()] * out_channel_layout_.num_channels, 0));
- }
+ send_completion_();
+ send_completion_.reset();
}
- else
- {
- auto frame = core::const_frame::empty();
- while(audio_frame_buffer_.try_pop(frame))
- {
- UINT32 buffered;
- output_->GetBufferedAudioSampleFrameCount(&buffered);
- graph_->set_value("buffered-audio", static_cast<double>(buffered) / (format_desc_.audio_cadence[0] * config_.buffer_depth()));
+ if (config_.embedded_audio)
+ schedule_next_audio(channel_remapper_.mix_and_rearrange(frame.audio_data()));
- send_completion_.try_completion();
- 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;
}
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();
-
- reference_signal_detector_.detect_change([this]() { return print(); });
}
std::future<bool> send(core::const_frame frame)
if(!is_running_)
CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Is not running."));
- bool audio_ready = !config_.embedded_audio;
- bool video_ready = false;
+ if (frame_buffer_.try_push(frame))
+ return make_ready_future(true);
- auto enqueue_task = [audio_ready, video_ready, frame, this]() mutable -> boost::optional<bool>
+ send_completion_ = std::packaged_task<bool ()>([frame, this] () mutable -> bool
{
- if (!audio_ready)
- audio_ready = audio_frame_buffer_.try_push(frame);
+ frame_buffer_.push(frame);
- if (!video_ready)
- video_ready = video_frame_buffer_.try_push(frame);
-
- if (audio_ready && video_ready)
- return true;
- else
- return boost::optional<bool>();
- };
-
- if (enqueue_task())
- return make_ready_future(true);
-
- send_completion_.set_task(enqueue_task);
+ return true;
+ });
return send_completion_.get_future();
}
}
};
+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)
executor_.invoke([=]
{
consumer_.reset();
- consumer_.reset(new decklink_consumer(config_, format_desc, channel_layout, channel_index));
+ consumer_.reset(new decklink_consumer<Configuration>(config_, format_desc, channel_layout, channel_index));
});
}
{
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)
{
config.out_channel_layout = *found_layout;
}
- 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);
}
spl::shared_ptr<core::frame_consumer> create_preconfigured_consumer(
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);
}
}}