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