X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;ds=sidebyside;f=modules%2Fdecklink%2Fconsumer%2Fdecklink_consumer.cpp;h=ca44c82c956d9c499f2022c40f79461874a0a1d7;hb=850c2bc019012444a7081e0347da9d7d24b213b4;hp=7d5454255ce5f5e5a157f9612363786cb58a066c;hpb=b79fe0e7c6c40fbb0e7192dcafe268c564fa6c54;p=casparcg diff --git a/modules/decklink/consumer/decklink_consumer.cpp b/modules/decklink/consumer/decklink_consumer.cpp index 7d5454255..ca44c82c9 100644 --- a/modules/decklink/consumer/decklink_consumer.cpp +++ b/modules/decklink/consumer/decklink_consumer.cpp @@ -24,6 +24,7 @@ #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 @@ -83,7 +87,7 @@ 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 @@ -117,8 +121,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) { @@ -168,19 +173,32 @@ void set_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 @@ -216,7 +234,7 @@ public: { 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 +247,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(...) { @@ -256,16 +283,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) @@ -326,15 +354,16 @@ 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_; @@ -343,6 +372,7 @@ struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLink 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_); @@ -356,8 +386,7 @@ struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLink 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_; caspar::timer tick_timer_; @@ -365,7 +394,7 @@ struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLink 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( @@ -382,15 +411,10 @@ public: 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("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f)); @@ -407,7 +431,7 @@ 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(); @@ -415,21 +439,31 @@ public: 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) - 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) { @@ -445,9 +479,6 @@ public: 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."; } @@ -492,6 +523,12 @@ public: 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_; @@ -508,10 +545,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"); @@ -522,10 +556,22 @@ public: output_->GetBufferedVideoFrameCount(&buffered); graph_->set_value("buffered-video", static_cast(buffered) / (config_.buffer_depth())); + if (config_.embedded_audio) + { + output_->GetBufferedAudioSampleFrameCount(&buffered); + graph_->set_value("buffered-audio", static_cast(buffered) / (format_desc_.audio_cadence[0] * config_.buffer_depth())); + } + auto frame = core::const_frame::empty(); - video_frame_buffer_.pop(frame); + + frame_buffer_.pop(frame); + send_completion_.try_completion(); - schedule_next_video(frame); + + if (config_.embedded_audio) + schedule_next_audio(channel_remapper_.mix_and_rearrange(frame.audio_data())); + + schedule_next_video(frame); } catch(...) { @@ -538,50 +584,6 @@ public: 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)); - } - } - 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(buffered) / (format_desc_.audio_cadence[0] * config_.buffer_depth())); - - send_completion_.try_completion(); - schedule_next_audio(channel_remapper_.mix_and_rearrange(frame.audio_data())); - } - } - } - catch(...) - { - tbb::spin_mutex::scoped_lock lock(exception_mutex_); - exception_ = std::current_exception(); - return E_FAIL; - } - - return S_OK; - } template void schedule_next_audio(const T& audio_data) @@ -600,21 +602,16 @@ public: { 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(); - - reference_signal_detector_.detect_change([this]() { return print(); }); } std::future send(core::const_frame frame) @@ -630,27 +627,21 @@ public: 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; + bool ready = false; - auto enqueue_task = [audio_ready, video_ready, frame, this]() mutable -> boost::optional + auto enqueue_task = [ready, frame, this]() mutable -> boost::optional { - if (!audio_ready) - audio_ready = audio_frame_buffer_.try_push(frame); + if (!ready) + ready = frame_buffer_.try_push(frame); - if (!video_ready) - video_ready = video_frame_buffer_.try_push(frame); - - if (audio_ready && video_ready) + if (ready) return true; else return boost::optional(); }; - - if (enqueue_task()) - return make_ready_future(true); send_completion_.set_task(enqueue_task); + send_completion_.try_completion(); return send_completion_.get_future(); } @@ -670,13 +661,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) @@ -708,7 +700,7 @@ public: 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)); }); } @@ -765,7 +757,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) { @@ -840,7 +846,12 @@ spl::shared_ptr create_consumer( 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( @@ -866,6 +877,8 @@ 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) @@ -880,7 +893,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); } }}