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