2 * Copyright 2013 Sveriges Television AB http://casparcg.com/
\r
4 * This file is part of CasparCG (www.casparcg.com).
\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
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
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
19 * Author: Robert Nagy, ronag89@gmail.com
\r
22 #include "../StdAfx.h"
\r
24 #include "decklink_consumer.h"
\r
26 #include "../util/util.h"
\r
27 #include "../util/decklink_allocator.h"
\r
29 #include "../interop/DeckLinkAPI_h.h"
\r
31 #include <core/mixer/read_frame.h>
\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
40 #include <core/parameters/parameters.h>
\r
41 #include <core/consumer/frame_consumer.h>
\r
42 #include <core/mixer/audio/audio_util.h>
\r
44 #include <tbb/concurrent_queue.h>
\r
45 #include <tbb/cache_aligned_allocator.h>
\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
52 namespace caspar { namespace decklink {
\r
54 struct key_video_context
\r
55 : public IDeckLinkVideoOutputCallback, boost::noncopyable
\r
57 CComPtr<IDeckLink> decklink_;
\r
58 CComQIPtr<IDeckLinkOutput> output_;
\r
59 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
60 CComQIPtr<IDeckLinkAttributes> attributes_;
\r
61 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
62 const std::unique_ptr<thread_safe_decklink_allocator>& allocator_;
\r
63 tbb::atomic<int64_t> current_presentation_delay_;
\r
64 tbb::atomic<int64_t> scheduled_frames_completed_;
\r
67 const configuration& config,
\r
68 const std::wstring& print,
\r
69 const std::unique_ptr<thread_safe_decklink_allocator>& allocator)
\r
70 : decklink_(get_device(config.key_device_index()))
\r
71 , output_(decklink_)
\r
73 , attributes_(decklink_)
\r
74 , configuration_(decklink_)
\r
75 , allocator_(allocator)
\r
77 current_presentation_delay_ = 0;
\r
78 scheduled_frames_completed_ = 0;
\r
80 set_latency(configuration_, config.latency, print);
\r
81 set_keyer(attributes_, keyer_, config.keyer, print);
\r
83 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
84 BOOST_THROW_EXCEPTION(caspar_exception()
\r
85 << msg_info(narrow(print) + " Failed to set key playback completion callback.")
\r
86 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
89 template<typename Print>
\r
90 void enable_video(BMDDisplayMode display_mode, const Print& print)
\r
94 if (FAILED(output_->SetVideoOutputFrameMemoryAllocator(allocator_.get())))
\r
95 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set key custom memory allocator."));
\r
98 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
99 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable key video output."));
\r
101 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
102 BOOST_THROW_EXCEPTION(caspar_exception()
\r
103 << msg_info(narrow(print()) + " Failed to set key playback completion callback.")
\r
104 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
107 virtual ~key_video_context()
\r
111 output_->StopScheduledPlayback(0, nullptr, 0);
\r
112 output_->DisableVideoOutput();
\r
116 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
117 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
118 STDMETHOD_(ULONG, Release()) {return 1;}
\r
120 STDMETHOD(ScheduledPlaybackHasStopped())
\r
125 STDMETHOD(ScheduledFrameCompleted(
\r
126 IDeckLinkVideoFrame* completed_frame,
\r
127 BMDOutputFrameCompletionResult result))
\r
129 auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);
\r
130 current_presentation_delay_ = dframe->get_age_millis();
\r
131 ++scheduled_frames_completed_;
\r
133 // Let the fill callback keep the pace, so no scheduling here.
\r
139 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
141 const int channel_index_;
\r
142 const configuration config_;
\r
144 std::unique_ptr<thread_safe_decklink_allocator> allocator_;
\r
145 CComPtr<IDeckLink> decklink_;
\r
146 CComQIPtr<IDeckLinkOutput> output_;
\r
147 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
148 CComQIPtr<IDeckLinkAttributes> attributes_;
\r
149 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
151 tbb::spin_mutex exception_mutex_;
\r
152 std::exception_ptr exception_;
\r
154 tbb::atomic<bool> is_running_;
\r
156 const std::wstring model_name_;
\r
157 const core::video_format_desc format_desc_;
\r
158 const size_t buffer_size_;
\r
160 long long video_scheduled_;
\r
161 long long audio_scheduled_;
\r
163 size_t preroll_count_;
\r
165 boost::circular_buffer<std::vector<int32_t, tbb::cache_aligned_allocator<int32_t>>> audio_container_;
\r
167 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;
\r
168 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;
\r
170 safe_ptr<diagnostics::graph> graph_;
\r
171 boost::timer tick_timer_;
\r
172 retry_task<bool> send_completion_;
\r
173 reference_signal_detector reference_signal_detector_;
\r
175 tbb::atomic<int64_t> current_presentation_delay_;
\r
176 tbb::atomic<int64_t> scheduled_frames_completed_;
\r
177 std::unique_ptr<key_video_context> key_context_;
\r
180 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index)
\r
181 : channel_index_(channel_index)
\r
183 , decklink_(get_device(config.device_index))
\r
184 , output_(decklink_)
\r
185 , keyer_(decklink_)
\r
186 , attributes_(decklink_)
\r
187 , configuration_(decklink_)
\r
188 , model_name_(get_model_name(decklink_))
\r
189 , format_desc_(format_desc)
\r
190 , buffer_size_(config.buffer_depth()) // Minimum buffer-size 3.
\r
191 , video_scheduled_(0)
\r
192 , audio_scheduled_(0)
\r
193 , preroll_count_(0)
\r
194 , audio_container_(buffer_size_+1)
\r
195 , reference_signal_detector_(output_)
\r
197 is_running_ = true;
\r
198 current_presentation_delay_ = 0;
\r
199 scheduled_frames_completed_ = 0;
\r
201 video_frame_buffer_.set_capacity(1);
\r
203 if (format_desc.fps > 50.0)
\r
204 // Blackmagic calls RenderAudioSamples() 50 times per second
\r
205 // regardless of video mode so we sometimes need to give them
\r
206 // samples from 2 frames in order to keep up
\r
207 audio_frame_buffer_.set_capacity(2);
\r
209 audio_frame_buffer_.set_capacity(1);
\r
211 if (config.keyer == configuration::external_separate_device_keyer)
\r
212 key_context_.reset(new key_video_context(config, print(), allocator_));
\r
214 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
\r
215 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
216 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
217 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
\r
218 graph_->set_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));
\r
219 graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));
\r
223 graph_->set_color("key-offset", diagnostics::color(1.0f, 0.0f, 0.0f));
\r
226 graph_->set_text(print());
\r
227 diagnostics::register_graph(graph_);
\r
229 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
231 if(config.embedded_audio)
\r
234 set_latency(configuration_, config.latency, print());
\r
235 set_keyer(attributes_, keyer_, config.keyer, print());
\r
237 if(config.embedded_audio)
\r
238 output_->BeginAudioPreroll();
\r
240 for(size_t n = 0; n < buffer_size_; ++n)
\r
241 schedule_next_video(make_safe<core::read_frame>());
\r
243 if(!config.embedded_audio)
\r
247 ~decklink_consumer()
\r
249 is_running_ = false;
\r
250 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
251 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
253 if(output_ != nullptr)
\r
255 output_->StopScheduledPlayback(0, nullptr, 0);
\r
256 if(config_.embedded_audio)
\r
257 output_->DisableAudioOutput();
\r
258 output_->DisableVideoOutput();
\r
262 void enable_audio()
\r
264 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, config_.num_out_channels(), bmdAudioOutputStreamTimestamped)))
\r
265 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
267 if(FAILED(output_->SetAudioCallback(this)))
\r
268 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
270 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
273 void enable_video(BMDDisplayMode display_mode)
\r
275 if (config_.custom_allocator)
\r
277 allocator_.reset(new thread_safe_decklink_allocator(print()));
\r
279 if (FAILED(output_->SetVideoOutputFrameMemoryAllocator(allocator_.get())))
\r
280 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set fill custom memory allocator."));
\r
282 CASPAR_LOG(info) << print() << L" Using custom allocator.";
\r
285 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
286 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable fill video output."));
\r
288 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
289 BOOST_THROW_EXCEPTION(caspar_exception()
\r
290 << msg_info(narrow(print()) + " Failed to set fill playback completion callback.")
\r
291 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
294 key_context_->enable_video(display_mode, [this]() { return print(); });
\r
297 void start_playback()
\r
299 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
300 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule fill playback."));
\r
302 if(key_context_ && FAILED(key_context_->output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
303 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule key playback."));
\r
306 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
307 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
308 STDMETHOD_(ULONG, Release()) {return 1;}
\r
310 STDMETHOD(ScheduledPlaybackHasStopped())
\r
312 is_running_ = false;
\r
313 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
317 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
319 win32_exception::ensure_handler_installed_for_thread("decklink-ScheduledFrameCompleted");
\r
325 auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);
\r
326 current_presentation_delay_ = dframe->get_age_millis();
\r
327 ++scheduled_frames_completed_;
\r
332 static_cast<double>(
\r
333 scheduled_frames_completed_
\r
334 - key_context_->scheduled_frames_completed_)
\r
337 if(result == bmdOutputFrameDisplayedLate)
\r
339 graph_->set_tag("late-frame");
\r
340 video_scheduled_ += format_desc_.duration;
\r
341 audio_scheduled_ += dframe->audio_data().size()/config_.num_out_channels();
\r
342 //++video_scheduled_;
\r
343 //audio_scheduled_ += format_desc_.audio_cadence[0];
\r
344 //++audio_scheduled_;
\r
346 else if(result == bmdOutputFrameDropped)
\r
347 graph_->set_tag("dropped-frame");
\r
348 else if(result == bmdOutputFrameFlushed)
\r
349 graph_->set_tag("flushed-frame");
\r
351 std::shared_ptr<core::read_frame> frame;
\r
352 video_frame_buffer_.pop(frame);
\r
353 send_completion_.try_completion();
\r
354 schedule_next_video(make_safe_ptr(frame));
\r
356 unsigned long buffered;
\r
357 output_->GetBufferedVideoFrameCount(&buffered);
\r
358 graph_->set_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);
\r
362 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
363 exception_ = std::current_exception();
\r
370 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
372 win32_exception::ensure_handler_installed_for_thread("decklink-RenderAudioSamples");
\r
381 if(++preroll_count_ >= buffer_size_)
\r
383 output_->EndAudioPreroll();
\r
388 core::audio_buffer silent_audio(format_desc_.audio_cadence[preroll_count_ % format_desc_.audio_cadence.size()] * config_.num_out_channels(), 0);
\r
389 auto view = core::make_multichannel_view<int32_t>(silent_audio.begin(), silent_audio.end(), config_.audio_layout, config_.num_out_channels());
\r
390 schedule_next_audio(view);
\r
395 std::shared_ptr<core::read_frame> frame;
\r
397 while (audio_frame_buffer_.try_pop(frame))
\r
399 send_completion_.try_completion();
\r
400 schedule_next_audio(frame->multichannel_view());
\r
404 unsigned long buffered;
\r
405 output_->GetBufferedAudioSampleFrameCount(&buffered);
\r
406 graph_->set_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_cadence[0] * config_.num_out_channels() * 2));
\r
410 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
411 exception_ = std::current_exception();
\r
418 template<typename View>
\r
419 void schedule_next_audio(const View& view)
\r
421 const int sample_frame_count = view.num_samples();
\r
423 audio_container_.push_back(core::get_rearranged_and_mixed(
\r
425 config_.audio_layout,
\r
426 config_.num_out_channels()));
\r
428 if(FAILED(output_->ScheduleAudioSamples(
\r
429 audio_container_.back().data(),
\r
430 sample_frame_count,
\r
432 format_desc_.audio_sample_rate,
\r
434 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
436 audio_scheduled_ += sample_frame_count;
\r
439 void schedule_next_video(const safe_ptr<core::read_frame>& frame)
\r
443 CComPtr<IDeckLinkVideoFrame> key_frame(new decklink_frame(frame, format_desc_, true));
\r
444 if(FAILED(key_context_->output_->ScheduleVideoFrame(key_frame, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
\r
445 CASPAR_LOG(error) << print() << L" Failed to schedule key video.";
\r
448 CComPtr<IDeckLinkVideoFrame> fill_frame(new decklink_frame(frame, format_desc_, config_.key_only));
\r
449 if(FAILED(output_->ScheduleVideoFrame(fill_frame, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
\r
450 CASPAR_LOG(error) << print() << L" Failed to schedule fill video.";
\r
452 video_scheduled_ += format_desc_.duration;
\r
454 graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
455 tick_timer_.restart();
\r
457 reference_signal_detector_.detect_change([this]() { return print(); });
\r
460 boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame)
\r
463 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
464 if(exception_ != nullptr)
\r
465 std::rethrow_exception(exception_);
\r
469 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
471 bool audio_ready = !config_.embedded_audio;
\r
472 bool video_ready = false;
\r
474 auto enqueue_task = [audio_ready, video_ready, frame, this]() mutable -> boost::optional<bool>
\r
477 audio_ready = audio_frame_buffer_.try_push(frame);
\r
480 video_ready = video_frame_buffer_.try_push(frame);
\r
482 if (audio_ready && video_ready)
\r
485 return boost::optional<bool>();
\r
488 if (enqueue_task())
\r
489 return wrap_as_future(true);
\r
491 send_completion_.set_task(enqueue_task);
\r
493 return send_completion_.get_future();
\r
496 std::wstring print() const
\r
498 if (config_.keyer == configuration::external_separate_device_keyer)
\r
499 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +
\r
500 boost::lexical_cast<std::wstring>(config_.device_index) +
\r
502 boost::lexical_cast<std::wstring>(config_.key_device_index()) +
\r
504 format_desc_.name + L"]";
\r
506 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +
\r
507 boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
511 struct decklink_consumer_proxy : public core::frame_consumer
\r
513 const configuration config_;
\r
514 com_context<decklink_consumer> context_;
\r
515 std::vector<size_t> audio_cadence_;
\r
516 core::video_format_desc format_desc_;
\r
519 decklink_consumer_proxy(const configuration& config)
\r
521 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
\r
525 ~decklink_consumer_proxy()
\r
529 auto str = print();
\r
531 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";
\r
537 virtual void initialize(
\r
538 const core::video_format_desc& format_desc,
\r
539 const core::channel_layout& audio_channel_layout,
\r
540 int channel_index) override
\r
542 context_.reset([&]{return new decklink_consumer(config_, format_desc, channel_index);});
\r
543 audio_cadence_ = format_desc.audio_cadence;
\r
544 format_desc_ = format_desc;
\r
546 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
549 virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override
\r
551 CASPAR_VERIFY(audio_cadence_.front() * frame->num_channels() == static_cast<size_t>(frame->audio_data().size()));
\r
552 boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);
\r
554 return context_->send(frame);
\r
557 virtual std::wstring print() const override
\r
559 return context_ ? context_->print() : L"[decklink_consumer]";
\r
562 virtual boost::property_tree::wptree info() const override
\r
564 boost::property_tree::wptree info;
\r
565 info.add(L"type", L"decklink-consumer");
\r
566 info.add(L"key-only", config_.key_only);
\r
567 info.add(L"device", config_.device_index);
\r
569 if (config_.keyer == configuration::external_separate_device_keyer)
\r
571 info.add(L"key-device", config_.key_device_index());
\r
574 info.add(L"low-latency", config_.latency == configuration::low_latency);
\r
575 info.add(L"embedded-audio", config_.embedded_audio);
\r
576 info.add(L"presentation-frame-age", presentation_frame_age_millis());
\r
577 //info.add(L"internal-key", config_.internal_key);
\r
581 virtual size_t buffer_depth() const override
\r
583 return config_.buffer_depth();
\r
586 virtual int index() const override
\r
588 return 300 + config_.device_index;
\r
591 virtual int64_t presentation_frame_age_millis() const
\r
593 return context_ ? context_->current_presentation_delay_ : 0;
\r
597 safe_ptr<core::frame_consumer> create_consumer(const core::parameters& params)
\r
599 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
600 return core::frame_consumer::empty();
\r
602 configuration config;
\r
604 if(params.size() > 1)
\r
605 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
607 if(std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end())
\r
608 config.keyer = configuration::internal_keyer;
\r
609 else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end())
\r
610 config.keyer = configuration::external_keyer;
\r
611 else if(std::find(params.begin(), params.end(), L"EXTERNAL_SEPARATE_DEVICE_KEY") != params.end())
\r
612 config.keyer = configuration::external_separate_device_keyer;
\r
614 config.keyer = configuration::default_keyer;
\r
616 if(std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end())
\r
617 config.latency = configuration::low_latency;
\r
619 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
620 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
621 config.audio_layout = core::default_channel_layout_repository().get_by_name(
\r
622 params.get(L"CHANNEL_LAYOUT", L"STEREO"));
\r
624 return make_safe<decklink_consumer_proxy>(config);
\r
627 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree)
\r
629 configuration config;
\r
631 auto keyer = ptree.get(L"keyer", L"external");
\r
632 if(keyer == L"external")
\r
633 config.keyer = configuration::external_keyer;
\r
634 else if(keyer == L"internal")
\r
635 config.keyer = configuration::internal_keyer;
\r
636 else if(keyer == L"external_separate_device")
\r
637 config.keyer = configuration::external_separate_device_keyer;
\r
639 auto latency = ptree.get(L"latency", L"normal");
\r
640 if(latency == L"low")
\r
641 config.latency = configuration::low_latency;
\r
642 else if(latency == L"normal")
\r
643 config.latency = configuration::normal_latency;
\r
645 config.key_only = ptree.get(L"key-only", config.key_only);
\r
646 config.device_index = ptree.get(L"device", config.device_index);
\r
647 config.key_device_idx = ptree.get(L"key-device", config.key_device_idx);
\r
648 config.embedded_audio = ptree.get(L"embedded-audio", config.embedded_audio);
\r
649 config.base_buffer_depth = ptree.get(L"buffer-depth", config.base_buffer_depth);
\r
650 config.custom_allocator = ptree.get(L"custom-allocator", config.custom_allocator);
\r
651 config.audio_layout =
\r
652 core::default_channel_layout_repository().get_by_name(
\r
653 boost::to_upper_copy(ptree.get(L"channel-layout", L"STEREO")));
\r
655 return make_safe<decklink_consumer_proxy>(config);
\r
661 ##############################################################################
\r
667 BMD Developer Support
\r
668 developer@blackmagic-design.com
\r
670 -----------------------------------------------------------------------------
\r
672 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
673 for scheduled playback is three frames for video and four frames for audio.
\r
674 As you mentioned if you preroll less frames then playback will not start or
\r
675 playback will be very sporadic. From our experience with Media Express, we
\r
676 recommended that at least seven frames are prerolled for smooth playback.
\r
678 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
679 There can be around 3 frames worth of latency on scheduled output.
\r
680 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
681 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
682 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
683 guarantee that the provided frame will be output as soon the previous
\r
684 frame output has been completed.
\r
685 ################################################################################
\r
689 ##############################################################################
\r
690 Async DMA Transfer without redundant copying
\r
695 BMD Developer Support
\r
696 developer@blackmagic-design.com
\r
698 -----------------------------------------------------------------------------
\r
700 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
701 and providing a pointer to your video buffer when GetBytes() is called.
\r
702 This may help to keep copying to a minimum. Please ensure that the pixel
\r
703 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
704 have to colourspace convert which may result in additional copying.
\r
705 ################################################################################
\r