]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
2.0.2: Merged ntsc-audio-exp branch for proper/native NTSC audio support.
[casparcg] / modules / decklink / consumer / decklink_consumer.cpp
1 /*\r
2 * copyright (c) 2010 Sveriges Television AB <info@casparcg.com>\r
3 *\r
4 *  This file is part of CasparCG.\r
5 *\r
6 *    CasparCG is free software: you can redistribute it and/or modify\r
7 *    it under the terms of the GNU General Public License as published by\r
8 *    the Free Software Foundation, either version 3 of the License, or\r
9 *    (at your option) any later version.\r
10 *\r
11 *    CasparCG is distributed in the hope that it will be useful,\r
12 *    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14 *    GNU General Public License for more details.\r
15 \r
16 *    You should have received a copy of the GNU General Public License\r
17 *    along with CasparCG.  If not, see <http://www.gnu.org/licenses/>.\r
18 *\r
19 */\r
20 \r
21 #include "../StdAfx.h"\r
22  \r
23 #include "decklink_consumer.h"\r
24 \r
25 #include "../util/util.h"\r
26 \r
27 #include "../interop/DeckLinkAPI_h.h"\r
28 \r
29 #include <core/mixer/read_frame.h>\r
30 \r
31 #include <common/concurrency/com_context.h>\r
32 #include <common/diagnostics/graph.h>\r
33 #include <common/exception/exceptions.h>\r
34 #include <common/memory/memcpy.h>\r
35 #include <common/memory/memclr.h>\r
36 #include <common/memory/memshfl.h>\r
37 \r
38 #include <core/consumer/frame_consumer.h>\r
39 \r
40 #include <tbb/concurrent_queue.h>\r
41 #include <tbb/cache_aligned_allocator.h>\r
42 \r
43 #include <boost/circular_buffer.hpp>\r
44 #include <boost/timer.hpp>\r
45 \r
46 namespace caspar { namespace decklink { \r
47         \r
48 struct configuration\r
49 {\r
50         size_t  device_index;\r
51         bool    embedded_audio;\r
52         bool    internal_key;\r
53         bool    low_latency;\r
54         bool    key_only;\r
55         size_t  base_buffer_depth;\r
56         size_t  buffer_depth;\r
57         \r
58         configuration()\r
59                 : device_index(1)\r
60                 , embedded_audio(false)\r
61                 , internal_key(false)\r
62                 , low_latency(true)\r
63                 , key_only(false)\r
64                 , base_buffer_depth(3)\r
65                 , buffer_depth(base_buffer_depth + (low_latency ? 0 : 1) + (embedded_audio ? 1 : 0)){}\r
66 };\r
67 \r
68 class decklink_frame : public IDeckLinkVideoFrame\r
69 {\r
70         tbb::atomic<int>                                                                                        ref_count_;\r
71         std::shared_ptr<core::read_frame>                                                       frame_;\r
72         const core::video_format_desc                                                           format_desc_;\r
73 \r
74         bool                                                                                                            key_only_;\r
75         std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> key_data_;\r
76 public:\r
77         decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, bool key_only)\r
78                 : frame_(frame)\r
79                 , format_desc_(format_desc)\r
80                 , key_only_(key_only)\r
81         {\r
82                 ref_count_ = 0;\r
83         }\r
84 \r
85         const boost::iterator_range<const int32_t*> audio_data()\r
86         {\r
87                 return frame_->audio_data();\r
88         }\r
89         \r
90         STDMETHOD (QueryInterface(REFIID, LPVOID*))             {return E_NOINTERFACE;}\r
91         STDMETHOD_(ULONG,                       AddRef())                       \r
92         {\r
93                 return ++ref_count_;\r
94         }\r
95         STDMETHOD_(ULONG,                       Release())                      \r
96         {\r
97                 --ref_count_;\r
98                 if(ref_count_ == 0)\r
99                         delete this;\r
100                 return ref_count_;\r
101         }\r
102 \r
103         STDMETHOD_(long,                        GetWidth())                     {return format_desc_.width;}        \r
104     STDMETHOD_(long,                    GetHeight())            {return format_desc_.height;}        \r
105     STDMETHOD_(long,                    GetRowBytes())          {return format_desc_.width*4;}        \r
106         STDMETHOD_(BMDPixelFormat,      GetPixelFormat())       {return bmdFormat8BitBGRA;}        \r
107     STDMETHOD_(BMDFrameFlags,   GetFlags())                     {return bmdFrameFlagDefault;}\r
108         \r
109     STDMETHOD(GetBytes(void** buffer))\r
110         {\r
111                 static std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> zeros(1920*1080*4, 0);\r
112                 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)\r
113                 {\r
114                         *buffer = zeros.data();\r
115                         return S_OK;\r
116                 }\r
117 \r
118                 if(!key_only_)\r
119                         *buffer = const_cast<uint8_t*>(frame_->image_data().begin());\r
120                 else\r
121                 {\r
122                         if(key_data_.empty())\r
123                         {\r
124                                 key_data_.resize(frame_->image_data().size());\r
125                                 fast_memshfl(key_data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);\r
126                                 frame_.reset();\r
127                         }\r
128                         *buffer = key_data_.data();\r
129                 }\r
130 \r
131                 return S_OK;\r
132         }\r
133         \r
134     STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}        \r
135     STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary))                 {return S_FALSE;}\r
136 };\r
137 \r
138 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
139 {               \r
140         const int                                                       channel_index_;\r
141         const int                                                       sub_index_;\r
142         const configuration                                     config_;\r
143 \r
144         CComPtr<IDeckLink>                                      decklink_;\r
145         CComQIPtr<IDeckLinkOutput>                      output_;\r
146         CComQIPtr<IDeckLinkConfiguration>       configuration_;\r
147         CComQIPtr<IDeckLinkKeyer>                       keyer_;\r
148 \r
149         tbb::spin_mutex                                         exception_mutex_;\r
150         std::exception_ptr                                      exception_;\r
151 \r
152         tbb::atomic<bool>                                       is_running_;\r
153                 \r
154         const std::wstring                                      model_name_;\r
155         const core::video_format_desc           format_desc_;\r
156         const size_t                                            buffer_size_;\r
157 \r
158         long long                                                       video_scheduled_;\r
159         long long                                                       audio_scheduled_;\r
160 \r
161         size_t                                                          preroll_count_;\r
162                 \r
163         boost::circular_buffer<std::vector<int32_t>>    audio_container_;\r
164 \r
165         tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;\r
166         tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;\r
167         \r
168         safe_ptr<diagnostics::graph> graph_;\r
169         boost::timer tick_timer_;\r
170 \r
171 public:\r
172         decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index, int sub_index) \r
173                 : channel_index_(channel_index)\r
174                 , sub_index_(sub_index)\r
175                 , config_(config)\r
176                 , decklink_(get_device(config.device_index))\r
177                 , output_(decklink_)\r
178                 , configuration_(decklink_)\r
179                 , keyer_(decklink_)\r
180                 , model_name_(get_model_name(decklink_))\r
181                 , format_desc_(format_desc)\r
182                 , buffer_size_(config.buffer_depth) // Minimum buffer-size 3.\r
183                 , video_scheduled_(0)\r
184                 , audio_scheduled_(0)\r
185                 , preroll_count_(0)\r
186                 , audio_container_(buffer_size_+1)\r
187         {\r
188                 is_running_ = true;\r
189                                 \r
190                 video_frame_buffer_.set_capacity(1);\r
191                 audio_frame_buffer_.set_capacity(1);\r
192 \r
193                 graph_->add_guide("tick-time", 0.5);\r
194                 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));   \r
195                 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));\r
196                 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));\r
197                 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));\r
198                 graph_->set_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));\r
199                 graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));\r
200                 graph_->set_text(print());\r
201                 diagnostics::register_graph(graph_);\r
202                 \r
203                 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));\r
204                                 \r
205                 if(config.embedded_audio)\r
206                         enable_audio();\r
207 \r
208                 set_latency(config.low_latency);                                \r
209                 set_keyer(config.internal_key);\r
210                                 \r
211                 if(config.embedded_audio)               \r
212                         output_->BeginAudioPreroll();           \r
213                 \r
214                 for(size_t n = 0; n < buffer_size_; ++n)\r
215                         schedule_next_video(make_safe<core::read_frame>());\r
216 \r
217                 if(!config.embedded_audio)\r
218                         start_playback();\r
219         }\r
220 \r
221         ~decklink_consumer()\r
222         {               \r
223                 is_running_ = false;\r
224                 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());\r
225                 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());\r
226 \r
227                 if(output_ != nullptr) \r
228                 {\r
229                         output_->StopScheduledPlayback(0, nullptr, 0);\r
230                         if(config_.embedded_audio)\r
231                                 output_->DisableAudioOutput();\r
232                         output_->DisableVideoOutput();\r
233                 }\r
234         }\r
235                         \r
236         void set_latency(bool low_latency)\r
237         {               \r
238                 if(!low_latency)\r
239                 {\r
240                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);\r
241                         CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";\r
242                 }\r
243                 else\r
244                 {                       \r
245                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);\r
246                         CASPAR_LOG(info) << print() << L" Enabled low-latency mode";\r
247                 }\r
248         }\r
249 \r
250         void set_keyer(bool internal_key)\r
251         {\r
252                 if(internal_key) \r
253                 {\r
254                         if(FAILED(keyer_->Enable(FALSE)))                       \r
255                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   \r
256                         else if(FAILED(keyer_->SetLevel(255)))                  \r
257                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
258                         else\r
259                                 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             \r
260                 }\r
261                 else\r
262                 {\r
263                         if(FAILED(keyer_->Enable(TRUE)))                        \r
264                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   \r
265                         else if(FAILED(keyer_->SetLevel(255)))                  \r
266                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
267                         else\r
268                                 CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     \r
269                 }\r
270         }\r
271         \r
272         void enable_audio()\r
273         {\r
274                 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))\r
275                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));\r
276                                 \r
277                 if(FAILED(output_->SetAudioCallback(this)))\r
278                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));\r
279 \r
280                 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";\r
281         }\r
282 \r
283         void enable_video(BMDDisplayMode display_mode)\r
284         {\r
285                 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) \r
286                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));\r
287                 \r
288                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
289                         BOOST_THROW_EXCEPTION(caspar_exception() \r
290                                                                         << msg_info(narrow(print()) + " Failed to set playback completion callback.")\r
291                                                                         << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));\r
292         }\r
293 \r
294         void start_playback()\r
295         {\r
296                 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) \r
297                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
298         }\r
299         \r
300         STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}\r
301         STDMETHOD_(ULONG, AddRef())                                     {return 1;}\r
302         STDMETHOD_(ULONG, Release())                            {return 1;}\r
303         \r
304         STDMETHOD(ScheduledPlaybackHasStopped())\r
305         {\r
306                 is_running_ = false;\r
307                 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";\r
308                 return S_OK;\r
309         }\r
310 \r
311         STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))\r
312         {\r
313                 if(!is_running_)\r
314                         return E_FAIL;\r
315                 \r
316                 try\r
317                 {\r
318                         if(result == bmdOutputFrameDisplayedLate)\r
319                         {\r
320                                 graph_->add_tag("late-frame");\r
321                                 video_scheduled_ += format_desc_.duration;\r
322                                 audio_scheduled_ += reinterpret_cast<decklink_frame*>(completed_frame)->audio_data().size()/format_desc_.audio_channels;\r
323                                 //++video_scheduled_;\r
324                                 //audio_scheduled_ += format_desc_.audio_cadence[0];\r
325                                 //++audio_scheduled_;\r
326                         }\r
327                         else if(result == bmdOutputFrameDropped)\r
328                                 graph_->add_tag("dropped-frame");\r
329                         else if(result == bmdOutputFrameFlushed)\r
330                                 graph_->add_tag("flushed-frame");\r
331 \r
332                         std::shared_ptr<core::read_frame> frame;        \r
333                         video_frame_buffer_.pop(frame);                                 \r
334                         schedule_next_video(make_safe_ptr(frame));      \r
335                         \r
336                         unsigned long buffered;\r
337                         output_->GetBufferedVideoFrameCount(&buffered);\r
338                         graph_->update_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);\r
339                 }\r
340                 catch(...)\r
341                 {\r
342                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
343                         exception_ = std::current_exception();\r
344                         return E_FAIL;\r
345                 }\r
346 \r
347                 return S_OK;\r
348         }\r
349                 \r
350         STDMETHOD(RenderAudioSamples(BOOL preroll))\r
351         {\r
352                 if(!is_running_)\r
353                         return E_FAIL;\r
354                 \r
355                 try\r
356                 {       \r
357                         if(preroll)\r
358                         {\r
359                                 if(++preroll_count_ >= buffer_size_)\r
360                                 {\r
361                                         output_->EndAudioPreroll();\r
362                                         start_playback();                               \r
363                                 }\r
364                                 else\r
365                                         schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()], 0));    \r
366                         }\r
367                         else\r
368                         {\r
369                                 std::shared_ptr<core::read_frame> frame;\r
370                                 audio_frame_buffer_.pop(frame);\r
371                                 schedule_next_audio(frame->audio_data());\r
372                         }\r
373 \r
374                         unsigned long buffered;\r
375                         output_->GetBufferedAudioSampleFrameCount(&buffered);\r
376                         graph_->update_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_cadence[0]*2));\r
377                 }\r
378                 catch(...)\r
379                 {\r
380                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
381                         exception_ = std::current_exception();\r
382                         return E_FAIL;\r
383                 }\r
384 \r
385                 return S_OK;\r
386         }\r
387 \r
388         template<typename T>\r
389         void schedule_next_audio(const T& audio_data)\r
390         {\r
391                 const int sample_frame_count = audio_data.size()/format_desc_.audio_channels;\r
392 \r
393                 audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));\r
394 \r
395                 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, audio_scheduled_, format_desc_.audio_sample_rate, nullptr)))\r
396                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
397 \r
398                 audio_scheduled_ += sample_frame_count;\r
399         }\r
400                         \r
401         void schedule_next_video(const safe_ptr<core::read_frame>& frame)\r
402         {\r
403                 CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));\r
404                 if(FAILED(output_->ScheduleVideoFrame(frame2, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))\r
405                         CASPAR_LOG(error) << print() << L" Failed to schedule video.";\r
406 \r
407                 video_scheduled_ += format_desc_.duration;\r
408 \r
409                 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);\r
410                 tick_timer_.restart();\r
411         }\r
412 \r
413         void send(const safe_ptr<core::read_frame>& frame)\r
414         {\r
415                 {\r
416                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
417                         if(exception_ != nullptr)\r
418                                 std::rethrow_exception(exception_);\r
419                 }\r
420 \r
421                 if(!is_running_)\r
422                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));\r
423                 \r
424                 if(config_.embedded_audio)\r
425                         audio_frame_buffer_.push(frame);        \r
426                 video_frame_buffer_.push(frame);        \r
427         }\r
428         \r
429         std::wstring print() const\r
430         {\r
431                 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" + boost::lexical_cast<std::wstring>(sub_index_) + L"|device " +\r
432                         boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";\r
433         }\r
434 };\r
435 \r
436 struct decklink_consumer_proxy : public core::frame_consumer\r
437 {\r
438         const configuration                             config_;\r
439         com_context<decklink_consumer>  context_;\r
440         std::vector<size_t>                             audio_cadence_;\r
441 public:\r
442 \r
443         decklink_consumer_proxy(const configuration& config)\r
444                 : config_(config)\r
445                 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")\r
446         {\r
447         }\r
448 \r
449         ~decklink_consumer_proxy()\r
450         {\r
451                 if(context_)\r
452                 {\r
453                         auto str = print();\r
454                         context_.reset();\r
455                         CASPAR_LOG(info) << str << L" Successfully Uninitialized.";     \r
456                 }\r
457         }\r
458 \r
459         // frame_consumer\r
460         \r
461         virtual void initialize(const core::video_format_desc& format_desc, int channel_index, int sub_index) override\r
462         {\r
463                 context_.reset([&]{return new decklink_consumer(config_, format_desc, channel_index, sub_index);});             \r
464                 audio_cadence_ = format_desc.audio_cadence;             \r
465 \r
466                 CASPAR_LOG(info) << print() << L" Successfully Initialized.";   \r
467         }\r
468         \r
469         virtual bool send(const safe_ptr<core::read_frame>& frame) override\r
470         {\r
471                 CASPAR_VERIFY(audio_cadence_.front() == static_cast<size_t>(frame->audio_data().size()));\r
472                 boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);\r
473 \r
474                 context_->send(frame);\r
475                 return true;\r
476         }\r
477         \r
478         virtual std::wstring print() const override\r
479         {\r
480                 return context_ ? context_->print() : L"[decklink_consumer]";\r
481         }                       \r
482 \r
483         virtual size_t buffer_depth() const override\r
484         {\r
485                 return config_.buffer_depth;\r
486         }\r
487 };      \r
488 \r
489 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params) \r
490 {\r
491         if(params.size() < 1 || params[0] != L"DECKLINK")\r
492                 return core::frame_consumer::empty();\r
493         \r
494         configuration config;\r
495                 \r
496         if(params.size() > 1)\r
497                 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);\r
498         \r
499         config.internal_key             = std::find(params.begin(), params.end(), L"INTERNAL_KEY")       != params.end();\r
500         config.low_latency              = std::find(params.begin(), params.end(), L"LOW_LATENCY")        != params.end();\r
501         config.embedded_audio   = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
502         config.key_only                 = std::find(params.begin(), params.end(), L"KEY_ONLY")           != params.end();\r
503 \r
504         return make_safe<decklink_consumer_proxy>(config);\r
505 }\r
506 \r
507 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::ptree& ptree) \r
508 {\r
509         configuration config;\r
510 \r
511         config.internal_key                     = ptree.get("internal-key",             config.internal_key);\r
512         config.low_latency                      = ptree.get("low-latency",              config.low_latency);\r
513         config.key_only                         = ptree.get("key-only",                 config.key_only);\r
514         config.device_index                     = ptree.get("device",                   config.device_index);\r
515         config.embedded_audio           = ptree.get("embedded-audio",   config.embedded_audio);\r
516         config.base_buffer_depth        = ptree.get("buffer-depth",             config.base_buffer_depth);\r
517 \r
518         return make_safe<decklink_consumer_proxy>(config);\r
519 }\r
520 \r
521 }}\r
522 \r
523 /*\r
524 ##############################################################################\r
525 Pre-rolling\r
526 \r
527 Mail: 2011-05-09\r
528 \r
529 Yoshan\r
530 BMD Developer Support\r
531 developer@blackmagic-design.com\r
532 \r
533 -----------------------------------------------------------------------------\r
534 \r
535 Thanks for your inquiry. The minimum number of frames that you can preroll \r
536 for scheduled playback is three frames for video and four frames for audio. \r
537 As you mentioned if you preroll less frames then playback will not start or\r
538 playback will be very sporadic. From our experience with Media Express, we \r
539 recommended that at least seven frames are prerolled for smooth playback. \r
540 \r
541 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:\r
542 There can be around 3 frames worth of latency on scheduled output.\r
543 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is\r
544 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() \r
545 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will \r
546 guarantee that the provided frame will be output as soon the previous \r
547 frame output has been completed.\r
548 ################################################################################\r
549 */\r
550 \r
551 /*\r
552 ##############################################################################\r
553 Async DMA Transfer without redundant copying\r
554 \r
555 Mail: 2011-05-10\r
556 \r
557 Yoshan\r
558 BMD Developer Support\r
559 developer@blackmagic-design.com\r
560 \r
561 -----------------------------------------------------------------------------\r
562 \r
563 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame \r
564 and providing a pointer to your video buffer when GetBytes() is called. \r
565 This may help to keep copying to a minimum. Please ensure that the pixel \r
566 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will \r
567 have to colourspace convert which may result in additional copying.\r
568 ################################################################################\r
569 */