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