From 850c2bc019012444a7081e0347da9d7d24b213b4 Mon Sep 17 00:00:00 2001 From: Helge Norberg Date: Mon, 11 Jul 2016 20:41:17 +0200 Subject: [PATCH] Use ScheduledFrameCompleted callback for audio scheduling in addition to video scheduling instead of using the audio callback. This is completely legal according to Blackmagic and simplifies the code a lot. This could also fix #440, #114 and #329 because one more audio frame is scheduled in relation to video instead of one more of both (as indicated by the old mail from Blackmagic about prerolling). --- .../decklink/consumer/decklink_consumer.cpp | 133 +++++++----------- 1 file changed, 47 insertions(+), 86 deletions(-) diff --git a/modules/decklink/consumer/decklink_consumer.cpp b/modules/decklink/consumer/decklink_consumer.cpp index c16e6ee2f..ca44c82c9 100644 --- a/modules/decklink/consumer/decklink_consumer.cpp +++ b/modules/decklink/consumer/decklink_consumer.cpp @@ -87,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 @@ -355,7 +355,7 @@ struct key_video_context : public IDeckLinkVideoOutputCallback, boost::noncopyab }; template -struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable +struct decklink_consumer : public IDeckLinkVideoOutputCallback, boost::noncopyable { const int channel_index_; const configuration config_; @@ -386,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_; @@ -412,12 +411,7 @@ 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())); @@ -445,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) { @@ -475,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."; } @@ -522,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_; @@ -538,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"); @@ -552,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(...) { @@ -568,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) @@ -640,11 +612,6 @@ public: 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) @@ -660,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 (!video_ready) - video_ready = video_frame_buffer_.try_push(frame); + if (!ready) + ready = 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(); } -- 2.39.2