]> git.sesse.net Git - casparcg/blobdiff - modules/decklink/consumer/decklink_consumer.cpp
[decklink_consumer] Don't schedule final empty frame at shutdown
[casparcg] / modules / decklink / consumer / decklink_consumer.cpp
index 58a53e658f10e618ff4d1d752936df04ab9b5ba2..ca847727eb8b6849834fdb989763757482749a1f 100644 (file)
 */
 
 #include "../StdAfx.h"
 */
 
 #include "../StdAfx.h"
+
 #include "decklink_consumer.h"
 
 #include "../util/util.h"
 #include "decklink_consumer.h"
 
 #include "../util/util.h"
+#include "../decklink.h"
 
 #include "../decklink_api.h"
 
 
 #include "../decklink_api.h"
 
@@ -47,6 +48,7 @@
 #include <common/cache_aligned_vector.h>
 #include <common/timer.h>
 #include <common/param.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/concurrent_queue.h>
 
 #include <boost/lexical_cast.hpp>
 #include <boost/circular_buffer.hpp>
 #include <boost/property_tree/ptree.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/circular_buffer.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 class keyer_t
 struct configuration
 {
        enum class keyer_t
@@ -76,16 +81,16 @@ struct configuration
 
        int                                                     device_index            = 1;
        int                                                     key_device_idx          = 0;
 
        int                                                     device_index            = 1;
        int                                                     key_device_idx          = 0;
-       bool                                            embedded_audio          = true;
+       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();
        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
        {
        int buffer_depth() const
        {
-               return base_buffer_depth + (latency == latency_t::low_latency ? 0 : 1) + (embedded_audio ? 1 : 0);
+               return base_buffer_depth + (latency == latency_t::low_latency ? 0 : 1);
        }
 
        int key_device_index() const
        }
 
        int key_device_index() const
@@ -119,8 +124,9 @@ struct configuration
        }
 };
 
        }
 };
 
+template <typename Configuration>
 void set_latency(
 void set_latency(
-               const com_iface_ptr<IDeckLinkConfiguration>& config,
+               const com_iface_ptr<Configuration>& config,
                configuration::latency_t latency,
                const std::wstring& print)
 {
                configuration::latency_t latency,
                const std::wstring& print)
 {
@@ -146,25 +152,25 @@ void set_keyer(
        {
                BOOL value = true;
                if (SUCCEEDED(attributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &value)) && !value)
        {
                BOOL value = true;
                if (SUCCEEDED(attributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &value)) && !value)
-                       CASPAR_LOG(error) << print << L" Failed to enable internal keyer.";     
+                       CASPAR_LOG(error) << print << L" Failed to enable internal keyer.";
                else if (FAILED(decklink_keyer->Enable(FALSE)))
                else if (FAILED(decklink_keyer->Enable(FALSE)))
-                       CASPAR_LOG(error) << print << L" Failed to enable internal keyer.";                     
+                       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
                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.";               
+                       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)
        }
        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.";     
+                       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
                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.";                       
+                       CASPAR_LOG(info) << print << L" Enabled external keyer.";
        }
 }
 
        }
 }
 
@@ -175,23 +181,36 @@ class decklink_frame : public IDeckLinkVideoFrame
        const core::video_format_desc                                   format_desc_;
 
        const bool                                                                              key_only_;
        const core::video_format_desc                                   format_desc_;
 
        const bool                                                                              key_only_;
+       bool                                                                                    needs_to_copy_;
        cache_aligned_vector<no_init_proxy<uint8_t>>    data_;
 public:
        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;
                : 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
 
        virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)
        {
                return E_NOINTERFACE;
        }
        // IUnknown
 
        virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)
        {
                return E_NOINTERFACE;
        }
-       
+
        virtual ULONG STDMETHODCALLTYPE AddRef()
        {
                return ++ref_count_;
        virtual ULONG STDMETHODCALLTYPE AddRef()
        {
                return ++ref_count_;
@@ -200,7 +219,12 @@ public:
        virtual ULONG STDMETHODCALLTYPE Release()
        {
                if(--ref_count_ == 0)
        virtual ULONG STDMETHODCALLTYPE Release()
        {
                if(--ref_count_ == 0)
+               {
                        delete this;
                        delete this;
+
+                       return 0;
+               }
+
                return ref_count_;
        }
 
                return ref_count_;
        }
 
@@ -211,7 +235,7 @@ public:
        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 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
        virtual HRESULT STDMETHODCALLTYPE GetBytes(void** buffer)
        {
                try
@@ -234,12 +258,12 @@ public:
                        {
                                *buffer = const_cast<uint8_t*>(frame_.image_data().begin());
 
                        {
                                *buffer = const_cast<uint8_t*>(frame_.image_data().begin());
 
-#if !defined(_MSC_VER)
-                               // On Linux Decklink cannot DMA transfer from memory returned by glMapBuffer
-                               data_.resize(frame_.image_data().size());
-                               fast_memcpy(data_.data(), *buffer, frame_.image_data().size());
-                               *buffer = data_.data();
-#endif
+                               if (needs_to_copy_)
+                               {
+                                       data_.resize(frame_.image_data().size());
+                                       fast_memcpy(data_.data(), *buffer, frame_.image_data().size());
+                                       *buffer = data_.data();
+                               }
                        }
                }
                catch(...)
                        }
                }
                catch(...)
@@ -250,11 +274,11 @@ public:
 
                return S_OK;
        }
 
                return S_OK;
        }
-               
+
        virtual HRESULT STDMETHODCALLTYPE GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode) {return S_FALSE;}
        virtual HRESULT STDMETHODCALLTYPE GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)              {return S_FALSE;}
 
        virtual HRESULT STDMETHODCALLTYPE GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode) {return S_FALSE;}
        virtual HRESULT STDMETHODCALLTYPE GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)              {return S_FALSE;}
 
-       // decklink_frame       
+       // decklink_frame
 
        const core::audio_buffer& audio_data()
        {
 
        const core::audio_buffer& audio_data()
        {
@@ -267,16 +291,17 @@ public:
        }
 };
 
        }
 };
 
+template <typename Configuration>
 struct key_video_context : public IDeckLinkVideoOutputCallback, boost::noncopyable
 {
 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_);
-       com_iface_ptr<IDeckLinkAttributes>                                              attributes_                                     = iface_cast<IDeckLinkAttributes>(decklink_);
-       com_iface_ptr<IDeckLinkConfiguration>                                   configuration_                          = iface_cast<IDeckLinkConfiguration>(decklink_);
-       tbb::atomic<int64_t>                                                                    current_presentation_delay_;
-       tbb::atomic<int64_t>                                                                    scheduled_frames_completed_;
+       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)
 
        key_video_context(const configuration& config, const std::wstring& print)
                : config_(config)
@@ -337,23 +362,25 @@ struct key_video_context : public IDeckLinkVideoOutputCallback, boost::noncopyab
        }
 };
 
        }
 };
 
-struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
-{              
+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_);
        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<IDeckLinkConfiguration>                           configuration_                  = iface_cast<IDeckLinkConfiguration>(decklink_);
-       com_iface_ptr<IDeckLinkKeyer>                                           keyer_                                  = iface_cast<IDeckLinkKeyer>(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_;
        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_);
        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_);
        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_);
@@ -364,26 +391,26 @@ struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLink
        long long                                                                                       audio_scheduled_                = 0;
 
        int                                                                                                     preroll_count_                  = 0;
        long long                                                                                       audio_scheduled_                = 0;
 
        int                                                                                                     preroll_count_                  = 0;
-               
+
        boost::circular_buffer<std::vector<int32_t>>            audio_container_                { buffer_size_ + 1 };
 
        boost::circular_buffer<std::vector<int32_t>>            audio_container_                { buffer_size_ + 1 };
 
-       tbb::concurrent_bounded_queue<core::const_frame>        video_frame_buffer_;
-       tbb::concurrent_bounded_queue<core::const_frame>        audio_frame_buffer_;
-       
+       tbb::concurrent_bounded_queue<core::const_frame>        frame_buffer_;
+
        spl::shared_ptr<diagnostics::graph>                                     graph_;
        caspar::timer                                                                           tick_timer_;
        spl::shared_ptr<diagnostics::graph>                                     graph_;
        caspar::timer                                                                           tick_timer_;
-       retry_task<bool>                                                                        send_completion_;
+       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_;
        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>                                      key_context_;
+       std::unique_ptr<key_video_context<Configuration>>       key_context_;
 
 public:
        decklink_consumer(
                        const configuration& config,
                        const core::video_format_desc& format_desc,
                        const core::audio_channel_layout& in_channel_layout,
 
 public:
        decklink_consumer(
                        const configuration& config,
                        const core::video_format_desc& format_desc,
                        const core::audio_channel_layout& in_channel_layout,
-                       int channel_index) 
+                       int channel_index)
                : channel_index_(channel_index)
                , config_(config)
                , format_desc_(format_desc)
                : channel_index_(channel_index)
                , config_(config)
                , format_desc_(format_desc)
@@ -392,18 +419,13 @@ public:
                is_running_ = true;
                current_presentation_delay_ = 0;
                scheduled_frames_completed_ = 0;
                is_running_ = true;
                current_presentation_delay_ = 0;
                scheduled_frames_completed_ = 0;
-                               
-               video_frame_buffer_.set_capacity(1);
 
 
-               // Blackmagic calls RenderAudioSamples() 50 times per second
-               // regardless of video mode so we sometimes need to give them
-               // samples from 2 frames in order to keep up
-               audio_frame_buffer_.set_capacity((format_desc.fps > 50.0) ? 2 : 1);
+               frame_buffer_.set_capacity(1);
 
                if (config.keyer == configuration::keyer_t::external_separate_device_keyer)
 
                if (config.keyer == configuration::keyer_t::external_separate_device_keyer)
-                       key_context_.reset(new key_video_context(config, print()));
+                       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("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("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));
@@ -417,32 +439,42 @@ public:
 
                graph_->set_text(print());
                diagnostics::register_graph(graph_);
 
                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();
                if(config.embedded_audio)
                        enable_audio();
-               
-               set_latency(configuration_, config.latency, print());                           
+
+               set_latency(configuration_, config.latency, print());
                set_keyer(attributes_, keyer_, config.keyer, 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)
+                       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());
                        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()
        }
 
        ~decklink_consumer()
-       {               
+       {
                is_running_ = false;
                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)
                {
                        output_->StopScheduledPlayback(0, nullptr, 0);
                        if(config_.embedded_audio)
@@ -450,25 +482,22 @@ public:
                        output_->DisableVideoOutput();
                }
        }
                        output_->DisableVideoOutput();
                }
        }
-       
+
        void enable_audio()
        {
                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."));
        void enable_audio()
        {
                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."));
-                               
-               if(FAILED(output_->SetAudioCallback(this)))
-                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Could not set audio callback."));
 
                CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
        }
 
        void enable_video(BMDDisplayMode display_mode)
        {
 
                CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
        }
 
        void enable_video(BMDDisplayMode display_mode)
        {
-               if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) 
+               if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
                        CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Could not enable fill video output."));
                        CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Could not enable fill video output."));
-               
+
                if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
                if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
-                       CASPAR_THROW_EXCEPTION(caspar_exception() 
+                       CASPAR_THROW_EXCEPTION(caspar_exception()
                                                                        << msg_info(print() + L" Failed to set fill playback completion callback.")
                                                                        << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
 
                                                                        << msg_info(print() + L" Failed to set fill playback completion callback.")
                                                                        << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
 
@@ -478,17 +507,17 @@ public:
 
        void start_playback()
        {
 
        void start_playback()
        {
-               if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) 
+               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."));
        }
                        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."));
        }
-       
+
        virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)       {return E_NOINTERFACE;}
        virtual ULONG STDMETHODCALLTYPE AddRef()                                        {return 1;}
        virtual ULONG STDMETHODCALLTYPE Release()                               {return 1;}
        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;
        virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped()
        {
                is_running_ = false;
@@ -500,9 +529,15 @@ public:
        {
                if(!is_running_)
                        return E_FAIL;
        {
                if(!is_running_)
                        return E_FAIL;
-               
+
                try
                {
                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_;
                        auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);
                        current_presentation_delay_ = dframe->get_age_millis();
                        ++scheduled_frames_completed_;
@@ -519,10 +554,7 @@ public:
                        {
                                graph_->set_tag(diagnostics::tag_severity::WARNING, "late-frame");
                                video_scheduled_ += format_desc_.duration;
                        {
                                graph_->set_tag(diagnostics::tag_severity::WARNING, "late-frame");
                                video_scheduled_ += format_desc_.duration;
-                               audio_scheduled_ += dframe->audio_data().size() / out_channel_layout_.num_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(diagnostics::tag_severity::WARNING, "dropped-frame");
                        }
                        else if(result == bmdOutputFrameDropped)
                                graph_->set_tag(diagnostics::tag_severity::WARNING, "dropped-frame");
@@ -533,61 +565,40 @@ public:
                        output_->GetBufferedVideoFrameCount(&buffered);
                        graph_->set_value("buffered-video", static_cast<double>(buffered) / (config_.buffer_depth()));
 
                        output_->GetBufferedVideoFrameCount(&buffered);
                        graph_->set_value("buffered-video", static_cast<double>(buffered) / (config_.buffer_depth()));
 
-                       auto frame = core::const_frame::empty();
-                       video_frame_buffer_.pop(frame);
-                       send_completion_.try_completion();
-                       schedule_next_video(frame);     
-               }
-               catch(...)
-               {
-                       lock(exception_mutex_, [&]
+                       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;
-       }
-               
-       virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples(BOOL preroll)
-       {
-               if(!is_running_)
-                       return E_FAIL;
-               
-               try
-               {       
-                       if(preroll)
                        {
                        {
-                               if(++preroll_count_ >= buffer_size_)
-                               {
-                                       output_->EndAudioPreroll();
-                                       start_playback();                               
-                               }
-                               else
+                               boost::lock_guard<boost::mutex> lock(send_completion_mutex_);
+
+                               if (send_completion_.valid())
                                {
                                {
-                                       schedule_next_audio(core::mutable_audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()] * out_channel_layout_.num_channels, 0));
+                                       send_completion_();
+                                       send_completion_ = std::packaged_task<bool()>();
                                }
                        }
                                }
                        }
-                       else
-                       {
-                               auto frame = core::const_frame::empty();
 
 
-                               while(audio_frame_buffer_.try_pop(frame))
-                               {
-                                       UINT32 buffered;
-                                       output_->GetBufferedAudioSampleFrameCount(&buffered);
-                                       graph_->set_value("buffered-audio", static_cast<double>(buffered) / (format_desc_.audio_cadence[0] * config_.buffer_depth()));
+                       if (!is_running_)
+                               return E_FAIL;
 
 
-                                       send_completion_.try_completion();
-                                       schedule_next_audio(channel_remapper_.mix_and_rearrange(frame.audio_data()));
-                               }
-                       }
+                       if (config_.embedded_audio)
+                               schedule_next_audio(channel_remapper_.mix_and_rearrange(frame.audio_data()));
+
+                       schedule_next_video(frame);
                }
                catch(...)
                {
                }
                catch(...)
                {
-                       tbb::spin_mutex::scoped_lock lock(exception_mutex_);
-                       exception_ = std::current_exception();
+                       lock(exception_mutex_, [&]
+                       {
+                               exception_ = std::current_exception();
+                       });
                        return E_FAIL;
                }
 
                        return E_FAIL;
                }
 
@@ -606,26 +617,21 @@ public:
 
                audio_scheduled_ += sample_frame_count;
        }
 
                audio_scheduled_ += sample_frame_count;
        }
-                       
+
        void schedule_next_video(core::const_frame frame)
        {
                if (key_context_)
                {
        void schedule_next_video(core::const_frame frame)
        {
                if (key_context_)
                {
-                       auto key_frame = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, true));
+                       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.";
                }
 
                        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.";
                }
 
-               auto fill_frame = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, config_.key_only));
+               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.";
 
                video_scheduled_ += format_desc_.duration;
                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.";
 
                video_scheduled_ += format_desc_.duration;
-
-               graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
-               tick_timer_.restart();
-
-               reference_signal_detector_.detect_change([this]() { return print(); });
        }
 
        std::future<bool> send(core::const_frame frame)
        }
 
        std::future<bool> send(core::const_frame frame)
@@ -636,36 +642,26 @@ public:
                });
 
                if(exception != nullptr)
                });
 
                if(exception != nullptr)
-                       std::rethrow_exception(exception);              
+                       std::rethrow_exception(exception);
 
                if(!is_running_)
                        CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Is not running."));
 
                if(!is_running_)
                        CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Is not running."));
-               
-               bool audio_ready = !config_.embedded_audio;
-               bool video_ready = false;
 
 
-               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 make_ready_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();
        }
 
                return send_completion_.get_future();
        }
-       
+
        std::wstring print() const
        {
                if (config_.keyer == configuration::keyer_t::external_separate_device_keyer)
        std::wstring print() const
        {
                if (config_.keyer == configuration::keyer_t::external_separate_device_keyer)
@@ -681,13 +677,14 @@ public:
        }
 };
 
        }
 };
 
+template <typename Configuration>
 struct decklink_consumer_proxy : public core::frame_consumer
 {
 struct decklink_consumer_proxy : public core::frame_consumer
 {
-       core::monitor::subject                          monitor_subject_;
-       const configuration                                     config_;
-       std::unique_ptr<decklink_consumer>      consumer_;
-       core::video_format_desc                         format_desc_;
-       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)
 public:
 
        decklink_consumer_proxy(const configuration& config)
@@ -712,26 +709,26 @@ public:
        }
 
        // frame_consumer
        }
 
        // frame_consumer
-       
+
        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();
        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_layout, channel_index));                    
+                       consumer_.reset(new decklink_consumer<Configuration>(config_, format_desc, channel_layout, channel_index));
                });
        }
                });
        }
-       
+
        std::future<bool> send(core::const_frame frame) override
        {
                return consumer_->send(frame);
        }
        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 print() const override
        {
                return consumer_ ? consumer_->print() : L"[decklink_consumer]";
-       }               
+       }
 
        std::wstring name() const override
        {
 
        std::wstring name() const override
        {
@@ -776,7 +773,21 @@ public:
        {
                return monitor_subject_;
        }
        {
                return monitor_subject_;
        }
-};     
+};
+
+const software_version<3>& get_driver_version()
+{
+       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)
 {
 
 void describe_consumer(core::help_sink& sink, const core::help_repository& repo)
 {
@@ -814,16 +825,16 @@ void describe_consumer(core::help_sink& sink, const core::help_repository& repo)
 }
 
 spl::shared_ptr<core::frame_consumer> create_consumer(
 }
 
 spl::shared_ptr<core::frame_consumer> create_consumer(
-               const std::vector<std::wstring>& params, core::interaction_sink*)
+               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();
 {
        if (params.size() < 1 || !boost::iequals(params.at(0), L"DECKLINK"))
                return core::frame_consumer::empty();
-       
+
        configuration config;
        configuration config;
-               
+
        if (params.size() > 1)
                config.device_index = boost::lexical_cast<int>(params.at(1));
        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))
        if (contains_param(L"INTERNAL_KEY", params))
                config.keyer = configuration::keyer_t::internal_keyer;
        else if (contains_param(L"EXTERNAL_KEY", params))
@@ -851,11 +862,16 @@ spl::shared_ptr<core::frame_consumer> create_consumer(
                config.out_channel_layout = *found_layout;
        }
 
                config.out_channel_layout = *found_layout;
        }
 
-       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);
 }
 
 spl::shared_ptr<core::frame_consumer> create_preconfigured_consumer(
 }
 
 spl::shared_ptr<core::frame_consumer> create_preconfigured_consumer(
-               const boost::property_tree::wptree& ptree, core::interaction_sink*)
+               const boost::property_tree::wptree& ptree, core::interaction_sink*, std::vector<spl::shared_ptr<core::video_channel>> channels)
 {
        configuration config;
 
 {
        configuration config;
 
@@ -893,7 +909,12 @@ spl::shared_ptr<core::frame_consumer> create_preconfigured_consumer(
        config.embedded_audio           = ptree.get(L"embedded-audio",  config.embedded_audio);
        config.base_buffer_depth        = ptree.get(L"buffer-depth",    config.base_buffer_depth);
 
        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);
 }
 
 }}
 }
 
 }}
@@ -910,18 +931,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
 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
 
 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.
 ################################################################################
 */
 frame output has been completed.
 ################################################################################
 */
@@ -938,10 +959,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.
 ################################################################################
 */
 have to colourspace convert which may result in additional copying.
 ################################################################################
 */