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