]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
2.0.0.2: SSE optimized key_only.
[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/consumer/frame/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 \r
37 #include <tbb/concurrent_queue.h>\r
38 \r
39 #include <boost/circular_buffer.hpp>\r
40 #include <boost/timer.hpp>\r
41 \r
42 namespace caspar { \r
43         \r
44 enum key\r
45 {\r
46         external_key,\r
47         internal_key,\r
48         default_key\r
49 };\r
50 \r
51 enum latency\r
52 {\r
53         low_latency,\r
54         normal_latency,\r
55         default_latency\r
56 };\r
57 \r
58 enum output_pixels\r
59 {\r
60         fill_and_key,\r
61         key_only\r
62 };\r
63 \r
64 struct configuration\r
65 {\r
66         size_t  device_index;\r
67         bool    embedded_audio;\r
68         key             keyer;\r
69         latency latency;\r
70         output_pixels  output;\r
71         \r
72         configuration()\r
73                 : device_index(1)\r
74                 , embedded_audio(false)\r
75                 , keyer(default_key)\r
76                 , latency(default_latency)\r
77                 , output(fill_and_key){}\r
78 };\r
79 \r
80 class decklink_frame_adapter : public IDeckLinkVideoFrame\r
81 {\r
82         const safe_ptr<const core::read_frame>  frame_;\r
83         const core::video_format_desc                   format_desc_;\r
84 public:\r
85         decklink_frame_adapter(const safe_ptr<const core::read_frame>& frame, const core::video_format_desc& format_desc)\r
86                 : frame_(frame)\r
87                 , format_desc_(format_desc){}\r
88         \r
89         STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}\r
90         STDMETHOD_(ULONG, AddRef())                                     {return 1;}\r
91         STDMETHOD_(ULONG, Release())                            {return 1;}\r
92 \r
93         STDMETHOD_(long, GetWidth())                            {return format_desc_.width;}        \r
94     STDMETHOD_(long, GetHeight())                               {return format_desc_.height;}        \r
95     STDMETHOD_(long, GetRowBytes())                             {return format_desc_.width*4;}        \r
96         STDMETHOD_(BMDPixelFormat, GetPixelFormat()){return bmdFormat8BitBGRA;}        \r
97     STDMETHOD_(BMDFrameFlags, GetFlags())               {return bmdFrameFlagDefault;}\r
98         \r
99     STDMETHOD(GetBytes(void** buffer))\r
100         {\r
101                 static std::vector<unsigned char> zeros(1920*1080*4, 0);\r
102                 *buffer = const_cast<unsigned char*>(frame_->image_data().begin());\r
103                 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)\r
104                         *buffer = zeros.data();\r
105                 return S_OK;\r
106         }\r
107         \r
108     STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}        \r
109     STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary))                 {return S_FALSE;}\r
110 };\r
111 \r
112 void make_alpha(void* dest, const void* source, size_t count)\r
113 {       \r
114         __m128i*           dest128 = reinterpret_cast<__m128i*>(dest);  \r
115         const __m128i* source128 = reinterpret_cast<const __m128i*>(source);\r
116 \r
117         count /= 16; // 128 bit\r
118 \r
119         __m128i xmm0, xmm1, xmm2, xmm3;\r
120 \r
121         const __m128i mask128 = _mm_set_epi8(3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15);\r
122         for(size_t n = 0; n < count/4; ++n)\r
123         {\r
124                 xmm0 = _mm_load_si128(source128++);     \r
125                 xmm1 = _mm_load_si128(source128++);     \r
126                 xmm2 = _mm_load_si128(source128++);     \r
127                 xmm3 = _mm_load_si128(source128++);     \r
128 \r
129                 _mm_stream_si128(dest128++, _mm_shuffle_epi8(xmm0, mask128));\r
130                 _mm_stream_si128(dest128++, _mm_shuffle_epi8(xmm1, mask128));\r
131                 _mm_stream_si128(dest128++, _mm_shuffle_epi8(xmm2, mask128));\r
132                 _mm_stream_si128(dest128++, _mm_shuffle_epi8(xmm3, mask128));\r
133         }\r
134 }\r
135 \r
136 std::shared_ptr<IDeckLinkVideoFrame> make_alpha_only_frame(const CComQIPtr<IDeckLinkOutput>& decklink, const safe_ptr<const core::read_frame>& frame, const core::video_format_desc& format_desc)\r
137 {\r
138         IDeckLinkMutableVideoFrame* result;\r
139 \r
140         if(FAILED(decklink->CreateVideoFrame(format_desc.width, format_desc.height, format_desc.size/format_desc.height, bmdFormat8BitBGRA, bmdFrameFlagDefault, &result)))\r
141                 BOOST_THROW_EXCEPTION(caspar_exception());\r
142 \r
143         void* bytes = nullptr;\r
144         if(FAILED(result->GetBytes(&bytes)))\r
145                 BOOST_THROW_EXCEPTION(caspar_exception());\r
146                 \r
147         unsigned char* data = reinterpret_cast<unsigned char*>(bytes);\r
148 \r
149         if(static_cast<size_t>(frame->image_data().size()) == format_desc.size)\r
150         {\r
151                 size_t count = frame->image_data().size();\r
152                 tbb::affinity_partitioner ap;\r
153                 tbb::parallel_for(tbb::blocked_range<size_t>(0, count/128), [&](const tbb::blocked_range<size_t>& r)\r
154                 {       \r
155                         make_alpha(reinterpret_cast<char*>(data) + r.begin()*128, reinterpret_cast<const char*>(frame->image_data().begin()) + r.begin()*128, r.size()*128);   \r
156                 }, ap);\r
157         }\r
158         else\r
159                 memset(data, 0, format_desc.size);\r
160 \r
161         return std::shared_ptr<IDeckLinkVideoFrame>(result, [](IDeckLinkMutableVideoFrame* p) {p->Release();});\r
162 }\r
163 \r
164 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
165 {               \r
166         const configuration config_;\r
167 \r
168         CComPtr<IDeckLink>                                      decklink_;\r
169         CComQIPtr<IDeckLinkOutput>                      output_;\r
170         CComQIPtr<IDeckLinkConfiguration>       configuration_;\r
171         CComQIPtr<IDeckLinkKeyer>                       keyer_;\r
172 \r
173         std::exception_ptr exception_;\r
174 \r
175         tbb::atomic<bool> is_running_;\r
176                 \r
177         const std::wstring model_name_;\r
178         const core::video_format_desc format_desc_;\r
179         const size_t buffer_size_;\r
180 \r
181         unsigned long frames_scheduled_;\r
182         unsigned long audio_scheduled_;\r
183                 \r
184         std::list<std::shared_ptr<IDeckLinkVideoFrame>> frame_container_; // Must be std::list in order to guarantee that pointers are always valid.\r
185         boost::circular_buffer<std::vector<short>> audio_container_;\r
186 \r
187         tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> video_frame_buffer_;\r
188         tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> audio_frame_buffer_;\r
189         \r
190         std::shared_ptr<diagnostics::graph> graph_;\r
191         boost::timer tick_timer_;\r
192 \r
193 public:\r
194         decklink_consumer(const configuration& config, const core::video_format_desc& format_desc) \r
195                 : config_(config)\r
196                 , decklink_(get_device(config.device_index))\r
197                 , output_(decklink_)\r
198                 , configuration_(decklink_)\r
199                 , keyer_(decklink_)\r
200                 , model_name_(get_model_name(decklink_))\r
201                 , format_desc_(format_desc)\r
202                 , buffer_size_(config.embedded_audio ? 5 : 4) // Minimum buffer-size (3 + 1 tolerance).\r
203                 , frames_scheduled_(0)\r
204                 , audio_scheduled_(0)\r
205                 , audio_container_(buffer_size_+1)\r
206         {\r
207                 is_running_ = true;\r
208                                 \r
209                 video_frame_buffer_.set_capacity(1);\r
210                 audio_frame_buffer_.set_capacity(1);\r
211 \r
212                 graph_ = diagnostics::create_graph(narrow(print()));\r
213                 graph_->add_guide("tick-time", 0.5);\r
214                 graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f));\r
215                 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));\r
216                 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));\r
217                 graph_->set_color("flushed-frame", diagnostics::color(0.3f, 0.3f, 0.6f));\r
218                 \r
219                 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));\r
220                                 \r
221                 if(config.embedded_audio)\r
222                         enable_audio();\r
223 \r
224                 set_latency(config.latency);                            \r
225                 set_keyer(config.keyer);\r
226                                                                 \r
227                 for(size_t n = 0; n < buffer_size_; ++n)\r
228                         schedule_next_video(core::read_frame::empty());\r
229                 \r
230                 if(config.embedded_audio)\r
231                         output_->BeginAudioPreroll();\r
232                 else\r
233                         start_playback();\r
234                 \r
235                 CASPAR_LOG(info) << print() << L" Buffer depth: " << buffer_size_;              \r
236                 CASPAR_LOG(info) << print() << L" Successfully initialized for " << format_desc_.name;  \r
237         }\r
238 \r
239         ~decklink_consumer()\r
240         {               \r
241                 is_running_ = false;\r
242                 video_frame_buffer_.try_push(core::read_frame::empty());\r
243                 audio_frame_buffer_.try_push(core::read_frame::empty());\r
244 \r
245                 if(output_ != nullptr) \r
246                 {\r
247                         output_->StopScheduledPlayback(0, nullptr, 0);\r
248                         if(config_.embedded_audio)\r
249                                 output_->DisableAudioOutput();\r
250                         output_->DisableVideoOutput();\r
251                 }\r
252         }\r
253                         \r
254         void set_latency(latency latency)\r
255         {               \r
256                 if(latency == normal_latency)\r
257                 {\r
258                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);\r
259                         CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";\r
260                 }\r
261                 else if(latency == low_latency)\r
262                 {                       \r
263                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);\r
264                         CASPAR_LOG(info) << print() << L" Enabled low-latency mode";\r
265                 }\r
266                 else\r
267                         CASPAR_LOG(info) << print() << L" Uses driver latency settings.";       \r
268         }\r
269 \r
270         void set_keyer(key keyer)\r
271         {\r
272                 if(keyer == internal_key) \r
273                 {\r
274                         if(FAILED(keyer_->Enable(FALSE)))                       \r
275                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   \r
276                         else if(FAILED(keyer_->SetLevel(255)))                  \r
277                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
278                         else\r
279                                 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             \r
280                 }\r
281                 else if(keyer == external_key)\r
282                 {\r
283                         if(FAILED(keyer_->Enable(TRUE)))                        \r
284                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   \r
285                         else if(FAILED(keyer_->SetLevel(255)))                  \r
286                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
287                         else\r
288                                 CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     \r
289                 }\r
290                 else\r
291                         CASPAR_LOG(info) << print() << L" Uses driver keyer settings."; \r
292         }\r
293         \r
294         void enable_audio()\r
295         {\r
296                 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))\r
297                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));\r
298                                 \r
299                 if(FAILED(output_->SetAudioCallback(this)))\r
300                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));\r
301 \r
302                 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";\r
303         }\r
304 \r
305         void enable_video(BMDDisplayMode display_mode)\r
306         {\r
307                 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) \r
308                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));\r
309                 \r
310                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
311                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to set playback completion callback."));\r
312         }\r
313 \r
314         void start_playback()\r
315         {\r
316                 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) \r
317                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
318         }\r
319         \r
320         STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}\r
321         STDMETHOD_(ULONG, AddRef())                                     {return 1;}\r
322         STDMETHOD_(ULONG, Release())                            {return 1;}\r
323         \r
324         STDMETHOD(ScheduledPlaybackHasStopped())\r
325         {\r
326                 is_running_ = false;\r
327                 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";\r
328                 return S_OK;\r
329         }\r
330 \r
331         STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))\r
332         {\r
333                 if(!is_running_)\r
334                         return E_FAIL;\r
335                 \r
336                 try\r
337                 {\r
338                         if(result == bmdOutputFrameDisplayedLate)\r
339                                 graph_->add_tag("late-frame");\r
340                         else if(result == bmdOutputFrameDropped)\r
341                                 graph_->add_tag("dropped-frame");\r
342                         else if(result == bmdOutputFrameFlushed)\r
343                                 graph_->add_tag("flushed-frame");\r
344 \r
345                         frame_container_.erase(std::find_if(frame_container_.begin(), frame_container_.end(), [&](const std::shared_ptr<IDeckLinkVideoFrame> frame)\r
346                         {\r
347                                 return frame.get() == completed_frame;\r
348                         }));\r
349 \r
350                         std::shared_ptr<const core::read_frame> frame;  \r
351                         video_frame_buffer_.pop(frame);         \r
352                         schedule_next_video(safe_ptr<const core::read_frame>(frame));                   \r
353                 }\r
354                 catch(...)\r
355                 {\r
356                         exception_ = std::current_exception();\r
357                         return E_FAIL;\r
358                 }\r
359 \r
360                 return S_OK;\r
361         }\r
362                 \r
363         STDMETHOD(RenderAudioSamples(BOOL preroll))\r
364         {\r
365                 if(!is_running_)\r
366                         return E_FAIL;\r
367                 \r
368                 try\r
369                 {\r
370                         std::shared_ptr<const core::read_frame> frame;\r
371                         audio_frame_buffer_.pop(frame);\r
372                         schedule_next_audio(safe_ptr<const core::read_frame>(frame));           \r
373 \r
374                         if(preroll)\r
375                                 start_playback();\r
376                 }\r
377                 catch(...)\r
378                 {\r
379                         exception_ = std::current_exception();\r
380                         return E_FAIL;\r
381                 }\r
382 \r
383                 return S_OK;\r
384         }\r
385 \r
386         void schedule_next_audio(const safe_ptr<const core::read_frame>& frame)\r
387         {\r
388                 static std::vector<short> silence(48000, 0);\r
389                 \r
390                 const int sample_count = format_desc_.audio_samples_per_frame;\r
391                 const int sample_frame_count = sample_count/2;\r
392 \r
393                 const short* frame_audio_data = frame->audio_data().size() == sample_count ? frame->audio_data().begin() : silence.data();\r
394                 audio_container_.push_back(std::vector<short>(frame_audio_data, frame_audio_data+sample_count));\r
395 \r
396                 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))\r
397                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
398         }\r
399                         \r
400         void schedule_next_video(const safe_ptr<const core::read_frame>& frame)\r
401         {\r
402                 std::shared_ptr<IDeckLinkVideoFrame> deck_frame;\r
403                 if(config_.output == key_only)\r
404                         deck_frame = make_alpha_only_frame(output_, frame, format_desc_);\r
405                 else \r
406                         deck_frame = std::make_shared<decklink_frame_adapter>(frame, format_desc_);\r
407 \r
408                 frame_container_.push_back(deck_frame);\r
409                 if(FAILED(output_->ScheduleVideoFrame(frame_container_.back().get(), (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))\r
410                         CASPAR_LOG(error) << print() << L" Failed to schedule video.";\r
411 \r
412                 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);\r
413                 tick_timer_.restart();\r
414         }\r
415 \r
416         void send(const safe_ptr<const core::read_frame>& frame)\r
417         {\r
418                 if(exception_ != nullptr)\r
419                         std::rethrow_exception(exception_);\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>(config_.device_index) + L"]";\r
432         }\r
433 };\r
434 \r
435 struct decklink_consumer_proxy : public core::frame_consumer\r
436 {\r
437         const configuration config_;\r
438 \r
439         com_context<decklink_consumer> context_;\r
440 public:\r
441 \r
442         decklink_consumer_proxy(const configuration& config)\r
443                 : config_(config)\r
444                 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]"){}\r
445         \r
446         virtual void initialize(const core::video_format_desc& format_desc)\r
447         {\r
448                 context_.reset([&]{return new decklink_consumer(config_, format_desc);});\r
449         }\r
450         \r
451         virtual void send(const safe_ptr<const core::read_frame>& frame)\r
452         {\r
453                 context_->send(frame);\r
454         }\r
455         \r
456         virtual std::wstring print() const\r
457         {\r
458                 return context_->print();\r
459         }\r
460 };      \r
461 \r
462 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params) \r
463 {\r
464         if(params.size() < 1 || params[0] != L"DECKLINK")\r
465                 return core::frame_consumer::empty();\r
466         \r
467         configuration config;\r
468                 \r
469         if(params.size() > 1)\r
470                 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);\r
471                 \r
472         if(std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end())\r
473                 config.keyer = internal_key;\r
474         else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end())\r
475                 config.keyer = external_key;\r
476         \r
477         if(std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end())\r
478                 config.latency = low_latency;\r
479         else if(std::find(params.begin(), params.end(), L"NORMAL_LATENCY") != params.end())\r
480                 config.latency = normal_latency;\r
481                 \r
482         config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
483 \r
484         return make_safe<decklink_consumer_proxy>(config);\r
485 }\r
486 \r
487 safe_ptr<core::frame_consumer> create_decklink_consumer(const boost::property_tree::ptree& ptree) \r
488 {\r
489         configuration config;\r
490 \r
491         auto key_str = ptree.get("key", "default");\r
492         if(key_str == "internal")\r
493                 config.keyer = internal_key;\r
494         else if(key_str == "external")\r
495                 config.keyer = external_key;\r
496 \r
497         auto latency_str = ptree.get("latency", "default");\r
498         if(latency_str == "normal")\r
499                 config.latency = normal_latency;\r
500         else if(latency_str == "low")\r
501                 config.latency = low_latency;\r
502 \r
503         auto output_str = ptree.get("output", "fill_and_key");\r
504         if(output_str == "key_only")\r
505                 config.output = key_only;\r
506 \r
507         config.device_index = ptree.get("device", 0);\r
508         config.embedded_audio  = ptree.get("embedded-audio", false);\r
509 \r
510         return make_safe<decklink_consumer_proxy>(config);\r
511 }\r
512 \r
513 }\r
514 \r
515 /*\r
516 ##############################################################################\r
517 Pre-rolling\r
518 \r
519 Mail: 2011-05-09\r
520 \r
521 Yoshan\r
522 BMD Developer Support\r
523 developer@blackmagic-design.com\r
524 \r
525 -----------------------------------------------------------------------------\r
526 \r
527 Thanks for your inquiry. The minimum number of frames that you can preroll \r
528 for scheduled playback is three frames for video and four frames for audio. \r
529 As you mentioned if you preroll less frames then playback will not start or\r
530 playback will be very sporadic. From our experience with Media Express, we \r
531 recommended that at least seven frames are prerolled for smooth playback. \r
532 \r
533 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:\r
534 There can be around 3 frames worth of latency on scheduled output.\r
535 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is\r
536 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() \r
537 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will \r
538 guarantee that the provided frame will be output as soon the previous \r
539 frame output has been completed.\r
540 ################################################################################\r
541 */\r
542 \r
543 /*\r
544 ##############################################################################\r
545 Async DMA Transfer without redundant copying\r
546 \r
547 Mail: 2011-05-10\r
548 \r
549 Yoshan\r
550 BMD Developer Support\r
551 developer@blackmagic-design.com\r
552 \r
553 -----------------------------------------------------------------------------\r
554 \r
555 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame \r
556 and providing a pointer to your video buffer when GetBytes() is called. \r
557 This may help to keep copying to a minimum. Please ensure that the pixel \r
558 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will \r
559 have to colourspace convert which may result in additional copying.\r
560 ################################################################################\r
561 */