]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
2.0.0.2: decklink_consumer: New options, fill_and_key, key_only, fill_only.
[casparcg] / modules / decklink / consumer / decklink_consumer.cpp
1 /*\r
2 * copyright (c) 2010 Sveriges Television AB <info@casparcg.com>\r
3 *\r
4 *  This file is part of CasparCG.\r
5 *\r
6 *    CasparCG is free software: you can redistribute it and/or modify\r
7 *    it under the terms of the GNU General Public License as published by\r
8 *    the Free Software Foundation, either version 3 of the License, or\r
9 *    (at your option) any later version.\r
10 *\r
11 *    CasparCG is distributed in the hope that it will be useful,\r
12 *    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14 *    GNU General Public License for more details.\r
15 \r
16 *    You should have received a copy of the GNU General Public License\r
17 *    along with CasparCG.  If not, see <http://www.gnu.org/licenses/>.\r
18 *\r
19 */\r
20 \r
21 #include "../StdAfx.h"\r
22  \r
23 #include "decklink_consumer.h"\r
24 \r
25 #include "../util/util.h"\r
26 \r
27 #include "../interop/DeckLinkAPI_h.h"\r
28 \r
29 #include <core/consumer/frame/read_frame.h>\r
30 \r
31 #include <common/concurrency/com_context.h>\r
32 #include <common/diagnostics/graph.h>\r
33 #include <common/exception/exceptions.h>\r
34 #include <common/memory/memcpy.h>\r
35 #include <common/memory/memclr.h>\r
36 \r
37 #include <tbb/concurrent_queue.h>\r
38 \r
39 #include <boost/circular_buffer.hpp>\r
40 #include <boost/timer.hpp>\r
41 \r
42 namespace caspar { \r
43         \r
44 enum key\r
45 {\r
46         external_key,\r
47         internal_key,\r
48         default_key\r
49 };\r
50 \r
51 enum latency\r
52 {\r
53         low_latency,\r
54         normal_latency,\r
55         default_latency\r
56 };\r
57 \r
58 enum output_pixels\r
59 {\r
60         fill_and_key,\r
61         fill_only,\r
62         key_only\r
63 };\r
64 \r
65 struct configuration\r
66 {\r
67         size_t  device_index;\r
68         bool    embedded_audio;\r
69         key             keyer;\r
70         latency latency;\r
71         output_pixels  output;\r
72         \r
73         configuration()\r
74                 : device_index(1)\r
75                 , embedded_audio(false)\r
76                 , keyer(default_key)\r
77                 , latency(default_latency)\r
78                 , output(fill_and_key){}\r
79 };\r
80 \r
81 class decklink_frame_adapter : public IDeckLinkVideoFrame\r
82 {\r
83         const safe_ptr<const core::read_frame>  frame_;\r
84         const core::video_format_desc                   format_desc_;\r
85 public:\r
86         decklink_frame_adapter(const safe_ptr<const core::read_frame>& frame, const core::video_format_desc& format_desc)\r
87                 : frame_(frame)\r
88                 , format_desc_(format_desc){}\r
89         \r
90         STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}\r
91         STDMETHOD_(ULONG, AddRef())                                     {return 1;}\r
92         STDMETHOD_(ULONG, Release())                            {return 1;}\r
93 \r
94         STDMETHOD_(long, GetWidth())                            {return format_desc_.width;}        \r
95     STDMETHOD_(long, GetHeight())                               {return format_desc_.height;}        \r
96     STDMETHOD_(long, GetRowBytes())                             {return format_desc_.width*4;}        \r
97         STDMETHOD_(BMDPixelFormat, GetPixelFormat()){return bmdFormat8BitBGRA;}        \r
98     STDMETHOD_(BMDFrameFlags, GetFlags())               {return bmdFrameFlagDefault;}\r
99         \r
100     STDMETHOD(GetBytes(void** buffer))\r
101         {\r
102                 static std::vector<unsigned char> zeros(1920*1080*4, 0);\r
103                 *buffer = const_cast<unsigned char*>(frame_->image_data().begin());\r
104                 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)\r
105                         *buffer = zeros.data();\r
106                 return S_OK;\r
107         }\r
108         \r
109     STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}        \r
110     STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary))                 {return S_FALSE;}\r
111 };\r
112 \r
113 std::shared_ptr<IDeckLinkVideoFrame> make_alpha_only_frame(const CComQIPtr<IDeckLinkOutput>& decklink, const safe_ptr<const core::read_frame>& frame, const core::video_format_desc& format_desc)\r
114 {\r
115         IDeckLinkMutableVideoFrame* result;\r
116 \r
117         if(FAILED(decklink->CreateVideoFrame(format_desc.width, format_desc.height, format_desc.size/format_desc.height, bmdFormat8BitBGRA, bmdFrameFlagDefault, &result)))\r
118                 BOOST_THROW_EXCEPTION(caspar_exception());\r
119 \r
120         void* bytes = nullptr;\r
121         if(FAILED(result->GetBytes(&bytes)))\r
122                 BOOST_THROW_EXCEPTION(caspar_exception());\r
123                 \r
124         unsigned char* data = reinterpret_cast<unsigned char*>(bytes);\r
125 \r
126         if(static_cast<size_t>(frame->image_data().size()) == format_desc.size)\r
127         {\r
128                 tbb::parallel_for(tbb::blocked_range<int>(0, frame->image_data().size()/4), [&](const tbb::blocked_range<int>& r)\r
129                 {\r
130                         for(int n = r.begin(); n != r.end(); ++n)\r
131                         {\r
132                                 data[n*4+0] = frame->image_data()[n*4+3];\r
133                                 data[n*4+1] = frame->image_data()[n*4+3];\r
134                                 data[n*4+2] = frame->image_data()[n*4+3];\r
135                                 data[n*4+3] = 255;\r
136                         }\r
137                 });\r
138         }\r
139         else\r
140                 memset(data, 0, format_desc.size);\r
141 \r
142         return std::shared_ptr<IDeckLinkVideoFrame>(result, [](IDeckLinkMutableVideoFrame* p) {p->Release();});\r
143 }\r
144 \r
145 std::shared_ptr<IDeckLinkVideoFrame> make_fill_only_frame(const CComQIPtr<IDeckLinkOutput>& decklink, const safe_ptr<const core::read_frame>& frame, const core::video_format_desc& format_desc)\r
146 {\r
147         IDeckLinkMutableVideoFrame* result;\r
148 \r
149         if(FAILED(decklink->CreateVideoFrame(format_desc.width, format_desc.height, format_desc.size/format_desc.height, bmdFormat8BitBGRA, bmdFrameFlagDefault, &result)))\r
150                 BOOST_THROW_EXCEPTION(caspar_exception());\r
151 \r
152         void* bytes = nullptr;\r
153         if(FAILED(result->GetBytes(&bytes)))\r
154                 BOOST_THROW_EXCEPTION(caspar_exception());\r
155                 \r
156         unsigned char* data = reinterpret_cast<unsigned char*>(bytes);\r
157 \r
158         if(static_cast<size_t>(frame->image_data().size()) == format_desc.size)\r
159         {\r
160                 tbb::parallel_for(tbb::blocked_range<int>(0, frame->image_data().size()/4), [&](const tbb::blocked_range<int>& r)\r
161                 {\r
162                         for(int n = r.begin(); n != r.end(); ++n)\r
163                         {\r
164                                 data[n*4+0] = frame->image_data()[n*4+0];\r
165                                 data[n*4+1] = frame->image_data()[n*4+1];\r
166                                 data[n*4+2] = frame->image_data()[n*4+2];\r
167                                 data[n*4+3] = 255;\r
168                         }\r
169                 });\r
170         }\r
171         else\r
172                 memset(data, 0, format_desc.size);\r
173 \r
174         return std::shared_ptr<IDeckLinkVideoFrame>(result, [](IDeckLinkMutableVideoFrame* p) {p->Release();});\r
175 }\r
176 \r
177 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
178 {               \r
179         const configuration config_;\r
180 \r
181         CComPtr<IDeckLink>                                      decklink_;\r
182         CComQIPtr<IDeckLinkOutput>                      output_;\r
183         CComQIPtr<IDeckLinkConfiguration>       configuration_;\r
184         CComQIPtr<IDeckLinkKeyer>                       keyer_;\r
185 \r
186         std::exception_ptr exception_;\r
187 \r
188         tbb::atomic<bool> is_running_;\r
189                 \r
190         const std::wstring model_name_;\r
191         const core::video_format_desc format_desc_;\r
192         const size_t buffer_size_;\r
193 \r
194         unsigned long frames_scheduled_;\r
195         unsigned long audio_scheduled_;\r
196                 \r
197         std::list<std::shared_ptr<IDeckLinkVideoFrame>> frame_container_; // Must be std::list in order to guarantee that pointers are always valid.\r
198         boost::circular_buffer<std::vector<short>> audio_container_;\r
199 \r
200         tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> video_frame_buffer_;\r
201         tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> audio_frame_buffer_;\r
202         \r
203         std::shared_ptr<diagnostics::graph> graph_;\r
204         boost::timer tick_timer_;\r
205 \r
206 public:\r
207         decklink_consumer(const configuration& config, const core::video_format_desc& format_desc) \r
208                 : config_(config)\r
209                 , decklink_(get_device(config.device_index))\r
210                 , output_(decklink_)\r
211                 , configuration_(decklink_)\r
212                 , keyer_(decklink_)\r
213                 , model_name_(get_model_name(decklink_))\r
214                 , format_desc_(format_desc)\r
215                 , buffer_size_(config.embedded_audio ? 5 : 4) // Minimum buffer-size (3 + 1 tolerance).\r
216                 , frames_scheduled_(0)\r
217                 , audio_scheduled_(0)\r
218                 , audio_container_(buffer_size_+1)\r
219         {\r
220                 is_running_ = true;\r
221                                 \r
222                 video_frame_buffer_.set_capacity(1);\r
223                 audio_frame_buffer_.set_capacity(1);\r
224 \r
225                 graph_ = diagnostics::create_graph(narrow(print()));\r
226                 graph_->add_guide("tick-time", 0.5);\r
227                 graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f));\r
228                 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));\r
229                 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));\r
230                 graph_->set_color("flushed-frame", diagnostics::color(0.3f, 0.3f, 0.6f));\r
231                 \r
232                 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));\r
233                                 \r
234                 if(config.embedded_audio)\r
235                         enable_audio();\r
236 \r
237                 set_latency(config.latency);                            \r
238                 set_keyer(config.keyer);\r
239                                                                 \r
240                 for(size_t n = 0; n < buffer_size_; ++n)\r
241                         schedule_next_video(core::read_frame::empty());\r
242                 \r
243                 if(config.embedded_audio)\r
244                         output_->BeginAudioPreroll();\r
245                 else\r
246                         start_playback();\r
247                 \r
248                 CASPAR_LOG(info) << print() << L" Buffer depth: " << buffer_size_;              \r
249                 CASPAR_LOG(info) << print() << L" Successfully initialized for " << format_desc_.name;  \r
250         }\r
251 \r
252         ~decklink_consumer()\r
253         {               \r
254                 is_running_ = false;\r
255                 video_frame_buffer_.try_push(core::read_frame::empty());\r
256                 audio_frame_buffer_.try_push(core::read_frame::empty());\r
257 \r
258                 if(output_ != nullptr) \r
259                 {\r
260                         output_->StopScheduledPlayback(0, nullptr, 0);\r
261                         if(config_.embedded_audio)\r
262                                 output_->DisableAudioOutput();\r
263                         output_->DisableVideoOutput();\r
264                 }\r
265         }\r
266                         \r
267         void set_latency(latency latency)\r
268         {               \r
269                 if(latency == normal_latency)\r
270                 {\r
271                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);\r
272                         CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";\r
273                 }\r
274                 else if(latency == low_latency)\r
275                 {                       \r
276                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);\r
277                         CASPAR_LOG(info) << print() << L" Enabled low-latency mode";\r
278                 }\r
279                 else\r
280                         CASPAR_LOG(info) << print() << L" Uses driver latency settings.";       \r
281         }\r
282 \r
283         void set_keyer(key keyer)\r
284         {\r
285                 if(keyer == internal_key) \r
286                 {\r
287                         if(FAILED(keyer_->Enable(FALSE)))                       \r
288                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   \r
289                         else if(FAILED(keyer_->SetLevel(255)))                  \r
290                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
291                         else\r
292                                 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             \r
293                 }\r
294                 else if(keyer == external_key)\r
295                 {\r
296                         if(FAILED(keyer_->Enable(TRUE)))                        \r
297                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   \r
298                         else if(FAILED(keyer_->SetLevel(255)))                  \r
299                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
300                         else\r
301                                 CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     \r
302                 }\r
303                 else\r
304                         CASPAR_LOG(info) << print() << L" Uses driver keyer settings."; \r
305         }\r
306         \r
307         void enable_audio()\r
308         {\r
309                 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))\r
310                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));\r
311                                 \r
312                 if(FAILED(output_->SetAudioCallback(this)))\r
313                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));\r
314 \r
315                 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";\r
316         }\r
317 \r
318         void enable_video(BMDDisplayMode display_mode)\r
319         {\r
320                 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) \r
321                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));\r
322                 \r
323                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
324                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to set playback completion callback."));\r
325         }\r
326 \r
327         void start_playback()\r
328         {\r
329                 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) \r
330                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
331         }\r
332         \r
333         STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}\r
334         STDMETHOD_(ULONG, AddRef())                                     {return 1;}\r
335         STDMETHOD_(ULONG, Release())                            {return 1;}\r
336         \r
337         STDMETHOD(ScheduledPlaybackHasStopped())\r
338         {\r
339                 is_running_ = false;\r
340                 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";\r
341                 return S_OK;\r
342         }\r
343 \r
344         STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))\r
345         {\r
346                 if(!is_running_)\r
347                         return E_FAIL;\r
348                 \r
349                 try\r
350                 {\r
351                         if(result == bmdOutputFrameDisplayedLate)\r
352                                 graph_->add_tag("late-frame");\r
353                         else if(result == bmdOutputFrameDropped)\r
354                                 graph_->add_tag("dropped-frame");\r
355                         else if(result == bmdOutputFrameFlushed)\r
356                                 graph_->add_tag("flushed-frame");\r
357 \r
358                         frame_container_.erase(std::find_if(frame_container_.begin(), frame_container_.end(), [&](const std::shared_ptr<IDeckLinkVideoFrame> frame)\r
359                         {\r
360                                 return frame.get() == completed_frame;\r
361                         }));\r
362 \r
363                         std::shared_ptr<const core::read_frame> frame;  \r
364                         video_frame_buffer_.pop(frame);         \r
365                         schedule_next_video(safe_ptr<const core::read_frame>(frame));                   \r
366                 }\r
367                 catch(...)\r
368                 {\r
369                         exception_ = std::current_exception();\r
370                         return E_FAIL;\r
371                 }\r
372 \r
373                 return S_OK;\r
374         }\r
375                 \r
376         STDMETHOD(RenderAudioSamples(BOOL preroll))\r
377         {\r
378                 if(!is_running_)\r
379                         return E_FAIL;\r
380                 \r
381                 try\r
382                 {\r
383                         std::shared_ptr<const core::read_frame> frame;\r
384                         audio_frame_buffer_.pop(frame);\r
385                         schedule_next_audio(safe_ptr<const core::read_frame>(frame));           \r
386 \r
387                         if(preroll)\r
388                                 start_playback();\r
389                 }\r
390                 catch(...)\r
391                 {\r
392                         exception_ = std::current_exception();\r
393                         return E_FAIL;\r
394                 }\r
395 \r
396                 return S_OK;\r
397         }\r
398 \r
399         void schedule_next_audio(const safe_ptr<const core::read_frame>& frame)\r
400         {\r
401                 static std::vector<short> silence(48000, 0);\r
402                 \r
403                 const int sample_count = format_desc_.audio_samples_per_frame;\r
404                 const int sample_frame_count = sample_count/2;\r
405 \r
406                 const short* frame_audio_data = frame->audio_data().size() == sample_count ? frame->audio_data().begin() : silence.data();\r
407                 audio_container_.push_back(std::vector<short>(frame_audio_data, frame_audio_data+sample_count));\r
408 \r
409                 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))\r
410                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
411         }\r
412                         \r
413         void schedule_next_video(const safe_ptr<const core::read_frame>& frame)\r
414         {\r
415                 std::shared_ptr<IDeckLinkVideoFrame> deck_frame;\r
416                 if(config_.output == key_only)\r
417                         deck_frame = make_alpha_only_frame(output_, frame, format_desc_);\r
418                 else if(config_.output == fill_only)\r
419                         deck_frame = make_fill_only_frame(output_, frame, format_desc_);\r
420                 else \r
421                         deck_frame = std::make_shared<decklink_frame_adapter>(frame, format_desc_);\r
422 \r
423                 frame_container_.push_back(deck_frame);\r
424                 if(FAILED(output_->ScheduleVideoFrame(frame_container_.back().get(), (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))\r
425                         CASPAR_LOG(error) << print() << L" Failed to schedule video.";\r
426 \r
427                 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);\r
428                 tick_timer_.restart();\r
429         }\r
430 \r
431         void send(const safe_ptr<const core::read_frame>& frame)\r
432         {\r
433                 if(exception_ != nullptr)\r
434                         std::rethrow_exception(exception_);\r
435 \r
436                 if(!is_running_)\r
437                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));\r
438                 \r
439                 if(config_.embedded_audio)\r
440                         audio_frame_buffer_.push(frame);        \r
441                 video_frame_buffer_.push(frame);        \r
442         }\r
443         \r
444         std::wstring print() const\r
445         {\r
446                 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + 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 \r
454         com_context<decklink_consumer> context_;\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         virtual void initialize(const core::video_format_desc& format_desc)\r
462         {\r
463                 context_.reset([&]{return new decklink_consumer(config_, format_desc);});\r
464         }\r
465         \r
466         virtual void send(const safe_ptr<const core::read_frame>& frame)\r
467         {\r
468                 context_->send(frame);\r
469         }\r
470         \r
471         virtual std::wstring print() const\r
472         {\r
473                 return context_->print();\r
474         }\r
475 };      \r
476 \r
477 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params) \r
478 {\r
479         if(params.size() < 1 || params[0] != L"DECKLINK")\r
480                 return core::frame_consumer::empty();\r
481         \r
482         configuration config;\r
483                 \r
484         if(params.size() > 1)\r
485                 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);\r
486                 \r
487         if(std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end())\r
488                 config.keyer = internal_key;\r
489         else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end())\r
490                 config.keyer = external_key;\r
491         \r
492         if(std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end())\r
493                 config.latency = low_latency;\r
494         else if(std::find(params.begin(), params.end(), L"NORMAL_LATENCY") != params.end())\r
495                 config.latency = normal_latency;\r
496                 \r
497         config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
498 \r
499         return make_safe<decklink_consumer_proxy>(config);\r
500 }\r
501 \r
502 safe_ptr<core::frame_consumer> create_decklink_consumer(const boost::property_tree::ptree& ptree) \r
503 {\r
504         configuration config;\r
505 \r
506         auto key_str = ptree.get("key", "default");\r
507         if(key_str == "internal")\r
508                 config.keyer = internal_key;\r
509         else if(key_str == "external")\r
510                 config.keyer = external_key;\r
511 \r
512         auto latency_str = ptree.get("latency", "default");\r
513         if(latency_str == "normal")\r
514                 config.latency = normal_latency;\r
515         else if(latency_str == "low")\r
516                 config.latency = low_latency;\r
517 \r
518         auto output_str = ptree.get("output", "fill_and_key");\r
519         if(output_str == "fill_only")\r
520                 config.output = fill_only;\r
521         else if(output_str == "key_only")\r
522                 config.output = key_only;\r
523 \r
524         config.device_index = ptree.get("device", 0);\r
525         config.embedded_audio  = ptree.get("embedded-audio", false);\r
526 \r
527         return make_safe<decklink_consumer_proxy>(config);\r
528 }\r
529 \r
530 }\r
531 \r
532 /*\r
533 ##############################################################################\r
534 Pre-rolling\r
535 \r
536 Mail: 2011-05-09\r
537 \r
538 Yoshan\r
539 BMD Developer Support\r
540 developer@blackmagic-design.com\r
541 \r
542 -----------------------------------------------------------------------------\r
543 \r
544 Thanks for your inquiry. The minimum number of frames that you can preroll \r
545 for scheduled playback is three frames for video and four frames for audio. \r
546 As you mentioned if you preroll less frames then playback will not start or\r
547 playback will be very sporadic. From our experience with Media Express, we \r
548 recommended that at least seven frames are prerolled for smooth playback. \r
549 \r
550 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:\r
551 There can be around 3 frames worth of latency on scheduled output.\r
552 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is\r
553 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() \r
554 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will \r
555 guarantee that the provided frame will be output as soon the previous \r
556 frame output has been completed.\r
557 ################################################################################\r
558 */\r
559 \r
560 /*\r
561 ##############################################################################\r
562 Async DMA Transfer without redundant copying\r
563 \r
564 Mail: 2011-05-10\r
565 \r
566 Yoshan\r
567 BMD Developer Support\r
568 developer@blackmagic-design.com\r
569 \r
570 -----------------------------------------------------------------------------\r
571 \r
572 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame \r
573 and providing a pointer to your video buffer when GetBytes() is called. \r
574 This may help to keep copying to a minimum. Please ensure that the pixel \r
575 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will \r
576 have to colourspace convert which may result in additional copying.\r
577 ################################################################################\r
578 */