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