2 * Copyright (c) 2011 Sveriges Television AB <info@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
28 #include "../interop/DeckLinkAPI_h.h"
\r
30 #include <core/mixer/read_frame.h>
\r
32 #include <common/concurrency/com_context.h>
\r
33 #include <common/diagnostics/graph.h>
\r
34 #include <common/exception/exceptions.h>
\r
35 #include <common/memory/memshfl.h>
\r
36 #include <common/utility/assert.h>
\r
38 #include <core/consumer/frame_consumer.h>
\r
40 #include <tbb/concurrent_queue.h>
\r
41 #include <tbb/cache_aligned_allocator.h>
\r
43 #include <boost/circular_buffer.hpp>
\r
44 #include <boost/timer.hpp>
\r
45 #include <boost/property_tree/ptree.hpp>
\r
47 namespace caspar { namespace decklink {
\r
49 struct configuration
\r
52 bool embedded_audio;
\r
56 int base_buffer_depth;
\r
61 , embedded_audio(false)
\r
62 , internal_key(false)
\r
65 , base_buffer_depth(3)
\r
66 , buffer_depth(base_buffer_depth + (low_latency ? 0 : 1) + (embedded_audio ? 1 : 0)){}
\r
69 class decklink_frame : public IDeckLinkVideoFrame
\r
71 tbb::atomic<int> ref_count_;
\r
72 std::shared_ptr<core::read_frame> frame_;
\r
73 const core::video_format_desc format_desc_;
\r
75 const bool key_only_;
\r
76 std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> key_data_;
\r
78 decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, bool key_only)
\r
80 , format_desc_(format_desc)
\r
81 , key_only_(key_only)
\r
86 const boost::iterator_range<const int32_t*> audio_data()
\r
88 return frame_->audio_data();
\r
91 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
92 STDMETHOD_(ULONG, AddRef())
\r
94 return ++ref_count_;
\r
96 STDMETHOD_(ULONG, Release())
\r
104 STDMETHOD_(long, GetWidth()) {return format_desc_.width;}
\r
105 STDMETHOD_(long, GetHeight()) {return format_desc_.height;}
\r
106 STDMETHOD_(long, GetRowBytes()) {return format_desc_.width*4;}
\r
107 STDMETHOD_(BMDPixelFormat, GetPixelFormat()) {return bmdFormat8BitBGRA;}
\r
108 STDMETHOD_(BMDFrameFlags, GetFlags()) {return bmdFrameFlagDefault;}
\r
110 STDMETHOD(GetBytes(void** buffer))
\r
112 static std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> zeros(1920*1080*4, 0);
\r
113 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)
\r
115 *buffer = zeros.data();
\r
120 *buffer = const_cast<uint8_t*>(frame_->image_data().begin());
\r
123 if(key_data_.empty())
\r
125 key_data_.resize(frame_->image_data().size());
\r
126 aligned_memshfl(key_data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);
\r
129 *buffer = key_data_.data();
\r
135 STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}
\r
136 STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}
\r
139 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
141 const int channel_index_;
\r
142 const configuration config_;
\r
144 CComPtr<IDeckLink> decklink_;
\r
145 CComQIPtr<IDeckLinkOutput> output_;
\r
146 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
147 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
149 tbb::spin_mutex exception_mutex_;
\r
150 std::exception_ptr exception_;
\r
152 tbb::atomic<bool> is_running_;
\r
154 const std::wstring model_name_;
\r
155 const core::video_format_desc format_desc_;
\r
156 const size_t buffer_size_;
\r
158 long long video_scheduled_;
\r
159 long long audio_scheduled_;
\r
161 size_t preroll_count_;
\r
163 boost::circular_buffer<std::vector<int32_t>> audio_container_;
\r
165 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;
\r
166 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;
\r
168 safe_ptr<diagnostics::graph> graph_;
\r
169 boost::timer tick_timer_;
\r
172 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index)
\r
173 : channel_index_(channel_index)
\r
175 , decklink_(get_device(config.device_index))
\r
176 , output_(decklink_)
\r
177 , configuration_(decklink_)
\r
178 , keyer_(decklink_)
\r
179 , model_name_(get_model_name(decklink_))
\r
180 , format_desc_(format_desc)
\r
181 , buffer_size_(config.buffer_depth) // Minimum buffer-size 3.
\r
182 , video_scheduled_(0)
\r
183 , audio_scheduled_(0)
\r
184 , preroll_count_(0)
\r
185 , audio_container_(buffer_size_+1)
\r
187 is_running_ = true;
\r
189 video_frame_buffer_.set_capacity(1);
\r
190 audio_frame_buffer_.set_capacity(1);
\r
192 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
\r
193 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
194 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
195 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
\r
196 graph_->set_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));
\r
197 graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));
\r
198 graph_->set_text(print());
\r
199 diagnostics::register_graph(graph_);
\r
201 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
203 if(config.embedded_audio)
\r
206 set_latency(config.low_latency);
\r
207 set_keyer(config.internal_key);
\r
209 if(config.embedded_audio)
\r
210 output_->BeginAudioPreroll();
\r
212 for(size_t n = 0; n < buffer_size_; ++n)
\r
213 schedule_next_video(make_safe<core::read_frame>());
\r
215 if(!config.embedded_audio)
\r
219 ~decklink_consumer()
\r
221 is_running_ = false;
\r
222 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
223 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
225 if(output_ != nullptr)
\r
227 output_->StopScheduledPlayback(0, nullptr, 0);
\r
228 if(config_.embedded_audio)
\r
229 output_->DisableAudioOutput();
\r
230 output_->DisableVideoOutput();
\r
234 void set_latency(bool low_latency)
\r
238 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
239 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
243 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
244 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
248 void set_keyer(bool internal_key)
\r
252 if(FAILED(keyer_->Enable(FALSE)))
\r
253 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
254 else if(FAILED(keyer_->SetLevel(255)))
\r
255 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
257 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
261 if(FAILED(keyer_->Enable(TRUE)))
\r
262 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
263 else if(FAILED(keyer_->SetLevel(255)))
\r
264 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
266 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
270 void enable_audio()
\r
272 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
273 BOOST_THROW_EXCEPTION(caspar_exception() << wmsg_info(print() + L" Could not enable audio output."));
\r
275 if(FAILED(output_->SetAudioCallback(this)))
\r
276 BOOST_THROW_EXCEPTION(caspar_exception() << wmsg_info(print() + L" Could not set audio callback."));
\r
278 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
281 void enable_video(BMDDisplayMode display_mode)
\r
283 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
284 BOOST_THROW_EXCEPTION(caspar_exception() << wmsg_info(print() + L" Could not enable video output."));
\r
286 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
287 BOOST_THROW_EXCEPTION(caspar_exception()
\r
288 << wmsg_info(print() + L" Failed to set playback completion callback.")
\r
289 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
292 void start_playback()
\r
294 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
295 BOOST_THROW_EXCEPTION(caspar_exception() << wmsg_info(print() + L" Failed to schedule playback."));
\r
298 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
299 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
300 STDMETHOD_(ULONG, Release()) {return 1;}
\r
302 STDMETHOD(ScheduledPlaybackHasStopped())
\r
304 is_running_ = false;
\r
305 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
309 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
316 if(result == bmdOutputFrameDisplayedLate)
\r
318 graph_->set_tag("late-frame");
\r
319 video_scheduled_ += format_desc_.duration;
\r
320 audio_scheduled_ += reinterpret_cast<decklink_frame*>(completed_frame)->audio_data().size()/format_desc_.audio_channels;
\r
321 //++video_scheduled_;
\r
322 //audio_scheduled_ += format_desc_.audio_cadence[0];
\r
323 //++audio_scheduled_;
\r
325 else if(result == bmdOutputFrameDropped)
\r
326 graph_->set_tag("dropped-frame");
\r
327 else if(result == bmdOutputFrameFlushed)
\r
328 graph_->set_tag("flushed-frame");
\r
330 std::shared_ptr<core::read_frame> frame;
\r
331 video_frame_buffer_.pop(frame);
\r
332 schedule_next_video(make_safe_ptr(frame));
\r
334 unsigned long buffered;
\r
335 output_->GetBufferedVideoFrameCount(&buffered);
\r
336 graph_->set_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);
\r
340 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
341 exception_ = std::current_exception();
\r
348 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
357 if(++preroll_count_ >= buffer_size_)
\r
359 output_->EndAudioPreroll();
\r
363 schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()], 0));
\r
367 std::shared_ptr<core::read_frame> frame;
\r
368 audio_frame_buffer_.pop(frame);
\r
369 schedule_next_audio(frame->audio_data());
\r
372 unsigned long buffered;
\r
373 output_->GetBufferedAudioSampleFrameCount(&buffered);
\r
374 graph_->set_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_cadence[0]*2));
\r
378 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
379 exception_ = std::current_exception();
\r
386 template<typename T>
\r
387 void schedule_next_audio(const T& audio_data)
\r
389 const int sample_frame_count = static_cast<int>(audio_data.size())/format_desc_.audio_channels;
\r
391 audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));
\r
393 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, audio_scheduled_, format_desc_.audio_sample_rate, nullptr)))
\r
394 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
396 audio_scheduled_ += sample_frame_count;
\r
399 void schedule_next_video(const safe_ptr<core::read_frame>& frame)
\r
401 CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));
\r
402 if(FAILED(output_->ScheduleVideoFrame(frame2, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
\r
403 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
405 video_scheduled_ += format_desc_.duration;
\r
407 graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
408 tick_timer_.restart();
\r
411 void send(const safe_ptr<core::read_frame>& frame)
\r
414 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
415 if(exception_ != nullptr)
\r
416 std::rethrow_exception(exception_);
\r
420 BOOST_THROW_EXCEPTION(caspar_exception() << wmsg_info(print() + L" Is not running."));
\r
422 if(config_.embedded_audio)
\r
423 audio_frame_buffer_.push(frame);
\r
424 video_frame_buffer_.push(frame);
\r
427 std::wstring print() const
\r
429 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +
\r
430 boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
434 struct decklink_consumer_proxy : public core::frame_consumer
\r
436 const configuration config_;
\r
437 com_context<decklink_consumer> context_;
\r
438 std::vector<int> audio_cadence_;
\r
441 decklink_consumer_proxy(const configuration& config)
\r
443 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
\r
447 ~decklink_consumer_proxy()
\r
451 auto str = print();
\r
453 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";
\r
459 virtual void initialize(const core::video_format_desc& format_desc, int channel_index) override
\r
461 context_.reset([&]{return new decklink_consumer(config_, format_desc, channel_index);});
\r
462 audio_cadence_ = format_desc.audio_cadence;
\r
464 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
467 virtual bool send(const safe_ptr<core::read_frame>& frame) override
\r
469 CASPAR_VERIFY(audio_cadence_.front() == static_cast<size_t>(frame->audio_data().size()));
\r
470 boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);
\r
472 context_->send(frame);
\r
476 virtual std::wstring print() const override
\r
478 return context_ ? context_->print() : L"[decklink_consumer]";
\r
481 virtual boost::property_tree::wptree info() const override
\r
483 boost::property_tree::wptree info;
\r
484 info.add(L"type", L"decklink-consumer");
\r
485 info.add(L"key-only", config_.key_only);
\r
486 info.add(L"device", config_.device_index);
\r
487 info.add(L"low-latency", config_.low_latency);
\r
488 info.add(L"embedded-audio", config_.embedded_audio);
\r
489 info.add(L"low-latency", config_.low_latency);
\r
490 info.add(L"internal-key", config_.internal_key);
\r
494 virtual int buffer_depth() const override
\r
496 return config_.buffer_depth;
\r
499 virtual int index() const override
\r
501 return 300 + config_.device_index;
\r
505 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params)
\r
507 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
508 return core::frame_consumer::empty();
\r
510 configuration config;
\r
512 if(params.size() > 1)
\r
513 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
515 config.internal_key = std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end();
\r
516 config.low_latency = std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end();
\r
517 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
518 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
520 return make_safe<decklink_consumer_proxy>(config);
\r
523 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree)
\r
525 configuration config;
\r
527 config.internal_key = ptree.get(L"internal-key", config.internal_key);
\r
528 config.low_latency = ptree.get(L"low-latency", config.low_latency);
\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
534 return make_safe<decklink_consumer_proxy>(config);
\r
540 ##############################################################################
\r
546 BMD Developer Support
\r
547 developer@blackmagic-design.com
\r
549 -----------------------------------------------------------------------------
\r
551 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
552 for scheduled playback is three frames for video and four frames for audio.
\r
553 As you mentioned if you preroll less frames then playback will not start or
\r
554 playback will be very sporadic. From our experience with Media Express, we
\r
555 recommended that at least seven frames are prerolled for smooth playback.
\r
557 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
558 There can be around 3 frames worth of latency on scheduled output.
\r
559 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
560 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
561 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
562 guarantee that the provided frame will be output as soon the previous
\r
563 frame output has been completed.
\r
564 ################################################################################
\r
568 ##############################################################################
\r
569 Async DMA Transfer without redundant copying
\r
574 BMD Developer Support
\r
575 developer@blackmagic-design.com
\r
577 -----------------------------------------------------------------------------
\r
579 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
580 and providing a pointer to your video buffer when GetBytes() is called.
\r
581 This may help to keep copying to a minimum. Please ensure that the pixel
\r
582 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
583 have to colourspace convert which may result in additional copying.
\r
584 ################################################################################
\r