]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
2.1.0: Fully ported (apart from legacy protocol proect) to UTF8, instead of a big...
[casparcg] / modules / decklink / consumer / decklink_consumer.cpp
1 /*\r
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
3 *\r
4 * This file is part of CasparCG (www.casparcg.com).\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 * Author: Robert Nagy, ronag89@gmail.com\r
20 */\r
21 \r
22 #include "../StdAfx.h"\r
23  \r
24 #include "decklink_consumer.h"\r
25 \r
26 #include "../util/util.h"\r
27 \r
28 #include "../interop/DeckLinkAPI_h.h"\r
29 \r
30 #include <core/mixer/read_frame.h>\r
31 \r
32 #include <common/concurrency/com_context.h>\r
33 #include <common/diagnostics/graph.h>\r
34 #include <common/exception/exceptions.h>\r
35 #include <common/memory/memcpy.h>\r
36 #include <common/memory/memclr.h>\r
37 #include <common/memory/memshfl.h>\r
38 \r
39 #include <core/consumer/frame_consumer.h>\r
40 \r
41 #include <tbb/concurrent_queue.h>\r
42 #include <tbb/cache_aligned_allocator.h>\r
43 \r
44 #include <boost/circular_buffer.hpp>\r
45 #include <boost/timer.hpp>\r
46 #include <boost/property_tree/ptree.hpp>\r
47 \r
48 namespace caspar { namespace decklink { \r
49         \r
50 struct configuration\r
51 {\r
52         size_t  device_index;\r
53         bool    embedded_audio;\r
54         bool    internal_key;\r
55         bool    low_latency;\r
56         bool    key_only;\r
57         size_t  base_buffer_depth;\r
58         size_t  buffer_depth;\r
59         \r
60         configuration()\r
61                 : device_index(1)\r
62                 , embedded_audio(false)\r
63                 , internal_key(false)\r
64                 , low_latency(true)\r
65                 , key_only(false)\r
66                 , base_buffer_depth(3)\r
67                 , buffer_depth(base_buffer_depth + (low_latency ? 0 : 1) + (embedded_audio ? 1 : 0)){}\r
68 };\r
69 \r
70 class decklink_frame : public IDeckLinkVideoFrame\r
71 {\r
72         tbb::atomic<int>                                                                                        ref_count_;\r
73         std::shared_ptr<core::read_frame>                                                       frame_;\r
74         const core::video_format_desc                                                           format_desc_;\r
75 \r
76         const bool                                                                                                      key_only_;\r
77         std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> key_data_;\r
78 public:\r
79         decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, bool key_only)\r
80                 : frame_(frame)\r
81                 , format_desc_(format_desc)\r
82                 , key_only_(key_only)\r
83         {\r
84                 ref_count_ = 0;\r
85         }\r
86 \r
87         const boost::iterator_range<const int32_t*> audio_data()\r
88         {\r
89                 return frame_->audio_data();\r
90         }\r
91         \r
92         STDMETHOD (QueryInterface(REFIID, LPVOID*))             {return E_NOINTERFACE;}\r
93         STDMETHOD_(ULONG,                       AddRef())                       \r
94         {\r
95                 return ++ref_count_;\r
96         }\r
97         STDMETHOD_(ULONG,                       Release())                      \r
98         {\r
99                 --ref_count_;\r
100                 if(ref_count_ == 0)\r
101                         delete this;\r
102                 return ref_count_;\r
103         }\r
104 \r
105         STDMETHOD_(long,                        GetWidth())                     {return format_desc_.width;}        \r
106     STDMETHOD_(long,                    GetHeight())            {return format_desc_.height;}        \r
107     STDMETHOD_(long,                    GetRowBytes())          {return format_desc_.width*4;}        \r
108         STDMETHOD_(BMDPixelFormat,      GetPixelFormat())       {return bmdFormat8BitBGRA;}        \r
109     STDMETHOD_(BMDFrameFlags,   GetFlags())                     {return bmdFrameFlagDefault;}\r
110         \r
111     STDMETHOD(GetBytes(void** buffer))\r
112         {\r
113                 static std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> zeros(1920*1080*4, 0);\r
114                 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)\r
115                 {\r
116                         *buffer = zeros.data();\r
117                         return S_OK;\r
118                 }\r
119 \r
120                 if(!key_only_)\r
121                         *buffer = const_cast<uint8_t*>(frame_->image_data().begin());\r
122                 else\r
123                 {\r
124                         if(key_data_.empty())\r
125                         {\r
126                                 key_data_.resize(frame_->image_data().size());\r
127                                 fast_memshfl(key_data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);\r
128                                 frame_.reset();\r
129                         }\r
130                         *buffer = key_data_.data();\r
131                 }\r
132 \r
133                 return S_OK;\r
134         }\r
135         \r
136     STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}        \r
137     STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary))                 {return S_FALSE;}\r
138 };\r
139 \r
140 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
141 {               \r
142         const int                                                       channel_index_;\r
143         const configuration                                     config_;\r
144 \r
145         CComPtr<IDeckLink>                                      decklink_;\r
146         CComQIPtr<IDeckLinkOutput>                      output_;\r
147         CComQIPtr<IDeckLinkConfiguration>       configuration_;\r
148         CComQIPtr<IDeckLinkKeyer>                       keyer_;\r
149 \r
150         tbb::spin_mutex                                         exception_mutex_;\r
151         std::exception_ptr                                      exception_;\r
152 \r
153         tbb::atomic<bool>                                       is_running_;\r
154                 \r
155         const std::string                                       model_name_;\r
156         const core::video_format_desc           format_desc_;\r
157         const size_t                                            buffer_size_;\r
158 \r
159         long long                                                       video_scheduled_;\r
160         long long                                                       audio_scheduled_;\r
161 \r
162         size_t                                                          preroll_count_;\r
163                 \r
164         boost::circular_buffer<std::vector<int32_t>>    audio_container_;\r
165 \r
166         tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;\r
167         tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;\r
168         \r
169         safe_ptr<diagnostics::graph> graph_;\r
170         boost::timer tick_timer_;\r
171 \r
172 public:\r
173         decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index) \r
174                 : channel_index_(channel_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() << " Enabled normal-latency mode";\r
242                 }\r
243                 else\r
244                 {                       \r
245                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);\r
246                         CASPAR_LOG(info) << print() << " 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() << " Failed to enable internal keyer.";                    \r
256                         else if(FAILED(keyer_->SetLevel(255)))                  \r
257                                 CASPAR_LOG(error) << print() << " Failed to set key-level to max.";\r
258                         else\r
259                                 CASPAR_LOG(info) << print() << " Enabled internal keyer.";              \r
260                 }\r
261                 else\r
262                 {\r
263                         if(FAILED(keyer_->Enable(TRUE)))                        \r
264                                 CASPAR_LOG(error) << print() << " Failed to enable external keyer.";    \r
265                         else if(FAILED(keyer_->SetLevel(255)))                  \r
266                                 CASPAR_LOG(error) << print() << " Failed to set key-level to max.";\r
267                         else\r
268                                 CASPAR_LOG(info) << print() << " 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(print() + " Could not enable audio output."));\r
276                                 \r
277                 if(FAILED(output_->SetAudioCallback(this)))\r
278                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(print() + " Could not set audio callback."));\r
279 \r
280                 CASPAR_LOG(info) << print() << " 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(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(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(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() << " 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() << " 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() << " 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(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::string print() const\r
430         {\r
431                 return model_name_ + " [" + boost::lexical_cast<std::string>(channel_index_) + "-" +\r
432                         boost::lexical_cast<std::string>(config_.device_index) + "|" +  format_desc_.name + "]";\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_("decklink_consumer[" + boost::lexical_cast<std::string>(config.device_index) + "]")\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 << " 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) override\r
462         {\r
463                 context_.reset([&]{return new decklink_consumer(config_, format_desc, channel_index);});                \r
464                 audio_cadence_ = format_desc.audio_cadence;             \r
465 \r
466                 CASPAR_LOG(info) << print() << " 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::string print() const override\r
479         {\r
480                 return context_ ? context_->print() : "[decklink_consumer]";\r
481         }               \r
482 \r
483         virtual boost::property_tree::ptree info() const override\r
484         {\r
485                 boost::property_tree::ptree info;\r
486                 info.add("type", "decklink-consumer");\r
487                 info.add("key-only", config_.key_only);\r
488                 info.add("device", config_.device_index);\r
489                 info.add("low-latency", config_.low_latency);\r
490                 info.add("embedded-audio", config_.embedded_audio);\r
491                 info.add("low-latency", config_.low_latency);\r
492                 info.add("internal-key", config_.internal_key);\r
493                 return info;\r
494         }\r
495 \r
496         virtual size_t buffer_depth() const override\r
497         {\r
498                 return config_.buffer_depth;\r
499         }\r
500 \r
501         virtual int index() const override\r
502         {\r
503                 return 300 + config_.device_index;\r
504         }\r
505 };      \r
506 \r
507 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::string>& params) \r
508 {\r
509         if(params.size() < 1 || params[0] != "DECKLINK")\r
510                 return core::frame_consumer::empty();\r
511         \r
512         configuration config;\r
513                 \r
514         if(params.size() > 1)\r
515                 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);\r
516         \r
517         config.internal_key             = std::find(params.begin(), params.end(), "INTERNAL_KEY")        != params.end();\r
518         config.low_latency              = std::find(params.begin(), params.end(), "LOW_LATENCY")         != params.end();\r
519         config.embedded_audio   = std::find(params.begin(), params.end(), "EMBEDDED_AUDIO") != params.end();\r
520         config.key_only                 = std::find(params.begin(), params.end(), "KEY_ONLY")            != params.end();\r
521 \r
522         return make_safe<decklink_consumer_proxy>(config);\r
523 }\r
524 \r
525 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::ptree& ptree) \r
526 {\r
527         configuration config;\r
528 \r
529         config.internal_key                     = ptree.get("internal-key",     config.internal_key);\r
530         config.low_latency                      = ptree.get("low-latency",              config.low_latency);\r
531         config.key_only                         = ptree.get("key-only",         config.key_only);\r
532         config.device_index                     = ptree.get("device",                   config.device_index);\r
533         config.embedded_audio           = ptree.get("embedded-audio",   config.embedded_audio);\r
534         config.base_buffer_depth        = ptree.get("buffer-depth",     config.base_buffer_depth);\r
535 \r
536         return make_safe<decklink_consumer_proxy>(config);\r
537 }\r
538 \r
539 }}\r
540 \r
541 /*\r
542 ##############################################################################\r
543 Pre-rolling\r
544 \r
545 Mail: 2011-05-09\r
546 \r
547 Yoshan\r
548 BMD Developer Support\r
549 developer@blackmagic-design.com\r
550 \r
551 -----------------------------------------------------------------------------\r
552 \r
553 Thanks for your inquiry. The minimum number of frames that you can preroll \r
554 for scheduled playback is three frames for video and four frames for audio. \r
555 As you mentioned if you preroll less frames then playback will not start or\r
556 playback will be very sporadic. From our experience with Media Express, we \r
557 recommended that at least seven frames are prerolled for smooth playback. \r
558 \r
559 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:\r
560 There can be around 3 frames worth of latency on scheduled output.\r
561 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is\r
562 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() \r
563 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will \r
564 guarantee that the provided frame will be output as soon the previous \r
565 frame output has been completed.\r
566 ################################################################################\r
567 */\r
568 \r
569 /*\r
570 ##############################################################################\r
571 Async DMA Transfer without redundant copying\r
572 \r
573 Mail: 2011-05-10\r
574 \r
575 Yoshan\r
576 BMD Developer Support\r
577 developer@blackmagic-design.com\r
578 \r
579 -----------------------------------------------------------------------------\r
580 \r
581 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame \r
582 and providing a pointer to your video buffer when GetBytes() is called. \r
583 This may help to keep copying to a minimum. Please ensure that the pixel \r
584 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will \r
585 have to colourspace convert which may result in additional copying.\r
586 ################################################################################\r
587 */