X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fdecklink%2Fconsumer%2Fdecklink_consumer.cpp;h=ca847727eb8b6849834fdb989763757482749a1f;hb=1dd7f451acbf1cac0f44679990e304bcfe29f631;hp=30e3d321a9e25dec10934df88088c6ed2efd217d;hpb=113b52fbe4bb449a0838603d49edd974f5f31183;p=casparcg diff --git a/modules/decklink/consumer/decklink_consumer.cpp b/modules/decklink/consumer/decklink_consumer.cpp index 30e3d321a..ca847727e 100644 --- a/modules/decklink/consumer/decklink_consumer.cpp +++ b/modules/decklink/consumer/decklink_consumer.cpp @@ -20,10 +20,11 @@ */ #include "../StdAfx.h" - + #include "decklink_consumer.h" #include "../util/util.h" +#include "../decklink.h" #include "../decklink_api.h" @@ -40,11 +41,14 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include @@ -52,9 +56,12 @@ #include #include #include +#include + +#include + +namespace caspar { namespace decklink { -namespace caspar { namespace decklink { - struct configuration { enum class keyer_t @@ -62,28 +69,28 @@ struct configuration 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; + 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 @@ -117,8 +124,9 @@ struct configuration } }; +template void set_latency( - const com_iface_ptr& config, + const com_iface_ptr& config, configuration::latency_t latency, const std::wstring& print) { @@ -144,52 +152,65 @@ void set_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 ref_count_; - core::const_frame frame_; - const core::video_format_desc format_desc_; + tbb::atomic ref_count_; + core::const_frame frame_; + const core::video_format_desc format_desc_; - const bool key_only_; - cache_aligned_vector data_; + const bool key_only_; + bool needs_to_copy_; + cache_aligned_vector> 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_; @@ -198,7 +219,12 @@ public: virtual ULONG STDMETHODCALLTYPE Release() { if(--ref_count_ == 0) + { delete this; + + return 0; + } + return ref_count_; } @@ -209,14 +235,14 @@ public: virtual long STDMETHODCALLTYPE GetRowBytes() {return static_cast(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(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_) @@ -229,7 +255,16 @@ public: *buffer = data_.data(); } else + { *buffer = const_cast(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(...) { @@ -239,11 +274,11 @@ public: 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() { @@ -256,16 +291,17 @@ public: } }; +template struct key_video_context : public IDeckLinkVideoOutputCallback, boost::noncopyable { - const configuration config_; - com_ptr decklink_ = get_device(config_.key_device_index()); - com_iface_ptr output_ = iface_cast(decklink_); - com_iface_ptr keyer_ = iface_cast(decklink_); - com_iface_ptr attributes_ = iface_cast(decklink_); - com_iface_ptr configuration_ = iface_cast(decklink_); - tbb::atomic current_presentation_delay_; - tbb::atomic scheduled_frames_completed_; + const configuration config_; + com_ptr decklink_ = get_device(config_.key_device_index()); + com_iface_ptr output_ = iface_cast(decklink_); + com_iface_ptr keyer_ = iface_cast(decklink_, true); + com_iface_ptr attributes_ = iface_cast(decklink_); + com_iface_ptr configuration_ = iface_cast(decklink_); + tbb::atomic current_presentation_delay_; + tbb::atomic scheduled_frames_completed_; key_video_context(const configuration& config, const std::wstring& print) : config_(config) @@ -278,7 +314,7 @@ struct key_video_context : public IDeckLinkVideoOutputCallback, boost::noncopyab 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")); } @@ -286,11 +322,11 @@ struct key_video_context : public IDeckLinkVideoOutputCallback, boost::noncopyab 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")); } @@ -326,23 +362,25 @@ struct key_video_context : public IDeckLinkVideoOutputCallback, boost::noncopyab } }; -struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable -{ +template +struct decklink_consumer : public IDeckLinkVideoOutputCallback, boost::noncopyable +{ const int channel_index_; const configuration config_; com_ptr decklink_ = get_device(config_.device_index); com_iface_ptr output_ = iface_cast(decklink_); - com_iface_ptr configuration_ = iface_cast(decklink_); - com_iface_ptr keyer_ = iface_cast(decklink_); + com_iface_ptr configuration_ = iface_cast(decklink_); + com_iface_ptr keyer_ = iface_cast(decklink_, true); com_iface_ptr attributes_ = iface_cast(decklink_); tbb::spin_mutex exception_mutex_; std::exception_ptr exception_; tbb::atomic 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_); @@ -353,25 +391,26 @@ struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLink long long audio_scheduled_ = 0; int preroll_count_ = 0; - + boost::circular_buffer> audio_container_ { buffer_size_ + 1 }; - tbb::concurrent_bounded_queue video_frame_buffer_; - tbb::concurrent_bounded_queue audio_frame_buffer_; - + tbb::concurrent_bounded_queue frame_buffer_; + spl::shared_ptr graph_; - tbb::atomic current_presentation_delay_; caspar::timer tick_timer_; - retry_task send_completion_; + boost::mutex send_completion_mutex_; + std::packaged_task send_completion_; + reference_signal_detector reference_signal_detector_ { output_ }; + tbb::atomic current_presentation_delay_; tbb::atomic scheduled_frames_completed_; - std::unique_ptr key_context_; + std::unique_ptr> key_context_; public: decklink_consumer( const configuration& config, const core::video_format_desc& format_desc, const core::audio_channel_layout& in_channel_layout, - int channel_index) + int channel_index) : channel_index_(channel_index) , config_(config) , format_desc_(format_desc) @@ -380,18 +419,13 @@ public: 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(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)); @@ -405,32 +439,42 @@ public: 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) @@ -438,26 +482,23 @@ public: output_->DisableVideoOutput(); } } - + void enable_audio() { if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, out_channel_layout_.num_channels, 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.")); + 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_) @@ -466,17 +507,17 @@ public: 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; @@ -488,9 +529,15 @@ public: { 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(completed_frame); current_presentation_delay_ = dframe->get_age_millis(); ++scheduled_frames_completed_; @@ -507,10 +554,7 @@ public: { 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"); @@ -521,61 +565,40 @@ public: output_->GetBufferedVideoFrameCount(&buffered); graph_->set_value("buffered-video", static_cast(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; - } - - return S_OK; - } - - virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples(BOOL preroll) - { - if(!is_running_) - return E_FAIL; - - try - { - if(preroll) - { - 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)); - } + output_->GetBufferedAudioSampleFrameCount(&buffered); + graph_->set_value("buffered-audio", static_cast(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 lock(send_completion_mutex_); - while(audio_frame_buffer_.try_pop(frame)) + if (send_completion_.valid()) { - send_completion_.try_completion(); - schedule_next_audio(channel_remapper_.mix_and_rearrange(frame.audio_data())); + send_completion_(); + send_completion_ = std::packaged_task(); } } - UINT32 buffered; - output_->GetBufferedAudioSampleFrameCount(&buffered); - graph_->set_value("buffered-audio", static_cast(buffered) / (format_desc_.audio_cadence[0] * config_.buffer_depth())); + if (!is_running_) + return E_FAIL; + + 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; } @@ -594,24 +617,21 @@ public: audio_scheduled_ += sample_frame_count; } - + void schedule_next_video(core::const_frame frame) { if (key_context_) { - auto key_frame = wrap_raw(new decklink_frame(frame, format_desc_, true)); + auto key_frame = wrap_raw(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(new decklink_frame(frame, format_desc_, config_.key_only)); + auto fill_frame = wrap_raw(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 send(core::const_frame frame) @@ -622,36 +642,26 @@ public: }); 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 - { - 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 lock(send_completion_mutex_); - if (audio_ready && video_ready) - return true; - else - return boost::optional(); - }; - - if (enqueue_task()) - return make_ready_future(true); + send_completion_ = std::packaged_task([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) @@ -667,13 +677,14 @@ public: } }; +template struct decklink_consumer_proxy : public core::frame_consumer { - core::monitor::subject monitor_subject_; - const configuration config_; - std::unique_ptr consumer_; - core::video_format_desc format_desc_; - executor executor_; + core::monitor::subject monitor_subject_; + const configuration config_; + std::unique_ptr> consumer_; + core::video_format_desc format_desc_; + executor executor_; public: decklink_consumer_proxy(const configuration& config) @@ -698,26 +709,26 @@ public: } // frame_consumer - + 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_layout, channel_index)); + consumer_.reset(new decklink_consumer(config_, format_desc, channel_layout, channel_index)); }); } - + std::future 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 { @@ -762,7 +773,21 @@ public: { 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) { @@ -800,16 +825,16 @@ void describe_consumer(core::help_sink& sink, const core::help_repository& repo) } spl::shared_ptr create_consumer( - const std::vector& params, core::interaction_sink*) + const std::vector& params, core::interaction_sink*, std::vector> 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(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)) @@ -832,16 +857,21 @@ spl::shared_ptr create_consumer( auto found_layout = core::audio_channel_layout_repository::get_default()->get_layout(channel_layout); if (!found_layout) - CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Channel layout " + channel_layout + L" not found.")); + CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Channel layout " + channel_layout + L" not found.")); config.out_channel_layout = *found_layout; } - return spl::make_shared(config); + bool old_configuration_api = get_driver_version() < get_new_configuration_api_version(); + + if (old_configuration_api) + return spl::make_shared>(config); + else + return spl::make_shared>(config); } spl::shared_ptr create_preconfigured_consumer( - const boost::property_tree::wptree& ptree, core::interaction_sink*) + const boost::property_tree::wptree& ptree, core::interaction_sink*, std::vector> channels) { configuration config; @@ -853,7 +883,7 @@ spl::shared_ptr create_preconfigured_consumer( 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") @@ -863,10 +893,12 @@ spl::shared_ptr create_preconfigured_consumer( 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(file_not_found() << msg_info(L"Channel layout " + *channel_layout + L" not found.")); + CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Channel layout " + *channel_layout + L" not found.")); config.out_channel_layout = *found_layout; } @@ -877,7 +909,12 @@ spl::shared_ptr 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(config); + bool old_configuration_api = get_driver_version() < get_new_configuration_api_version(); + + if (old_configuration_api) + return spl::make_shared>(config); + else + return spl::make_shared>(config); } }} @@ -894,18 +931,18 @@ developer@blackmagic-design.com ----------------------------------------------------------------------------- -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. ################################################################################ */ @@ -922,10 +959,10 @@ developer@blackmagic-design.com ----------------------------------------------------------------------------- -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. ################################################################################ */