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