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>> 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 if (core::needs_rearranging(
\r
424 view, config_.audio_layout, config_.num_out_channels()))
\r
426 std::vector<int32_t> resulting_audio_data;
\r
427 resulting_audio_data.resize(
\r
428 sample_frame_count * config_.num_out_channels());
\r
430 auto dest_view = core::make_multichannel_view<int32_t>(
\r
431 resulting_audio_data.begin(),
\r
432 resulting_audio_data.end(),
\r
433 config_.audio_layout,
\r
434 config_.num_out_channels());
\r
436 core::rearrange_or_rearrange_and_mix(
\r
437 view, dest_view, core::default_mix_config_repository());
\r
439 if (config_.audio_layout.num_channels == 1) // mono
\r
440 boost::copy( // duplicate L to R
\r
441 dest_view.channel(0),
\r
442 dest_view.channel(1).begin());
\r
444 audio_container_.push_back(std::move(resulting_audio_data));
\r
448 audio_container_.push_back(
\r
449 std::vector<int32_t>(view.raw_begin(), view.raw_end()));
\r
452 if(FAILED(output_->ScheduleAudioSamples(
\r
453 audio_container_.back().data(),
\r
454 sample_frame_count,
\r
456 format_desc_.audio_sample_rate,
\r
458 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
460 audio_scheduled_ += sample_frame_count;
\r
463 void schedule_next_video(const safe_ptr<core::read_frame>& frame)
\r
467 CComPtr<IDeckLinkVideoFrame> key_frame(new decklink_frame(frame, format_desc_, true));
\r
468 if(FAILED(key_context_->output_->ScheduleVideoFrame(key_frame, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
\r
469 CASPAR_LOG(error) << print() << L" Failed to schedule key video.";
\r
472 CComPtr<IDeckLinkVideoFrame> fill_frame(new decklink_frame(frame, format_desc_, config_.key_only));
\r
473 if(FAILED(output_->ScheduleVideoFrame(fill_frame, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
\r
474 CASPAR_LOG(error) << print() << L" Failed to schedule fill video.";
\r
476 video_scheduled_ += format_desc_.duration;
\r
478 graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
479 tick_timer_.restart();
\r
481 reference_signal_detector_.detect_change([this]() { return print(); });
\r
484 boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame)
\r
487 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
488 if(exception_ != nullptr)
\r
489 std::rethrow_exception(exception_);
\r
493 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
495 bool audio_ready = !config_.embedded_audio;
\r
496 bool video_ready = false;
\r
498 auto enqueue_task = [audio_ready, video_ready, frame, this]() mutable -> boost::optional<bool>
\r
501 audio_ready = audio_frame_buffer_.try_push(frame);
\r
504 video_ready = video_frame_buffer_.try_push(frame);
\r
506 if (audio_ready && video_ready)
\r
509 return boost::optional<bool>();
\r
512 if (enqueue_task())
\r
513 return wrap_as_future(true);
\r
515 send_completion_.set_task(enqueue_task);
\r
517 return send_completion_.get_future();
\r
520 std::wstring print() const
\r
522 if (config_.keyer == configuration::external_separate_device_keyer)
\r
523 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +
\r
524 boost::lexical_cast<std::wstring>(config_.device_index) +
\r
526 boost::lexical_cast<std::wstring>(config_.key_device_index()) +
\r
528 format_desc_.name + L"]";
\r
530 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +
\r
531 boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
535 struct decklink_consumer_proxy : public core::frame_consumer
\r
537 const configuration config_;
\r
538 com_context<decklink_consumer> context_;
\r
539 std::vector<size_t> audio_cadence_;
\r
540 core::video_format_desc format_desc_;
\r
543 decklink_consumer_proxy(const configuration& config)
\r
545 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
\r
549 ~decklink_consumer_proxy()
\r
553 auto str = print();
\r
555 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";
\r
561 virtual void initialize(const core::video_format_desc& format_desc, int channel_index) override
\r
563 context_.reset([&]{return new decklink_consumer(config_, format_desc, channel_index);});
\r
564 audio_cadence_ = format_desc.audio_cadence;
\r
565 format_desc_ = format_desc;
\r
567 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
570 virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override
\r
572 CASPAR_VERIFY(audio_cadence_.front() * frame->num_channels() == static_cast<size_t>(frame->audio_data().size()));
\r
573 boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);
\r
575 return context_->send(frame);
\r
578 virtual std::wstring print() const override
\r
580 return context_ ? context_->print() : L"[decklink_consumer]";
\r
583 virtual boost::property_tree::wptree info() const override
\r
585 boost::property_tree::wptree info;
\r
586 info.add(L"type", L"decklink-consumer");
\r
587 info.add(L"key-only", config_.key_only);
\r
588 info.add(L"device", config_.device_index);
\r
590 if (config_.keyer == configuration::external_separate_device_keyer)
\r
592 info.add(L"key-device", config_.key_device_index());
\r
595 info.add(L"low-latency", config_.latency == configuration::low_latency);
\r
596 info.add(L"embedded-audio", config_.embedded_audio);
\r
597 info.add(L"presentation-frame-age", presentation_frame_age_millis());
\r
598 //info.add(L"internal-key", config_.internal_key);
\r
602 virtual size_t buffer_depth() const override
\r
604 return config_.buffer_depth();
\r
607 virtual int index() const override
\r
609 return 300 + config_.device_index;
\r
612 virtual int64_t presentation_frame_age_millis() const
\r
614 return context_ ? context_->current_presentation_delay_ : 0;
\r
618 safe_ptr<core::frame_consumer> create_consumer(const core::parameters& params)
\r
620 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
621 return core::frame_consumer::empty();
\r
623 configuration config;
\r
625 if(params.size() > 1)
\r
626 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
628 if(std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end())
\r
629 config.keyer = configuration::internal_keyer;
\r
630 else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end())
\r
631 config.keyer = configuration::external_keyer;
\r
632 else if(std::find(params.begin(), params.end(), L"EXTERNAL_SEPARATE_DEVICE_KEY") != params.end())
\r
633 config.keyer = configuration::external_separate_device_keyer;
\r
635 config.keyer = configuration::default_keyer;
\r
637 if(std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end())
\r
638 config.latency = configuration::low_latency;
\r
640 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
641 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
642 config.audio_layout = core::default_channel_layout_repository().get_by_name(
\r
643 params.get(L"CHANNEL_LAYOUT", L"STEREO"));
\r
645 return make_safe<decklink_consumer_proxy>(config);
\r
648 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree)
\r
650 configuration config;
\r
652 auto keyer = ptree.get(L"keyer", L"external");
\r
653 if(keyer == L"external")
\r
654 config.keyer = configuration::external_keyer;
\r
655 else if(keyer == L"internal")
\r
656 config.keyer = configuration::internal_keyer;
\r
657 else if(keyer == L"external_separate_device")
\r
658 config.keyer = configuration::external_separate_device_keyer;
\r
660 auto latency = ptree.get(L"latency", L"normal");
\r
661 if(latency == L"low")
\r
662 config.latency = configuration::low_latency;
\r
663 else if(latency == L"normal")
\r
664 config.latency = configuration::normal_latency;
\r
666 config.key_only = ptree.get(L"key-only", config.key_only);
\r
667 config.device_index = ptree.get(L"device", config.device_index);
\r
668 config.key_device_idx = ptree.get(L"key-device", config.key_device_idx);
\r
669 config.embedded_audio = ptree.get(L"embedded-audio", config.embedded_audio);
\r
670 config.base_buffer_depth = ptree.get(L"buffer-depth", config.base_buffer_depth);
\r
671 config.custom_allocator = ptree.get(L"custom-allocator", config.custom_allocator);
\r
672 config.audio_layout =
\r
673 core::default_channel_layout_repository().get_by_name(
\r
674 boost::to_upper_copy(ptree.get(L"channel-layout", L"STEREO")));
\r
676 return make_safe<decklink_consumer_proxy>(config);
\r
682 ##############################################################################
\r
688 BMD Developer Support
\r
689 developer@blackmagic-design.com
\r
691 -----------------------------------------------------------------------------
\r
693 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
694 for scheduled playback is three frames for video and four frames for audio.
\r
695 As you mentioned if you preroll less frames then playback will not start or
\r
696 playback will be very sporadic. From our experience with Media Express, we
\r
697 recommended that at least seven frames are prerolled for smooth playback.
\r
699 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
700 There can be around 3 frames worth of latency on scheduled output.
\r
701 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
702 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
703 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
704 guarantee that the provided frame will be output as soon the previous
\r
705 frame output has been completed.
\r
706 ################################################################################
\r
710 ##############################################################################
\r
711 Async DMA Transfer without redundant copying
\r
716 BMD Developer Support
\r
717 developer@blackmagic-design.com
\r
719 -----------------------------------------------------------------------------
\r
721 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
722 and providing a pointer to your video buffer when GetBytes() is called.
\r
723 This may help to keep copying to a minimum. Please ensure that the pixel
\r
724 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
725 have to colourspace convert which may result in additional copying.
\r
726 ################################################################################
\r