http://casparcg.com/forum/viewtopic.php?f=3&t=1453 for more information.\r
o ATI graphics card support. Static linking against SFML instead of dynamic.\r
Should inprove ATI card support. Needs testing.\r
+ o An attempt to improve output synchronization of consumers has been made. Use\r
+ for example:\r
+ \r
+ <consumers>\r
+ <synchronizing>\r
+ <decklink>\r
+ <device>1</device>\r
+ <embedded-audio>true</embedded-audio>\r
+ </decklink>\r
+ <decklink>\r
+ <device>2</device>\r
+ <key-only>true</key-only>\r
+ </decklink>\r
+ </synchronizing>\r
+ </consumers>\r
+ \r
+ to say that both decklink consumers should be in sync with each other.\r
+ Consider this experimental, so don't wrap everything in <synchronizing />\r
+ unless synchronization of consumer outputs is needed. For synchronization to\r
+ be effective both cards (or same if it is for example a Decklink Quad) must\r
+ have reference signal connected.\r
\r
Layer\r
-----\r
MIXER FILL, causing the image to be flipped.\r
o MIXER <ch> STRAIGHT_ALPHA_OUTPUT added to control whether to output straight\r
alpha or not.\r
+ o Added INFO <ch> DELAY and INFO <ch>-<layer> DELAY commands for showing some\r
+ delay measurements.\r
\r
OSC\r
---\r
consumer_->initialize(format_desc, channel_index);\r
}\r
\r
+ virtual int64_t presentation_frame_age_millis() const\r
+ {\r
+ return consumer_->presentation_frame_age_millis();\r
+ }\r
+\r
virtual boost::unique_future<bool> send(const safe_ptr<read_frame>& frame) override\r
{ \r
if(audio_cadence_.size() == 1)\r
{\r
virtual boost::unique_future<bool> send(const safe_ptr<read_frame>&) override { return caspar::wrap_as_future(false); }\r
virtual void initialize(const video_format_desc&, int) override{}\r
+ virtual int64_t presentation_frame_age_millis() const { return 0; }\r
virtual std::wstring print() const override {return L"empty";}\r
virtual bool has_synchronization_clock() const override {return false;}\r
virtual size_t buffer_depth() const override {return 0;};\r
\r
virtual boost::unique_future<bool> send(const safe_ptr<read_frame>& frame) = 0;\r
virtual void initialize(const video_format_desc& format_desc, int channel_index) = 0;\r
+ virtual int64_t presentation_frame_age_millis() const = 0;\r
virtual std::wstring print() const = 0;\r
virtual boost::property_tree::wptree info() const = 0;\r
virtual bool has_synchronization_clock() const {return true;}\r
high_prec_timer sync_timer_;\r
\r
boost::circular_buffer<safe_ptr<read_frame>> frames_;\r
+ std::map<int, int64_t> send_to_consumers_delays_;\r
\r
executor executor_;\r
\r
, executor_(L"output")\r
{\r
graph_->set_color("consume-time", diagnostics::color(1.0f, 0.4f, 0.0f, 0.8));\r
- } \r
- \r
+ }\r
+\r
void add(int index, safe_ptr<frame_consumer> consumer)\r
{ \r
remove(index);\r
if(it != consumers_.end())\r
{\r
old_consumer = it->second;\r
+ send_to_consumers_delays_.erase(it->first);\r
consumers_.erase(it);\r
}\r
}, high_priority);\r
{\r
CASPAR_LOG_CURRENT_EXCEPTION();\r
CASPAR_LOG(info) << print() << L" " << it->second->print() << L" Removed.";\r
+ send_to_consumers_delays_.erase(it->first);\r
consumers_.erase(it++);\r
}\r
}\r
frames_.clear();\r
});\r
}\r
+ \r
+ std::map<int, size_t> buffer_depths_snapshot() const\r
+ {\r
+ std::map<int, size_t> result;\r
+\r
+ BOOST_FOREACH(auto& consumer, consumers_)\r
+ result.insert(std::make_pair(\r
+ consumer.first,\r
+ consumer.second->buffer_depth()));\r
\r
- std::pair<size_t, size_t> minmax_buffer_depth() const\r
+ return std::move(result);\r
+ }\r
+\r
+ std::pair<size_t, size_t> minmax_buffer_depth(\r
+ const std::map<int, size_t>& buffer_depths) const\r
{ \r
if(consumers_.empty())\r
return std::make_pair(0, 0);\r
\r
- auto buffer_depths = consumers_ | \r
- boost::adaptors::map_values | // std::function is MSVC workaround\r
- boost::adaptors::transformed(std::function<int(const safe_ptr<frame_consumer>&)>([](const safe_ptr<frame_consumer>& x){return x->buffer_depth();})); \r
+ auto depths = buffer_depths | boost::adaptors::map_values; \r
\r
-\r
- return std::make_pair(*boost::range::min_element(buffer_depths), *boost::range::max_element(buffer_depths));\r
+ return std::make_pair(\r
+ *boost::range::min_element(depths),\r
+ *boost::range::max_element(depths));\r
}\r
\r
bool has_synchronization_clock() const\r
sync_timer_.tick(1.0/format_desc_.fps);\r
return;\r
}\r
- \r
- auto minmax = minmax_buffer_depth();\r
+ \r
+ auto buffer_depths = buffer_depths_snapshot();\r
+ auto minmax = minmax_buffer_depth(buffer_depths);\r
\r
frames_.set_capacity(minmax.second - minmax.first + 1);\r
frames_.push_back(input_frame);\r
for (auto it = consumers_.begin(); it != consumers_.end();)\r
{\r
auto consumer = it->second;\r
- auto frame = frames_.at(consumer->buffer_depth()-minmax.first);\r
+ auto frame = frames_.at(buffer_depths[it->first]-minmax.first);\r
+\r
+ send_to_consumers_delays_[it->first] = frame->get_age_millis();\r
\r
try\r
{\r
{\r
CASPAR_LOG_CURRENT_EXCEPTION();\r
CASPAR_LOG(error) << "Failed to recover consumer: " << consumer->print() << L". Removing it.";\r
+ send_to_consumers_delays_.erase(it->first);\r
it = consumers_.erase(it);\r
}\r
}\r
for (auto result_it = send_results.begin(); result_it != send_results.end(); ++result_it)\r
{\r
auto consumer = consumers_.at(result_it->first);\r
- auto frame = frames_.at(consumer->buffer_depth()-minmax.first);\r
+ auto frame = frames_.at(buffer_depths[result_it->first]-minmax.first);\r
auto& result_future = result_it->second;\r
\r
try\r
if(!result_future.get())\r
{\r
CASPAR_LOG(info) << print() << L" " << consumer->print() << L" Removed.";\r
+ send_to_consumers_delays_.erase(result_it->first);\r
consumers_.erase(result_it->first);\r
}\r
}\r
if(!consumer->send(frame).get())\r
{\r
CASPAR_LOG(info) << print() << L" " << consumer->print() << L" Removed.";\r
+ send_to_consumers_delays_.erase(result_it->first);\r
consumers_.erase(result_it->first);\r
}\r
}\r
{\r
CASPAR_LOG_CURRENT_EXCEPTION();\r
CASPAR_LOG(error) << "Failed to recover consumer: " << consumer->print() << L". Removing it.";\r
+ send_to_consumers_delays_.erase(result_it->first);\r
consumers_.erase(result_it->first);\r
}\r
}\r
}, high_priority));\r
}\r
\r
+ boost::unique_future<boost::property_tree::wptree> delay_info()\r
+ {\r
+ return std::move(executor_.begin_invoke([&]() -> boost::property_tree::wptree\r
+ { \r
+ boost::property_tree::wptree info;\r
+ BOOST_FOREACH(auto& consumer, consumers_)\r
+ {\r
+ auto total_age =\r
+ consumer.second->presentation_frame_age_millis();\r
+ auto sendoff_age = send_to_consumers_delays_[consumer.first];\r
+ auto presentation_time = total_age - sendoff_age;\r
+\r
+ boost::property_tree::wptree child;\r
+ child.add(L"name", consumer.second->print());\r
+ child.add(L"age-at-arrival", sendoff_age);\r
+ child.add(L"presentation-time", presentation_time);\r
+ child.add(L"age-at-presentation", total_age);\r
+\r
+ info.add_child(L"consumer", child);\r
+ }\r
+ return info;\r
+ }, high_priority));\r
+ }\r
+\r
bool empty()\r
{\r
return executor_.invoke([this]\r
void output::send(const std::pair<safe_ptr<read_frame>, std::shared_ptr<void>>& frame) {impl_->send(frame); }\r
void output::set_video_format_desc(const video_format_desc& format_desc){impl_->set_video_format_desc(format_desc);}\r
boost::unique_future<boost::property_tree::wptree> output::info() const{return impl_->info();}\r
+boost::unique_future<boost::property_tree::wptree> output::delay_info() const{return impl_->delay_info();}\r
bool output::empty() const{return impl_->empty();}\r
}}
\ No newline at end of file
void set_video_format_desc(const video_format_desc& format_desc);\r
\r
boost::unique_future<boost::property_tree::wptree> info() const;\r
+ boost::unique_future<boost::property_tree::wptree> delay_info() const;\r
\r
bool empty() const;\r
private:\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();
+ }
+};
+
+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;
+
+ graph_->set_text(print());
+ 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)
+ {
+ boost::for_each(
+ consumers_,
+ [&] (const safe_ptr<frame_consumer>& consumer)
+ {
+ consumer->initialize(format_desc, channel_index);
+ });
+ 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_;
+};
+
+}}
-<?xml version="1.0" encoding="utf-8"?>\r
+<?xml version="1.0" encoding="utf-8"?>\r
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
<ItemGroup Label="ProjectConfigurations">\r
<ProjectConfiguration Include="Profile|Win32">\r
<Lib />\r
</ItemDefinitionGroup>\r
<ItemGroup>\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\monitor">\r
<UniqueIdentifier>{d8525088-072a-47d2-b6e1-ad662881f505}</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
#include <common/env.h>\r
#include <common/concurrency/executor.h>\r
+#include <common/concurrency/future_util.h>\r
#include <common/exception/exceptions.h>\r
#include <common/gl/gl_check.h>\r
#include <common/utility/tweener.h>\r
\r
#include <tbb/concurrent_queue.h>\r
#include <tbb/spin_mutex.h>\r
+#include <tbb/atomic.h>\r
\r
#include <unordered_map>\r
\r
{ \r
safe_ptr<diagnostics::graph> graph_;\r
boost::timer mix_timer_;\r
+ tbb::atomic<int64_t> current_mix_time_;\r
\r
safe_ptr<mixer::target_t> target_;\r
mutable tbb::spin_mutex format_desc_mutex_;\r
, executor_(L"mixer")\r
{ \r
graph_->set_color("mix-time", diagnostics::color(1.0f, 0.0f, 0.9f, 0.8));\r
+ current_mix_time_ = 0;\r
}\r
\r
void send(const std::pair<std::map<int, safe_ptr<core::basic_frame>>, std::shared_ptr<void>>& packet)\r
auto audio = audio_mixer_(format_desc_, audio_channel_layout_);\r
image.wait();\r
\r
- graph_->set_value("mix-time", mix_timer_.elapsed()*format_desc_.fps*0.5);\r
+ auto mix_time = mix_timer_.elapsed();\r
+ graph_->set_value("mix-time", mix_time*format_desc_.fps*0.5);\r
+ current_mix_time_ = static_cast<int64_t>(mix_time * 1000.0);\r
\r
target_->send(std::make_pair(make_safe<read_frame>(ogl_, format_desc_.size, std::move(image.get()), std::move(audio), audio_channel_layout_), packet.second));\r
}\r
\r
boost::unique_future<boost::property_tree::wptree> info() const\r
{\r
- boost::promise<boost::property_tree::wptree> info;\r
- info.set_value(boost::property_tree::wptree());\r
- return info.get_future();\r
+ boost::property_tree::wptree info;\r
+ info.add(L"mix-time", current_mix_time_);\r
+\r
+ return wrap_as_future(std::move(info));\r
+ }\r
+\r
+ boost::unique_future<boost::property_tree::wptree> delay_info() const\r
+ {\r
+ boost::property_tree::wptree info;\r
+ info.put_value(current_mix_time_);\r
+\r
+ return wrap_as_future(std::move(info));\r
}\r
};\r
\r
void mixer::set_master_volume(float volume) { impl_->set_master_volume(volume); }\r
void mixer::set_video_format_desc(const video_format_desc& format_desc){impl_->set_video_format_desc(format_desc);}\r
boost::unique_future<boost::property_tree::wptree> mixer::info() const{return impl_->info();}\r
+boost::unique_future<boost::property_tree::wptree> mixer::delay_info() const{return impl_->delay_info();}\r
}}
\ No newline at end of file
void set_master_volume(float volume);\r
\r
boost::unique_future<boost::property_tree::wptree> info() const;\r
+ boost::unique_future<boost::property_tree::wptree> delay_info() const;\r
\r
private:\r
struct implementation;\r
\r
#include <tbb/mutex.h>\r
\r
+#include <boost/chrono.hpp>\r
+\r
namespace caspar { namespace core {\r
+\r
+int64_t get_current_time_millis()\r
+{\r
+ using namespace boost::chrono;\r
+\r
+ return duration_cast<milliseconds>(\r
+ high_resolution_clock::now().time_since_epoch()).count();\r
+}\r
\r
struct read_frame::implementation : boost::noncopyable\r
{\r
tbb::mutex mutex_;\r
audio_buffer audio_data_;\r
channel_layout audio_channel_layout_;\r
+ int64_t created_timestamp_;\r
\r
public:\r
implementation(\r
, image_data_(std::move(image_data))\r
, audio_data_(std::move(audio_data))\r
, audio_channel_layout_(audio_channel_layout)\r
+ , created_timestamp_(get_current_time_millis())\r
{\r
} \r
\r
impl_->audio_channel_layout_);\r
}\r
\r
+int64_t read_frame::get_age_millis() const\r
+{\r
+ return impl_ ? get_current_time_millis() - impl_->created_timestamp_ : 0;\r
+}\r
+\r
//#include <tbb/scalable_allocator.h>\r
//#include <tbb/parallel_for.h>\r
//#include <tbb/enumerable_thread_specific.h>\r
\r
virtual size_t image_size() const;\r
virtual int num_channels() const;\r
+ virtual int64_t get_age_millis() const;\r
virtual const multichannel_view<const int32_t, boost::iterator_range<const int32_t*>::const_iterator> multichannel_view() const;\r
\r
private:\r
#include <core/mixer/audio/audio_util.h>\r
\r
#include <boost/lexical_cast.hpp>\r
+#include <boost/timer.hpp>\r
+\r
+#include <tbb/atomic.h>\r
\r
namespace caspar { namespace core {\r
\r
const channel_layout channel_layout_;\r
const void* tag_;\r
core::field_mode::type mode_;\r
+ boost::timer since_created_timer_;\r
+ tbb::atomic<int64_t> recorded_frame_age_;\r
\r
implementation(const void* tag, const channel_layout& channel_layout)\r
: channel_layout_(channel_layout)\r
, tag_(tag)\r
{\r
+ recorded_frame_age_ = -1;\r
}\r
\r
implementation(const safe_ptr<ogl_device>& ogl, const void* tag, const core::pixel_format_desc& desc, const channel_layout& channel_layout) \r
{\r
return ogl_->create_device_buffer(plane.width, plane.height, plane.channels); \r
});\r
+\r
+ recorded_frame_age_ = -1;\r
}\r
\r
void accept(write_frame& self, core::frame_visitor& visitor)\r
visitor.end();\r
}\r
\r
+ int64_t get_and_record_age_millis()\r
+ {\r
+ if (recorded_frame_age_ == -1)\r
+ recorded_frame_age_ = static_cast<int64_t>(\r
+ since_created_timer_.elapsed() * 1000.0);\r
+\r
+ return recorded_frame_age_;\r
+ }\r
+\r
boost::iterator_range<uint8_t*> image_data(size_t index)\r
{\r
if(index >= buffers_.size() || !buffers_[index]->data())\r
void write_frame::set_type(const field_mode::type& mode){impl_->mode_ = mode;}\r
core::field_mode::type write_frame::get_type() const{return impl_->mode_;}\r
void write_frame::accept(core::frame_visitor& visitor){impl_->accept(*this, visitor);}\r
+int64_t write_frame::get_and_record_age_millis() { return impl_->get_and_record_age_millis(); }\r
\r
}}
\ No newline at end of file
// basic_frame\r
\r
virtual void accept(frame_visitor& visitor) override;\r
+ virtual int64_t get_and_record_age_millis() override;\r
\r
// write _frame\r
\r
const core::pixel_format_desc& get_pixel_format_desc() const;\r
const channel_layout& get_channel_layout() const;\r
multichannel_view<int32_t, audio_buffer::iterator> get_multichannel_view();\r
- \r
private:\r
friend class image_mixer;\r
\r
core::video_format_desc format_desc_;\r
int channel_index_;\r
tbb::atomic<bool> is_running_;\r
+ tbb::atomic<int64_t> current_age_;\r
\r
public:\r
channel_consumer() \r
{\r
is_running_ = true;\r
+ current_age_ = 0;\r
frame_buffer_.set_capacity(3);\r
}\r
\r
channel_index_ = channel_index;\r
}\r
\r
+ virtual int64_t presentation_frame_age_millis() const override\r
+ {\r
+ return current_age_;\r
+ }\r
+\r
virtual std::wstring print() const override\r
{\r
return L"[channel-consumer|" + boost::lexical_cast<std::wstring>(channel_index_) + L"]";\r
return make_safe<read_frame>();\r
std::shared_ptr<read_frame> frame;\r
frame_buffer_.try_pop(frame);\r
+ current_age_ = frame->get_age_millis();\r
return frame;\r
}\r
};\r
std::vector<safe_ptr<basic_frame>> frames_;\r
\r
frame_transform frame_transform_; \r
- \r
public:\r
implementation(const std::vector<safe_ptr<basic_frame>>& frames) : frames_(frames) \r
{\r
{ \r
frames_.push_back(frame);\r
}\r
+\r
+ int64_t get_and_record_age_millis(const basic_frame& self)\r
+ {\r
+ int64_t result = 0;\r
+\r
+ BOOST_FOREACH(auto& frame, frames_)\r
+ {\r
+ if (is_concrete_frame(frame) && frame.get() != &self)\r
+ result = std::max(result, frame->get_and_record_age_millis());\r
+ }\r
+\r
+ return result;\r
+ }\r
\r
void accept(basic_frame& self, frame_visitor& visitor)\r
{\r
\r
const frame_transform& basic_frame::get_frame_transform() const { return impl_->frame_transform_;}\r
frame_transform& basic_frame::get_frame_transform() { return impl_->frame_transform_;}\r
+int64_t basic_frame::get_and_record_age_millis() { return impl_->get_and_record_age_millis(*this); }\r
void basic_frame::accept(frame_visitor& visitor){impl_->accept(*this, visitor);}\r
\r
safe_ptr<basic_frame> basic_frame::interlace(const safe_ptr<basic_frame>& frame1, const safe_ptr<basic_frame>& frame2, field_mode::type mode)\r
\r
const frame_transform& get_frame_transform() const;\r
frame_transform& get_frame_transform();\r
- \r
+ virtual int64_t get_and_record_age_millis();\r
+\r
static safe_ptr<basic_frame> interlace(const safe_ptr<basic_frame>& frame1, const safe_ptr<basic_frame>& frame2, field_mode::type mode);\r
static safe_ptr<basic_frame> combine(const safe_ptr<basic_frame>& frame1, const safe_ptr<basic_frame>& frame2);\r
static safe_ptr<basic_frame> fill_and_key(const safe_ptr<basic_frame>& fill, const safe_ptr<basic_frame>& key);\r
int64_t frame_number_;\r
int32_t auto_play_delta_;\r
bool is_paused_;\r
+ int64_t current_frame_age_;\r
monitor::subject monitor_subject_;\r
\r
public:\r
play();\r
return receive(hints);\r
}\r
- \r
+\r
+ current_frame_age_ = frame->get_and_record_age_millis();\r
+\r
return frame;\r
}\r
catch(...)\r
\r
info.add(L"nb_frames", nb_frames == std::numeric_limits<int64_t>::max() ? -1 : nb_frames);\r
info.add(L"frames-left", nb_frames == std::numeric_limits<int64_t>::max() ? -1 : (foreground_->nb_frames() - frame_number_ - auto_play_delta_));\r
+ info.add(L"frame-age", current_frame_age_);\r
info.add_child(L"foreground.producer", foreground_->info());\r
info.add_child(L"background.producer", background_->info());\r
return info;\r
}\r
\r
+ boost::property_tree::wptree delay_info() const\r
+ {\r
+ boost::property_tree::wptree info;\r
+ info.add(L"producer", foreground_->print());\r
+ info.add(L"frame-age", current_frame_age_);\r
+ return info;\r
+ }\r
+\r
void set_foreground(safe_ptr<core::frame_producer> producer)\r
{\r
foreground_->monitor_output().unlink_target(&monitor_subject_);\r
bool layer::empty() const {return impl_->empty();}\r
boost::unique_future<std::wstring> layer::call(bool foreground, const std::wstring& param){return impl_->call(foreground, param);}\r
boost::property_tree::wptree layer::info() const{return impl_->info();}\r
+boost::property_tree::wptree layer::delay_info() const{return impl_->delay_info();}\r
monitor::source& layer::monitor_output(){return impl_->monitor_subject_;}\r
}}
\ No newline at end of file
safe_ptr<basic_frame> receive(int hints); // nothrow\r
\r
boost::property_tree::wptree info() const;\r
+ boost::property_tree::wptree delay_info() const;\r
\r
monitor::source& monitor_output();\r
private:\r
return get_layer(index).info();\r
}, high_priority));\r
}\r
+\r
+ boost::unique_future<boost::property_tree::wptree> delay_info()\r
+ {\r
+ return std::move(executor_.begin_invoke([this]() -> boost::property_tree::wptree\r
+ {\r
+ boost::property_tree::wptree info;\r
+ BOOST_FOREACH(auto& layer, layers_) \r
+ info.add_child(L"layer", layer.second->delay_info())\r
+ .add(L"index", layer.first); \r
+ return info;\r
+ }, high_priority));\r
+ }\r
+\r
+ boost::unique_future<boost::property_tree::wptree> delay_info(int index)\r
+ {\r
+ return std::move(executor_.begin_invoke([=]() -> boost::property_tree::wptree\r
+ {\r
+ return get_layer(index).delay_info();\r
+ }, high_priority));\r
+ }\r
};\r
\r
stage::stage(const safe_ptr<diagnostics::graph>& graph, const safe_ptr<target_t>& target, const video_format_desc& format_desc) \r
void stage::set_video_format_desc(const video_format_desc& format_desc){impl_->set_video_format_desc(format_desc);}\r
boost::unique_future<boost::property_tree::wptree> stage::info() const{return impl_->info();}\r
boost::unique_future<boost::property_tree::wptree> stage::info(int index) const{return impl_->info(index);}\r
+boost::unique_future<boost::property_tree::wptree> stage::delay_info() const{return impl_->delay_info();}\r
+boost::unique_future<boost::property_tree::wptree> stage::delay_info(int index) const{return impl_->delay_info(index);}\r
monitor::source& stage::monitor_output(){return impl_->monitor_subject_;}\r
}}
\ No newline at end of file
\r
boost::unique_future<boost::property_tree::wptree> info() const;\r
boost::unique_future<boost::property_tree::wptree> info(int layer) const;\r
+\r
+ boost::unique_future<boost::property_tree::wptree> delay_info() const;\r
+ boost::unique_future<boost::property_tree::wptree> delay_info(int layer) const;\r
\r
void set_video_format_desc(const video_format_desc& format_desc);\r
\r
\r
CASPAR_LOG(info) << print() << " Successfully Initialized.";\r
}\r
- \r
+\r
void set_video_format_desc(const video_format_desc& format_desc)\r
{\r
if(format_desc.format == core::video_format::invalid)\r
\r
return info; \r
}\r
+\r
+ boost::property_tree::wptree delay_info() const\r
+ {\r
+ boost::property_tree::wptree info;\r
+\r
+ auto stage_info = stage_->delay_info();\r
+ auto mixer_info = mixer_->delay_info();\r
+ auto output_info = output_->delay_info();\r
+\r
+ stage_info.timed_wait(boost::posix_time::seconds(2));\r
+ mixer_info.timed_wait(boost::posix_time::seconds(2));\r
+ output_info.timed_wait(boost::posix_time::seconds(2));\r
+\r
+ info.add_child(L"layers", stage_info.get());\r
+ info.add_child(L"mix-time", mixer_info.get());\r
+ info.add_child(L"consumers", output_info.get());\r
+\r
+ return info;\r
+ }\r
};\r
\r
video_channel::video_channel(int index, const video_format_desc& format_desc, const safe_ptr<ogl_device>& ogl, const channel_layout& audio_channel_layout) \r
boost::property_tree::wptree video_channel::info() const{return impl_->info();}\r
int video_channel::index() const {return impl_->index_;}\r
monitor::source& video_channel::monitor_output(){return impl_->monitor_subject_;}\r
+boost::property_tree::wptree video_channel::delay_info() const { return impl_->delay_info(); }\r
}}
\ No newline at end of file
void set_video_format_desc(const video_format_desc& format_desc);\r
\r
boost::property_tree::wptree info() const;\r
+ boost::property_tree::wptree delay_info() const;\r
\r
int index() const;\r
\r
#include <core/mixer/audio/audio_util.h>\r
\r
#include <tbb/concurrent_queue.h>\r
+#include <tbb/atomic.h>\r
\r
#include <boost/timer.hpp>\r
#include <boost/range/algorithm.hpp>\r
\r
std::array<blue_dma_buffer_ptr, 4> reserved_frames_; \r
tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> frame_buffer_;\r
+ tbb::atomic<int64_t> presentation_delay_millis_;\r
+ std::shared_ptr<core::read_frame> previous_frame_;\r
\r
const bool embedded_audio_;\r
const bool key_only_;\r
, executor_(print())\r
{\r
executor_.set_capacity(1);\r
+ presentation_delay_millis_ = 0;\r
\r
graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f)); \r
graph_->set_color("sync-time", diagnostics::color(1.0f, 0.0f, 0.0f));\r
blue_->wait_output_video_synch(UPD_FMT_FRAME, n_field);\r
graph_->set_value("sync-time", sync_timer_.elapsed()*format_desc_.fps*0.5);\r
\r
- frame_timer_.restart(); \r
+ frame_timer_.restart();\r
+\r
+ if (previous_frame_)\r
+ presentation_delay_millis_ = previous_frame_->get_age_millis();\r
+\r
+ previous_frame_ = frame;\r
\r
// Copy to local buffers\r
\r
return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" + \r
boost::lexical_cast<std::wstring>(device_index_) + L"|" + format_desc_.name + L"]";\r
}\r
+\r
+ int64_t presentation_delay_millis() const\r
+ {\r
+ return presentation_delay_millis_;\r
+ }\r
};\r
\r
struct bluefish_consumer_proxy : public core::frame_consumer\r
format_desc_ = format_desc;\r
CASPAR_LOG(info) << print() << L" Successfully Initialized."; \r
}\r
- \r
+\r
virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override\r
{\r
CASPAR_VERIFY(audio_cadence_.front() * frame->num_channels() == static_cast<size_t>(frame->audio_data().size()));\r
info.add(L"key-only", key_only_);\r
info.add(L"device", device_index_);\r
info.add(L"embedded-audio", embedded_audio_);\r
+ info.add(L"presentation-frame-age", presentation_frame_age_millis());\r
return info;\r
}\r
\r
- size_t buffer_depth() const override\r
+ virtual size_t buffer_depth() const override\r
{\r
return 1;\r
}\r
{\r
return 400 + device_index_;\r
}\r
+\r
+ virtual int64_t presentation_frame_age_millis() const override\r
+ {\r
+ return consumer_ ? consumer_->presentation_delay_millis() : 0;\r
+ }\r
}; \r
\r
safe_ptr<core::frame_consumer> create_consumer(const core::parameters& params)\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
BMDReferenceStatus last_reference_status_;\r
retry_task<bool> send_completion_;\r
\r
+ tbb::atomic<int64_t> current_presentation_delay_;\r
+\r
public:\r
decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index) \r
: channel_index_(channel_index)\r
, last_reference_status_(static_cast<BMDReferenceStatus>(-1))\r
{\r
is_running_ = true;\r
+ current_presentation_delay_ = 0;\r
\r
video_frame_buffer_.set_capacity(1);\r
\r
\r
try\r
{\r
+ auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);\r
+ current_presentation_delay_ = dframe->get_age_millis();\r
+\r
if(result == bmdOutputFrameDisplayedLate)\r
{\r
graph_->set_tag("late-frame");\r
video_scheduled_ += format_desc_.duration;\r
- auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);\r
audio_scheduled_ += dframe->audio_data().size()/config_.num_out_channels();\r
//++video_scheduled_;\r
//audio_scheduled_ += format_desc_.audio_cadence[0];\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
}\r
{\r
return 300 + config_.device_index;\r
}\r
+\r
+ virtual int64_t presentation_frame_age_millis() const\r
+ {\r
+ return context_ ? context_->current_presentation_delay_ : 0;\r
+ }\r
}; \r
\r
safe_ptr<core::frame_consumer> create_consumer(const core::parameters& params) \r
</Command>\r
</PreBuildEvent>\r
<ClCompile>\r
- <Optimization>Disabled</Optimization>\r
- <InlineFunctionExpansion>Disabled</InlineFunctionExpansion>\r
+ <Optimization>MaxSpeed</Optimization>\r
+ <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>\r
<IntrinsicFunctions>true</IntrinsicFunctions>\r
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>\r
<AdditionalIncludeDirectories>../;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
\r
#include <tbb/cache_aligned_allocator.h>\r
#include <tbb/parallel_invoke.h>\r
+#include <tbb/atomic.h>\r
\r
#include <boost/range/algorithm.hpp>\r
#include <boost/range/algorithm_ext.hpp>\r
\r
output_format output_format_;\r
bool key_only_;\r
+ tbb::atomic<int64_t> current_encoding_delay_;\r
\r
public:\r
ffmpeg_consumer(const std::string& filename, const core::video_format_desc& format_desc, std::vector<option> options, bool key_only, const core::channel_layout& audio_channel_layout)\r
, output_format_(format_desc, filename, options)\r
, key_only_(key_only)\r
{\r
+ current_encoding_delay_ = 0;\r
+\r
// TODO: Ask stakeholders about case where file already exists.\r
boost::filesystem2::remove(boost::filesystem2::wpath(env::media_folder() + widen(filename))); // Delete the file if it exists\r
\r
if (!key_only_)\r
encode_audio_frame(*frame);\r
\r
- graph_->set_value("frame-time", frame_timer.elapsed()*format_desc_.fps*0.5); \r
+ graph_->set_value("frame-time", frame_timer.elapsed()*format_desc_.fps*0.5);\r
+ current_encoding_delay_ = frame->get_age_millis();\r
});\r
}\r
\r
{\r
format_desc_ = format_desc;\r
}\r
+\r
+ virtual int64_t presentation_frame_age_millis() const override\r
+ {\r
+ return consumer_ ? consumer_->current_encoding_delay_ : 0;\r
+ }\r
\r
virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override\r
{\r
ax_->Tick();\r
if(ax_->InvalidRect())\r
{\r
- fast_memclr(bmp_.data(), width_*height_*4);\r
- ax_->DrawControl(bmp_);\r
- \r
core::pixel_format_desc desc;\r
desc.pix_fmt = core::pixel_format::bgra;\r
desc.planes.push_back(core::pixel_format_desc::plane(width_, height_, 4));\r
auto frame = frame_factory_->create_frame(this, desc);\r
\r
+ fast_memclr(bmp_.data(), width_*height_*4);\r
+ ax_->DrawControl(bmp_);\r
+\r
if(frame->image_data().size() == static_cast<int>(width_*height_*4))\r
{\r
fast_memcpy(frame->image_data().begin(), bmp_.data(), width_*height_*4);\r
#include <boost/date_time/posix_time/posix_time.hpp>\r
#include <boost/thread.hpp>\r
\r
-#include <tbb/concurrent_queue.h>\r
-\r
#include <FreeImage.h>\r
#include <vector>\r
#include <algorithm>\r
{\r
format_desc_ = format_desc;\r
}\r
+\r
+ virtual int64_t presentation_frame_age_millis() const override\r
+ {\r
+ return 0;\r
+ }\r
\r
virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override\r
{ \r
}
CASPAR_LOG(info) << print() << " Sucessfully Initialized.";
}
-
+
+ virtual int64_t presentation_frame_age_millis() const override
+ {
+ return 0;
+ }
+
virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override
{
std::shared_ptr<audio_buffer_16> buffer;
\r
boost::thread thread_;\r
tbb::atomic<bool> is_running_;\r
+ tbb::atomic<int64_t> current_presentation_age_;\r
\r
ffmpeg::filter filter_;\r
public:\r
screen_height_ = config_.windowed ? square_height_ : devmode.dmPelsHeight;\r
\r
is_running_ = true;\r
+ current_presentation_age_ = 0;\r
thread_ = boost::thread([this]{run();});\r
}\r
\r
\r
wait_for_vblank_and_display(); // field2\r
}\r
+\r
+ current_presentation_age_ = frame->get_age_millis();\r
}\r
\r
void render(safe_ptr<AVFrame> av_frame, int image_data_size)\r
consumer_.reset(new ogl_consumer(config_, format_desc, channel_index));\r
CASPAR_LOG(info) << print() << L" Successfully Initialized."; \r
}\r
- \r
+\r
+ virtual int64_t presentation_frame_age_millis() const override\r
+ {\r
+ return consumer_ ? consumer_->current_presentation_age_ : 0;\r
+ }\r
+\r
virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override\r
{\r
return consumer_->send(frame);\r
\r
boost::property_tree::write_xml(replyString, info, w);\r
}\r
+ else if(_parameters.size() >= 2 && _parameters[1] == L"DELAY")\r
+ {\r
+ replyString << L"201 INFO DELAY OK\r\n";\r
+ boost::property_tree::wptree info;\r
+\r
+ std::vector<std::wstring> split;\r
+ boost::split(split, _parameters[0], boost::is_any_of("-"));\r
+ \r
+ int layer = std::numeric_limits<int>::min();\r
+ int channel = boost::lexical_cast<int>(split[0]) - 1;\r
+\r
+ if(split.size() > 1)\r
+ layer = boost::lexical_cast<int>(split[1]);\r
+ \r
+ if(layer == std::numeric_limits<int>::min())\r
+ { \r
+ info.add_child(L"channel-delay", channels_.at(channel)->delay_info());\r
+ }\r
+ else\r
+ {\r
+ info.add_child(L"layer-delay", channels_.at(channel)->stage()->delay_info(layer).get())\r
+ .add(L"index", layer);\r
+ }\r
+ boost::property_tree::xml_parser::write_xml(replyString, info, w);\r
+ }\r
else // channel\r
{ \r
if(_parameters.size() >= 1)\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
\r
#include <modules/bluefish/bluefish.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