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