]> git.sesse.net Git - casparcg/commitdiff
- Enabled a single instance of decklink_consumer to manage a separate key output...
authorHelge Norberg <helge.norberg@gmail.com>
Tue, 10 Jun 2014 11:40:48 +0000 (13:40 +0200)
committerHelge Norberg <helge.norberg@gmail.com>
Tue, 10 Jun 2014 11:40:48 +0000 (13:40 +0200)
  <keyer>external_separate_device</keyer>
  the default is that the key device will be fill device + 1 but can be overridden via
  <key-device>[1..]</key-device>

- Removed synchronizing_consumer.

casparcg.sln
core/consumer/synchronizing/synchronizing_consumer.cpp [deleted file]
core/consumer/synchronizing/synchronizing_consumer.h [deleted file]
core/core.vcxproj
core/core.vcxproj.filters
modules/decklink/consumer/decklink_consumer.cpp
modules/decklink/util/util.h
shell/casparcg.config
shell/server.cpp

index 879216d5b3fa8bee0575ce8d179cdcf60699c5f4..837bbd1daf0c3290583a580287de81e24b34d992 100644 (file)
@@ -1,6 +1,6 @@
 \r
 Microsoft Visual Studio Solution File, Format Version 12.00\r
-# Visual Studio 2012\r
+# Visual Studio 2010\r
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core", "core\core.vcxproj", "{79388C20-6499-4BF6-B8B9-D8C33D7D4DDD}"\r
 EndProject\r
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shell", "shell\shell.vcxproj", "{8C26C94F-8092-4769-8D84-DEA479721C5B}"\r
diff --git a/core/consumer/synchronizing/synchronizing_consumer.cpp b/core/consumer/synchronizing/synchronizing_consumer.cpp
deleted file mode 100644 (file)
index 676f46a..0000000
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
-* Copyright (c) 2011 Sveriges Television AB <info@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: Helge Norberg, helge.norberg@svt.se
-*/
-
-#include "../../StdAfx.h"
-
-#include "synchronizing_consumer.h"
-
-#include <common/log/log.h>
-#include <common/diagnostics/graph.h>
-#include <common/concurrency/future_util.h>
-
-#include <core/video_format.h>
-
-#include <boost/range/adaptor/transformed.hpp>
-#include <boost/range/algorithm/min_element.hpp>
-#include <boost/range/algorithm/max_element.hpp>
-#include <boost/range/algorithm/for_each.hpp>
-#include <boost/range/algorithm/count_if.hpp>
-#include <boost/range/numeric.hpp>
-#include <boost/algorithm/string/join.hpp>
-#include <boost/thread/future.hpp>
-
-#include <functional>
-#include <vector>
-#include <queue>
-#include <utility>
-
-#include <tbb/atomic.h>
-
-namespace caspar { namespace core {
-
-using namespace boost::adaptors;
-
-class delegating_frame_consumer : public frame_consumer
-{
-       safe_ptr<frame_consumer> consumer_;
-public:
-       delegating_frame_consumer(const safe_ptr<frame_consumer>& consumer)
-               : consumer_(consumer)
-       {
-       }
-
-       frame_consumer& get_delegate()
-       {
-               return *consumer_;
-       }
-
-       const frame_consumer& get_delegate() const
-       {
-               return *consumer_;
-       }
-       
-       virtual void initialize(
-                       const video_format_desc& format_desc, int channel_index) override
-       {
-               get_delegate().initialize(format_desc, channel_index);
-       }
-
-       virtual int64_t presentation_frame_age_millis() const
-       {
-               return get_delegate().presentation_frame_age_millis();
-       }
-
-       virtual boost::unique_future<bool> send(
-                       const safe_ptr<read_frame>& frame) override
-       {               
-               return get_delegate().send(frame);
-       }
-
-       virtual std::wstring print() const override
-       {
-               return get_delegate().print();
-       }
-
-       virtual boost::property_tree::wptree info() const override
-       {
-               return get_delegate().info();
-       }
-
-       virtual bool has_synchronization_clock() const override
-       {
-               return get_delegate().has_synchronization_clock();
-       }
-
-       virtual size_t buffer_depth() const override
-       {
-               return get_delegate().buffer_depth();
-       }
-
-       virtual int index() const override
-       {
-               return get_delegate().index();
-       }
-};
-
-const std::vector<int>& diag_colors()
-{
-       static std::vector<int> colors = boost::assign::list_of<int>
-                       (diagnostics::color(0.0f, 0.6f, 0.9f))
-                       (diagnostics::color(0.6f, 0.3f, 0.3f))
-                       (diagnostics::color(0.3f, 0.6f, 0.3f))
-                       (diagnostics::color(0.4f, 0.3f, 0.8f))
-                       (diagnostics::color(0.9f, 0.9f, 0.5f))
-                       (diagnostics::color(0.2f, 0.9f, 0.9f));
-
-       return colors;
-}
-
-class buffering_consumer_adapter : public delegating_frame_consumer
-{
-       std::queue<safe_ptr<read_frame>>        buffer_;
-       tbb::atomic<size_t>                                     buffered_;
-       tbb::atomic<int64_t>                            duplicate_next_;
-public:
-       buffering_consumer_adapter(const safe_ptr<frame_consumer>& consumer)
-               : delegating_frame_consumer(consumer)
-       {
-               buffered_ = 0;
-               duplicate_next_ = 0;
-       }
-
-       boost::unique_future<bool> consume_one()
-       {
-               if (!buffer_.empty())
-               {
-                       buffer_.pop();
-                       --buffered_;
-               }
-
-               return get_delegate().send(buffer_.front());
-       }
-
-       virtual boost::unique_future<bool> send(
-                       const safe_ptr<read_frame>& frame) override
-       {
-               if (duplicate_next_)
-               {
-                       --duplicate_next_;
-               }
-               else if (!buffer_.empty())
-               {
-                       buffer_.pop();
-                       --buffered_;
-               }
-
-               buffer_.push(frame);
-               ++buffered_;
-
-               return get_delegate().send(buffer_.front());
-       }
-
-       void duplicate_next(int64_t to_duplicate)
-       {
-               duplicate_next_ += to_duplicate;
-       }
-
-       size_t num_buffered() const
-       {
-               return buffered_ - 1;
-       }
-
-       virtual std::wstring print() const override
-       {
-               return L"buffering[" + get_delegate().print() + L"]";
-       }
-
-       virtual boost::property_tree::wptree info() const override
-       {
-               boost::property_tree::wptree info;
-
-               info.add(L"type", L"buffering-consumer-adapter");
-               info.add_child(L"consumer", get_delegate().info());
-               info.add(L"buffered-frames", num_buffered());
-
-               return info;
-       }
-};
-
-static const uint64_t MAX_BUFFERED_OUT_OF_MEMORY_GUARD = 5;
-
-struct synchronizing_consumer::implementation
-{
-private:
-       std::vector<safe_ptr<buffering_consumer_adapter>>       consumers_;
-       size_t                                                                                          buffer_depth_;
-       bool                                                                                            has_synchronization_clock_;
-       std::vector<boost::unique_future<bool>>                         results_;
-       boost::promise<bool>                                                            promise_;
-       video_format_desc                                                                       format_desc_;
-       safe_ptr<diagnostics::graph>                                            graph_;
-       int64_t                                                                                         grace_period_;
-       tbb::atomic<int64_t>                                                            current_diff_;
-public:
-       implementation(const std::vector<safe_ptr<frame_consumer>>& consumers)
-               : grace_period_(0)
-       {
-               BOOST_FOREACH(auto& consumer, consumers)
-                       consumers_.push_back(make_safe<buffering_consumer_adapter>(consumer));
-
-               current_diff_ = 0;
-               auto buffer_depths = consumers | transformed(std::mem_fn(&frame_consumer::buffer_depth));
-               std::vector<size_t> depths(buffer_depths.begin(), buffer_depths.end());
-               buffer_depth_ = *boost::max_element(depths);
-               has_synchronization_clock_ = boost::count_if(consumers, std::mem_fn(&frame_consumer::has_synchronization_clock)) > 0;
-
-               diagnostics::register_graph(graph_);
-       }
-
-       boost::unique_future<bool> send(const safe_ptr<read_frame>& frame)
-       {
-               results_.clear();
-
-               BOOST_FOREACH(auto& consumer, consumers_)
-                       results_.push_back(consumer->send(frame));
-
-               promise_ = boost::promise<bool>();
-               promise_.set_wait_callback(std::function<void(boost::promise<bool>&)>([this](boost::promise<bool>& promise)
-               {
-                       BOOST_FOREACH(auto& result, results_)
-                       {
-                               result.get();
-                       }
-
-                       auto frame_ages = consumers_ | transformed(std::mem_fn(&frame_consumer::presentation_frame_age_millis));
-                       std::vector<int64_t> ages(frame_ages.begin(), frame_ages.end());
-                       auto max_age_iter = boost::max_element(ages);
-                       auto min_age_iter = boost::min_element(ages);
-                       int64_t min_age = *min_age_iter;
-                       
-                       if (min_age == 0)
-                       {
-                               // One of the consumers have yet no measurement, wait until next 
-                               // frame until we make any assumptions.
-                               promise.set_value(true);
-                               return;
-                       }
-
-                       int64_t max_age = *max_age_iter;
-                       int64_t age_diff = max_age - min_age;
-
-                       current_diff_ = age_diff;
-
-                       for (unsigned i = 0; i < ages.size(); ++i)
-                               graph_->set_value(
-                                               narrow(consumers_[i]->print()),
-                                               static_cast<double>(ages[i]) / *max_age_iter);
-
-                       bool grace_period_over = grace_period_ == 1;
-
-                       if (grace_period_)
-                               --grace_period_;
-
-                       if (grace_period_ == 0)
-                       {
-                               int64_t frame_duration = static_cast<int64_t>(1000 / format_desc_.fps);
-
-                               if (age_diff >= frame_duration)
-                               {
-                                       CASPAR_LOG(info) << print() << L" Consumers not in sync. min: " << min_age << L" max: " << max_age;
-
-                                       auto index = min_age_iter - ages.begin();
-                                       auto to_duplicate = age_diff / frame_duration;
-                                       auto& consumer = *consumers_.at(index);
-
-                                       auto currently_buffered = consumer.num_buffered();
-
-                                       if (currently_buffered + to_duplicate > MAX_BUFFERED_OUT_OF_MEMORY_GUARD)
-                                       {
-                                               CASPAR_LOG(info) << print() << L" Protecting from out of memory. Duplicating less frames than calculated";
-
-                                               to_duplicate = MAX_BUFFERED_OUT_OF_MEMORY_GUARD - currently_buffered;
-                                       }
-
-                                       consumer.duplicate_next(to_duplicate);
-
-                                       grace_period_ = 10 + to_duplicate + buffer_depth_;
-                               }
-                               else if (grace_period_over)
-                               {
-                                       CASPAR_LOG(info) << print() << L" Consumers resynced. min: " << min_age << L" max: " << max_age;
-                               }
-                       }
-
-                       blocking_consume_unnecessarily_buffered();
-
-                       promise.set_value(true);
-               }));
-
-               return promise_.get_future();
-       }
-
-       void blocking_consume_unnecessarily_buffered()
-       {
-               auto buffered = consumers_ | transformed(std::mem_fn(&buffering_consumer_adapter::num_buffered));
-               std::vector<size_t> num_buffered(buffered.begin(), buffered.end());
-               auto min_buffered = *boost::min_element(num_buffered);
-
-               if (min_buffered)
-                       CASPAR_LOG(info) << print() << L" " << min_buffered 
-                                       << L" frames unnecessarily buffered. Consuming and letting channel pause during that time.";
-
-               while (min_buffered)
-               {
-                       std::vector<boost::unique_future<bool>> results;
-
-                       BOOST_FOREACH(auto& consumer, consumers_)
-                               results.push_back(consumer->consume_one());
-
-                       BOOST_FOREACH(auto& result, results)
-                               result.get();
-
-                       --min_buffered;
-               }
-       }
-
-       void initialize(const video_format_desc& format_desc, int channel_index)
-       {
-               for (size_t i = 0; i < consumers_.size(); ++i)
-               {
-                       auto& consumer = consumers_.at(i);
-                       consumer->initialize(format_desc, channel_index); 
-                       graph_->set_color(
-                                       narrow(consumer->print()),
-                                       diag_colors().at(i % diag_colors().size()));
-               }
-
-               graph_->set_text(print());
-               format_desc_ = format_desc;
-       }
-
-       int64_t presentation_frame_age_millis() const
-       {
-               int64_t result = 0;
-
-               BOOST_FOREACH(auto& consumer, consumers_)
-                       result = std::max(result, consumer->presentation_frame_age_millis());
-
-               return result;
-       }
-
-       std::wstring print() const
-       {
-               return L"synchronized[" + boost::algorithm::join(consumers_ | transformed(std::mem_fn(&frame_consumer::print)), L"|") +  L"]";
-       }
-
-       boost::property_tree::wptree info() const
-       {
-               boost::property_tree::wptree info;
-
-               info.add(L"type", L"synchronized-consumer");
-
-               BOOST_FOREACH(auto& consumer, consumers_)
-                       info.add_child(L"consumer", consumer->info());
-
-               info.add(L"age-diff", current_diff_);
-
-               return info;
-       }
-
-       bool has_synchronization_clock() const
-       {
-               return has_synchronization_clock_;
-       }
-
-       size_t buffer_depth() const
-       {
-               return buffer_depth_;
-       }
-
-       int index() const
-       {
-               return boost::accumulate(consumers_ | transformed(std::mem_fn(&frame_consumer::index)), 10000);
-       }
-};
-
-synchronizing_consumer::synchronizing_consumer(const std::vector<safe_ptr<frame_consumer>>& consumers)
-       : impl_(new implementation(consumers))
-{
-}
-
-boost::unique_future<bool> synchronizing_consumer::send(const safe_ptr<read_frame>& frame)
-{
-       return impl_->send(frame);
-}
-
-void synchronizing_consumer::initialize(const video_format_desc& format_desc, int channel_index)
-{
-       impl_->initialize(format_desc, channel_index);
-}
-
-int64_t synchronizing_consumer::presentation_frame_age_millis() const
-{
-       return impl_->presentation_frame_age_millis();
-}
-
-std::wstring synchronizing_consumer::print() const
-{
-       return impl_->print();
-}
-
-boost::property_tree::wptree synchronizing_consumer::info() const
-{
-       return impl_->info();
-}
-
-bool synchronizing_consumer::has_synchronization_clock() const
-{
-       return impl_->has_synchronization_clock();
-}
-
-size_t synchronizing_consumer::buffer_depth() const
-{
-       return impl_->buffer_depth();
-}
-
-int synchronizing_consumer::index() const
-{
-       return impl_->index();
-}
-
-}}
diff --git a/core/consumer/synchronizing/synchronizing_consumer.h b/core/consumer/synchronizing/synchronizing_consumer.h
deleted file mode 100644 (file)
index 022537b..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
-* Copyright (c) 2011 Sveriges Television AB <info@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: Helge Norberg, helge.norberg@svt.se
-*/
-
-#pragma once
-
-#include "../frame_consumer.h"
-
-#include <vector>
-#include <memory>
-
-namespace caspar { namespace core {
-
-class read_frame;
-struct video_format_desc;
-
-class synchronizing_consumer : public frame_consumer
-{
-public:
-       synchronizing_consumer(
-                       const std::vector<safe_ptr<frame_consumer>>& consumers);
-       virtual boost::unique_future<bool> send(
-                       const safe_ptr<read_frame>& frame) override;
-       virtual void initialize(
-                       const video_format_desc& format_desc, int channel_index) override;
-       virtual int64_t presentation_frame_age_millis() const override;
-       virtual std::wstring print() const override;
-       virtual boost::property_tree::wptree info() const override;
-       virtual bool has_synchronization_clock() const override;
-       virtual size_t buffer_depth() const override;
-       virtual int index() const override;
-private:
-       struct implementation;
-       std::unique_ptr<implementation> impl_;
-};
-
-}}
index 6dafa35cfdb0b008b98b90e46420f3f7347b4c8e..b0ec10c622b1ab83c97eedbf5a5b2a8ceb6609e7 100644 (file)
   </ItemDefinitionGroup>\r
   <ItemGroup>\r
     <ClInclude Include="consumer\write_frame_consumer.h" />\r
-    <ClInclude Include="consumer\synchronizing\synchronizing_consumer.h" />\r
     <ClInclude Include="mixer\audio\audio_util.h" />\r
     <ClInclude Include="mixer\gpu\fence.h" />\r
     <ClInclude Include="mixer\gpu\shader.h" />\r
     <ClInclude Include="StdAfx.h" />\r
   </ItemGroup>\r
   <ItemGroup>\r
-    <ClCompile Include="consumer\synchronizing\synchronizing_consumer.cpp">\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)'=='Develop|Win32'">../../StdAfx.h</PrecompiledHeaderFile>\r
-      <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">../../StdAfx.h</PrecompiledHeaderFile>\r
-    </ClCompile>\r
     <ClCompile Include="mixer\audio\audio_util.cpp">\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">../../StdAfx.h</PrecompiledHeaderFile>\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../../StdAfx.h</PrecompiledHeaderFile>\r
index c1bc8689be0ff58a5c583944c686d72783b3339f..2732d90fcc93dda4b1b608e6d8619da1790242e6 100644 (file)
@@ -46,9 +46,6 @@
     <Filter Include="source\producer\layer">\r
       <UniqueIdentifier>{4e098e4c-7467-446f-990d-d4486d179edb}</UniqueIdentifier>\r
     </Filter>\r
-    <Filter Include="source\consumer\synchronizing">\r
-      <UniqueIdentifier>{19ddc31c-5865-46d5-a8a6-a96d6fa1ffc7}</UniqueIdentifier>\r
-    </Filter>\r
     <Filter Include="source\parameters">\r
       <UniqueIdentifier>{d04737a6-96b2-40cd-b1e7-e90b69006cd1}</UniqueIdentifier>\r
     </Filter>\r
     <ClInclude Include="monitor\monitor.h">\r
       <Filter>source\monitor</Filter>\r
     </ClInclude>\r
-    <ClInclude Include="consumer\synchronizing\synchronizing_consumer.h">\r
-      <Filter>source\consumer\synchronizing</Filter>\r
-    </ClInclude>\r
     <ClInclude Include="parameters\parameters.h">\r
       <Filter>source\parameters</Filter>\r
     </ClInclude>\r
     <ClCompile Include="mixer\audio\audio_util.cpp">\r
       <Filter>source\mixer\audio</Filter>\r
     </ClCompile>\r
-    <ClCompile Include="consumer\synchronizing\synchronizing_consumer.cpp">\r
-      <Filter>source\consumer\synchronizing</Filter>\r
-    </ClCompile>\r
     <ClCompile Include="parameters\parameters.cpp">\r
       <Filter>source\parameters</Filter>\r
     </ClCompile>\r
index 8b30bd2571e3f312a7a4a2087ff72839f402b2dc..94109e912a6a6070295e2b911bf3066000e75c3c 100644 (file)
 \r
 namespace caspar { namespace decklink { \r
 \r
+struct key_video_context\r
+               : public IDeckLinkVideoOutputCallback, boost::noncopyable\r
+{\r
+       CComPtr<IDeckLink>                                                                              decklink_;\r
+       CComQIPtr<IDeckLinkOutput>                                                              output_;\r
+       CComQIPtr<IDeckLinkKeyer>                                                               keyer_;\r
+       CComQIPtr<IDeckLinkAttributes>                                                  attributes_;\r
+       CComQIPtr<IDeckLinkConfiguration>                                               configuration_;\r
+       const std::unique_ptr<thread_safe_decklink_allocator>&  allocator_;\r
+       tbb::atomic<int64_t>                                                                    current_presentation_delay_;\r
+       tbb::atomic<int64_t>                                                                    scheduled_frames_completed_;\r
+\r
+       key_video_context(\r
+                       const configuration& config,\r
+                       const std::wstring& print,\r
+                       const std::unique_ptr<thread_safe_decklink_allocator>& allocator)\r
+               : decklink_(get_device(config.key_device_index()))\r
+               , output_(decklink_)\r
+               , keyer_(decklink_)\r
+               , attributes_(decklink_)\r
+               , configuration_(decklink_)\r
+               , allocator_(allocator)\r
+       {\r
+               current_presentation_delay_ = 0;\r
+               scheduled_frames_completed_ = 0;\r
+\r
+               set_latency(configuration_, config.latency, print);\r
+               set_keyer(attributes_, keyer_, config.keyer, print);\r
+\r
+               if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
+                       BOOST_THROW_EXCEPTION(caspar_exception() \r
+                                                                       << msg_info(narrow(print) + " Failed to set key playback completion callback.")\r
+                                                                       << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));\r
+       }\r
+\r
+       template<typename Print>\r
+       void enable_video(BMDDisplayMode display_mode, const Print& print)\r
+       {\r
+               if (allocator_)\r
+               {\r
+                       if (FAILED(output_->SetVideoOutputFrameMemoryAllocator(allocator_.get())))\r
+                               BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set key custom memory allocator."));\r
+               }\r
+\r
+               if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) \r
+                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable key video output."));\r
+               \r
+               if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
+                       BOOST_THROW_EXCEPTION(caspar_exception() \r
+                                                                       << msg_info(narrow(print()) + " Failed to set key playback completion callback.")\r
+                                                                       << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));\r
+       }\r
+\r
+       virtual ~key_video_context()\r
+       {\r
+               if (output_) \r
+               {\r
+                       output_->StopScheduledPlayback(0, nullptr, 0);\r
+                       output_->DisableVideoOutput();\r
+               }\r
+       }\r
+       \r
+       STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}\r
+       STDMETHOD_(ULONG, AddRef())                                     {return 1;}\r
+       STDMETHOD_(ULONG, Release())                            {return 1;}\r
+\r
+       STDMETHOD(ScheduledPlaybackHasStopped())\r
+       {\r
+               return S_OK;\r
+       }\r
+\r
+       STDMETHOD(ScheduledFrameCompleted(\r
+                       IDeckLinkVideoFrame* completed_frame,\r
+                       BMDOutputFrameCompletionResult result))\r
+       {\r
+               auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);\r
+               current_presentation_delay_ = dframe->get_age_millis();\r
+               ++scheduled_frames_completed_;\r
+\r
+               // Let the fill callback keep the pace, so no scheduling here.\r
+\r
+               return S_OK;\r
+       }\r
+};\r
+\r
 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
 {\r
        const int                                                       channel_index_;\r
@@ -88,6 +173,8 @@ struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLink
        reference_signal_detector reference_signal_detector_;\r
 \r
        tbb::atomic<int64_t>                            current_presentation_delay_;\r
+       tbb::atomic<int64_t>                            scheduled_frames_completed_;\r
+       std::unique_ptr<key_video_context>      key_context_;\r
 \r
 public:\r
        decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index) \r
@@ -109,6 +196,7 @@ public:
        {\r
                is_running_ = true;\r
                current_presentation_delay_ = 0;\r
+               scheduled_frames_completed_ = 0;\r
                                \r
                video_frame_buffer_.set_capacity(1);\r
 \r
@@ -120,12 +208,21 @@ public:
                else\r
                        audio_frame_buffer_.set_capacity(1);\r
 \r
+               if (config.keyer == configuration::external_separate_device_keyer)\r
+                       key_context_.reset(new key_video_context(config, print(), allocator_));\r
+\r
                graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));   \r
                graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));\r
                graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));\r
                graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));\r
                graph_->set_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));\r
                graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));\r
+\r
+               if (key_context_)\r
+               {\r
+                       graph_->set_color("key-offset", diagnostics::color(1.0f, 0.0f, 0.0f));\r
+               }\r
+\r
                graph_->set_text(print());\r
                diagnostics::register_graph(graph_);\r
                \r
@@ -180,24 +277,30 @@ public:
                        allocator_.reset(new thread_safe_decklink_allocator(print()));\r
 \r
                        if (FAILED(output_->SetVideoOutputFrameMemoryAllocator(allocator_.get())))\r
-                               BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set custom memory allocator."));\r
+                               BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set fill custom memory allocator."));\r
 \r
                        CASPAR_LOG(info) << print() << L" Using custom allocator.";\r
                }\r
 \r
                if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) \r
-                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));\r
+                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable fill video output."));\r
                \r
                if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
                        BOOST_THROW_EXCEPTION(caspar_exception() \r
-                                                                       << msg_info(narrow(print()) + " Failed to set playback completion callback.")\r
+                                                                       << msg_info(narrow(print()) + " Failed to set fill playback completion callback.")\r
                                                                        << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));\r
+\r
+               if (key_context_)\r
+                       key_context_->enable_video(display_mode, [this]() { return print(); });\r
        }\r
 \r
        void start_playback()\r
        {\r
                if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) \r
-                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
+                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule fill playback."));\r
+\r
+               if(key_context_ && FAILED(key_context_->output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) \r
+                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule key playback."));\r
        }\r
        \r
        STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}\r
@@ -221,6 +324,15 @@ public:
                {\r
                        auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);\r
                        current_presentation_delay_ = dframe->get_age_millis();\r
+                       ++scheduled_frames_completed_;\r
+\r
+                       if (key_context_)\r
+                               graph_->set_value(\r
+                                               "key-offset",\r
+                                               static_cast<double>(\r
+                                                               scheduled_frames_completed_\r
+                                                               - key_context_->scheduled_frames_completed_)\r
+                                               * 0.1 + 0.5);\r
 \r
                        if(result == bmdOutputFrameDisplayedLate)\r
                        {\r
@@ -350,9 +462,16 @@ public:
                        \r
        void schedule_next_video(const safe_ptr<core::read_frame>& frame)\r
        {\r
-               CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));\r
-               if(FAILED(output_->ScheduleVideoFrame(frame2, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))\r
-                       CASPAR_LOG(error) << print() << L" Failed to schedule video.";\r
+               if (key_context_)\r
+               {\r
+                       CComPtr<IDeckLinkVideoFrame> key_frame(new decklink_frame(frame, format_desc_, true));\r
+                       if(FAILED(key_context_->output_->ScheduleVideoFrame(key_frame, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))\r
+                               CASPAR_LOG(error) << print() << L" Failed to schedule key video.";\r
+               }\r
+\r
+               CComPtr<IDeckLinkVideoFrame> fill_frame(new decklink_frame(frame, format_desc_, config_.key_only));\r
+               if(FAILED(output_->ScheduleVideoFrame(fill_frame, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))\r
+                       CASPAR_LOG(error) << print() << L" Failed to schedule fill video.";\r
 \r
                video_scheduled_ += format_desc_.duration;\r
 \r
@@ -400,8 +519,16 @@ public:
        \r
        std::wstring print() const\r
        {\r
-               return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +\r
-                       boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";\r
+               if (config_.keyer == configuration::external_separate_device_keyer)\r
+                       return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +\r
+                               boost::lexical_cast<std::wstring>(config_.device_index) +\r
+                               L"&&" +\r
+                               boost::lexical_cast<std::wstring>(config_.key_device_index()) +\r
+                               L"|" +\r
+                               format_desc_.name + L"]";\r
+               else\r
+                       return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +\r
+                               boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";\r
        }\r
 };\r
 \r
@@ -459,7 +586,13 @@ public:
                info.add(L"type", L"decklink-consumer");\r
                info.add(L"key-only", config_.key_only);\r
                info.add(L"device", config_.device_index);\r
-               info.add(L"low-latency", config_.low_latency);\r
+\r
+               if (config_.keyer == configuration::external_separate_device_keyer)\r
+               {\r
+                       info.add(L"key-device", config_.key_device_index());\r
+               }\r
+\r
+               info.add(L"low-latency", config_.latency == configuration::low_latency);\r
                info.add(L"embedded-audio", config_.embedded_audio);\r
                info.add(L"presentation-frame-age", presentation_frame_age_millis());\r
                //info.add(L"internal-key", config_.internal_key);\r
@@ -492,10 +625,12 @@ safe_ptr<core::frame_consumer> create_consumer(const core::parameters& params)
        if(params.size() > 1)\r
                config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);\r
        \r
-       if(std::find(params.begin(), params.end(), L"INTERNAL_KEY")                     != params.end())\r
+       if(std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end())\r
                config.keyer = configuration::internal_keyer;\r
-       else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY")        != params.end())\r
+       else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end())\r
                config.keyer = configuration::external_keyer;\r
+       else if(std::find(params.begin(), params.end(), L"EXTERNAL_SEPARATE_DEVICE_KEY") != params.end())\r
+               config.keyer = configuration::external_separate_device_keyer;\r
        else\r
                config.keyer = configuration::default_keyer;\r
 \r
@@ -519,6 +654,8 @@ safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptre
                config.keyer = configuration::external_keyer;\r
        else if(keyer == L"internal")\r
                config.keyer = configuration::internal_keyer;\r
+       else if(keyer == L"external_separate_device")\r
+               config.keyer = configuration::external_separate_device_keyer;\r
 \r
        auto latency = ptree.get(L"latency", L"normal");\r
        if(latency == L"low")\r
@@ -528,6 +665,7 @@ safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptre
 \r
        config.key_only                         = ptree.get(L"key-only",                        config.key_only);\r
        config.device_index                     = ptree.get(L"device",                          config.device_index);\r
+       config.key_device_idx           = ptree.get(L"key-device",                      config.key_device_idx);\r
        config.embedded_audio           = ptree.get(L"embedded-audio",          config.embedded_audio);\r
        config.base_buffer_depth        = ptree.get(L"buffer-depth",            config.base_buffer_depth);\r
        config.custom_allocator         = ptree.get(L"custom-allocator",        config.custom_allocator);\r
index d4abe5cb66965188080b1fd1dcdfa0ef94aa996c..a5b3aa5d9548d33fc1f0c7637453c76197b5be3d 100644 (file)
@@ -300,6 +300,7 @@ struct configuration
        {\r
                internal_keyer,\r
                external_keyer,\r
+               external_separate_device_keyer,\r
                default_keyer\r
        };\r
 \r
@@ -311,6 +312,7 @@ struct configuration
        };\r
 \r
        size_t                                  device_index;\r
+       size_t                                  key_device_idx;\r
        bool                                    embedded_audio;\r
        core::channel_layout    audio_layout;\r
        keyer_t                                 keyer;\r
@@ -321,6 +323,7 @@ struct configuration
        \r
        configuration()\r
                : device_index(1)\r
+               , key_device_idx(0)\r
                , embedded_audio(false)\r
                , audio_layout(core::default_channel_layout_repository().get_by_name(L"STEREO"))\r
                , keyer(default_keyer)\r
@@ -336,6 +339,11 @@ struct configuration
                return base_buffer_depth + (latency == low_latency ? 0 : 1) + (embedded_audio ? 1 : 0);\r
        }\r
 \r
+       size_t key_device_index() const\r
+       {\r
+               return key_device_idx == 0 ? device_index + 1 : key_device_idx;\r
+       }\r
+\r
        int num_out_channels() const\r
        {\r
                if (audio_layout.num_channels <= 2)\r
@@ -371,7 +379,8 @@ static void set_keyer(
                configuration::keyer_t keyer,\r
                const std::wstring& print)\r
 {\r
-       if (keyer == configuration::internal_keyer) \r
+       if (keyer == configuration::internal_keyer\r
+                       || keyer == configuration::external_separate_device_keyer) \r
        {\r
                BOOL value = true;\r
                if (SUCCEEDED(attributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &value)) && !value)\r
index e33959199b4a1de53732eb9b1b5e804c904068a0..5d371ea5c420d7c9724e029d35928797a3a28e58 100644 (file)
         <consumers>\r
             <decklink>\r
                 <device>[1..]</device>\r
+                <key-device>device + 1 [1..]</key-device>\r
                 <embedded-audio>false [true|false]</embedded-audio>\r
                 <channel-layout>stereo [mono|stereo|dts|dolbye|dolbydigital|smpte|passthru]</channel-layout>\r
                 <latency>normal [normal|low|default]</latency>\r
-                <keyer>external [external|internal|default]</keyer>\r
+                <keyer>external [external|external_separate_device|internal|default]</keyer>\r
                 <key-only>false [true|false]</key-only>\r
                 <buffer-depth>3 [1..]</buffer-depth>\r
                 <custom-allocator>true [true|false]</custom-allocator>\r
                 <key-only>false [true|false]</key-only>\r
             </bluefish>\r
             <system-audio></system-audio>\r
-            <synchronizing>\r
-                ... consumer1\r
-                ... consumer2\r
-            </synchronizing>\r
             <screen>\r
                 <device>[0..]</device>\r
                 <aspect-ratio>default [default|4:3|16:9]</aspect-ratio>\r
index ccf65d9381264ebcf8e7ed2ab305c275d2e379a2..86b1736157d75f0902e11f410d723caec961e5a4 100644 (file)
@@ -35,7 +35,6 @@
 #include <core/video_channel.h>\r
 #include <core/producer/stage.h>\r
 #include <core/consumer/output.h>\r
-#include <core/consumer/synchronizing/synchronizing_consumer.h>\r
 #include <core/thumbnail_generator.h>\r
 #include <core/producer/media_info/media_info.h>\r
 #include <core/producer/media_info/media_info_repository.h>\r
@@ -275,10 +274,6 @@ struct server::implementation : boost::noncopyable
                                        on_consumer(ffmpeg::create_consumer(xml_consumer.second));                                              \r
                                else if (name == L"system-audio")\r
                                        on_consumer(oal::create_consumer());\r
-                               else if (name == L"synchronizing")\r
-                                       on_consumer(make_safe<core::synchronizing_consumer>(\r
-                                                       create_consumers<core::frame_consumer>(\r
-                                                                       xml_consumer.second)));\r
                                else if (name != L"<xmlcomment>")\r
                                        CASPAR_LOG(warning) << "Invalid consumer: " << widen(name);     \r
                        }\r