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