]> git.sesse.net Git - casparcg/commitdiff
Added new decklink consumer implementation which does not use scheduled playback...
authorHelge Norberg <helge.norberg@gmail.com>
Tue, 25 Jun 2013 13:55:46 +0000 (15:55 +0200)
committerHelge Norberg <helge.norberg@gmail.com>
Tue, 25 Jun 2013 13:55:46 +0000 (15:55 +0200)
CHANGES.txt
common/utility/timer.h
modules/decklink/consumer/blocking_decklink_consumer.cpp [new file with mode: 0644]
modules/decklink/consumer/blocking_decklink_consumer.h [new file with mode: 0644]
modules/decklink/consumer/decklink_consumer.cpp
modules/decklink/decklink.cpp
modules/decklink/decklink.vcxproj
modules/decklink/decklink.vcxproj.filters
modules/decklink/util/util.h
shell/casparcg.config
shell/server.cpp

index 2ea84170028f81f3b648cf9bd6e8dfadd53fa349..39f58f0c6ee60a1dad3e03bf08abefb97e82c932 100644 (file)
@@ -1,4 +1,4 @@
-CasparCG 2.0.4 Stable (w.r.t 2.0.3 Stable)\r
+CasparCG 2.0.4 Beta (w.r.t 2.0.3 Stable)\r
 ==========================================\r
 \r
 General\r
@@ -60,7 +60,6 @@ Video mixer
 Consumers\r
 ---------\r
 \r
-  o Simplified implementation of bluefish consumer, with regards to\r
   o Avoid that the ffmpeg consumer blocks the channel output when it can't keep\r
     up with the frame rate (drops instead).\r
   o Added support for to create a separate key and fill file when recording with\r
@@ -69,8 +68,15 @@ Consumers
     separated_producer when doing playback.\r
   o The image consumer now writes to the media folder instead of the data\r
     folder.\r
-  o Fixed bug in decklink consumer where we submit too few samples to the driver\r
-    when the video format has a frame rate > 50.\r
+  o Fixed bug in decklink consumer where we submit too few audio samples to the\r
+    driver when the video format has a frame rate > 50.\r
+  o Added another experimental decklink consumer implementation where scheduled\r
+    playback is not used, but a similar approach as in the bluefish consumer\r
+       where we wait for a frame to be displayed and then display the next frame.\r
+       It is configured via a <blocking-decklink> consumer element. The benefits of\r
+       this consumer is lower latency and more deterministic synchronization\r
+       between multiple instances (should not need to be wrapped in a\r
+       <synchronizing> element when separate key-fill is used).\r
 \r
 Producers\r
 ---------\r
index a39159a779af7ee40e81c5141261d28faa970ed8..6cb110035119fa5e2d597fea0ec68010bc17b190 100644 (file)
@@ -39,15 +39,19 @@ public:
        // Author: Ryan M. Geiss\r
        // http://www.geisswerks.com/ryan/FAQS/timing.html\r
        void tick(double interval)\r
-       {       \r
+       {\r
+               tick_millis(static_cast<DWORD>(interval * 1000.0));\r
+       }\r
+\r
+       void tick_millis(DWORD ticks_to_wait)\r
+       {\r
                auto t = ::timeGetTime();\r
 \r
                if (time_ != 0)\r
                {\r
-                       auto ticks_to_wait = static_cast<DWORD>(interval*1000.0);\r
                        bool done = 0;\r
                        do\r
-                       {                               \r
+                       {\r
                                auto ticks_passed = t - time_;\r
                                auto ticks_left   = ticks_to_wait - ticks_passed;\r
 \r
@@ -55,10 +59,10 @@ public:
                                        done = 1;\r
                                if (ticks_passed >= ticks_to_wait)\r
                                        done = 1;\r
-                               \r
+\r
                                if (!done)\r
                                {\r
-                                       // if > 0.002s left, do Sleep(1), which will actually sleep some \r
+                                       // if > 0.002s left, do Sleep(1), which will actually sleep some\r
                                        //   steady amount, probably 1-2 ms,\r
                                        //   and do so in a nice way (cpu meter drops; laptop battery spared).\r
                                        // otherwise, do a few Sleep(0)'s, which just give up the timeslice,\r
@@ -66,18 +70,18 @@ public:
                                        //   amount of time.\r
                                        if (ticks_left > 2)\r
                                                Sleep(1);\r
-                                       else                        \r
-                                               for (int i = 0; i < 10; ++i) \r
+                                       else\r
+                                               for (int i = 0; i < 10; ++i)\r
                                                        Sleep(0);  // causes thread to give up its timeslice\r
                                }\r
 \r
                                t = ::timeGetTime();\r
                        }\r
-                       while (!done);            \r
+                       while (!done);\r
                }\r
 \r
                time_ = t;\r
-       }               \r
+       }\r
 private:       \r
        DWORD time_;\r
 };\r
diff --git a/modules/decklink/consumer/blocking_decklink_consumer.cpp b/modules/decklink/consumer/blocking_decklink_consumer.cpp
new file mode 100644 (file)
index 0000000..35b2f8b
--- /dev/null
@@ -0,0 +1,479 @@
+/*
+* Copyright 2013 Sveriges Television AB http://casparcg.com/
+*
+* This file is part of CasparCG (www.casparcg.com).
+*
+* CasparCG is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* CasparCG is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
+*
+* Author: Robert Nagy, ronag89@gmail.com
+*/
+
+#include "../StdAfx.h"
+#include "decklink_consumer.h"
+
+#include "../util/util.h"
+
+#include "../interop/DeckLinkAPI_h.h"
+
+#include <map>
+
+#include <common/concurrency/com_context.h>
+#include <common/concurrency/executor.h>
+#include <common/diagnostics/graph.h>
+#include <common/exception/exceptions.h>
+#include <common/memory/memcpy.h>
+#include <common/memory/memclr.h>
+#include <common/utility/timer.h>
+
+#include <core/parameters/parameters.h>
+#include <core/consumer/frame_consumer.h>
+#include <core/mixer/read_frame.h>
+#include <core/mixer/audio/audio_util.h>
+
+#include <tbb/cache_aligned_allocator.h>
+
+#include <boost/timer.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/circular_buffer.hpp>
+
+namespace caspar { namespace decklink { 
+
+// second is the index in the array to consume the next time
+typedef std::pair<std::vector<int32_t>, size_t> audio_buffer;
+
+struct blocking_decklink_consumer : boost::noncopyable
+{              
+       const int                                                               channel_index_;
+       const configuration                                             config_;
+
+       CComPtr<IDeckLink>                                              decklink_;
+       CComQIPtr<IDeckLinkOutput>                              output_;
+
+       const std::wstring                                              model_name_;
+       const core::video_format_desc                   format_desc_;
+       std::shared_ptr<core::read_frame>               previous_frame_;
+       boost::circular_buffer<audio_buffer>    audio_samples_;
+       size_t                                                                  buffered_audio_samples_;
+       BMDTimeValue                                                    last_reference_clock_value_;
+
+       safe_ptr<diagnostics::graph>                    graph_;
+       boost::timer                                                    frame_timer_;
+       boost::timer                                                    tick_timer_;
+       boost::timer                                                    sync_timer_;    
+       reference_signal_detector                               reference_signal_detector_;
+
+       tbb::atomic<int64_t>                                    current_presentation_delay_;
+       executor                                                                executor_;
+
+public:
+       blocking_decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index) 
+               : channel_index_(channel_index)
+               , config_(config)
+               , decklink_(get_device(config.device_index))
+               , output_(decklink_)
+               , model_name_(get_model_name(decklink_))
+               , format_desc_(format_desc)
+               , buffered_audio_samples_(0)
+               , last_reference_clock_value_(-1)
+               , reference_signal_detector_(output_)
+               , executor_(L"blocking_decklink_consumer")
+       {
+               audio_samples_.set_capacity(2);
+               current_presentation_delay_ = 0;
+               executor_.set_capacity(1);
+
+               graph_->set_color("sync-time", diagnostics::color(1.0f, 0.0f, 0.0f));
+               graph_->set_color("frame-time", diagnostics::color(0.5f, 1.0f, 0.2f));
+               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));
+
+               if (config_.embedded_audio)
+                       graph_->set_color(
+                                       "buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));
+
+               graph_->set_text(print());
+               diagnostics::register_graph(graph_);
+               
+               enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
+                               
+               if(config.embedded_audio)
+                       enable_audio();
+
+               set_latency(
+                               CComQIPtr<IDeckLinkConfiguration>(decklink_),
+                               configuration::low_latency,
+                               print());
+               set_keyer(
+                               CComQIPtr<IDeckLinkAttributes>(decklink_),
+                               CComQIPtr<IDeckLinkKeyer>(decklink_),
+                               config.keyer,
+                               print());
+       }
+
+       ~blocking_decklink_consumer()
+       {               
+               if(output_ != nullptr) 
+               {
+                       if(config_.embedded_audio)
+                               output_->DisableAudioOutput();
+                       output_->DisableVideoOutput();
+               }
+       }
+       
+       void enable_audio()
+       {
+               if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, config_.num_out_channels(), bmdAudioOutputStreamContinuous)))
+                               BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " 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))) 
+                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
+       }
+
+       void queue_audio_samples(const safe_ptr<core::read_frame>& frame)
+       {
+               auto view = frame->multichannel_view();
+               const int sample_frame_count = view.num_samples();
+
+               if (audio_samples_.full())
+               {
+                       CASPAR_LOG(warning) << print() << L" Too much audio buffered. Discarding samples.";
+                       audio_samples_.clear();
+                       buffered_audio_samples_ = 0;
+               }
+
+               if (core::needs_rearranging(
+                               view, config_.audio_layout, config_.num_out_channels()))
+               {
+                       std::vector<int32_t> resulting_audio_data;
+                       resulting_audio_data.resize(
+                                       sample_frame_count * config_.num_out_channels());
+
+                       auto dest_view = core::make_multichannel_view<int32_t>(
+                                       resulting_audio_data.begin(), 
+                                       resulting_audio_data.end(),
+                                       config_.audio_layout,
+                                       config_.num_out_channels());
+
+                       core::rearrange_or_rearrange_and_mix(
+                                       view, dest_view, core::default_mix_config_repository());
+
+                       if (config_.audio_layout.num_channels == 1) // mono
+                               boost::copy(                            // duplicate L to R
+                                               dest_view.channel(0),
+                                               dest_view.channel(1).begin());
+
+                       audio_samples_.push_back(
+                                       std::make_pair(std::move(resulting_audio_data), 0));
+               }
+               else
+               {
+                       audio_samples_.push_back(std::make_pair(
+                                       std::vector<int32_t>(
+                                                       frame->audio_data().begin(),
+                                                       frame->audio_data().end()),
+                                       0));
+               }
+
+               buffered_audio_samples_ += sample_frame_count;
+               graph_->set_value("buffered-audio",
+                               static_cast<double>(buffered_audio_samples_)
+                                               / format_desc_.audio_cadence[0] * 0.5);
+       }
+
+       bool try_consume_audio(std::pair<std::vector<int32_t>, size_t>& buffer)
+       {
+               size_t to_offer = (buffer.first.size() - buffer.second)
+                               / config_.num_out_channels();
+               auto begin = buffer.first.data() + buffer.second;
+               unsigned long samples_written;
+
+               if (FAILED(output_->WriteAudioSamplesSync(
+                               begin,
+                               to_offer,
+                               &samples_written)))
+                       CASPAR_LOG(error) << print() << L" Failed to write audio samples.";
+
+               buffered_audio_samples_ -= samples_written;
+
+               if (samples_written == to_offer)
+                       return true; // depleted buffer
+
+               size_t consumed = samples_written * config_.num_out_channels();
+               buffer.second += consumed;
+
+
+               return false;
+       }
+
+       void write_audio_samples()
+       {
+               while (!audio_samples_.empty())
+               {
+                       auto buffer = audio_samples_.front();
+
+                       if (try_consume_audio(buffer))
+                               audio_samples_.pop_front();
+                       else
+                               break;
+               }
+       }
+
+       void wait_for_frame_to_be_displayed()
+       {
+               sync_timer_.restart();
+               BMDTimeScale time_scale = 1000;
+               BMDTimeValue hardware_time;
+               BMDTimeValue time_in_frame;
+               BMDTimeValue ticks_per_frame;
+
+               output_->GetHardwareReferenceClock(
+                               time_scale, &hardware_time, &time_in_frame, &ticks_per_frame);
+
+               auto reference_clock_value = hardware_time - time_in_frame;
+               auto frame_duration = static_cast<int>(1000 / format_desc_.fps);
+               auto actual_duration = last_reference_clock_value_ == -1 
+                               ? frame_duration
+                               : reference_clock_value - last_reference_clock_value_;
+
+               if (std::abs(frame_duration - actual_duration) > 1)
+                       graph_->set_tag("late-frame");
+               else
+               {
+                       auto to_wait = ticks_per_frame - time_in_frame;
+                       high_prec_timer timer;
+                       timer.tick_millis(0);
+                       timer.tick_millis(static_cast<DWORD>(to_wait));
+               }
+
+
+               last_reference_clock_value_ = reference_clock_value;
+               graph_->set_value("sync-time",
+                               sync_timer_.elapsed() * format_desc_.fps * 0.5);
+       }
+
+       void write_video_frame(
+                       const safe_ptr<core::read_frame>& frame,
+                       std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>>&& key)
+       {
+               CComPtr<IDeckLinkVideoFrame> frame2;
+
+               if (config_.key_only)
+                       frame2 = CComPtr<IDeckLinkVideoFrame>(
+                                       new decklink_frame(frame, format_desc_, std::move(key)));
+               else
+                       frame2 = CComPtr<IDeckLinkVideoFrame>(
+                                       new decklink_frame(frame, format_desc_, config_.key_only));
+
+               if (FAILED(output_->DisplayVideoFrameSync(frame2)))
+                       CASPAR_LOG(error) << print() << L" Failed to display video frame.";
+
+               reference_signal_detector_.detect_change([this]() { return print(); });
+       }
+
+       boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame)
+       {
+               return executor_.begin_invoke([=]() -> bool
+               {
+                       frame_timer_.restart();
+                       std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> key;
+
+                       if (config_.key_only)
+                               key = std::move(extract_key(frame));
+
+                       if (config_.embedded_audio)
+                       {
+                               write_audio_samples();
+                               queue_audio_samples(frame);
+                       }
+
+                       double frame_time = frame_timer_.elapsed();
+
+                       wait_for_frame_to_be_displayed();
+
+                       if (previous_frame_)
+                       {
+                               // According to SDK when DisplayVideoFrameSync is used in
+                               // combination with low latency, the next frame displayed should
+                               // be the submitted frame but it appears to be an additional 2
+                               // frame delay before it is sent on SDI.
+                               int adjustment = static_cast<int>(2000 / format_desc_.fps);
+
+                               current_presentation_delay_ =
+                                               previous_frame_->get_age_millis() + adjustment;
+                       }
+
+                       previous_frame_ = frame;
+
+                       graph_->set_value(
+                                       "tick-time",
+                                       tick_timer_.elapsed() * format_desc_.fps * 0.5);
+                       tick_timer_.restart();
+
+                       frame_timer_.restart();
+                       write_video_frame(frame, std::move(key));
+
+                       if (config_.embedded_audio)
+                               write_audio_samples();
+
+                       frame_time += frame_timer_.elapsed();
+                       graph_->set_value(
+                                       "frame-time", frame_time * format_desc_.fps * 0.5);
+
+                       return true;
+               });
+       }
+       
+       std::wstring print() const
+       {
+               return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +
+                       boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";
+       }
+};
+
+struct blocking_decklink_consumer_proxy : public core::frame_consumer
+{
+       const configuration                                             config_;
+       com_context<blocking_decklink_consumer> context_;
+       std::vector<size_t>                                             audio_cadence_;
+       core::video_format_desc                                 format_desc_;
+public:
+
+       blocking_decklink_consumer_proxy(const configuration& config)
+               : config_(config)
+               , context_(L"blocking_decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
+       {
+       }
+
+       ~blocking_decklink_consumer_proxy()
+       {
+               if(context_)
+               {
+                       auto str = print();
+                       context_.reset();
+                       CASPAR_LOG(info) << str << L" Successfully Uninitialized.";     
+               }
+       }
+
+       // frame_consumer
+       
+       virtual void initialize(const core::video_format_desc& format_desc, int channel_index) override
+       {
+               context_.reset([&]{return new blocking_decklink_consumer(config_, format_desc, channel_index);});               
+               audio_cadence_ = format_desc.audio_cadence;             
+               format_desc_ = format_desc;
+
+               CASPAR_LOG(info) << print() << L" Successfully Initialized.";   
+       }
+       
+       virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override
+       {
+               CASPAR_VERIFY(audio_cadence_.front() * frame->num_channels() == static_cast<size_t>(frame->audio_data().size()));
+               boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);
+
+               return context_->send(frame);
+       }
+       
+       virtual std::wstring print() const override
+       {
+               return context_ ? context_->print() : L"[blocking_decklink_consumer]";
+       }               
+
+       virtual boost::property_tree::wptree info() const override
+       {
+               boost::property_tree::wptree info;
+               info.add(L"type", L"decklink-consumer");
+               info.add(L"key-only", config_.key_only);
+               info.add(L"device", config_.device_index);
+               info.add(L"embedded-audio", config_.embedded_audio);
+               info.add(L"presentation-frame-age", presentation_frame_age_millis());
+               //info.add(L"internal-key", config_.internal_key);
+               return info;
+       }
+
+       virtual size_t buffer_depth() const override
+       {
+               // Should be 1 according to SDK when DisplayVideoFrameSync is used in
+               // combination with low latency, but it does not seem so.
+               return 3; 
+       }
+
+       virtual bool has_synchronization_clock() const override
+       {
+               return true;
+       }
+
+       virtual int index() const override
+       {
+               return 350 + config_.device_index;
+       }
+
+       virtual int64_t presentation_frame_age_millis() const
+       {
+               return context_ ? context_->current_presentation_delay_ : 0;
+       }
+};     
+
+safe_ptr<core::frame_consumer> create_blocking_consumer(const core::parameters& params) 
+{
+       if(params.size() < 1 || params[0] != L"BLOCKING_DECKLINK")
+               return core::frame_consumer::empty();
+       
+       configuration config;
+               
+       if(params.size() > 1)
+               config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
+       
+       if(std::find(params.begin(), params.end(), L"INTERNAL_KEY")                     != params.end())
+               config.keyer = configuration::internal_keyer;
+       else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY")        != params.end())
+               config.keyer = configuration::external_keyer;
+       else
+               config.keyer = configuration::default_keyer;
+
+       config.embedded_audio   = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
+       config.key_only                 = std::find(params.begin(), params.end(), L"KEY_ONLY")           != params.end();
+       config.audio_layout             = core::default_channel_layout_repository().get_by_name(
+                       params.get(L"CHANNEL_LAYOUT", L"STEREO"));
+
+       return make_safe<blocking_decklink_consumer_proxy>(config);
+}
+
+safe_ptr<core::frame_consumer> create_blocking_consumer(const boost::property_tree::wptree& ptree) 
+{
+       configuration config;
+
+       auto keyer = ptree.get(L"keyer", L"external");
+       if(keyer == L"external")
+               config.keyer = configuration::external_keyer;
+       else if(keyer == L"internal")
+               config.keyer = configuration::internal_keyer;
+
+       config.key_only                         = ptree.get(L"key-only",                config.key_only);
+       config.device_index                     = ptree.get(L"device",                  config.device_index);
+       config.embedded_audio           = ptree.get(L"embedded-audio",  config.embedded_audio);
+       config.audio_layout =
+               core::default_channel_layout_repository().get_by_name(
+                               boost::to_upper_copy(ptree.get(L"channel-layout", L"STEREO")));
+
+       return make_safe<blocking_decklink_consumer_proxy>(config);
+}
+
+}}
diff --git a/modules/decklink/consumer/blocking_decklink_consumer.h b/modules/decklink/consumer/blocking_decklink_consumer.h
new file mode 100644 (file)
index 0000000..fb1faa5
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+* Copyright 2013 Sveriges Television AB http://casparcg.com/
+*
+* This file is part of CasparCG (www.casparcg.com).
+*
+* CasparCG is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* CasparCG is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
+*
+* Author: Robert Nagy, ronag89@gmail.com
+*/
+
+#pragma once
+
+#include <common/memory/safe_ptr.h>
+
+#include <core/video_format.h>
+
+#include <boost/property_tree/ptree.hpp>
+
+#include <string>
+#include <vector>
+
+namespace caspar {
+
+namespace core {
+       struct frame_consumer;
+       class parameters;
+}
+
+namespace decklink {
+
+safe_ptr<core::frame_consumer> create_blocking_consumer(const core::parameters& params);
+safe_ptr<core::frame_consumer> create_blocking_consumer(const boost::property_tree::wptree& ptree);
+
+}}
\ No newline at end of file
index d99b1442c6e42b28979c95c9e9e543e7d6a25279..d21bda255486f98d618a66fd14e4713b2756c31b 100644 (file)
@@ -33,9 +33,7 @@
 #include <common/concurrency/future_util.h>\r
 #include <common/diagnostics/graph.h>\r
 #include <common/exception/exceptions.h>\r
-#include <common/memory/memcpy.h>\r
-#include <common/memory/memclr.h>\r
-#include <common/memory/memshfl.h>\r
+#include <common/utility/assert.h>\r
 \r
 #include <core/parameters/parameters.h>\r
 #include <core/consumer/frame_consumer.h>\r
 #include <boost/algorithm/string.hpp>\r
 \r
 namespace caspar { namespace decklink { \r
-       \r
-struct configuration\r
-{\r
-       enum keyer_t\r
-       {\r
-               internal_keyer,\r
-               external_keyer,\r
-               default_keyer\r
-       };\r
-\r
-       enum latency_t\r
-       {\r
-               low_latency,\r
-               normal_latency,\r
-               default_latency\r
-       };\r
-\r
-       size_t                                  device_index;\r
-       bool                                    embedded_audio;\r
-       core::channel_layout    audio_layout;\r
-       keyer_t                                 keyer;\r
-       latency_t                               latency;\r
-       bool                                    key_only;\r
-       size_t                                  base_buffer_depth;\r
-       \r
-       configuration()\r
-               : device_index(1)\r
-               , embedded_audio(false)\r
-               , audio_layout(core::default_channel_layout_repository().get_by_name(L"STEREO"))\r
-               , keyer(default_keyer)\r
-               , latency(default_latency)\r
-               , key_only(false)\r
-               , base_buffer_depth(3)\r
-       {\r
-       }\r
-       \r
-       size_t buffer_depth() const\r
-       {\r
-               return base_buffer_depth + (latency == low_latency ? 0 : 1) + (embedded_audio ? 1 : 0);\r
-       }\r
-\r
-       int num_out_channels() const\r
-       {\r
-               if (audio_layout.num_channels <= 2)\r
-                       return 2;\r
-               \r
-               if (audio_layout.num_channels <= 8)\r
-                       return 8;\r
-\r
-               return 16;\r
-       }\r
-};\r
-\r
-class decklink_frame : public IDeckLinkVideoFrame\r
-{\r
-       tbb::atomic<int>                                                                                        ref_count_;\r
-       std::shared_ptr<core::read_frame>                                                       frame_;\r
-       const core::video_format_desc                                                           format_desc_;\r
-\r
-       const bool                                                                                                      key_only_;\r
-       std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> data_;\r
-public:\r
-       decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, bool key_only)\r
-               : frame_(frame)\r
-               , format_desc_(format_desc)\r
-               , key_only_(key_only)\r
-       {\r
-               ref_count_ = 0;\r
-       }\r
-       \r
-       // IUnknown\r
-\r
-       STDMETHOD (QueryInterface(REFIID, LPVOID*))             \r
-       {\r
-               return E_NOINTERFACE;\r
-       }\r
-       \r
-       STDMETHOD_(ULONG,                       AddRef())                       \r
-       {\r
-               return ++ref_count_;\r
-       }\r
-\r
-       STDMETHOD_(ULONG,                       Release())                      \r
-       {\r
-               if(--ref_count_ == 0)\r
-                       delete this;\r
-               return ref_count_;\r
-       }\r
-\r
-       // IDecklinkVideoFrame\r
-\r
-       STDMETHOD_(long,                        GetWidth())                     {return format_desc_.width;}        \r
-    STDMETHOD_(long,                   GetHeight())            {return format_desc_.height;}        \r
-    STDMETHOD_(long,                   GetRowBytes())          {return format_desc_.width*4;}        \r
-       STDMETHOD_(BMDPixelFormat,      GetPixelFormat())       {return bmdFormat8BitBGRA;}        \r
-    STDMETHOD_(BMDFrameFlags,  GetFlags())                     {return bmdFrameFlagDefault;}\r
-        \r
-    STDMETHOD(GetBytes(void** buffer))\r
-       {\r
-               try\r
-               {\r
-                       if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)\r
-                       {\r
-                               data_.resize(format_desc_.size, 0);\r
-                               *buffer = data_.data();\r
-                       }\r
-                       else if(key_only_)\r
-                       {\r
-                               if(data_.empty())\r
-                               {\r
-                                       data_.resize(frame_->image_data().size());\r
-                                       fast_memshfl(data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);\r
-                               }\r
-                               *buffer = data_.data();\r
-                       }\r
-                       else\r
-                               *buffer = const_cast<uint8_t*>(frame_->image_data().begin());\r
-               }\r
-               catch(...)\r
-               {\r
-                       CASPAR_LOG_CURRENT_EXCEPTION();\r
-                       return E_FAIL;\r
-               }\r
-\r
-               return S_OK;\r
-       }\r
-        \r
-    STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}        \r
-    STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary))                {return S_FALSE;}\r
-\r
-       // decklink_frame       \r
-\r
-       const boost::iterator_range<const int32_t*> audio_data()\r
-       {\r
-               return frame_->audio_data();\r
-       }\r
-\r
-       int64_t get_age_millis() const\r
-       {\r
-               return frame_->get_age_millis();\r
-       }\r
-};\r
 \r
 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
 {              \r
@@ -200,9 +56,6 @@ struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLink
 \r
        CComPtr<IDeckLink>                                      decklink_;\r
        CComQIPtr<IDeckLinkOutput>                      output_;\r
-       CComQIPtr<IDeckLinkConfiguration>       configuration_;\r
-       CComQIPtr<IDeckLinkKeyer>                       keyer_;\r
-       CComQIPtr<IDeckLinkAttributes>          attributes_;\r
 \r
        tbb::spin_mutex                                         exception_mutex_;\r
        std::exception_ptr                                      exception_;\r
@@ -225,8 +78,8 @@ struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLink
        \r
        safe_ptr<diagnostics::graph> graph_;\r
        boost::timer tick_timer_;\r
-       BMDReferenceStatus last_reference_status_;\r
        retry_task<bool> send_completion_;\r
+       reference_signal_detector reference_signal_detector_;\r
 \r
        tbb::atomic<int64_t>                            current_presentation_delay_;\r
 \r
@@ -236,9 +89,6 @@ public:
                , config_(config)\r
                , decklink_(get_device(config.device_index))\r
                , output_(decklink_)\r
-               , configuration_(decklink_)\r
-               , keyer_(decklink_)\r
-               , attributes_(decklink_)\r
                , model_name_(get_model_name(decklink_))\r
                , format_desc_(format_desc)\r
                , buffer_size_(config.buffer_depth()) // Minimum buffer-size 3.\r
@@ -246,7 +96,7 @@ public:
                , audio_scheduled_(0)\r
                , preroll_count_(0)\r
                , audio_container_(buffer_size_+1)\r
-               , last_reference_status_(static_cast<BMDReferenceStatus>(-1))\r
+               , reference_signal_detector_(output_)\r
        {\r
                is_running_ = true;\r
                current_presentation_delay_ = 0;\r
@@ -275,8 +125,15 @@ public:
                if(config.embedded_audio)\r
                        enable_audio();\r
 \r
-               set_latency(config.latency);                            \r
-               set_keyer(config.keyer);\r
+               set_latency(\r
+                               CComQIPtr<IDeckLinkConfiguration>(decklink_),\r
+                               config.latency,\r
+                               print());\r
+               set_keyer(\r
+                               CComQIPtr<IDeckLinkAttributes>(decklink_),\r
+                               CComQIPtr<IDeckLinkKeyer>(decklink_),\r
+                               config.keyer,\r
+                               print());\r
                                \r
                if(config.embedded_audio)               \r
                        output_->BeginAudioPreroll();           \r
@@ -302,48 +159,6 @@ public:
                        output_->DisableVideoOutput();\r
                }\r
        }\r
-                       \r
-       void set_latency(configuration::latency_t latency)\r
-       {               \r
-               if(latency == configuration::low_latency)\r
-               {\r
-                       configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);\r
-                       CASPAR_LOG(info) << print() << L" Enabled low-latency mode.";\r
-               }\r
-               else if(latency == configuration::normal_latency)\r
-               {                       \r
-                       configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);\r
-                       CASPAR_LOG(info) << print() << L" Disabled low-latency mode.";\r
-               }\r
-       }\r
-\r
-       void set_keyer(configuration::keyer_t keyer)\r
-       {\r
-               if(keyer == configuration::internal_keyer) \r
-               {\r
-                       BOOL value = true;\r
-                       if(SUCCEEDED(attributes_->GetFlag(BMDDeckLinkSupportsInternalKeying, &value)) && !value)\r
-                               CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";   \r
-                       else if(FAILED(keyer_->Enable(FALSE)))                  \r
-                               CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   \r
-                       else if(FAILED(keyer_->SetLevel(255)))                  \r
-                               CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
-                       else\r
-                               CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             \r
-               }\r
-               else if(keyer == configuration::external_keyer)\r
-               {\r
-                       BOOL value = true;\r
-                       if(SUCCEEDED(attributes_->GetFlag(BMDDeckLinkSupportsExternalKeying, &value)) && !value)\r
-                               CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   \r
-                       else if(FAILED(keyer_->Enable(TRUE)))                   \r
-                               CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   \r
-                       else if(FAILED(keyer_->SetLevel(255)))                  \r
-                               CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
-                       else\r
-                               CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     \r
-               }\r
-       }\r
        \r
        void enable_audio()\r
        {\r
@@ -529,30 +344,7 @@ public:
                graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);\r
                tick_timer_.restart();\r
 \r
-               detect_reference_signal_change();\r
-       }\r
-\r
-       void detect_reference_signal_change()\r
-       {\r
-               BMDReferenceStatus reference_status;\r
-\r
-               if (output_->GetReferenceStatus(&reference_status) != S_OK)\r
-               {\r
-                       CASPAR_LOG(error) << print() << L" Reference signal: failed while querying status";\r
-               }\r
-               else if (reference_status != last_reference_status_)\r
-               {\r
-                       last_reference_status_ = reference_status;\r
-\r
-                       if (reference_status == 0)\r
-                               CASPAR_LOG(info) << print() << L" Reference signal: not detected.";\r
-                       else if (reference_status & bmdReferenceNotSupportedByHardware)\r
-                               CASPAR_LOG(info) << print() << L" Reference signal: not supported by hardware.";\r
-                       else if (reference_status & bmdReferenceLocked)\r
-                               CASPAR_LOG(info) << print() << L" Reference signal: locked.";\r
-                       else\r
-                               CASPAR_LOG(info) << print() << L" Reference signal: Unhandled enum bitfield: " << reference_status;\r
-               }\r
+               reference_signal_detector_.detect_change([this]() { return print(); });\r
        }\r
 \r
        boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame)\r
@@ -654,7 +446,6 @@ public:
                info.add(L"device", config_.device_index);\r
                info.add(L"low-latency", config_.low_latency);\r
                info.add(L"embedded-audio", config_.embedded_audio);\r
-               info.add(L"low-latency", config_.low_latency);\r
                info.add(L"presentation-frame-age", presentation_frame_age_millis());\r
                //info.add(L"internal-key", config_.internal_key);\r
                return info;\r
index 0dbd88436f90612c2e4563475696243559b8a37e..13fdbb55364ee1f3814b603414218ee782da2446 100644 (file)
@@ -25,6 +25,7 @@
 #include "util/util.h"\r
 \r
 #include "consumer/decklink_consumer.h"\r
+#include "consumer/blocking_decklink_consumer.h"\r
 #include "producer/decklink_producer.h"\r
 \r
 #include <core/parameters/parameters.h>\r
@@ -58,6 +59,7 @@ void init()
                return;\r
                \r
        core::register_consumer_factory([](const core::parameters& params){return decklink::create_consumer(params);});\r
+       core::register_consumer_factory([](const core::parameters& params){return decklink::create_blocking_consumer(params);});\r
        core::register_producer_factory(create_producer);\r
 }\r
 \r
index 200f579a16aa650117f8d22b1662356aad9aa9dd..1b5f9e818d61876f5b6c99c33ecbf9e1b59e5473 100644 (file)
     <Lib />\r
   </ItemDefinitionGroup>\r
   <ItemGroup>\r
+    <ClCompile Include="consumer\blocking_decklink_consumer.cpp">\r
+      <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Develop|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
+      <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
+      <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
+      <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
+    </ClCompile>\r
     <ClCompile Include="consumer\decklink_consumer.cpp">\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
     </ClCompile>\r
   </ItemGroup>\r
   <ItemGroup>\r
+    <ClInclude Include="consumer\blocking_decklink_consumer.h" />\r
     <ClInclude Include="consumer\decklink_consumer.h" />\r
     <ClInclude Include="decklink.h" />\r
     <ClInclude Include="interop\DeckLinkAPIVersion.h" />\r
index a194af36c3fee5855f6218bed9a291cd68597b30..709b5cd17bd7c881d050910fe52b49bf30a9b0dd 100644 (file)
@@ -31,6 +31,9 @@
     <ClCompile Include="interop\DeckLinkAPI_i.c">\r
       <Filter>source\interop</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="consumer\blocking_decklink_consumer.cpp">\r
+      <Filter>source\consumer</Filter>\r
+    </ClCompile>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ClInclude Include="consumer\decklink_consumer.h">\r
@@ -52,5 +55,8 @@
     <ClInclude Include="interop\DeckLinkAPI_h.h">\r
       <Filter>source\interop</Filter>\r
     </ClInclude>\r
+    <ClInclude Include="consumer\blocking_decklink_consumer.h">\r
+      <Filter>source\consumer</Filter>\r
+    </ClInclude>\r
   </ItemGroup>\r
 </Project>
\ No newline at end of file
index 4c6ab50514734323fcd0342d462e4d4b33eb901b..99add07f61222caf585739693d74b974e3e8d938 100644 (file)
@@ -23,7 +23,9 @@
 \r
 #include <common/exception/exceptions.h>\r
 #include <common/log/log.h>\r
+#include <common/memory/memshfl.h>\r
 #include <core/video_format.h>\r
+#include <core/mixer/read_frame.h>\r
 \r
 #include "../interop/DeckLinkAPI_h.h"\r
 \r
@@ -178,4 +180,255 @@ static std::wstring get_model_name(const T& device)
        return std::wstring(pModelName);\r
 }\r
 \r
+static std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> extract_key(\r
+               const safe_ptr<core::read_frame>& frame)\r
+{\r
+       std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> result;\r
+\r
+       result.resize(frame->image_data().size());\r
+       fast_memshfl(\r
+                       result.data(),\r
+                       frame->image_data().begin(),\r
+                       frame->image_data().size(),\r
+                       0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);\r
+\r
+       return std::move(result);\r
+}\r
+\r
+class decklink_frame : public IDeckLinkVideoFrame\r
+{\r
+       tbb::atomic<int>                                                                                        ref_count_;\r
+       std::shared_ptr<core::read_frame>                                                       frame_;\r
+       const core::video_format_desc                                                           format_desc_;\r
+\r
+       const bool                                                                                                      key_only_;\r
+       std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> data_;\r
+public:\r
+       decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, bool key_only)\r
+               : frame_(frame)\r
+               , format_desc_(format_desc)\r
+               , key_only_(key_only)\r
+       {\r
+               ref_count_ = 0;\r
+       }\r
+\r
+       decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>>&& key_data)\r
+               : frame_(frame)\r
+               , format_desc_(format_desc)\r
+               , key_only_(true)\r
+               , data_(std::move(key_data))\r
+       {\r
+               ref_count_ = 0;\r
+       }\r
+       \r
+       // IUnknown\r
+\r
+       STDMETHOD (QueryInterface(REFIID, LPVOID*))             \r
+       {\r
+               return E_NOINTERFACE;\r
+       }\r
+       \r
+       STDMETHOD_(ULONG,                       AddRef())                       \r
+       {\r
+               return ++ref_count_;\r
+       }\r
+\r
+       STDMETHOD_(ULONG,                       Release())                      \r
+       {\r
+               if(--ref_count_ == 0)\r
+                       delete this;\r
+               return ref_count_;\r
+       }\r
+\r
+       // IDecklinkVideoFrame\r
+\r
+       STDMETHOD_(long,                        GetWidth())                     {return format_desc_.width;}        \r
+    STDMETHOD_(long,                   GetHeight())            {return format_desc_.height;}        \r
+    STDMETHOD_(long,                   GetRowBytes())          {return format_desc_.width*4;}        \r
+       STDMETHOD_(BMDPixelFormat,      GetPixelFormat())       {return bmdFormat8BitBGRA;}        \r
+    STDMETHOD_(BMDFrameFlags,  GetFlags())                     {return bmdFrameFlagDefault;}\r
+        \r
+    STDMETHOD(GetBytes(void** buffer))\r
+       {\r
+               try\r
+               {\r
+                       if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)\r
+                       {\r
+                               data_.resize(format_desc_.size, 0);\r
+                               *buffer = data_.data();\r
+                       }\r
+                       else if(key_only_)\r
+                       {\r
+                               if(data_.empty())\r
+                               {\r
+                                       data_.resize(frame_->image_data().size());\r
+                                       fast_memshfl(data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);\r
+                               }\r
+                               *buffer = data_.data();\r
+                       }\r
+                       else\r
+                               *buffer = const_cast<uint8_t*>(frame_->image_data().begin());\r
+               }\r
+               catch(...)\r
+               {\r
+                       CASPAR_LOG_CURRENT_EXCEPTION();\r
+                       return E_FAIL;\r
+               }\r
+\r
+               return S_OK;\r
+       }\r
+        \r
+    STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}        \r
+    STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary))                {return S_FALSE;}\r
+\r
+       // decklink_frame       \r
+\r
+       const boost::iterator_range<const int32_t*> audio_data()\r
+       {\r
+               return frame_->audio_data();\r
+       }\r
+\r
+       int64_t get_age_millis() const\r
+       {\r
+               return frame_->get_age_millis();\r
+       }\r
+};\r
+\r
+struct configuration\r
+{\r
+       enum keyer_t\r
+       {\r
+               internal_keyer,\r
+               external_keyer,\r
+               default_keyer\r
+       };\r
+\r
+       enum latency_t\r
+       {\r
+               low_latency,\r
+               normal_latency,\r
+               default_latency\r
+       };\r
+\r
+       size_t                                  device_index;\r
+       bool                                    embedded_audio;\r
+       core::channel_layout    audio_layout;\r
+       keyer_t                                 keyer;\r
+       latency_t                               latency;\r
+       bool                                    key_only;\r
+       size_t                                  base_buffer_depth;\r
+       \r
+       configuration()\r
+               : device_index(1)\r
+               , embedded_audio(false)\r
+               , audio_layout(core::default_channel_layout_repository().get_by_name(L"STEREO"))\r
+               , keyer(default_keyer)\r
+               , latency(default_latency)\r
+               , key_only(false)\r
+               , base_buffer_depth(3)\r
+       {\r
+       }\r
+       \r
+       size_t buffer_depth() const\r
+       {\r
+               return base_buffer_depth + (latency == low_latency ? 0 : 1) + (embedded_audio ? 1 : 0);\r
+       }\r
+\r
+       int num_out_channels() const\r
+       {\r
+               if (audio_layout.num_channels <= 2)\r
+                       return 2;\r
+               \r
+               if (audio_layout.num_channels <= 8)\r
+                       return 8;\r
+\r
+               return 16;\r
+       }\r
+};\r
+\r
+static void set_latency(\r
+               const CComQIPtr<IDeckLinkConfiguration>& config,\r
+               configuration::latency_t latency,\r
+               const std::wstring& print)\r
+{              \r
+       if (latency == configuration::low_latency)\r
+       {\r
+               config->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);\r
+               CASPAR_LOG(info) << print << L" Enabled low-latency mode.";\r
+       }\r
+       else if (latency == configuration::normal_latency)\r
+       {                       \r
+               config->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);\r
+               CASPAR_LOG(info) << print << L" Disabled low-latency mode.";\r
+       }\r
+}\r
+\r
+static void set_keyer(\r
+               const CComQIPtr<IDeckLinkAttributes>& attributes,\r
+               const CComQIPtr<IDeckLinkKeyer>& decklink_keyer,\r
+               configuration::keyer_t keyer,\r
+               const std::wstring& print)\r
+{\r
+       if (keyer == configuration::internal_keyer) \r
+       {\r
+               BOOL value = true;\r
+               if (SUCCEEDED(attributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &value)) && !value)\r
+                       CASPAR_LOG(error) << print << L" Failed to enable internal keyer.";     \r
+               else if (FAILED(decklink_keyer->Enable(FALSE)))                 \r
+                       CASPAR_LOG(error) << print << L" Failed to enable internal keyer.";                     \r
+               else if (FAILED(decklink_keyer->SetLevel(255)))                 \r
+                       CASPAR_LOG(error) << print << L" Failed to set key-level to max.";\r
+               else\r
+                       CASPAR_LOG(info) << print << L" Enabled internal keyer.";               \r
+       }\r
+       else if (keyer == configuration::external_keyer)\r
+       {\r
+               BOOL value = true;\r
+               if (SUCCEEDED(attributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &value)) && !value)\r
+                       CASPAR_LOG(error) << print << L" Failed to enable external keyer.";     \r
+               else if (FAILED(decklink_keyer->Enable(TRUE)))                  \r
+                       CASPAR_LOG(error) << print << L" Failed to enable external keyer.";     \r
+               else if (FAILED(decklink_keyer->SetLevel(255)))                 \r
+                       CASPAR_LOG(error) << print << L" Failed to set key-level to max.";\r
+               else\r
+                       CASPAR_LOG(info) << print << L" Enabled external keyer.";                       \r
+       }\r
+}\r
+\r
+class reference_signal_detector\r
+{\r
+       CComQIPtr<IDeckLinkOutput> output_;\r
+       BMDReferenceStatus last_reference_status_;\r
+public:\r
+       reference_signal_detector(const CComQIPtr<IDeckLinkOutput>& output)\r
+               : output_(output)\r
+               , last_reference_status_(static_cast<BMDReferenceStatus>(-1))\r
+       {\r
+       }\r
+\r
+       template<typename Print>\r
+       void detect_change(const Print& print)\r
+       {\r
+               BMDReferenceStatus reference_status;\r
+\r
+               if (output_->GetReferenceStatus(&reference_status) != S_OK)\r
+               {\r
+                       CASPAR_LOG(error) << print() << L" Reference signal: failed while querying status";\r
+               }\r
+               else if (reference_status != last_reference_status_)\r
+               {\r
+                       last_reference_status_ = reference_status;\r
+\r
+                       if (reference_status == 0)\r
+                               CASPAR_LOG(info) << print() << L" Reference signal: not detected.";\r
+                       else if (reference_status & bmdReferenceNotSupportedByHardware)\r
+                               CASPAR_LOG(info) << print() << L" Reference signal: not supported by hardware.";\r
+                       else if (reference_status & bmdReferenceLocked)\r
+                               CASPAR_LOG(info) << print() << L" Reference signal: locked.";\r
+                       else\r
+                               CASPAR_LOG(info) << print() << L" Reference signal: Unhandled enum bitfield: " << reference_status;\r
+               }\r
+       }\r
+};\r
+\r
 }}
\ No newline at end of file
index a84f3dd82ac9094ecde0947c746d0df2364fe76b..f628c50bcb5781db778b3046c3091ee9d49b5fe7 100644 (file)
                 <keyer>external [external|internal|default]</keyer>\r
                 <key-only>false [true|false]</key-only>\r
                 <buffer-depth>3 [1..]</buffer-depth>\r
-            </decklink> \r
+            </decklink>\r
+            <blocking-decklink>\r
+                <device>[1..]</device>\r
+                <embedded-audio>false [true|false]</embedded-audio>\r
+                <channel-layout>stereo [mono|stereo|dts|dolbye|dolbydigital|smpte|passthru]</channel-layout>\r
+                <keyer>external [external|internal|default]</keyer>\r
+                <key-only>false [true|false]</key-only>\r
+            </blocking-decklink>\r
             <bluefish>\r
                 <device>[1..]</device>\r
                 <embedded-audio>false [true|false]</embedded-audio>\r
index a781482b503c7dc992bcaceace1841d44222ae99..8173eecb1dfe5eeede4beb13419eeed5999641f0 100644 (file)
@@ -51,6 +51,7 @@
 #include <modules/oal/consumer/oal_consumer.h>\r
 #include <modules/bluefish/consumer/bluefish_consumer.h>\r
 #include <modules/decklink/consumer/decklink_consumer.h>\r
+#include <modules/decklink/consumer/blocking_decklink_consumer.h>\r
 #include <modules/ogl/consumer/ogl_consumer.h>\r
 #include <modules/ffmpeg/consumer/ffmpeg_consumer.h>\r
 \r
@@ -213,6 +214,8 @@ struct server::implementation : boost::noncopyable
                                        on_consumer(bluefish::create_consumer(xml_consumer.second));                                    \r
                                else if (name == L"decklink")                                   \r
                                        on_consumer(decklink::create_consumer(xml_consumer.second));                            \r
+                               else if (name == L"blocking-decklink")\r
+                                       on_consumer(decklink::create_blocking_consumer(xml_consumer.second));                           \r
                                else if (name == L"file" || name == L"stream")                                  \r
                                        on_consumer(ffmpeg::create_consumer(xml_consumer.second));                                              \r
                                else if (name == L"system-audio")\r