2 * Copyright 2013 Sveriges Television AB http://casparcg.com/
4 * This file is part of CasparCG (www.casparcg.com).
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
19 * Author: Robert Nagy, ronag89@gmail.com
22 #include "../StdAfx.h"
24 #include "decklink_consumer.h"
26 #include "../util/util.h"
27 #include "../util/decklink_allocator.h"
29 #include "../interop/DeckLinkAPI_h.h"
33 #include <common/concurrency/com_context.h>
34 #include <common/concurrency/executor.h>
35 #include <common/diagnostics/graph.h>
36 #include <common/exception/exceptions.h>
37 #include <common/memory/memcpy.h>
38 #include <common/memory/memclr.h>
39 #include <common/utility/timer.h>
41 #include <core/parameters/parameters.h>
42 #include <core/consumer/frame_consumer.h>
43 #include <core/mixer/read_frame.h>
44 #include <core/mixer/audio/audio_util.h>
46 #include <tbb/cache_aligned_allocator.h>
48 #include <boost/timer.hpp>
49 #include <boost/property_tree/ptree.hpp>
50 #include <boost/algorithm/string.hpp>
51 #include <boost/circular_buffer.hpp>
53 namespace caspar { namespace decklink {
55 // second is the index in the array to consume the next time
56 typedef std::pair<std::vector<int32_t>, size_t> audio_buffer;
58 struct blocking_decklink_consumer : boost::noncopyable
60 const int channel_index_;
61 const configuration config_;
63 std::unique_ptr<thread_safe_decklink_allocator> allocator_;
64 CComPtr<IDeckLink> decklink_;
65 CComQIPtr<IDeckLinkOutput> output_;
66 CComQIPtr<IDeckLinkKeyer> keyer_;
67 CComQIPtr<IDeckLinkAttributes> attributes_;
68 CComQIPtr<IDeckLinkConfiguration> configuration_;
70 const std::wstring model_name_;
71 const core::video_format_desc format_desc_;
72 std::shared_ptr<core::read_frame> previous_frame_;
73 boost::circular_buffer<audio_buffer> audio_samples_;
74 size_t buffered_audio_samples_;
75 BMDTimeValue last_reference_clock_value_;
77 safe_ptr<diagnostics::graph> graph_;
78 boost::timer frame_timer_;
79 boost::timer tick_timer_;
80 boost::timer sync_timer_;
81 reference_signal_detector reference_signal_detector_;
83 tbb::atomic<int64_t> current_presentation_delay_;
87 blocking_decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index)
88 : channel_index_(channel_index)
90 , decklink_(get_device(config.device_index))
93 , attributes_(decklink_)
94 , configuration_(decklink_)
95 , model_name_(get_model_name(decklink_))
96 , format_desc_(format_desc)
97 , buffered_audio_samples_(0)
98 , last_reference_clock_value_(-1)
99 , reference_signal_detector_(output_)
100 , executor_(L"blocking_decklink_consumer")
102 audio_samples_.set_capacity(2);
103 current_presentation_delay_ = 0;
104 executor_.set_capacity(1);
106 graph_->set_color("sync-time", diagnostics::color(1.0f, 0.0f, 0.0f));
107 graph_->set_color("frame-time", diagnostics::color(0.5f, 1.0f, 0.2f));
108 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
109 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
111 if (config_.embedded_audio)
113 "buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));
115 graph_->set_text(print());
116 diagnostics::register_graph(graph_);
118 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
120 if(config.embedded_audio)
123 set_latency(configuration_, configuration::low_latency, print());
124 set_keyer(attributes_, keyer_, config.keyer, print());
127 ~blocking_decklink_consumer()
129 if(output_ != nullptr)
131 if(config_.embedded_audio)
132 output_->DisableAudioOutput();
133 output_->DisableVideoOutput();
139 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, config_.num_out_channels(), bmdAudioOutputStreamContinuous)))
140 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
142 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
145 void enable_video(BMDDisplayMode display_mode)
147 if (config_.custom_allocator)
149 allocator_.reset(new thread_safe_decklink_allocator(print()));
151 if (FAILED(output_->SetVideoOutputFrameMemoryAllocator(allocator_.get())))
152 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set custom memory allocator."));
154 CASPAR_LOG(info) << print() << L" Using custom allocator.";
157 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
158 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
161 void queue_audio_samples(const safe_ptr<core::read_frame>& frame)
163 auto view = frame->multichannel_view();
164 const int sample_frame_count = view.num_samples();
166 if (audio_samples_.full())
168 CASPAR_LOG(warning) << print() << L" Too much audio buffered. Discarding samples.";
169 audio_samples_.clear();
170 buffered_audio_samples_ = 0;
173 if (core::needs_rearranging(
174 view, config_.audio_layout, config_.num_out_channels()))
176 std::vector<int32_t> resulting_audio_data;
177 resulting_audio_data.resize(
178 sample_frame_count * config_.num_out_channels());
180 auto dest_view = core::make_multichannel_view<int32_t>(
181 resulting_audio_data.begin(),
182 resulting_audio_data.end(),
183 config_.audio_layout,
184 config_.num_out_channels());
186 core::rearrange_or_rearrange_and_mix(
187 view, dest_view, core::default_mix_config_repository());
189 if (config_.audio_layout.num_channels == 1) // mono
190 boost::copy( // duplicate L to R
191 dest_view.channel(0),
192 dest_view.channel(1).begin());
194 audio_samples_.push_back(
195 std::make_pair(std::move(resulting_audio_data), 0));
199 audio_samples_.push_back(std::make_pair(
200 std::vector<int32_t>(
201 frame->audio_data().begin(),
202 frame->audio_data().end()),
206 buffered_audio_samples_ += sample_frame_count;
207 graph_->set_value("buffered-audio",
208 static_cast<double>(buffered_audio_samples_)
209 / format_desc_.audio_cadence[0] * 0.5);
212 bool try_consume_audio(std::pair<std::vector<int32_t>, size_t>& buffer)
214 size_t to_offer = (buffer.first.size() - buffer.second)
215 / config_.num_out_channels();
216 auto begin = buffer.first.data() + buffer.second;
217 unsigned long samples_written;
219 if (FAILED(output_->WriteAudioSamplesSync(
223 CASPAR_LOG(error) << print() << L" Failed to write audio samples.";
225 buffered_audio_samples_ -= samples_written;
227 if (samples_written == to_offer)
228 return true; // depleted buffer
230 size_t consumed = samples_written * config_.num_out_channels();
231 buffer.second += consumed;
237 void write_audio_samples()
239 while (!audio_samples_.empty())
241 auto buffer = audio_samples_.front();
243 if (try_consume_audio(buffer))
244 audio_samples_.pop_front();
250 void wait_for_frame_to_be_displayed()
252 sync_timer_.restart();
253 BMDTimeScale time_scale = 1000;
254 BMDTimeValue hardware_time;
255 BMDTimeValue time_in_frame;
256 BMDTimeValue ticks_per_frame;
258 if (FAILED(output_->GetHardwareReferenceClock(
259 time_scale, &hardware_time, &time_in_frame, &ticks_per_frame)))
260 CASPAR_LOG(error) << print() << L" Failed to determine time in frame.";
262 auto reference_clock_value = hardware_time - time_in_frame;
263 auto frame_duration = static_cast<int>(1000 / format_desc_.fps);
264 auto actual_duration = last_reference_clock_value_ == -1
266 : reference_clock_value - last_reference_clock_value_;
268 if (std::abs(frame_duration - actual_duration) > 1)
269 graph_->set_tag("late-frame");
272 auto to_wait = ticks_per_frame - time_in_frame;
273 high_prec_timer timer;
274 timer.tick_millis(0);
275 timer.tick_millis(static_cast<DWORD>(to_wait));
279 last_reference_clock_value_ = reference_clock_value;
280 graph_->set_value("sync-time",
281 sync_timer_.elapsed() * format_desc_.fps * 0.5);
284 void write_video_frame(
285 const safe_ptr<core::read_frame>& frame,
286 std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>>&& key)
288 CComPtr<IDeckLinkVideoFrame> frame2;
290 if (config_.key_only)
291 frame2 = CComPtr<IDeckLinkVideoFrame>(
292 new decklink_frame(frame, format_desc_, std::move(key)));
294 frame2 = CComPtr<IDeckLinkVideoFrame>(
295 new decklink_frame(frame, format_desc_, config_.key_only));
297 if (FAILED(output_->DisplayVideoFrameSync(frame2)))
298 CASPAR_LOG(error) << print() << L" Failed to display video frame.";
300 reference_signal_detector_.detect_change([this]() { return print(); });
303 boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame)
305 return executor_.begin_invoke([=]() -> bool
307 frame_timer_.restart();
308 std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> key;
310 if (config_.key_only)
311 key = std::move(extract_key(frame));
313 if (config_.embedded_audio)
314 queue_audio_samples(frame);
316 double frame_time = frame_timer_.elapsed();
318 wait_for_frame_to_be_displayed();
322 // According to SDK when DisplayVideoFrameSync is used in
323 // combination with low latency, the next frame displayed should
324 // be the submitted frame but it appears to be an additional 2
325 // frame delay before it is sent on SDI.
326 int adjustment = static_cast<int>(2000 / format_desc_.fps);
328 current_presentation_delay_ =
329 previous_frame_->get_age_millis() + adjustment;
332 previous_frame_ = frame;
336 tick_timer_.elapsed() * format_desc_.fps * 0.5);
337 tick_timer_.restart();
339 frame_timer_.restart();
340 write_video_frame(frame, std::move(key));
342 if (config_.embedded_audio)
343 write_audio_samples();
345 frame_time += frame_timer_.elapsed();
347 "frame-time", frame_time * format_desc_.fps * 0.5);
353 std::wstring print() const
355 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +
356 boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
360 struct blocking_decklink_consumer_proxy : public core::frame_consumer
362 const configuration config_;
363 com_context<blocking_decklink_consumer> context_;
364 std::vector<size_t> audio_cadence_;
365 core::video_format_desc format_desc_;
368 blocking_decklink_consumer_proxy(const configuration& config)
370 , context_(L"blocking_decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
374 ~blocking_decklink_consumer_proxy()
380 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";
386 virtual void initialize(const core::video_format_desc& format_desc, int channel_index) override
388 context_.reset([&]{return new blocking_decklink_consumer(config_, format_desc, channel_index);});
389 audio_cadence_ = format_desc.audio_cadence;
390 format_desc_ = format_desc;
392 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
395 virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override
397 CASPAR_VERIFY(audio_cadence_.front() * frame->num_channels() == static_cast<size_t>(frame->audio_data().size()));
398 boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);
400 return context_->send(frame);
403 virtual std::wstring print() const override
405 return context_ ? context_->print() : L"[blocking_decklink_consumer]";
408 virtual boost::property_tree::wptree info() const override
410 boost::property_tree::wptree info;
411 info.add(L"type", L"blocking-decklink-consumer");
412 info.add(L"key-only", config_.key_only);
413 info.add(L"device", config_.device_index);
414 info.add(L"embedded-audio", config_.embedded_audio);
415 info.add(L"presentation-frame-age", presentation_frame_age_millis());
416 //info.add(L"internal-key", config_.internal_key);
420 virtual size_t buffer_depth() const override
422 // Should be 1 according to SDK when DisplayVideoFrameSync is used in
423 // combination with low latency, but it does not seem so.
427 virtual bool has_synchronization_clock() const override
432 virtual int index() const override
434 return 350 + config_.device_index;
437 virtual int64_t presentation_frame_age_millis() const
439 return context_ ? context_->current_presentation_delay_ : 0;
443 safe_ptr<core::frame_consumer> create_blocking_consumer(const core::parameters& params)
445 if(params.size() < 1 || params[0] != L"BLOCKING_DECKLINK")
446 return core::frame_consumer::empty();
448 configuration config;
450 if(params.size() > 1)
451 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
453 if(std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end())
454 config.keyer = configuration::internal_keyer;
455 else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end())
456 config.keyer = configuration::external_keyer;
458 config.keyer = configuration::default_keyer;
460 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
461 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
462 config.audio_layout = core::default_channel_layout_repository().get_by_name(
463 params.get(L"CHANNEL_LAYOUT", L"STEREO"));
465 return make_safe<blocking_decklink_consumer_proxy>(config);
468 safe_ptr<core::frame_consumer> create_blocking_consumer(const boost::property_tree::wptree& ptree)
470 configuration config;
472 auto keyer = ptree.get(L"keyer", L"external");
473 if(keyer == L"external")
474 config.keyer = configuration::external_keyer;
475 else if(keyer == L"internal")
476 config.keyer = configuration::internal_keyer;
478 config.key_only = ptree.get(L"key-only", config.key_only);
479 config.device_index = ptree.get(L"device", config.device_index);
480 config.embedded_audio = ptree.get(L"embedded-audio", config.embedded_audio);
481 config.audio_layout =
482 core::default_channel_layout_repository().get_by_name(
483 boost::to_upper_copy(ptree.get(L"channel-layout", L"STEREO")));
485 return make_safe<blocking_decklink_consumer_proxy>(config);