]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
* Merged streaming_consumer from 2.0
[casparcg] / modules / decklink / consumer / decklink_consumer.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Robert Nagy, ronag89@gmail.com
20 */
21
22 #include "../StdAfx.h"
23  
24 #include "decklink_consumer.h"
25
26 #include "../util/util.h"
27
28 #include "../decklink_api.h"
29
30 #include <core/frame/frame.h>
31 #include <core/mixer/audio/audio_mixer.h>
32
33 #include <common/executor.h>
34 #include <common/lock.h>
35 #include <common/diagnostics/graph.h>
36 #include <common/except.h>
37 #include <common/memshfl.h>
38 #include <common/array.h>
39 #include <common/future.h>
40 #include <common/cache_aligned_vector.h>
41 #include <common/timer.h>
42 #include <common/param.h>
43
44 #include <core/consumer/frame_consumer.h>
45 #include <core/diagnostics/call_context.h>
46
47 #include <tbb/concurrent_queue.h>
48
49 #include <common/assert.h>
50 #include <boost/lexical_cast.hpp>
51 #include <boost/circular_buffer.hpp>
52 #include <boost/property_tree/ptree.hpp>
53
54 namespace caspar { namespace decklink { 
55         
56 struct configuration
57 {
58         enum class keyer_t
59         {
60                 internal_keyer,
61                 external_keyer,
62                 default_keyer
63         };
64
65         enum class latency_t
66         {
67                 low_latency,
68                 normal_latency,
69                 default_latency
70         };
71
72         int                     device_index            = 1;
73         bool            embedded_audio          = true;
74         keyer_t         keyer                           = keyer_t::default_keyer;
75         latency_t       latency                         = latency_t::default_latency;
76         bool            key_only                        = false;
77         int                     base_buffer_depth       = 3;
78         
79         int buffer_depth() const
80         {
81                 return base_buffer_depth + (latency == latency_t::low_latency ? 0 : 1) + (embedded_audio ? 1 : 0);
82         }
83 };
84
85 class decklink_frame : public IDeckLinkVideoFrame
86 {
87         tbb::atomic<int>                                ref_count_;
88         core::const_frame                               frame_;
89         const core::video_format_desc   format_desc_;
90
91         const bool                                              key_only_;
92         cache_aligned_vector<uint8_t>   data_;
93 public:
94         decklink_frame(core::const_frame frame, const core::video_format_desc& format_desc, bool key_only)
95                 : frame_(frame)
96                 , format_desc_(format_desc)
97                 , key_only_(key_only)
98         {
99                 ref_count_ = 0;
100         }
101         
102         // IUnknown
103
104         virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)
105         {
106                 return E_NOINTERFACE;
107         }
108         
109         virtual ULONG STDMETHODCALLTYPE AddRef()
110         {
111                 return ++ref_count_;
112         }
113
114         virtual ULONG STDMETHODCALLTYPE Release()
115         {
116                 if(--ref_count_ == 0)
117                         delete this;
118                 return ref_count_;
119         }
120
121         // IDecklinkVideoFrame
122
123         virtual long STDMETHODCALLTYPE GetWidth()                   {return static_cast<long>(format_desc_.width);}
124         virtual long STDMETHODCALLTYPE GetHeight()                  {return static_cast<long>(format_desc_.height);}
125         virtual long STDMETHODCALLTYPE GetRowBytes()                {return static_cast<long>(format_desc_.width*4);}
126         virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat()   {return bmdFormat8BitBGRA;}
127         virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags()                      {return bmdFrameFlagDefault;}
128                 
129         virtual HRESULT STDMETHODCALLTYPE GetBytes(void** buffer)
130         {
131                 try
132                 {
133                         if(static_cast<int>(frame_.image_data().size()) != format_desc_.size)
134                         {
135                                 data_.resize(format_desc_.size, 0);
136                                 *buffer = data_.data();
137                         }
138                         else if(key_only_)
139                         {
140                                 if(data_.empty())
141                                 {
142                                         data_.resize(frame_.image_data().size());
143                                         aligned_memshfl(data_.data(), frame_.image_data().begin(), frame_.image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);
144                                 }
145                                 *buffer = data_.data();
146                         }
147                         else
148                                 *buffer = const_cast<uint8_t*>(frame_.image_data().begin());
149                 }
150                 catch(...)
151                 {
152                         CASPAR_LOG_CURRENT_EXCEPTION();
153                         return E_FAIL;
154                 }
155
156                 return S_OK;
157         }
158                 
159         virtual HRESULT STDMETHODCALLTYPE GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode) {return S_FALSE;}
160         virtual HRESULT STDMETHODCALLTYPE GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)              {return S_FALSE;}
161
162         // decklink_frame       
163
164         const core::audio_buffer& audio_data()
165         {
166                 return frame_.audio_data();
167         }
168 };
169
170 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
171 {               
172         const int                                                                                       channel_index_;
173         const configuration                                                                     config_;
174
175         com_ptr<IDeckLink>                                                                      decklink_                               = get_device(config_.device_index);
176         com_iface_ptr<IDeckLinkOutput>                                          output_                                 = iface_cast<IDeckLinkOutput>(decklink_);
177         com_iface_ptr<IDeckLinkConfiguration>                           configuration_                  = iface_cast<IDeckLinkConfiguration>(decklink_);
178         com_iface_ptr<IDeckLinkKeyer>                                           keyer_                                  = iface_cast<IDeckLinkKeyer>(decklink_);
179         com_iface_ptr<IDeckLinkAttributes>                                      attributes_                             = iface_cast<IDeckLinkAttributes>(decklink_);
180
181         tbb::spin_mutex                                     exception_mutex_;
182         std::exception_ptr                                  exception_;
183
184         tbb::atomic<bool>                                   is_running_;
185                 
186         const std::wstring                                  model_name_                         = get_model_name(decklink_);
187         const core::video_format_desc                       format_desc_;
188         const int                                           buffer_size_                        = config_.buffer_depth(); // Minimum buffer-size 3.
189
190         long long                                           video_scheduled_            = 0;
191         long long                                           audio_scheduled_            = 0;
192
193         int                                                 preroll_count_                      = 0;
194                 
195         boost::circular_buffer<std::vector<int32_t>>        audio_container_            { buffer_size_ + 1 };
196
197         tbb::concurrent_bounded_queue<core::const_frame>    video_frame_buffer_;
198         tbb::concurrent_bounded_queue<core::const_frame>    audio_frame_buffer_;
199         
200         spl::shared_ptr<diagnostics::graph>                 graph_;
201         caspar::timer                                                                           tick_timer_;
202         retry_task<bool>                                    send_completion_;
203
204 public:
205         decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index) 
206                 : channel_index_(channel_index)
207                 , config_(config)
208                 , format_desc_(format_desc)
209         {
210                 is_running_ = true;
211                                 
212                 video_frame_buffer_.set_capacity(1);
213
214                 // Blackmagic calls RenderAudioSamples() 50 times per second
215                 // regardless of video mode so we sometimes need to give them
216                 // samples from 2 frames in order to keep up
217                 audio_frame_buffer_.set_capacity((format_desc.fps > 50.0) ? 2 : 1);
218
219                 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));   
220                 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
221                 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
222                 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
223                 graph_->set_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));
224                 graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));
225                 graph_->set_text(print());
226                 diagnostics::register_graph(graph_);
227                 
228                 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
229                                 
230                 if(config.embedded_audio)
231                         enable_audio();
232                 
233                 set_latency(config.latency);                            
234                 set_keyer(config.keyer);
235                                 
236                 if(config.embedded_audio)               
237                         output_->BeginAudioPreroll();           
238                 
239                 for(int n = 0; n < buffer_size_; ++n)
240                         schedule_next_video(core::const_frame::empty());
241
242                 if(!config.embedded_audio)
243                         start_playback();
244         }
245
246         ~decklink_consumer()
247         {               
248                 is_running_ = false;
249                 video_frame_buffer_.try_push(core::const_frame::empty());
250                 audio_frame_buffer_.try_push(core::const_frame::empty());
251
252                 if(output_ != nullptr) 
253                 {
254                         output_->StopScheduledPlayback(0, nullptr, 0);
255                         if(config_.embedded_audio)
256                                 output_->DisableAudioOutput();
257                         output_->DisableVideoOutput();
258                 }
259         }
260                         
261         void set_latency(configuration::latency_t latency)
262         {               
263                 if(latency == configuration::latency_t::low_latency)
264                 {
265                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
266                         CASPAR_LOG(info) << print() << L" Enabled low-latency mode.";
267                 }
268                 else if(latency == configuration::latency_t::normal_latency)
269                 {                       
270                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
271                         CASPAR_LOG(info) << print() << L" Disabled low-latency mode.";
272                 }
273         }
274
275         void set_keyer(configuration::keyer_t keyer)
276         {
277                 if(keyer == configuration::keyer_t::internal_keyer) 
278                 {
279                         BOOL value = true;
280                         if(SUCCEEDED(attributes_->GetFlag(BMDDeckLinkSupportsInternalKeying, &value)) && !value)
281                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";   
282                         else if(FAILED(keyer_->Enable(FALSE)))                  
283                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   
284                         else if(FAILED(keyer_->SetLevel(255)))                  
285                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
286                         else
287                                 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             
288                 }
289                 else if(keyer == configuration::keyer_t::external_keyer)
290                 {
291                         BOOL value = true;
292                         if(SUCCEEDED(attributes_->GetFlag(BMDDeckLinkSupportsExternalKeying, &value)) && !value)
293                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   
294                         else if(FAILED(keyer_->Enable(TRUE)))                   
295                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   
296                         else if(FAILED(keyer_->SetLevel(255)))                  
297                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
298                         else
299                                 CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     
300                 }
301         }
302         
303         void enable_audio()
304         {
305                 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))
306                                 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable audio output."));
307                                 
308                 if(FAILED(output_->SetAudioCallback(this)))
309                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not set audio callback."));
310
311                 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
312         }
313
314         void enable_video(BMDDisplayMode display_mode)
315         {
316                 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) 
317                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable video output."));
318                 
319                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
320                         CASPAR_THROW_EXCEPTION(caspar_exception() 
321                                                                         << msg_info(u8(print()) + " Failed to set playback completion callback.")
322                                                                         << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
323         }
324
325         void start_playback()
326         {
327                 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) 
328                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Failed to schedule playback."));
329         }
330         
331         virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)       {return E_NOINTERFACE;}
332         virtual ULONG STDMETHODCALLTYPE AddRef()                                        {return 1;}
333         virtual ULONG STDMETHODCALLTYPE Release()                               {return 1;}
334         
335         virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped()
336         {
337                 is_running_ = false;
338                 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
339                 return S_OK;
340         }
341
342         virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result)
343         {
344                 if(!is_running_)
345                         return E_FAIL;
346                 
347                 try
348                 {
349                         if(result == bmdOutputFrameDisplayedLate)
350                         {
351                                 graph_->set_tag("late-frame");
352                                 video_scheduled_ += format_desc_.duration;
353                                 audio_scheduled_ += reinterpret_cast<decklink_frame*>(completed_frame)->audio_data().size()/format_desc_.audio_channels;
354                                 //++video_scheduled_;
355                                 //audio_scheduled_ += format_desc_.audio_cadence[0];
356                                 //++audio_scheduled_;
357                         }
358                         else if(result == bmdOutputFrameDropped)
359                                 graph_->set_tag("dropped-frame");
360                         else if(result == bmdOutputFrameFlushed)
361                                 graph_->set_tag("flushed-frame");
362
363                         auto frame = core::const_frame::empty();        
364                         video_frame_buffer_.pop(frame);
365                         send_completion_.try_completion();
366                         schedule_next_video(frame);     
367                         
368                         UINT32 buffered;
369                         output_->GetBufferedVideoFrameCount(&buffered);
370                         graph_->set_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);
371                 }
372                 catch(...)
373                 {
374                         lock(exception_mutex_, [&]
375                         {
376                                 exception_ = std::current_exception();
377                         });
378                         return E_FAIL;
379                 }
380
381                 return S_OK;
382         }
383                 
384         virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples(BOOL preroll)
385         {
386                 if(!is_running_)
387                         return E_FAIL;
388                 
389                 try
390                 {       
391                         if(preroll)
392                         {
393                                 if(++preroll_count_ >= buffer_size_)
394                                 {
395                                         output_->EndAudioPreroll();
396                                         start_playback();                               
397                                 }
398                                 else
399                                 {
400                                         schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()] * format_desc_.audio_channels, 0));
401                                 }
402                         }
403                         else
404                         {
405                                 auto frame = core::const_frame::empty();
406
407                                 while(audio_frame_buffer_.try_pop(frame))
408                                 {
409                                         send_completion_.try_completion();
410                                         schedule_next_audio(frame.audio_data());
411                                 }
412                         }
413
414                         UINT32 buffered;
415                         output_->GetBufferedAudioSampleFrameCount(&buffered);
416                         graph_->set_value("buffered-audio", static_cast<double>(buffered) / (format_desc_.audio_cadence[0] * format_desc_.audio_channels * 2));
417                 }
418                 catch(...)
419                 {
420                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);
421                         exception_ = std::current_exception();
422                         return E_FAIL;
423                 }
424
425                 return S_OK;
426         }
427
428         template<typename T>
429         void schedule_next_audio(const T& audio_data)
430         {
431                 auto sample_frame_count = static_cast<int>(audio_data.size()/format_desc_.audio_channels);
432
433                 audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));
434
435                 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, audio_scheduled_, format_desc_.audio_sample_rate, nullptr)))
436                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
437
438                 audio_scheduled_ += sample_frame_count;
439         }
440                         
441         void schedule_next_video(core::const_frame frame)
442         {
443                 auto frame2 = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, config_.key_only));
444                 if(FAILED(output_->ScheduleVideoFrame(get_raw(frame2), video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
445                         CASPAR_LOG(error) << print() << L" Failed to schedule video.";
446
447                 video_scheduled_ += format_desc_.duration;
448
449                 graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
450                 tick_timer_.restart();
451         }
452
453         std::future<bool> send(core::const_frame frame)
454         {
455                 auto exception = lock(exception_mutex_, [&]
456                 {
457                         return exception_;
458                 });
459
460                 if(exception != nullptr)
461                         std::rethrow_exception(exception);              
462
463                 if(!is_running_)
464                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Is not running."));
465                 
466                 bool audio_ready = !config_.embedded_audio;
467                 bool video_ready = false;
468
469                 auto enqueue_task = [audio_ready, video_ready, frame, this]() mutable -> boost::optional<bool>
470                 {
471                         if (!audio_ready)
472                                 audio_ready = audio_frame_buffer_.try_push(frame);
473
474                         if (!video_ready)
475                                 video_ready = video_frame_buffer_.try_push(frame);
476
477                         if (audio_ready && video_ready)
478                                 return true;
479                         else
480                                 return boost::optional<bool>();
481                 };
482                 
483                 if (enqueue_task())
484                         return make_ready_future(true);
485
486                 send_completion_.set_task(enqueue_task);
487
488                 return send_completion_.get_future();
489         }
490         
491         std::wstring print() const
492         {
493                 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +
494                         boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";
495         }
496 };
497
498 struct decklink_consumer_proxy : public core::frame_consumer
499 {
500         core::monitor::subject                          monitor_subject_;
501         const configuration                                     config_;
502         std::unique_ptr<decklink_consumer>      consumer_;
503         executor                                                        executor_;
504 public:
505
506         decklink_consumer_proxy(const configuration& config)
507                 : config_(config)
508                 , executor_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
509         {
510                 auto ctx = core::diagnostics::call_context::for_thread();
511                 executor_.begin_invoke([=]
512                 {
513                         core::diagnostics::call_context::for_thread() = ctx;
514                         com_initialize();
515                 });
516         }
517
518         ~decklink_consumer_proxy()
519         {
520                 executor_.invoke([=]
521                 {
522                         consumer_.reset();
523                         com_uninitialize();
524                 });
525         }
526
527         // frame_consumer
528         
529         void initialize(const core::video_format_desc& format_desc, int channel_index) override
530         {
531                 executor_.invoke([=]
532                 {
533                         consumer_.reset();
534                         consumer_.reset(new decklink_consumer(config_, format_desc, channel_index));                    
535                 });
536         }
537         
538         std::future<bool> send(core::const_frame frame) override
539         {
540                 return consumer_->send(frame);
541         }
542         
543         std::wstring print() const override
544         {
545                 return consumer_ ? consumer_->print() : L"[decklink_consumer]";
546         }               
547
548         std::wstring name() const override
549         {
550                 return L"decklink";
551         }
552
553         boost::property_tree::wptree info() const override
554         {
555                 boost::property_tree::wptree info;
556                 info.add(L"type", L"decklink");
557                 info.add(L"key-only", config_.key_only);
558                 info.add(L"device", config_.device_index);
559                 info.add(L"low-latency", config_.latency == configuration::latency_t::low_latency);
560                 info.add(L"embedded-audio", config_.embedded_audio);
561                 //info.add(L"internal-key", config_.internal_key);
562                 return info;
563         }
564
565         int buffer_depth() const override
566         {
567                 return config_.buffer_depth();
568         }
569
570         int index() const override
571         {
572                 return 300 + config_.device_index;
573         }
574
575         core::monitor::subject& monitor_output()
576         {
577                 return monitor_subject_;
578         }
579 };      
580
581 spl::shared_ptr<core::frame_consumer> create_consumer(
582                 const std::vector<std::wstring>& params, core::interaction_sink*)
583 {
584         if (params.size() < 1 || !boost::iequals(params.at(0), L"DECKLINK"))
585                 return core::frame_consumer::empty();
586         
587         configuration config;
588                 
589         if (params.size() > 1)
590                 config.device_index = boost::lexical_cast<int>(params.at(1));
591         
592         if (contains_param(L"INTERNAL_KEY", params))
593                 config.keyer = configuration::keyer_t::internal_keyer;
594         else if (contains_param(L"EXTERNAL_KEY", params))
595                 config.keyer = configuration::keyer_t::external_keyer;
596         else
597                 config.keyer = configuration::keyer_t::default_keyer;
598
599         if (contains_param(L"LOW_LATENCY", params))
600                 config.latency = configuration::latency_t::low_latency;
601
602         config.embedded_audio   = contains_param(L"EMBEDDED_AUDIO", params);
603         config.key_only                 = contains_param(L"KEY_ONLY", params);
604
605         return spl::make_shared<decklink_consumer_proxy>(config);
606 }
607
608 spl::shared_ptr<core::frame_consumer> create_preconfigured_consumer(
609                 const boost::property_tree::wptree& ptree, core::interaction_sink*)
610 {
611         configuration config;
612
613         auto keyer = ptree.get(L"keyer", L"default");
614         if(keyer == L"external")
615                 config.keyer = configuration::keyer_t::external_keyer;
616         else if(keyer == L"internal")
617                 config.keyer = configuration::keyer_t::internal_keyer;
618
619         auto latency = ptree.get(L"latency", L"normal");
620         if(latency == L"low")
621                 config.latency = configuration::latency_t::low_latency;
622         else if(latency == L"normal")
623                 config.latency = configuration::latency_t::normal_latency;
624
625         config.key_only                         = ptree.get(L"key-only",                config.key_only);
626         config.device_index                     = ptree.get(L"device",                  config.device_index);
627         config.embedded_audio           = ptree.get(L"embedded-audio",  config.embedded_audio);
628         config.base_buffer_depth        = ptree.get(L"buffer-depth",    config.base_buffer_depth);
629
630         return spl::make_shared<decklink_consumer_proxy>(config);
631 }
632
633 }}
634
635 /*
636 ##############################################################################
637 Pre-rolling
638
639 Mail: 2011-05-09
640
641 Yoshan
642 BMD Developer Support
643 developer@blackmagic-design.com
644
645 -----------------------------------------------------------------------------
646
647 Thanks for your inquiry. The minimum number of frames that you can preroll 
648 for scheduled playback is three frames for video and four frames for audio. 
649 As you mentioned if you preroll less frames then playback will not start or
650 playback will be very sporadic. From our experience with Media Express, we 
651 recommended that at least seven frames are prerolled for smooth playback. 
652
653 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
654 There can be around 3 frames worth of latency on scheduled output.
655 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
656 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() 
657 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will 
658 guarantee that the provided frame will be output as soon the previous 
659 frame output has been completed.
660 ################################################################################
661 */
662
663 /*
664 ##############################################################################
665 Async DMA Transfer without redundant copying
666
667 Mail: 2011-05-10
668
669 Yoshan
670 BMD Developer Support
671 developer@blackmagic-design.com
672
673 -----------------------------------------------------------------------------
674
675 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame 
676 and providing a pointer to your video buffer when GetBytes() is called. 
677 This may help to keep copying to a minimum. Please ensure that the pixel 
678 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will 
679 have to colourspace convert which may result in additional copying.
680 ################################################################################
681 */