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