]> git.sesse.net Git - casparcg/blobdiff - modules/decklink/consumer/decklink_consumer.cpp
set svn:eol-style native on .h and .cpp files
[casparcg] / modules / decklink / consumer / decklink_consumer.cpp
index 185207e13a3ce604588b7bff0d4832ebdfa83ab1..73a39432aa5e3b358e8aaf22b3a26ea567f728c9 100644 (file)
-/*\r
-* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
-*\r
-* This file is part of CasparCG (www.casparcg.com).\r
-*\r
-* CasparCG is free software: you can redistribute it and/or modify\r
-* it under the terms of the GNU General Public License as published by\r
-* the Free Software Foundation, either version 3 of the License, or\r
-* (at your option) any later version.\r
-*\r
-* CasparCG is distributed in the hope that it will be useful,\r
-* but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
-* GNU General Public License for more details.\r
-*\r
-* You should have received a copy of the GNU General Public License\r
-* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
-*\r
-* Author: Robert Nagy, ronag89@gmail.com\r
-*/\r
-\r
-#include "../StdAfx.h"\r
\r
-#include "decklink_consumer.h"\r
-\r
-#include "../util/util.h"\r
-\r
-#include "../interop/DeckLinkAPI_h.h"\r
-\r
-#include <core/frame/frame.h>\r
-#include <core/mixer/audio/audio_mixer.h>\r
-\r
-#include <common/executor.h>\r
-#include <common/lock.h>\r
-#include <common/diagnostics/graph.h>\r
-#include <common/except.h>\r
-#include <common/memshfl.h>\r
-#include <common/array.h>\r
-\r
-#include <core/consumer/frame_consumer.h>\r
-\r
-#include <tbb/concurrent_queue.h>\r
-#include <tbb/cache_aligned_allocator.h>\r
-\r
-#include <common/assert.h>\r
-#include <boost/lexical_cast.hpp>\r
-#include <boost/circular_buffer.hpp>\r
-#include <boost/timer.hpp>\r
-#include <boost/property_tree/ptree.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
-       int                     device_index;\r
-       bool            embedded_audio;\r
-       keyer_t         keyer;\r
-       latency_t       latency;\r
-       bool            key_only;\r
-       int                     base_buffer_depth;\r
-       \r
-       configuration()\r
-               : device_index(1)\r
-               , embedded_audio(true)\r
-               , keyer(default_keyer)\r
-               , latency(default_latency)\r
-               , key_only(false)\r
-               , base_buffer_depth(3)\r
-       {\r
-       }\r
-       \r
-       int buffer_depth() const\r
-       {\r
-               return base_buffer_depth + (latency == low_latency ? 0 : 1) + (embedded_audio ? 1 : 0);\r
-       }\r
-};\r
-\r
-class decklink_frame : public IDeckLinkVideoFrame\r
-{\r
-       tbb::atomic<int>                                                                                        ref_count_;\r
-       core::const_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(core::const_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 static_cast<long>(format_desc_.width);}        \r
-    STDMETHOD_(long,                   GetHeight())            {return static_cast<long>(format_desc_.height);}        \r
-    STDMETHOD_(long,                   GetRowBytes())          {return static_cast<long>(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<int>(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
-                                       aligned_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 core::audio_buffer& audio_data()\r
-       {\r
-               return frame_.audio_data();\r
-       }\r
-};\r
-\r
-struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
-{              \r
-       const int                                                       channel_index_;\r
-       const configuration                                     config_;\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
-       tbb::atomic<bool>                                       is_running_;\r
-               \r
-       const std::wstring                                      model_name_;\r
-       const core::video_format_desc           format_desc_;\r
-       const int                                                       buffer_size_;\r
-\r
-       long long                                                       video_scheduled_;\r
-       long long                                                       audio_scheduled_;\r
-\r
-       int                                                                     preroll_count_;\r
-               \r
-       boost::circular_buffer<std::vector<int32_t>>    audio_container_;\r
-\r
-       tbb::concurrent_bounded_queue<core::const_frame> video_frame_buffer_;\r
-       tbb::concurrent_bounded_queue<core::const_frame> audio_frame_buffer_;\r
-       \r
-       spl::shared_ptr<diagnostics::graph> graph_;\r
-       boost::timer tick_timer_;\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
-               , 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
-               , video_scheduled_(0)\r
-               , audio_scheduled_(0)\r
-               , preroll_count_(0)\r
-               , audio_container_(buffer_size_+1)\r
-       {\r
-               is_running_ = true;\r
-                               \r
-               video_frame_buffer_.set_capacity(1);\r
-               audio_frame_buffer_.set_capacity(1);\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
-               graph_->set_text(print());\r
-               diagnostics::register_graph(graph_);\r
-               \r
-               enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));\r
-                               \r
-               if(config.embedded_audio)\r
-                       enable_audio();\r
-               \r
-               set_latency(config.latency);                            \r
-               set_keyer(config.keyer);\r
-                               \r
-               if(config.embedded_audio)               \r
-                       output_->BeginAudioPreroll();           \r
-               \r
-               for(int n = 0; n < buffer_size_; ++n)\r
-                       schedule_next_video(core::const_frame::empty());\r
-\r
-               if(!config.embedded_audio)\r
-                       start_playback();\r
-       }\r
-\r
-       ~decklink_consumer()\r
-       {               \r
-               is_running_ = false;\r
-               video_frame_buffer_.try_push(core::const_frame::empty());\r
-               audio_frame_buffer_.try_push(core::const_frame::empty());\r
-\r
-               if(output_ != nullptr) \r
-               {\r
-                       output_->StopScheduledPlayback(0, nullptr, 0);\r
-                       if(config_.embedded_audio)\r
-                               output_->DisableAudioOutput();\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
-               if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))\r
-                               CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable audio output."));\r
-                               \r
-               if(FAILED(output_->SetAudioCallback(this)))\r
-                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not set audio callback."));\r
-\r
-               CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";\r
-       }\r
-\r
-       void enable_video(BMDDisplayMode display_mode)\r
-       {\r
-               if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) \r
-                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable video output."));\r
-               \r
-               if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
-                       CASPAR_THROW_EXCEPTION(caspar_exception() \r
-                                                                       << msg_info(u8(print()) + " Failed to set playback completion callback.")\r
-                                                                       << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));\r
-       }\r
-\r
-       void start_playback()\r
-       {\r
-               if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) \r
-                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Failed to schedule playback."));\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
-               is_running_ = false;\r
-               CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";\r
-               return S_OK;\r
-       }\r
-\r
-       STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))\r
-       {\r
-               if(!is_running_)\r
-                       return E_FAIL;\r
-               \r
-               try\r
-               {\r
-                       if(result == bmdOutputFrameDisplayedLate)\r
-                       {\r
-                               graph_->set_tag("late-frame");\r
-                               video_scheduled_ += format_desc_.duration;\r
-                               audio_scheduled_ += reinterpret_cast<decklink_frame*>(completed_frame)->audio_data().size()/format_desc_.audio_channels;\r
-                               //++video_scheduled_;\r
-                               //audio_scheduled_ += format_desc_.audio_cadence[0];\r
-                               //++audio_scheduled_;\r
-                       }\r
-                       else if(result == bmdOutputFrameDropped)\r
-                               graph_->set_tag("dropped-frame");\r
-                       else if(result == bmdOutputFrameFlushed)\r
-                               graph_->set_tag("flushed-frame");\r
-\r
-                       auto frame = core::const_frame::empty();        \r
-                       video_frame_buffer_.pop(frame);                                 \r
-                       schedule_next_video(frame);     \r
-                       \r
-                       unsigned long buffered;\r
-                       output_->GetBufferedVideoFrameCount(&buffered);\r
-                       graph_->set_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);\r
-               }\r
-               catch(...)\r
-               {\r
-                       lock(exception_mutex_, [&]\r
-                       {\r
-                               exception_ = std::current_exception();\r
-                       });\r
-                       return E_FAIL;\r
-               }\r
-\r
-               return S_OK;\r
-       }\r
-               \r
-       STDMETHOD(RenderAudioSamples(BOOL preroll))\r
-       {\r
-               if(!is_running_)\r
-                       return E_FAIL;\r
-               \r
-               try\r
-               {       \r
-                       if(preroll)\r
-                       {\r
-                               if(++preroll_count_ >= buffer_size_)\r
-                               {\r
-                                       output_->EndAudioPreroll();\r
-                                       start_playback();                               \r
-                               }\r
-                               else\r
-                                       schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()], 0));    \r
-                       }\r
-                       else\r
-                       {\r
-                               auto frame = core::const_frame::empty();\r
-                               audio_frame_buffer_.pop(frame);\r
-                               schedule_next_audio(frame.audio_data());\r
-                       }\r
-\r
-                       unsigned long buffered;\r
-                       output_->GetBufferedAudioSampleFrameCount(&buffered);\r
-                       graph_->set_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_cadence[0]*2));\r
-               }\r
-               catch(...)\r
-               {\r
-                       tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
-                       exception_ = std::current_exception();\r
-                       return E_FAIL;\r
-               }\r
-\r
-               return S_OK;\r
-       }\r
-\r
-       template<typename T>\r
-       void schedule_next_audio(const T& audio_data)\r
-       {\r
-               auto sample_frame_count = static_cast<int>(audio_data.size()/format_desc_.audio_channels);\r
-\r
-               audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));\r
-\r
-               if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, audio_scheduled_, format_desc_.audio_sample_rate, nullptr)))\r
-                       CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
-\r
-               audio_scheduled_ += sample_frame_count;\r
-       }\r
-                       \r
-       void schedule_next_video(core::const_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
-\r
-               video_scheduled_ += format_desc_.duration;\r
-\r
-               graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);\r
-               tick_timer_.restart();\r
-       }\r
-\r
-       void send(core::const_frame frame)\r
-       {\r
-               auto exception = lock(exception_mutex_, [&]\r
-               {\r
-                       return exception_;\r
-               });\r
-\r
-               if(exception != nullptr)\r
-                       std::rethrow_exception(exception);              \r
-\r
-               if(!is_running_)\r
-                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Is not running."));\r
-               \r
-               if(config_.embedded_audio)\r
-                       audio_frame_buffer_.push(frame);        \r
-               video_frame_buffer_.push(frame);        \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
-       }\r
-};\r
-\r
-struct decklink_consumer_proxy : public core::frame_consumer\r
-{\r
-       const configuration                                     config_;\r
-       std::unique_ptr<decklink_consumer>      consumer_;\r
-       executor                                                        executor_;\r
-public:\r
-\r
-       decklink_consumer_proxy(const configuration& config)\r
-               : config_(config)\r
-               , executor_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")\r
-       {\r
-               executor_.begin_invoke([=]\r
-               {\r
-                       ::CoInitialize(nullptr);\r
-               });\r
-       }\r
-\r
-       ~decklink_consumer_proxy()\r
-       {\r
-               executor_.invoke([=]\r
-               {\r
-                       ::CoUninitialize();\r
-               });\r
-       }\r
-\r
-       // frame_consumer\r
-       \r
-       void initialize(const core::video_format_desc& format_desc, int channel_index) override\r
-       {\r
-               executor_.invoke([=]\r
-               {\r
-                       consumer_.reset();\r
-                       consumer_.reset(new decklink_consumer(config_, format_desc, channel_index));                    \r
-               });\r
-       }\r
-       \r
-       bool send(core::const_frame frame) override\r
-       {\r
-               consumer_->send(frame);\r
-               return true;\r
-       }\r
-       \r
-       std::wstring print() const override\r
-       {\r
-               return consumer_ ? consumer_->print() : L"[decklink_consumer]";\r
-       }               \r
-\r
-       std::wstring name() const override\r
-       {\r
-               return L"decklink";\r
-       }\r
-\r
-       boost::property_tree::wptree info() const override\r
-       {\r
-               boost::property_tree::wptree info;\r
-               info.add(L"type", L"decklink");\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
-               info.add(L"embedded-audio", config_.embedded_audio);\r
-               info.add(L"low-latency", config_.low_latency);\r
-               //info.add(L"internal-key", config_.internal_key);\r
-               return info;\r
-       }\r
-\r
-       int buffer_depth() const override\r
-       {\r
-               return config_.buffer_depth();\r
-       }\r
-\r
-       int index() const override\r
-       {\r
-               return 300 + config_.device_index;\r
-       }\r
-\r
-       void subscribe(const monitor::observable::observer_ptr& o) override\r
-       {\r
-       }\r
-\r
-       void unsubscribe(const monitor::observable::observer_ptr& o) override\r
-       {\r
-       }       \r
-};     \r
-\r
-spl::shared_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params) \r
-{\r
-       if(params.size() < 1 || params[0] != L"DECKLINK")\r
-               return core::frame_consumer::empty();\r
-       \r
-       configuration config;\r
-               \r
-       if(params.size() > 1)\r
-               config.device_index = boost::lexical_cast<int>(params[1]);\r
-       \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
-               config.keyer = configuration::external_keyer;\r
-       else\r
-               config.keyer = configuration::default_keyer;\r
-\r
-       if(std::find(params.begin(), params.end(), L"LOW_LATENCY")       != params.end())\r
-               config.latency = configuration::low_latency;\r
-\r
-       config.embedded_audio   = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
-       config.key_only                 = std::find(params.begin(), params.end(), L"KEY_ONLY")           != params.end();\r
-\r
-       return spl::make_shared<decklink_consumer_proxy>(config);\r
-}\r
-\r
-spl::shared_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree) \r
-{\r
-       configuration config;\r
-\r
-       auto keyer = ptree.get(L"keyer", L"default");\r
-       if(keyer == L"external")\r
-               config.keyer = configuration::external_keyer;\r
-       else if(keyer == L"internal")\r
-               config.keyer = configuration::internal_keyer;\r
-\r
-       auto latency = ptree.get(L"latency", L"normal");\r
-       if(latency == L"low")\r
-               config.latency = configuration::low_latency;\r
-       else if(latency == L"normal")\r
-               config.latency = configuration::normal_latency;\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.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
-\r
-       return spl::make_shared<decklink_consumer_proxy>(config);\r
-}\r
-\r
-}}\r
-\r
-/*\r
-##############################################################################\r
-Pre-rolling\r
-\r
-Mail: 2011-05-09\r
-\r
-Yoshan\r
-BMD Developer Support\r
-developer@blackmagic-design.com\r
-\r
------------------------------------------------------------------------------\r
-\r
-Thanks for your inquiry. The minimum number of frames that you can preroll \r
-for scheduled playback is three frames for video and four frames for audio. \r
-As you mentioned if you preroll less frames then playback will not start or\r
-playback will be very sporadic. From our experience with Media Express, we \r
-recommended that at least seven frames are prerolled for smooth playback. \r
-\r
-Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:\r
-There can be around 3 frames worth of latency on scheduled output.\r
-When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is\r
-reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() \r
-method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will \r
-guarantee that the provided frame will be output as soon the previous \r
-frame output has been completed.\r
-################################################################################\r
-*/\r
-\r
-/*\r
-##############################################################################\r
-Async DMA Transfer without redundant copying\r
-\r
-Mail: 2011-05-10\r
-\r
-Yoshan\r
-BMD Developer Support\r
-developer@blackmagic-design.com\r
-\r
------------------------------------------------------------------------------\r
-\r
-Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame \r
-and providing a pointer to your video buffer when GetBytes() is called. \r
-This may help to keep copying to a minimum. Please ensure that the pixel \r
-format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will \r
-have to colourspace convert which may result in additional copying.\r
-################################################################################\r
+/*
+* 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: Robert Nagy, ronag89@gmail.com
+*/
+
+#include "../StdAfx.h"
+#include "decklink_consumer.h"
+
+#include "../util/util.h"
+
+#include "../interop/DeckLinkAPI_h.h"
+
+#include <core/frame/frame.h>
+#include <core/mixer/audio/audio_mixer.h>
+
+#include <common/executor.h>
+#include <common/lock.h>
+#include <common/diagnostics/graph.h>
+#include <common/except.h>
+#include <common/memshfl.h>
+#include <common/array.h>
+
+#include <core/consumer/frame_consumer.h>
+
+#include <tbb/concurrent_queue.h>
+#include <tbb/cache_aligned_allocator.h>
+
+#include <common/assert.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/circular_buffer.hpp>
+#include <boost/timer.hpp>
+#include <boost/property_tree/ptree.hpp>
+
+namespace caspar { namespace decklink { 
+       
+struct configuration
+{
+       enum keyer_t
+       {
+               internal_keyer,
+               external_keyer,
+               default_keyer
+       };
+
+       enum latency_t
+       {
+               low_latency,
+               normal_latency,
+               default_latency
+       };
+
+       int                     device_index;
+       bool            embedded_audio;
+       keyer_t         keyer;
+       latency_t       latency;
+       bool            key_only;
+       int                     base_buffer_depth;
+       
+       configuration()
+               : device_index(1)
+               , embedded_audio(true)
+               , keyer(default_keyer)
+               , latency(default_latency)
+               , key_only(false)
+               , base_buffer_depth(3)
+       {
+       }
+       
+       int buffer_depth() const
+       {
+               return base_buffer_depth + (latency == low_latency ? 0 : 1) + (embedded_audio ? 1 : 0);
+       }
+};
+
+class decklink_frame : public IDeckLinkVideoFrame
+{
+       tbb::atomic<int>                                                                                        ref_count_;
+       core::const_frame                                                                                       frame_;
+       const core::video_format_desc                                                           format_desc_;
+
+       const bool                                                                                                      key_only_;
+       std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> data_;
+public:
+       decklink_frame(core::const_frame frame, const core::video_format_desc& format_desc, bool key_only)
+               : frame_(frame)
+               , format_desc_(format_desc)
+               , key_only_(key_only)
+       {
+               ref_count_ = 0;
+       }
+       
+       // IUnknown
+
+       STDMETHOD (QueryInterface(REFIID, LPVOID*))             
+       {
+               return E_NOINTERFACE;
+       }
+       
+       STDMETHOD_(ULONG,                       AddRef())                       
+       {
+               return ++ref_count_;
+       }
+
+       STDMETHOD_(ULONG,                       Release())                      
+       {
+               if(--ref_count_ == 0)
+                       delete this;
+               return ref_count_;
+       }
+
+       // IDecklinkVideoFrame
+
+       STDMETHOD_(long,                        GetWidth())                     {return static_cast<long>(format_desc_.width);}        
+    STDMETHOD_(long,                   GetHeight())            {return static_cast<long>(format_desc_.height);}        
+    STDMETHOD_(long,                   GetRowBytes())          {return static_cast<long>(format_desc_.width*4);}        
+       STDMETHOD_(BMDPixelFormat,      GetPixelFormat())       {return bmdFormat8BitBGRA;}        
+    STDMETHOD_(BMDFrameFlags,  GetFlags())                     {return bmdFrameFlagDefault;}
+        
+    STDMETHOD(GetBytes(void** buffer))
+       {
+               try
+               {
+                       if(static_cast<int>(frame_.image_data().size()) != format_desc_.size)
+                       {
+                               data_.resize(format_desc_.size, 0);
+                               *buffer = data_.data();
+                       }
+                       else if(key_only_)
+                       {
+                               if(data_.empty())
+                               {
+                                       data_.resize(frame_.image_data().size());
+                                       aligned_memshfl(data_.data(), frame_.image_data().begin(), frame_.image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);
+                               }
+                               *buffer = data_.data();
+                       }
+                       else
+                               *buffer = const_cast<uint8_t*>(frame_.image_data().begin());
+               }
+               catch(...)
+               {
+                       CASPAR_LOG_CURRENT_EXCEPTION();
+                       return E_FAIL;
+               }
+
+               return S_OK;
+       }
+        
+    STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}        
+    STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary))                {return S_FALSE;}
+
+       // decklink_frame       
+
+       const core::audio_buffer& audio_data()
+       {
+               return frame_.audio_data();
+       }
+};
+
+struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
+{              
+       const int                                                       channel_index_;
+       const configuration                                     config_;
+
+       CComPtr<IDeckLink>                                      decklink_;
+       CComQIPtr<IDeckLinkOutput>                      output_;
+       CComQIPtr<IDeckLinkConfiguration>       configuration_;
+       CComQIPtr<IDeckLinkKeyer>                       keyer_;
+       CComQIPtr<IDeckLinkAttributes>          attributes_;
+
+       tbb::spin_mutex                                         exception_mutex_;
+       std::exception_ptr                                      exception_;
+
+       tbb::atomic<bool>                                       is_running_;
+               
+       const std::wstring                                      model_name_;
+       const core::video_format_desc           format_desc_;
+       const int                                                       buffer_size_;
+
+       long long                                                       video_scheduled_;
+       long long                                                       audio_scheduled_;
+
+       int                                                                     preroll_count_;
+               
+       boost::circular_buffer<std::vector<int32_t>>    audio_container_;
+
+       tbb::concurrent_bounded_queue<core::const_frame> video_frame_buffer_;
+       tbb::concurrent_bounded_queue<core::const_frame> audio_frame_buffer_;
+       
+       spl::shared_ptr<diagnostics::graph> graph_;
+       boost::timer tick_timer_;
+
+public:
+       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_)
+               , configuration_(decklink_)
+               , keyer_(decklink_)
+               , attributes_(decklink_)
+               , model_name_(get_model_name(decklink_))
+               , format_desc_(format_desc)
+               , buffer_size_(config.buffer_depth()) // Minimum buffer-size 3.
+               , video_scheduled_(0)
+               , audio_scheduled_(0)
+               , preroll_count_(0)
+               , audio_container_(buffer_size_+1)
+       {
+               is_running_ = true;
+                               
+               video_frame_buffer_.set_capacity(1);
+               audio_frame_buffer_.set_capacity(1);
+
+               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));
+               graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
+               graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
+               graph_->set_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));
+               graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));
+               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(config.latency);                            
+               set_keyer(config.keyer);
+                               
+               if(config.embedded_audio)               
+                       output_->BeginAudioPreroll();           
+               
+               for(int n = 0; n < buffer_size_; ++n)
+                       schedule_next_video(core::const_frame::empty());
+
+               if(!config.embedded_audio)
+                       start_playback();
+       }
+
+       ~decklink_consumer()
+       {               
+               is_running_ = false;
+               video_frame_buffer_.try_push(core::const_frame::empty());
+               audio_frame_buffer_.try_push(core::const_frame::empty());
+
+               if(output_ != nullptr) 
+               {
+                       output_->StopScheduledPlayback(0, nullptr, 0);
+                       if(config_.embedded_audio)
+                               output_->DisableAudioOutput();
+                       output_->DisableVideoOutput();
+               }
+       }
+                       
+       void set_latency(configuration::latency_t latency)
+       {               
+               if(latency == configuration::low_latency)
+               {
+                       configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
+                       CASPAR_LOG(info) << print() << L" Enabled low-latency mode.";
+               }
+               else if(latency == configuration::normal_latency)
+               {                       
+                       configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
+                       CASPAR_LOG(info) << print() << L" Disabled low-latency mode.";
+               }
+       }
+
+       void set_keyer(configuration::keyer_t keyer)
+       {
+               if(keyer == configuration::internal_keyer) 
+               {
+                       BOOL value = true;
+                       if(SUCCEEDED(attributes_->GetFlag(BMDDeckLinkSupportsInternalKeying, &value)) && !value)
+                               CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";   
+                       else if(FAILED(keyer_->Enable(FALSE)))                  
+                               CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   
+                       else if(FAILED(keyer_->SetLevel(255)))                  
+                               CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
+                       else
+                               CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             
+               }
+               else if(keyer == configuration::external_keyer)
+               {
+                       BOOL value = true;
+                       if(SUCCEEDED(attributes_->GetFlag(BMDDeckLinkSupportsExternalKeying, &value)) && !value)
+                               CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   
+                       else if(FAILED(keyer_->Enable(TRUE)))                   
+                               CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   
+                       else if(FAILED(keyer_->SetLevel(255)))                  
+                               CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
+                       else
+                               CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     
+               }
+       }
+       
+       void enable_audio()
+       {
+               if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))
+                               CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable audio output."));
+                               
+               if(FAILED(output_->SetAudioCallback(this)))
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not set audio callback."));
+
+               CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
+       }
+
+       void enable_video(BMDDisplayMode display_mode)
+       {
+               if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) 
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable video output."));
+               
+               if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
+                       CASPAR_THROW_EXCEPTION(caspar_exception() 
+                                                                       << msg_info(u8(print()) + " Failed to set playback completion callback.")
+                                                                       << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
+       }
+
+       void start_playback()
+       {
+               if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) 
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Failed to schedule playback."));
+       }
+       
+       STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}
+       STDMETHOD_(ULONG, AddRef())                                     {return 1;}
+       STDMETHOD_(ULONG, Release())                            {return 1;}
+       
+       STDMETHOD(ScheduledPlaybackHasStopped())
+       {
+               is_running_ = false;
+               CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
+               return S_OK;
+       }
+
+       STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
+       {
+               if(!is_running_)
+                       return E_FAIL;
+               
+               try
+               {
+                       if(result == bmdOutputFrameDisplayedLate)
+                       {
+                               graph_->set_tag("late-frame");
+                               video_scheduled_ += format_desc_.duration;
+                               audio_scheduled_ += reinterpret_cast<decklink_frame*>(completed_frame)->audio_data().size()/format_desc_.audio_channels;
+                               //++video_scheduled_;
+                               //audio_scheduled_ += format_desc_.audio_cadence[0];
+                               //++audio_scheduled_;
+                       }
+                       else if(result == bmdOutputFrameDropped)
+                               graph_->set_tag("dropped-frame");
+                       else if(result == bmdOutputFrameFlushed)
+                               graph_->set_tag("flushed-frame");
+
+                       auto frame = core::const_frame::empty();        
+                       video_frame_buffer_.pop(frame);                                 
+                       schedule_next_video(frame);     
+                       
+                       unsigned long buffered;
+                       output_->GetBufferedVideoFrameCount(&buffered);
+                       graph_->set_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);
+               }
+               catch(...)
+               {
+                       lock(exception_mutex_, [&]
+                       {
+                               exception_ = std::current_exception();
+                       });
+                       return E_FAIL;
+               }
+
+               return S_OK;
+       }
+               
+       STDMETHOD(RenderAudioSamples(BOOL preroll))
+       {
+               if(!is_running_)
+                       return E_FAIL;
+               
+               try
+               {       
+                       if(preroll)
+                       {
+                               if(++preroll_count_ >= buffer_size_)
+                               {
+                                       output_->EndAudioPreroll();
+                                       start_playback();                               
+                               }
+                               else
+                                       schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()], 0));    
+                       }
+                       else
+                       {
+                               auto frame = core::const_frame::empty();
+                               audio_frame_buffer_.pop(frame);
+                               schedule_next_audio(frame.audio_data());
+                       }
+
+                       unsigned long buffered;
+                       output_->GetBufferedAudioSampleFrameCount(&buffered);
+                       graph_->set_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_cadence[0]*2));
+               }
+               catch(...)
+               {
+                       tbb::spin_mutex::scoped_lock lock(exception_mutex_);
+                       exception_ = std::current_exception();
+                       return E_FAIL;
+               }
+
+               return S_OK;
+       }
+
+       template<typename T>
+       void schedule_next_audio(const T& audio_data)
+       {
+               auto sample_frame_count = static_cast<int>(audio_data.size()/format_desc_.audio_channels);
+
+               audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));
+
+               if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, audio_scheduled_, format_desc_.audio_sample_rate, nullptr)))
+                       CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
+
+               audio_scheduled_ += sample_frame_count;
+       }
+                       
+       void schedule_next_video(core::const_frame frame)
+       {
+               CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));
+               if(FAILED(output_->ScheduleVideoFrame(frame2, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
+                       CASPAR_LOG(error) << print() << L" Failed to schedule video.";
+
+               video_scheduled_ += format_desc_.duration;
+
+               graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
+               tick_timer_.restart();
+       }
+
+       void send(core::const_frame frame)
+       {
+               auto exception = lock(exception_mutex_, [&]
+               {
+                       return exception_;
+               });
+
+               if(exception != nullptr)
+                       std::rethrow_exception(exception);              
+
+               if(!is_running_)
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Is not running."));
+               
+               if(config_.embedded_audio)
+                       audio_frame_buffer_.push(frame);        
+               video_frame_buffer_.push(frame);        
+       }
+       
+       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 decklink_consumer_proxy : public core::frame_consumer
+{
+       const configuration                                     config_;
+       std::unique_ptr<decklink_consumer>      consumer_;
+       executor                                                        executor_;
+public:
+
+       decklink_consumer_proxy(const configuration& config)
+               : config_(config)
+               , executor_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
+       {
+               executor_.begin_invoke([=]
+               {
+                       ::CoInitialize(nullptr);
+               });
+       }
+
+       ~decklink_consumer_proxy()
+       {
+               executor_.invoke([=]
+               {
+                       ::CoUninitialize();
+               });
+       }
+
+       // frame_consumer
+       
+       void initialize(const core::video_format_desc& format_desc, int channel_index) override
+       {
+               executor_.invoke([=]
+               {
+                       consumer_.reset();
+                       consumer_.reset(new decklink_consumer(config_, format_desc, channel_index));                    
+               });
+       }
+       
+       bool send(core::const_frame frame) override
+       {
+               consumer_->send(frame);
+               return true;
+       }
+       
+       std::wstring print() const override
+       {
+               return consumer_ ? consumer_->print() : L"[decklink_consumer]";
+       }               
+
+       std::wstring name() const override
+       {
+               return L"decklink";
+       }
+
+       boost::property_tree::wptree info() const override
+       {
+               boost::property_tree::wptree info;
+               info.add(L"type", L"decklink");
+               info.add(L"key-only", config_.key_only);
+               info.add(L"device", config_.device_index);
+               info.add(L"low-latency", config_.low_latency);
+               info.add(L"embedded-audio", config_.embedded_audio);
+               info.add(L"low-latency", config_.low_latency);
+               //info.add(L"internal-key", config_.internal_key);
+               return info;
+       }
+
+       int buffer_depth() const override
+       {
+               return config_.buffer_depth();
+       }
+
+       int index() const override
+       {
+               return 300 + config_.device_index;
+       }
+
+       void subscribe(const monitor::observable::observer_ptr& o) override
+       {
+       }
+
+       void unsubscribe(const monitor::observable::observer_ptr& o) override
+       {
+       }       
+};     
+
+spl::shared_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params) 
+{
+       if(params.size() < 1 || params[0] != L"DECKLINK")
+               return core::frame_consumer::empty();
+       
+       configuration config;
+               
+       if(params.size() > 1)
+               config.device_index = boost::lexical_cast<int>(params[1]);
+       
+       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;
+
+       if(std::find(params.begin(), params.end(), L"LOW_LATENCY")       != params.end())
+               config.latency = configuration::low_latency;
+
+       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();
+
+       return spl::make_shared<decklink_consumer_proxy>(config);
+}
+
+spl::shared_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree) 
+{
+       configuration config;
+
+       auto keyer = ptree.get(L"keyer", L"default");
+       if(keyer == L"external")
+               config.keyer = configuration::external_keyer;
+       else if(keyer == L"internal")
+               config.keyer = configuration::internal_keyer;
+
+       auto latency = ptree.get(L"latency", L"normal");
+       if(latency == L"low")
+               config.latency = configuration::low_latency;
+       else if(latency == L"normal")
+               config.latency = configuration::normal_latency;
+
+       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.base_buffer_depth        = ptree.get(L"buffer-depth",    config.base_buffer_depth);
+
+       return spl::make_shared<decklink_consumer_proxy>(config);
+}
+
+}}
+
+/*
+##############################################################################
+Pre-rolling
+
+Mail: 2011-05-09
+
+Yoshan
+BMD Developer Support
+developer@blackmagic-design.com
+
+-----------------------------------------------------------------------------
+
+Thanks for your inquiry. The minimum number of frames that you can preroll 
+for scheduled playback is three frames for video and four frames for audio. 
+As you mentioned if you preroll less frames then playback will not start or
+playback will be very sporadic. From our experience with Media Express, we 
+recommended that at least seven frames are prerolled for smooth playback. 
+
+Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
+There can be around 3 frames worth of latency on scheduled output.
+When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
+reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() 
+method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will 
+guarantee that the provided frame will be output as soon the previous 
+frame output has been completed.
+################################################################################
+*/
+
+/*
+##############################################################################
+Async DMA Transfer without redundant copying
+
+Mail: 2011-05-10
+
+Yoshan
+BMD Developer Support
+developer@blackmagic-design.com
+
+-----------------------------------------------------------------------------
+
+Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame 
+and providing a pointer to your video buffer when GetBytes() is called. 
+This may help to keep copying to a minimum. Please ensure that the pixel 
+format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will 
+have to colourspace convert which may result in additional copying.
+################################################################################
 */
\ No newline at end of file