\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
+++ /dev/null
-/*
-* 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();
-}
-
-}}
+++ /dev/null
-/*
-* 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_;
-};
-
-}}
</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
<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
\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
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
{\r
is_running_ = true;\r
current_presentation_delay_ = 0;\r
+ scheduled_frames_completed_ = 0;\r
\r
video_frame_buffer_.set_capacity(1);\r
\r
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
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
{\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
\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
\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
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
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
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
\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
{\r
internal_keyer,\r
external_keyer,\r
+ external_separate_device_keyer,\r
default_keyer\r
};\r
\r
};\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
\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
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
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
<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
#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
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