]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
Merge branch '2.1.0' of https://github.com/CasparCG/Server into 2.1.0
[casparcg] / modules / decklink / consumer / decklink_consumer.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Robert Nagy, ronag89@gmail.com
20 */
21
22 #include "../StdAfx.h"
23  
24 #include "decklink_consumer.h"
25
26 #include "../util/util.h"
27
28 #include "../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
233                 // Blackmagic calls RenderAudioSamples() 50 times per second
234                 // regardless of video mode so we sometimes need to give them
235                 // samples from 2 frames in order to keep up
236                 audio_frame_buffer_.set_capacity((format_desc.fps > 50.0) ? 2 : 1);
237
238                 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));   
239                 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
240                 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
241                 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
242                 graph_->set_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));
243                 graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));
244                 graph_->set_text(print());
245                 diagnostics::register_graph(graph_);
246                 
247                 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
248                                 
249                 if(config.embedded_audio)
250                         enable_audio();
251                 
252                 set_latency(config.latency);                            
253                 set_keyer(config.keyer);
254                                 
255                 if(config.embedded_audio)               
256                         output_->BeginAudioPreroll();           
257                 
258                 for(int n = 0; n < buffer_size_; ++n)
259                         schedule_next_video(core::const_frame::empty());
260
261                 if(!config.embedded_audio)
262                         start_playback();
263         }
264
265         ~decklink_consumer()
266         {               
267                 is_running_ = false;
268                 video_frame_buffer_.try_push(core::const_frame::empty());
269                 audio_frame_buffer_.try_push(core::const_frame::empty());
270
271                 if(output_ != nullptr) 
272                 {
273                         output_->StopScheduledPlayback(0, nullptr, 0);
274                         if(config_.embedded_audio)
275                                 output_->DisableAudioOutput();
276                         output_->DisableVideoOutput();
277                 }
278         }
279                         
280         void set_latency(configuration::latency_t latency)
281         {               
282                 if(latency == configuration::low_latency)
283                 {
284                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
285                         CASPAR_LOG(info) << print() << L" Enabled low-latency mode.";
286                 }
287                 else if(latency == configuration::normal_latency)
288                 {                       
289                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
290                         CASPAR_LOG(info) << print() << L" Disabled low-latency mode.";
291                 }
292         }
293
294         void set_keyer(configuration::keyer_t keyer)
295         {
296                 if(keyer == configuration::internal_keyer) 
297                 {
298                         BOOL value = true;
299                         if(SUCCEEDED(attributes_->GetFlag(BMDDeckLinkSupportsInternalKeying, &value)) && !value)
300                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";   
301                         else if(FAILED(keyer_->Enable(FALSE)))                  
302                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   
303                         else if(FAILED(keyer_->SetLevel(255)))                  
304                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
305                         else
306                                 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             
307                 }
308                 else if(keyer == configuration::external_keyer)
309                 {
310                         BOOL value = true;
311                         if(SUCCEEDED(attributes_->GetFlag(BMDDeckLinkSupportsExternalKeying, &value)) && !value)
312                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   
313                         else if(FAILED(keyer_->Enable(TRUE)))                   
314                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   
315                         else if(FAILED(keyer_->SetLevel(255)))                  
316                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
317                         else
318                                 CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     
319                 }
320         }
321         
322         void enable_audio()
323         {
324                 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))
325                                 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable audio output."));
326                                 
327                 if(FAILED(output_->SetAudioCallback(this)))
328                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not set audio callback."));
329
330                 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
331         }
332
333         void enable_video(BMDDisplayMode display_mode)
334         {
335                 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) 
336                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable video output."));
337                 
338                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
339                         CASPAR_THROW_EXCEPTION(caspar_exception() 
340                                                                         << msg_info(u8(print()) + " Failed to set playback completion callback.")
341                                                                         << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
342         }
343
344         void start_playback()
345         {
346                 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) 
347                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Failed to schedule playback."));
348         }
349         
350         STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}
351         STDMETHOD_(ULONG, AddRef())                                     {return 1;}
352         STDMETHOD_(ULONG, Release())                            {return 1;}
353         
354         STDMETHOD(ScheduledPlaybackHasStopped())
355         {
356                 is_running_ = false;
357                 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
358                 return S_OK;
359         }
360
361         STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
362         {
363                 if(!is_running_)
364                         return E_FAIL;
365                 
366                 try
367                 {
368                         if(result == bmdOutputFrameDisplayedLate)
369                         {
370                                 graph_->set_tag("late-frame");
371                                 video_scheduled_ += format_desc_.duration;
372                                 audio_scheduled_ += reinterpret_cast<decklink_frame*>(completed_frame)->audio_data().size()/format_desc_.audio_channels;
373                                 //++video_scheduled_;
374                                 //audio_scheduled_ += format_desc_.audio_cadence[0];
375                                 //++audio_scheduled_;
376                         }
377                         else if(result == bmdOutputFrameDropped)
378                                 graph_->set_tag("dropped-frame");
379                         else if(result == bmdOutputFrameFlushed)
380                                 graph_->set_tag("flushed-frame");
381
382                         auto frame = core::const_frame::empty();        
383                         video_frame_buffer_.pop(frame);
384                         send_completion_.try_completion();
385                         schedule_next_video(frame);     
386                         
387                         unsigned long buffered;
388                         output_->GetBufferedVideoFrameCount(&buffered);
389                         graph_->set_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);
390                 }
391                 catch(...)
392                 {
393                         lock(exception_mutex_, [&]
394                         {
395                                 exception_ = std::current_exception();
396                         });
397                         return E_FAIL;
398                 }
399
400                 return S_OK;
401         }
402                 
403         STDMETHOD(RenderAudioSamples(BOOL preroll))
404         {
405                 if(!is_running_)
406                         return E_FAIL;
407                 
408                 try
409                 {       
410                         if(preroll)
411                         {
412                                 if(++preroll_count_ >= buffer_size_)
413                                 {
414                                         output_->EndAudioPreroll();
415                                         start_playback();                               
416                                 }
417                                 else
418                                 {
419                                         schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()] * format_desc_.audio_channels, 0));
420                                 }
421                         }
422                         else
423                         {
424                                 auto frame = core::const_frame::empty();
425
426                                 while(audio_frame_buffer_.try_pop(frame))
427                                 {
428                                         send_completion_.try_completion();
429                                         schedule_next_audio(frame.audio_data());
430                                 }
431                         }
432
433                         unsigned long buffered;
434                         output_->GetBufferedAudioSampleFrameCount(&buffered);
435                         graph_->set_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_cadence[0]*format_desc_.audio_channels*2));
436                 }
437                 catch(...)
438                 {
439                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);
440                         exception_ = std::current_exception();
441                         return E_FAIL;
442                 }
443
444                 return S_OK;
445         }
446
447         template<typename T>
448         void schedule_next_audio(const T& audio_data)
449         {
450                 auto sample_frame_count = static_cast<int>(audio_data.size()/format_desc_.audio_channels);
451
452                 audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));
453
454                 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, audio_scheduled_, format_desc_.audio_sample_rate, nullptr)))
455                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
456
457                 audio_scheduled_ += sample_frame_count;
458         }
459                         
460         void schedule_next_video(core::const_frame frame)
461         {
462                 CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));
463                 if(FAILED(output_->ScheduleVideoFrame(frame2, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
464                         CASPAR_LOG(error) << print() << L" Failed to schedule video.";
465
466                 video_scheduled_ += format_desc_.duration;
467
468                 graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
469                 tick_timer_.restart();
470         }
471
472         boost::unique_future<bool> send(core::const_frame frame)
473         {
474                 auto exception = lock(exception_mutex_, [&]
475                 {
476                         return exception_;
477                 });
478
479                 if(exception != nullptr)
480                         std::rethrow_exception(exception);              
481
482                 if(!is_running_)
483                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Is not running."));
484                 
485                 bool audio_ready = !config_.embedded_audio;
486                 bool video_ready = false;
487
488                 auto enqueue_task = [audio_ready, video_ready, frame, this]() mutable -> boost::optional<bool>
489                 {
490                         if (!audio_ready)
491                                 audio_ready = audio_frame_buffer_.try_push(frame);
492
493                         if (!video_ready)
494                                 video_ready = video_frame_buffer_.try_push(frame);
495
496                         if (audio_ready && video_ready)
497                                 return true;
498                         else
499                                 return boost::optional<bool>();
500                 };
501                 
502                 if (enqueue_task())
503                         return wrap_as_future(true);
504
505                 send_completion_.set_task(enqueue_task);
506
507                 return send_completion_.get_future();
508         }
509         
510         std::wstring print() const
511         {
512                 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +
513                         boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";
514         }
515 };
516
517 struct decklink_consumer_proxy : public core::frame_consumer
518 {
519         const configuration                                     config_;
520         std::unique_ptr<decklink_consumer>      consumer_;
521         executor                                                        executor_;
522 public:
523
524         decklink_consumer_proxy(const configuration& config)
525                 : config_(config)
526                 , executor_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
527         {
528                 executor_.begin_invoke([=]
529                 {
530                         ::CoInitialize(nullptr);
531                 });
532         }
533
534         ~decklink_consumer_proxy()
535         {
536                 executor_.invoke([=]
537                 {
538                         consumer_.reset();
539                         ::CoUninitialize();
540                 });
541         }
542
543         // frame_consumer
544         
545         void initialize(const core::video_format_desc& format_desc, int channel_index) override
546         {
547                 executor_.invoke([=]
548                 {
549                         consumer_.reset();
550                         consumer_.reset(new decklink_consumer(config_, format_desc, channel_index));                    
551                 });
552         }
553         
554         boost::unique_future<bool> send(core::const_frame frame) override
555         {
556                 return consumer_->send(frame);
557         }
558         
559         std::wstring print() const override
560         {
561                 return consumer_ ? consumer_->print() : L"[decklink_consumer]";
562         }               
563
564         std::wstring name() const override
565         {
566                 return L"decklink";
567         }
568
569         boost::property_tree::wptree info() const override
570         {
571                 boost::property_tree::wptree info;
572                 info.add(L"type", L"decklink");
573                 info.add(L"key-only", config_.key_only);
574                 info.add(L"device", config_.device_index);
575                 info.add(L"low-latency", config_.low_latency);
576                 info.add(L"embedded-audio", config_.embedded_audio);
577                 info.add(L"low-latency", config_.low_latency);
578                 //info.add(L"internal-key", config_.internal_key);
579                 return info;
580         }
581
582         int buffer_depth() const override
583         {
584                 return config_.buffer_depth();
585         }
586
587         int index() const override
588         {
589                 return 300 + config_.device_index;
590         }
591
592         monitor::source& monitor_output()
593         {
594                 static monitor::subject monitor_subject(""); return monitor_subject;
595         }
596 };      
597
598 spl::shared_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params) 
599 {
600         if(params.size() < 1 || params[0] != L"DECKLINK")
601                 return core::frame_consumer::empty();
602         
603         configuration config;
604                 
605         if(params.size() > 1)
606                 config.device_index = boost::lexical_cast<int>(params[1]);
607         
608         if(std::find(params.begin(), params.end(), L"INTERNAL_KEY")                     != params.end())
609                 config.keyer = configuration::internal_keyer;
610         else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY")        != params.end())
611                 config.keyer = configuration::external_keyer;
612         else
613                 config.keyer = configuration::default_keyer;
614
615         if(std::find(params.begin(), params.end(), L"LOW_LATENCY")       != params.end())
616                 config.latency = configuration::low_latency;
617
618         config.embedded_audio   = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
619         config.key_only                 = std::find(params.begin(), params.end(), L"KEY_ONLY")           != params.end();
620
621         return spl::make_shared<decklink_consumer_proxy>(config);
622 }
623
624 spl::shared_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree) 
625 {
626         configuration config;
627
628         auto keyer = ptree.get(L"keyer", L"default");
629         if(keyer == L"external")
630                 config.keyer = configuration::external_keyer;
631         else if(keyer == L"internal")
632                 config.keyer = configuration::internal_keyer;
633
634         auto latency = ptree.get(L"latency", L"normal");
635         if(latency == L"low")
636                 config.latency = configuration::low_latency;
637         else if(latency == L"normal")
638                 config.latency = configuration::normal_latency;
639
640         config.key_only                         = ptree.get(L"key-only",                config.key_only);
641         config.device_index                     = ptree.get(L"device",                  config.device_index);
642         config.embedded_audio           = ptree.get(L"embedded-audio",  config.embedded_audio);
643         config.base_buffer_depth        = ptree.get(L"buffer-depth",    config.base_buffer_depth);
644
645         return spl::make_shared<decklink_consumer_proxy>(config);
646 }
647
648 }}
649
650 /*
651 ##############################################################################
652 Pre-rolling
653
654 Mail: 2011-05-09
655
656 Yoshan
657 BMD Developer Support
658 developer@blackmagic-design.com
659
660 -----------------------------------------------------------------------------
661
662 Thanks for your inquiry. The minimum number of frames that you can preroll 
663 for scheduled playback is three frames for video and four frames for audio. 
664 As you mentioned if you preroll less frames then playback will not start or
665 playback will be very sporadic. From our experience with Media Express, we 
666 recommended that at least seven frames are prerolled for smooth playback. 
667
668 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
669 There can be around 3 frames worth of latency on scheduled output.
670 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
671 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() 
672 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will 
673 guarantee that the provided frame will be output as soon the previous 
674 frame output has been completed.
675 ################################################################################
676 */
677
678 /*
679 ##############################################################################
680 Async DMA Transfer without redundant copying
681
682 Mail: 2011-05-10
683
684 Yoshan
685 BMD Developer Support
686 developer@blackmagic-design.com
687
688 -----------------------------------------------------------------------------
689
690 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame 
691 and providing a pointer to your video buffer when GetBytes() is called. 
692 This may help to keep copying to a minimum. Please ensure that the pixel 
693 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will 
694 have to colourspace convert which may result in additional copying.
695 ################################################################################
696 */