]> git.sesse.net Git - casparcg/blobdiff - modules/decklink/consumer/decklink_consumer.cpp
[decklink_consumer] Fixed incorrect default value for embedded_audio
[casparcg] / modules / decklink / consumer / decklink_consumer.cpp
index 97043a03f51de99f30b7d000e1a8558166aee3b6..c9ad1a5c12d9dbbfece9dfbe29e1586c01fb1c57 100644 (file)
 */
 
 #include "../StdAfx.h"
+
 #include "decklink_consumer.h"
 
 #include "../util/util.h"
+#include "../decklink.h"
 
-#include "../interop/DeckLinkAPI_h.h"
+#include "../decklink_api.h"
 
 #include <core/frame/frame.h>
+#include <core/frame/audio_channel_layout.h>
 #include <core/mixer/audio/audio_mixer.h>
+#include <core/consumer/frame_consumer.h>
+#include <core/diagnostics/call_context.h>
+#include <core/help/help_sink.h>
+#include <core/help/help_repository.h>
 
 #include <common/executor.h>
 #include <common/lock.h>
 #include <common/diagnostics/graph.h>
 #include <common/except.h>
 #include <common/memshfl.h>
+#include <common/memcpy.h>
+#include <common/no_init_proxy.h>
 #include <common/array.h>
 #include <common/future.h>
-
-#include <core/consumer/frame_consumer.h>
+#include <common/cache_aligned_vector.h>
+#include <common/timer.h>
+#include <common/param.h>
+#include <common/software_version.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>
+#include <boost/thread/mutex.hpp>
+
+#include <future>
+
+namespace caspar { namespace decklink {
 
-namespace caspar { namespace decklink { 
-       
 struct configuration
 {
-       enum keyer_t
+       enum class keyer_t
        {
                internal_keyer,
                external_keyer,
-               default_keyer
+               external_separate_device_keyer,
+               default_keyer                                   = external_keyer
        };
 
-       enum latency_t
+       enum class latency_t
        {
                low_latency,
                normal_latency,
-               default_latency
+               default_latency = normal_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                                                     device_index            = 1;
+       int                                                     key_device_idx          = 0;
+       bool                                            embedded_audio          = false;
+       keyer_t                                         keyer                           = keyer_t::default_keyer;
+       latency_t                                       latency                         = latency_t::default_latency;
+       bool                                            key_only                        = false;
+       int                                                     base_buffer_depth       = 3;
+       core::audio_channel_layout      out_channel_layout      = core::audio_channel_layout::invalid();
+
        int buffer_depth() const
        {
-               return base_buffer_depth + (latency == low_latency ? 0 : 1) + (embedded_audio ? 1 : 0);
+               return base_buffer_depth + (latency == latency_t::low_latency ? 0 : 1);
+       }
+
+       int key_device_index() const
+       {
+               return key_device_idx == 0 ? device_index + 1 : key_device_idx;
+       }
+
+       core::audio_channel_layout get_adjusted_layout(const core::audio_channel_layout& in_layout) const
+       {
+               auto adjusted = out_channel_layout == core::audio_channel_layout::invalid() ? in_layout : out_channel_layout;
+
+               if (adjusted.num_channels == 1) // Duplicate mono-signal into both left and right.
+               {
+                       adjusted.num_channels = 2;
+                       adjusted.channel_order.push_back(adjusted.channel_order.at(0)); // Usually FC -> FC FC
+               }
+               else if (adjusted.num_channels == 2)
+               {
+                       adjusted.num_channels = 2;
+               }
+               else if (adjusted.num_channels <= 8)
+               {
+                       adjusted.num_channels = 8;
+               }
+               else // Over 8 always pad to 16 or drop >16
+               {
+                       adjusted.num_channels = 16;
+               }
+
+               return adjusted;
        }
 };
 
+template <typename Configuration>
+void set_latency(
+               const com_iface_ptr<Configuration>& config,
+               configuration::latency_t latency,
+               const std::wstring& print)
+{
+       if (latency == configuration::latency_t::low_latency)
+       {
+               config->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
+               CASPAR_LOG(info) << print << L" Enabled low-latency mode.";
+       }
+       else if (latency == configuration::latency_t::normal_latency)
+       {
+               config->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
+               CASPAR_LOG(info) << print << L" Disabled low-latency mode.";
+       }
+}
+
+void set_keyer(
+               const com_iface_ptr<IDeckLinkAttributes>& attributes,
+               const com_iface_ptr<IDeckLinkKeyer>& decklink_keyer,
+               configuration::keyer_t keyer,
+               const std::wstring& print)
+{
+       if (keyer == configuration::keyer_t::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(decklink_keyer->Enable(FALSE)))
+                       CASPAR_LOG(error) << print << L" Failed to enable internal keyer.";
+               else if (FAILED(decklink_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::keyer_t::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(decklink_keyer->Enable(TRUE)))
+                       CASPAR_LOG(error) << print << L" Failed to enable external keyer.";
+               else if (FAILED(decklink_keyer->SetLevel(255)))
+                       CASPAR_LOG(error) << print << L" Failed to set key-level to max.";
+               else
+                       CASPAR_LOG(info) << print << L" Enabled external keyer.";
+       }
+}
+
 class decklink_frame : public IDeckLinkVideoFrame
 {
-       tbb::atomic<int>                                                                                        ref_count_;
-       core::const_frame                                                                                       frame_;
-       const core::video_format_desc                                                           format_desc_;
+       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_;
+       const bool                                                                              key_only_;
+       bool                                                                                    needs_to_copy_;
+       cache_aligned_vector<no_init_proxy<uint8_t>>    data_;
 public:
-       decklink_frame(core::const_frame frame, const core::video_format_desc& format_desc, bool key_only)
+       decklink_frame(core::const_frame frame, const core::video_format_desc& format_desc, bool key_only, bool will_attempt_dma)
                : frame_(frame)
                , format_desc_(format_desc)
                , key_only_(key_only)
        {
                ref_count_ = 0;
+
+               bool dma_transfer_from_gl_buffer_impossible;
+
+#if !defined(_MSC_VER)
+               // On Linux Decklink cannot DMA transfer from memory returned by glMapBuffer (at least on nvidia)
+               dma_transfer_from_gl_buffer_impossible = true;
+#else
+               // On Windows it is possible.
+               dma_transfer_from_gl_buffer_impossible = false;
+#endif
+
+               needs_to_copy_ = will_attempt_dma && dma_transfer_from_gl_buffer_impossible;
        }
-       
+
        // IUnknown
 
-       STDMETHOD (QueryInterface(REFIID, LPVOID*))             
+       virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)
        {
                return E_NOINTERFACE;
        }
-       
-       STDMETHOD_(ULONG,                       AddRef())                       
+
+       virtual ULONG STDMETHODCALLTYPE AddRef()
        {
                return ++ref_count_;
        }
 
-       STDMETHOD_(ULONG,                       Release())                      
+       virtual ULONG STDMETHODCALLTYPE Release()
        {
                if(--ref_count_ == 0)
+               {
                        delete this;
+
+                       return 0;
+               }
+
                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))
+       virtual long STDMETHODCALLTYPE GetWidth()                   {return static_cast<long>(format_desc_.width);}
+       virtual long STDMETHODCALLTYPE GetHeight()                  {return static_cast<long>(format_desc_.height);}
+       virtual long STDMETHODCALLTYPE GetRowBytes()                {return static_cast<long>(format_desc_.width*4);}
+       virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat()   {return bmdFormat8BitBGRA;}
+       virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags()                      {return bmdFrameFlagDefault;}
+
+       virtual HRESULT STDMETHODCALLTYPE GetBytes(void** buffer)
        {
                try
                {
                        if(static_cast<int>(frame_.image_data().size()) != format_desc_.size)
                        {
-                               data_.resize(format_desc_.size, 0);
+                               data_.resize(format_desc_.size);
                                *buffer = data_.data();
                        }
                        else if(key_only_)
@@ -153,7 +255,16 @@ public:
                                *buffer = data_.data();
                        }
                        else
+                       {
                                *buffer = const_cast<uint8_t*>(frame_.image_data().begin());
+
+                               if (needs_to_copy_)
+                               {
+                                       data_.resize(frame_.image_data().size());
+                                       fast_memcpy(data_.data(), *buffer, frame_.image_data().size());
+                                       *buffer = data_.data();
+                               }
+                       }
                }
                catch(...)
                {
@@ -163,108 +274,207 @@ public:
 
                return S_OK;
        }
-        
-    STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}        
-    STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary))                {return S_FALSE;}
 
-       // decklink_frame       
+       virtual HRESULT STDMETHODCALLTYPE GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode) {return S_FALSE;}
+       virtual HRESULT STDMETHODCALLTYPE GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)              {return S_FALSE;}
+
+       // decklink_frame
 
        const core::audio_buffer& audio_data()
        {
                return frame_.audio_data();
        }
+
+       int64_t get_age_millis() const
+       {
+               return frame_.get_age_millis();
+       }
 };
 
-struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
-{              
-       const int                                                       channel_index_;
+template <typename Configuration>
+struct key_video_context : public IDeckLinkVideoOutputCallback, boost::noncopyable
+{
        const configuration                                     config_;
+       com_ptr<IDeckLink>                                      decklink_                                       = get_device(config_.key_device_index());
+       com_iface_ptr<IDeckLinkOutput>          output_                                         = iface_cast<IDeckLinkOutput>(decklink_);
+       com_iface_ptr<IDeckLinkKeyer>           keyer_                                          = iface_cast<IDeckLinkKeyer>(decklink_, true);
+       com_iface_ptr<IDeckLinkAttributes>      attributes_                                     = iface_cast<IDeckLinkAttributes>(decklink_);
+       com_iface_ptr<Configuration>            configuration_                          = iface_cast<Configuration>(decklink_);
+       tbb::atomic<int64_t>                            current_presentation_delay_;
+       tbb::atomic<int64_t>                            scheduled_frames_completed_;
+
+       key_video_context(const configuration& config, const std::wstring& print)
+               : config_(config)
+       {
+               current_presentation_delay_ = 0;
+               scheduled_frames_completed_ = 0;
 
-       CComPtr<IDeckLink>                                      decklink_;
-       CComQIPtr<IDeckLinkOutput>                      output_;
-       CComQIPtr<IDeckLinkConfiguration>       configuration_;
-       CComQIPtr<IDeckLinkKeyer>                       keyer_;
-       CComQIPtr<IDeckLinkAttributes>          attributes_;
+               set_latency(configuration_, config.latency, print);
+               set_keyer(attributes_, keyer_, config.keyer, print);
 
-       tbb::spin_mutex                                         exception_mutex_;
-       std::exception_ptr                                      exception_;
+               if (FAILED(output_->SetScheduledFrameCompletionCallback(this)))
+                       CASPAR_THROW_EXCEPTION(caspar_exception()
+                                       << msg_info(print + L" Failed to set key playback completion callback.")
+                                       << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
+       }
 
-       tbb::atomic<bool>                                       is_running_;
-               
-       const std::wstring                                      model_name_;
-       const core::video_format_desc           format_desc_;
-       const int                                                       buffer_size_;
+       template<typename Print>
+       void enable_video(BMDDisplayMode display_mode, const Print& print)
+       {
+               if (FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Could not enable key video output."));
 
-       long long                                                       video_scheduled_;
-       long long                                                       audio_scheduled_;
+               if (FAILED(output_->SetScheduledFrameCompletionCallback(this)))
+                       CASPAR_THROW_EXCEPTION(caspar_exception()
+                                       << msg_info(print() + L" Failed to set key playback completion callback.")
+                                       << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
+       }
 
-       int                                                                     preroll_count_;
-               
-       boost::circular_buffer<std::vector<int32_t>>    audio_container_;
+       virtual ~key_video_context()
+       {
+               if (output_)
+               {
+                       output_->StopScheduledPlayback(0, nullptr, 0);
+                       output_->DisableVideoOutput();
+               }
+       }
 
-       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_;
-       retry_task<bool> send_completion_;
+       virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)       {return E_NOINTERFACE;}
+       virtual ULONG STDMETHODCALLTYPE AddRef()                                                        {return 1;}
+       virtual ULONG STDMETHODCALLTYPE Release()                                                       {return 1;}
+
+       virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped()
+       {
+               return S_OK;
+       }
+
+       virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted(
+                       IDeckLinkVideoFrame* completed_frame,
+                       BMDOutputFrameCompletionResult result)
+       {
+               auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);
+               current_presentation_delay_ = dframe->get_age_millis();
+               ++scheduled_frames_completed_;
+
+               // Let the fill callback keep the pace, so no scheduling here.
+
+               return S_OK;
+       }
+};
+
+template <typename Configuration>
+struct decklink_consumer : public IDeckLinkVideoOutputCallback, boost::noncopyable
+{
+       const int                                                                                       channel_index_;
+       const configuration                                                                     config_;
+
+       com_ptr<IDeckLink>                                                                      decklink_                               = get_device(config_.device_index);
+       com_iface_ptr<IDeckLinkOutput>                                          output_                                 = iface_cast<IDeckLinkOutput>(decklink_);
+       com_iface_ptr<Configuration>                                            configuration_                  = iface_cast<Configuration>(decklink_);
+       com_iface_ptr<IDeckLinkKeyer>                                           keyer_                                  = iface_cast<IDeckLinkKeyer>(decklink_, true);
+       com_iface_ptr<IDeckLinkAttributes>                                      attributes_                             = iface_cast<IDeckLinkAttributes>(decklink_);
+
+       tbb::spin_mutex                                                                         exception_mutex_;
+       std::exception_ptr                                                                      exception_;
+
+       tbb::atomic<bool>                                                                       is_running_;
+
+       const std::wstring                                                                      model_name_                             = get_model_name(decklink_);
+       bool                                                                                            will_attempt_dma_;
+       const core::video_format_desc                                           format_desc_;
+       const core::audio_channel_layout                                        in_channel_layout_;
+       const core::audio_channel_layout                                        out_channel_layout_             = config_.get_adjusted_layout(in_channel_layout_);
+       core::audio_channel_remapper                                            channel_remapper_               { in_channel_layout_, out_channel_layout_ };
+       const int                                                                                       buffer_size_                    = config_.buffer_depth(); // Minimum buffer-size 3.
+
+       long long                                                                                       video_scheduled_                = 0;
+       long long                                                                                       audio_scheduled_                = 0;
+
+       int                                                                                                     preroll_count_                  = 0;
+
+       boost::circular_buffer<std::vector<int32_t>>            audio_container_                { buffer_size_ + 1 };
+
+       tbb::concurrent_bounded_queue<core::const_frame>        frame_buffer_;
+
+       spl::shared_ptr<diagnostics::graph>                                     graph_;
+       caspar::timer                                                                           tick_timer_;
+       boost::mutex                                                                            send_completion_mutex_;
+       std::packaged_task<bool ()>                                                     send_completion_;
+       reference_signal_detector                                                       reference_signal_detector_      { output_ };
+       tbb::atomic<int64_t>                                                            current_presentation_delay_;
+       tbb::atomic<int64_t>                                                            scheduled_frames_completed_;
+       std::unique_ptr<key_video_context<Configuration>>       key_context_;
 
 public:
-       decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index) 
+       decklink_consumer(
+                       const configuration& config,
+                       const core::video_format_desc& format_desc,
+                       const core::audio_channel_layout& in_channel_layout,
+                       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)
+               , in_channel_layout_(in_channel_layout)
        {
                is_running_ = true;
-                               
-               video_frame_buffer_.set_capacity(1);
-               audio_frame_buffer_.set_capacity(1);
+               current_presentation_delay_ = 0;
+               scheduled_frames_completed_ = 0;
+
+               frame_buffer_.set_capacity(1);
 
-               graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));   
+               if (config.keyer == configuration::keyer_t::external_separate_device_keyer)
+                       key_context_.reset(new key_video_context<Configuration>(config, print()));
+
+               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));
+
+               if (key_context_)
+               {
+                       graph_->set_color("key-offset", diagnostics::color(1.0f, 0.0f, 0.0f));
+               }
+
                graph_->set_text(print());
                diagnostics::register_graph(graph_);
-               
-               enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
-                               
+
+               enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault, will_attempt_dma_));
+
                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)
+
+               set_latency(configuration_, config.latency, print());
+               set_keyer(attributes_, keyer_, config.keyer, print());
+
+               if(config.embedded_audio)
+                       output_->BeginAudioPreroll();
+
+               for (int n = 0; n < buffer_size_; ++n)
+               {
+                       if (config.embedded_audio)
+                               schedule_next_audio(core::mutable_audio_buffer(format_desc_.audio_cadence[n % format_desc_.audio_cadence.size()] * out_channel_layout_.num_channels, 0));
+
                        schedule_next_video(core::const_frame::empty());
+               }
+
+               if (config.embedded_audio)
+               {
+                       // Preroll one extra frame worth of audio
+                       schedule_next_audio(core::mutable_audio_buffer(format_desc_.audio_cadence[buffer_size_ % format_desc_.audio_cadence.size()] * out_channel_layout_.num_channels, 0));
+                       output_->EndAudioPreroll();
+               }
 
-               if(!config.embedded_audio)
-                       start_playback();
+               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());
+               frame_buffer_.try_push(core::const_frame::empty());
 
-               if(output_ != nullptr) 
+               if(output_ != nullptr)
                {
                        output_->StopScheduledPlayback(0, nullptr, 0);
                        if(config_.embedded_audio)
@@ -272,163 +482,120 @@ public:
                        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."));
+               if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, out_channel_layout_.num_channels, bmdAudioOutputStreamTimestamped)))
+                               CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" 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))) 
-                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable video output."));
-               
+               if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Could not enable fill video output."));
+
                if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
-                       CASPAR_THROW_EXCEPTION(caspar_exception() 
-                                                                       << msg_info(u8(print()) + " Failed to set playback completion callback.")
+                       CASPAR_THROW_EXCEPTION(caspar_exception()
+                                                                       << msg_info(print() + L" Failed to set fill playback completion callback.")
                                                                        << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
+
+               if (key_context_)
+                       key_context_->enable_video(display_mode, [this]() { return print(); });
        }
 
        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."));
+               if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to schedule fill playback."));
+
+               if (key_context_ && FAILED(key_context_->output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to schedule key playback."));
        }
-       
-       STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}
-       STDMETHOD_(ULONG, AddRef())                                     {return 1;}
-       STDMETHOD_(ULONG, Release())                            {return 1;}
-       
-       STDMETHOD(ScheduledPlaybackHasStopped())
+
+       virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)       {return E_NOINTERFACE;}
+       virtual ULONG STDMETHODCALLTYPE AddRef()                                        {return 1;}
+       virtual ULONG STDMETHODCALLTYPE Release()                               {return 1;}
+
+       virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped()
        {
                is_running_ = false;
                CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
                return S_OK;
        }
 
-       STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
+       virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result)
        {
                if(!is_running_)
                        return E_FAIL;
-               
+
                try
                {
+                       auto tick_time = tick_timer_.elapsed()*format_desc_.fps * 0.5;
+                       graph_->set_value("tick-time", tick_time);
+                       tick_timer_.restart();
+
+                       reference_signal_detector_.detect_change([this]() { return print(); });
+
+                       auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);
+                       current_presentation_delay_ = dframe->get_age_millis();
+                       ++scheduled_frames_completed_;
+
+                       if (key_context_)
+                               graph_->set_value(
+                                               "key-offset",
+                                               static_cast<double>(
+                                                               scheduled_frames_completed_
+                                                               - key_context_->scheduled_frames_completed_)
+                                               * 0.1 + 0.5);
+
                        if(result == bmdOutputFrameDisplayedLate)
                        {
-                               graph_->set_tag("late-frame");
+                               graph_->set_tag(diagnostics::tag_severity::WARNING, "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_;
+                               audio_scheduled_ += dframe->audio_data().size() / in_channel_layout_.num_channels;
                        }
                        else if(result == bmdOutputFrameDropped)
-                               graph_->set_tag("dropped-frame");
+                               graph_->set_tag(diagnostics::tag_severity::WARNING, "dropped-frame");
                        else if(result == bmdOutputFrameFlushed)
-                               graph_->set_tag("flushed-frame");
-
-                       auto frame = core::const_frame::empty();        
-                       video_frame_buffer_.pop(frame);
-                       send_completion_.try_completion();
-                       schedule_next_video(frame);     
-                       
-                       unsigned long buffered;
+                               graph_->set_tag(diagnostics::tag_severity::WARNING, "flushed-frame");
+
+                       UINT32 buffered;
                        output_->GetBufferedVideoFrameCount(&buffered);
-                       graph_->set_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);
-               }
-               catch(...)
-               {
-                       lock(exception_mutex_, [&]
+                       graph_->set_value("buffered-video", static_cast<double>(buffered) / (config_.buffer_depth()));
+
+                       if (config_.embedded_audio)
                        {
-                               exception_ = std::current_exception();
-                       });
-                       return E_FAIL;
-               }
+                               output_->GetBufferedAudioSampleFrameCount(&buffered);
+                               graph_->set_value("buffered-audio", static_cast<double>(buffered) / (format_desc_.audio_cadence[0] * config_.buffer_depth()));
+                       }
+
+                       auto frame = core::const_frame::empty();
+
+                       frame_buffer_.pop(frame);
 
-               return S_OK;
-       }
-               
-       STDMETHOD(RenderAudioSamples(BOOL preroll))
-       {
-               if(!is_running_)
-                       return E_FAIL;
-               
-               try
-               {       
-                       if(preroll)
                        {
-                               if(++preroll_count_ >= buffer_size_)
+                               boost::lock_guard<boost::mutex> lock(send_completion_mutex_);
+
+                               if (send_completion_.valid())
                                {
-                                       output_->EndAudioPreroll();
-                                       start_playback();                               
+                                       send_completion_();
+                                       send_completion_ = std::packaged_task<bool()>();
                                }
-                               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);
-                               send_completion_.try_completion();
-                               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));
+                       if (config_.embedded_audio)
+                               schedule_next_audio(channel_remapper_.mix_and_rearrange(frame.audio_data()));
+
+                       schedule_next_video(frame);
                }
                catch(...)
                {
-                       tbb::spin_mutex::scoped_lock lock(exception_mutex_);
-                       exception_ = std::current_exception();
+                       lock(exception_mutex_, [&]
+                       {
+                               exception_ = std::current_exception();
+                       });
                        return E_FAIL;
                }
 
@@ -438,7 +605,7 @@ public:
        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);
+               auto sample_frame_count = static_cast<int>(audio_data.size()/out_channel_layout_.num_channels);
 
                audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));
 
@@ -447,20 +614,24 @@ public:
 
                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.";
+               if (key_context_)
+               {
+                       auto key_frame = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, true, will_attempt_dma_));
+                       if (FAILED(key_context_->output_->ScheduleVideoFrame(get_raw(key_frame), video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
+                               CASPAR_LOG(error) << print() << L" Failed to schedule key video.";
+               }
 
-               video_scheduled_ += format_desc_.duration;
+               auto fill_frame = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, config_.key_only, will_attempt_dma_));
+               if (FAILED(output_->ScheduleVideoFrame(get_raw(fill_frame), video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
+                       CASPAR_LOG(error) << print() << L" Failed to schedule fill video.";
 
-               graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
-               tick_timer_.restart();
+               video_scheduled_ += format_desc_.duration;
        }
 
-       boost::unique_future<bool> send(core::const_frame frame)
+       std::future<bool> send(core::const_frame frame)
        {
                auto exception = lock(exception_mutex_, [&]
                {
@@ -468,57 +639,60 @@ public:
                });
 
                if(exception != nullptr)
-                       std::rethrow_exception(exception);              
+                       std::rethrow_exception(exception);
 
                if(!is_running_)
-                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Is not running."));
-               
-               bool audio_ready = !config_.embedded_audio;
-               bool video_ready = false;
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Is not running."));
 
-               auto enqueue_task = [audio_ready, video_ready, frame, this]() mutable -> boost::optional<bool>
-               {
-                       if (!audio_ready)
-                               audio_ready = audio_frame_buffer_.try_push(frame);
+               if (frame_buffer_.try_push(frame))
+                       return make_ready_future(true);
 
-                       if (!video_ready)
-                               video_ready = video_frame_buffer_.try_push(frame);
+               boost::lock_guard<boost::mutex> lock(send_completion_mutex_);
 
-                       if (audio_ready && video_ready)
-                               return true;
-                       else
-                               return boost::optional<bool>();
-               };
-               
-               if (enqueue_task())
-                       return wrap_as_future(true);
+               send_completion_ = std::packaged_task<bool ()>([frame, this] () mutable -> bool
+               {
+                       frame_buffer_.push(frame);
 
-               send_completion_.set_task(enqueue_task);
+                       return true;
+               });
 
                return send_completion_.get_future();
        }
-       
+
        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"]";
+               if (config_.keyer == configuration::keyer_t::external_separate_device_keyer)
+                       return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_)+L"-" +
+                               boost::lexical_cast<std::wstring>(config_.device_index) +
+                               L"&&" +
+                               boost::lexical_cast<std::wstring>(config_.key_device_index()) +
+                               L"|" +
+                               format_desc_.name + L"]";
+               else
+                       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"]";
        }
 };
 
+template <typename Configuration>
 struct decklink_consumer_proxy : public core::frame_consumer
 {
-       const configuration                                     config_;
-       std::unique_ptr<decklink_consumer>      consumer_;
-       executor                                                        executor_;
+       core::monitor::subject                                                          monitor_subject_;
+       const configuration                                                                     config_;
+       std::unique_ptr<decklink_consumer<Configuration>>       consumer_;
+       core::video_format_desc                                                         format_desc_;
+       executor                                                                                        executor_;
 public:
 
        decklink_consumer_proxy(const configuration& config)
                : config_(config)
                , executor_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
        {
+               auto ctx = core::diagnostics::call_context::for_thread();
                executor_.begin_invoke([=]
                {
-                       ::CoInitialize(nullptr);
+                       core::diagnostics::call_context::for_thread() = ctx;
+                       com_initialize();
                });
        }
 
@@ -526,30 +700,32 @@ public:
        {
                executor_.invoke([=]
                {
-                       ::CoUninitialize();
+                       consumer_.reset();
+                       com_uninitialize();
                });
        }
 
        // frame_consumer
-       
-       void initialize(const core::video_format_desc& format_desc, int channel_index) override
+
+       void initialize(const core::video_format_desc& format_desc, const core::audio_channel_layout& channel_layout, int channel_index) override
        {
+               format_desc_ = format_desc;
                executor_.invoke([=]
                {
                        consumer_.reset();
-                       consumer_.reset(new decklink_consumer(config_, format_desc, channel_index));                    
+                       consumer_.reset(new decklink_consumer<Configuration>(config_, format_desc, channel_layout, channel_index));
                });
        }
-       
-       boost::unique_future<bool> send(core::const_frame frame) override
+
+       std::future<bool> send(core::const_frame frame) override
        {
                return consumer_->send(frame);
        }
-       
+
        std::wstring print() const override
        {
                return consumer_ ? consumer_->print() : L"[decklink_consumer]";
-       }               
+       }
 
        std::wstring name() const override
        {
@@ -562,16 +738,22 @@ public:
                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);
+
+               if (config_.keyer == configuration::keyer_t::external_separate_device_keyer)
+               {
+                       info.add(L"key-device", config_.key_device_index());
+               }
+
+               info.add(L"low-latency", config_.latency == configuration::latency_t::low_latency);
                info.add(L"embedded-audio", config_.embedded_audio);
-               info.add(L"low-latency", config_.low_latency);
+               info.add(L"presentation-frame-age", presentation_frame_age_millis());
                //info.add(L"internal-key", config_.internal_key);
                return info;
        }
 
        int buffer_depth() const override
        {
-               return config_.buffer_depth();
+               return config_.buffer_depth() + 2;
        }
 
        int index() const override
@@ -579,63 +761,157 @@ public:
                return 300 + config_.device_index;
        }
 
-       void subscribe(const monitor::observable::observer_ptr& o) override
+       int64_t presentation_frame_age_millis() const override
        {
+               return consumer_ ? static_cast<int64_t>(consumer_->current_presentation_delay_) : 0;
        }
 
-       void unsubscribe(const monitor::observable::observer_ptr& o) override
+       core::monitor::subject& monitor_output()
        {
-       }       
-};     
+               return monitor_subject_;
+       }
+};
 
-spl::shared_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params) 
+const software_version<3>& get_driver_version()
 {
-       if(params.size() < 1 || params[0] != L"DECKLINK")
+       static software_version<3> version(u8(get_version()));
+
+       return version;
+}
+
+const software_version<3> get_new_configuration_api_version()
+{
+       static software_version<3> NEW_CONFIGURATION_API("10.2");
+
+       return NEW_CONFIGURATION_API;
+}
+
+void describe_consumer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Sends video on an SDI output using Blackmagic Decklink video cards.");
+       sink.syntax(L"DECKLINK "
+                               L"{[device_index:int]|1} "
+                               L"{[keyer:INTERNAL_KEY,EXTERNAL_KEY,EXTERNAL_SEPARATE_DEVICE_KEY]} "
+                               L"{[low_latency:LOW_LATENCY]} "
+                               L"{[embedded_audio:EMBEDDED_AUDIO]} "
+                               L"{[key_only:KEY_ONLY]} "
+                               L"{CHANNEL_LAYOUT [channel_layout:string]}");
+       sink.para()->text(L"Sends video on an SDI output using Blackmagic Decklink video cards.");
+       sink.definitions()
+               ->item(L"device_index", L"The Blackmagic video card to use (See Blackmagic control panel for card order). Default is 1.")
+               ->item(L"keyer",
+                               L"If given tries to enable either internal or external keying. Not all Blackmagic cards supports this. "
+                               L"There is also a third experimental option (EXTERNAL_SEPARATE_DEVICE_KEY) which allocates device_index + 1 for synhronized key output.")
+               ->item(L"low_latency", L"Tries to enable low latency if given.")
+               ->item(L"embedded_audio", L"Embeds the audio into the SDI signal if given.")
+               ->item(L"key_only",
+                               L" will extract only the alpha channel from the "
+                               L"channel. This is useful when you have two SDI video cards, and neither has native support "
+                               L"for separate fill/key output")
+               ->item(L"channel_layout", L"If specified, overrides the audio channel layout used by the channel.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> ADD 1 DECKLINK", L"for using the default device_index of 1.");
+       sink.example(L">> ADD 1 DECKLINK 2", L"uses device_index 2.");
+       sink.example(L">> ADD 1 DECKLINK 1 EXTERNAL_KEY EMBEDDED_AUDIO");
+       sink.example(
+                       L">> ADD 1 DECKLINK 1 EMBEDDED_AUDIO\n"
+                       L">> ADD 1 DECKLINK 2 KEY_ONLY", L"uses device with index 1 as fill output with audio and device with index 2 as key output.");
+       sink.example(
+                       L">> ADD 1 DECKLINK 1 EXTERNAL_SEPARATE_DEVICE_KEY EMBEDDED_AUDIO",
+                       L"Uses device 2 for key output. May give better sync between key and fill than the previous method.");
+}
+
+spl::shared_ptr<core::frame_consumer> create_consumer(
+               const std::vector<std::wstring>& params, core::interaction_sink*, std::vector<spl::shared_ptr<core::video_channel>> channels)
+{
+       if (params.size() < 1 || !boost::iequals(params.at(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;
+
+       if (params.size() > 1)
+               config.device_index = boost::lexical_cast<int>(params.at(1));
+
+       if (contains_param(L"INTERNAL_KEY", params))
+               config.keyer = configuration::keyer_t::internal_keyer;
+       else if (contains_param(L"EXTERNAL_KEY", params))
+               config.keyer = configuration::keyer_t::external_keyer;
+       else if (contains_param(L"EXTERNAL_SEPARATE_DEVICE_KEY", params))
+               config.keyer = configuration::keyer_t::external_separate_device_keyer;
        else
-               config.keyer = configuration::default_keyer;
+               config.keyer = configuration::keyer_t::default_keyer;
+
+       if (contains_param(L"LOW_LATENCY", params))
+               config.latency = configuration::latency_t::low_latency;
+
+       config.embedded_audio   = contains_param(L"EMBEDDED_AUDIO", params);
+       config.key_only                 = contains_param(L"KEY_ONLY", params);
+
+       auto channel_layout = get_param(L"CHANNEL_LAYOUT", params);
+
+       if (!channel_layout.empty())
+       {
+               auto found_layout = core::audio_channel_layout_repository::get_default()->get_layout(channel_layout);
 
-       if(std::find(params.begin(), params.end(), L"LOW_LATENCY")       != params.end())
-               config.latency = configuration::low_latency;
+               if (!found_layout)
+                       CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Channel layout " + channel_layout + L" not found."));
 
-       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.out_channel_layout = *found_layout;
+       }
+
+       bool old_configuration_api = get_driver_version() < get_new_configuration_api_version();
 
-       return spl::make_shared<decklink_consumer_proxy>(config);
+       if (old_configuration_api)
+               return spl::make_shared<decklink_consumer_proxy<IDeckLinkConfiguration_v10_2>>(config);
+       else
+               return spl::make_shared<decklink_consumer_proxy<IDeckLinkConfiguration>>(config);
 }
 
-spl::shared_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree) 
+spl::shared_ptr<core::frame_consumer> create_preconfigured_consumer(
+               const boost::property_tree::wptree& ptree, core::interaction_sink*, std::vector<spl::shared_ptr<core::video_channel>> channels)
 {
        configuration config;
 
        auto keyer = ptree.get(L"keyer", L"default");
        if(keyer == L"external")
-               config.keyer = configuration::external_keyer;
+               config.keyer = configuration::keyer_t::external_keyer;
        else if(keyer == L"internal")
-               config.keyer = configuration::internal_keyer;
+               config.keyer = configuration::keyer_t::internal_keyer;
+       else if (keyer == L"external_separate_device")
+               config.keyer = configuration::keyer_t::external_separate_device_keyer;
 
-       auto latency = ptree.get(L"latency", L"normal");
+       auto latency = ptree.get(L"latency", L"default");
        if(latency == L"low")
-               config.latency = configuration::low_latency;
+               config.latency = configuration::latency_t::low_latency;
        else if(latency == L"normal")
-               config.latency = configuration::normal_latency;
+               config.latency = configuration::latency_t::normal_latency;
+
+       auto channel_layout = ptree.get_optional<std::wstring>(L"channel-layout");
+
+       if (channel_layout)
+       {
+               CASPAR_SCOPED_CONTEXT_MSG("/channel-layout")
+
+               auto found_layout = core::audio_channel_layout_repository::get_default()->get_layout(*channel_layout);
+
+               if (!found_layout)
+                       CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Channel layout " + *channel_layout + L" not found."));
+
+               config.out_channel_layout = *found_layout;
+       }
 
        config.key_only                         = ptree.get(L"key-only",                config.key_only);
        config.device_index                     = ptree.get(L"device",                  config.device_index);
+       config.key_device_idx           = ptree.get(L"key-device",              config.key_device_idx);
        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);
+       bool old_configuration_api = get_driver_version() < get_new_configuration_api_version();
+
+       if (old_configuration_api)
+               return spl::make_shared<decklink_consumer_proxy<IDeckLinkConfiguration_v10_2>>(config);
+       else
+               return spl::make_shared<decklink_consumer_proxy<IDeckLinkConfiguration>>(config);
 }
 
 }}
@@ -652,18 +928,18 @@ 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. 
+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. 
+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 
+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.
 ################################################################################
 */
@@ -680,10 +956,10 @@ 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 
+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
+*/