2 * copyright (c) 2010 Sveriges Television AB <info@casparcg.com>
\r
4 * This file is part of CasparCG.
\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
21 #include "../StdAfx.h"
\r
23 #include "decklink_consumer.h"
\r
25 #include "../util/util.h"
\r
27 #include "../interop/DeckLinkAPI_h.h"
\r
29 #include <core/mixer/read_frame.h>
\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 #include <common/memory/memshfl.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
48 struct configuration
\r
50 size_t device_index;
\r
51 bool embedded_audio;
\r
55 size_t buffer_depth;
\r
59 , embedded_audio(false)
\r
60 , internal_key(false)
\r
61 , low_latency(false)
\r
63 , buffer_depth(core::consumer_buffer_depth()){}
\r
66 class decklink_frame : public IDeckLinkVideoFrame
\r
68 std::shared_ptr<core::read_frame> frame_;
\r
69 const core::video_format_desc format_desc_;
\r
72 std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> key_data_;
\r
74 decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, bool key_only)
\r
76 , format_desc_(format_desc)
\r
77 , key_only_(key_only){}
\r
79 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
80 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
81 STDMETHOD_(ULONG, Release()) {return 1;}
\r
83 STDMETHOD_(long, GetWidth()) {return format_desc_.width;}
\r
84 STDMETHOD_(long, GetHeight()) {return format_desc_.height;}
\r
85 STDMETHOD_(long, GetRowBytes()) {return format_desc_.width*4;}
\r
86 STDMETHOD_(BMDPixelFormat, GetPixelFormat()) {return bmdFormat8BitBGRA;}
\r
87 STDMETHOD_(BMDFrameFlags, GetFlags()) {return bmdFrameFlagDefault;}
\r
89 STDMETHOD(GetBytes(void** buffer))
\r
91 static std::vector<uint8_t> zeros(1920*1080*4, 0);
\r
92 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)
\r
94 *buffer = zeros.data();
\r
99 *buffer = const_cast<uint8_t*>(frame_->image_data().begin());
\r
102 if(key_data_.empty())
\r
104 key_data_.resize(frame_->image_data().size());
\r
105 fast_memshfl(key_data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);
\r
108 *buffer = key_data_.data();
\r
114 STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}
\r
115 STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}
\r
118 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
120 const configuration config_;
\r
122 CComPtr<IDeckLink> decklink_;
\r
123 CComQIPtr<IDeckLinkOutput> output_;
\r
124 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
125 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
127 tbb::spin_mutex exception_mutex_;
\r
128 std::exception_ptr exception_;
\r
130 tbb::atomic<bool> is_running_;
\r
132 const std::wstring model_name_;
\r
133 const core::video_format_desc format_desc_;
\r
134 const size_t buffer_size_;
\r
136 long long frames_scheduled_;
\r
137 long long audio_scheduled_;
\r
139 size_t preroll_count_;
\r
141 std::list<std::shared_ptr<IDeckLinkVideoFrame>> frame_container_; // Must be std::list in order to guarantee that pointers are always valid.
\r
142 boost::circular_buffer<std::vector<int16_t>> audio_container_;
\r
144 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;
\r
145 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;
\r
147 std::shared_ptr<diagnostics::graph> graph_;
\r
148 boost::timer tick_timer_;
\r
151 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc)
\r
153 , decklink_(get_device(config.device_index))
\r
154 , output_(decklink_)
\r
155 , configuration_(decklink_)
\r
156 , keyer_(decklink_)
\r
157 , model_name_(get_model_name(decklink_))
\r
158 , format_desc_(format_desc)
\r
159 , buffer_size_(config.embedded_audio ? config.buffer_depth + 1 : config.buffer_depth) // Minimum buffer-size 3.
\r
160 , frames_scheduled_(0)
\r
161 , audio_scheduled_(0)
\r
162 , preroll_count_(0)
\r
163 , audio_container_(buffer_size_+1)
\r
165 is_running_ = true;
\r
167 video_frame_buffer_.set_capacity(1);
\r
168 audio_frame_buffer_.set_capacity(1);
\r
170 graph_ = diagnostics::create_graph(narrow(print()));
\r
171 graph_->add_guide("tick-time", 0.5);
\r
172 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
\r
173 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
174 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
175 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
\r
177 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
179 if(config.embedded_audio)
\r
182 set_latency(config.low_latency);
\r
183 set_keyer(config.internal_key);
\r
185 if(config.embedded_audio)
\r
186 output_->BeginAudioPreroll();
\r
188 for(size_t n = 0; n < buffer_size_; ++n)
\r
189 schedule_next_video(make_safe<core::read_frame>());
\r
191 if(!config.embedded_audio)
\r
195 ~decklink_consumer()
\r
197 is_running_ = false;
\r
198 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
199 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
201 if(output_ != nullptr)
\r
203 output_->StopScheduledPlayback(0, nullptr, 0);
\r
204 if(config_.embedded_audio)
\r
205 output_->DisableAudioOutput();
\r
206 output_->DisableVideoOutput();
\r
210 const core::video_format_desc& get_video_format_desc() const
\r
212 return format_desc_;
\r
215 void set_latency(bool low_latency)
\r
219 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
220 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
224 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
225 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
229 void set_keyer(bool internal_key)
\r
233 if(FAILED(keyer_->Enable(FALSE)))
\r
234 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
235 else if(FAILED(keyer_->SetLevel(255)))
\r
236 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
238 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
242 if(FAILED(keyer_->Enable(TRUE)))
\r
243 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
244 else if(FAILED(keyer_->SetLevel(255)))
\r
245 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
247 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
251 void enable_audio()
\r
253 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
254 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
256 if(FAILED(output_->SetAudioCallback(this)))
\r
257 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
259 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
262 void enable_video(BMDDisplayMode display_mode)
\r
264 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
265 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
267 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
268 BOOST_THROW_EXCEPTION(caspar_exception()
\r
269 << msg_info(narrow(print()) + " Failed to set playback completion callback.")
\r
270 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
273 void start_playback()
\r
275 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
276 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
279 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
280 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
281 STDMETHOD_(ULONG, Release()) {return 1;}
\r
283 STDMETHOD(ScheduledPlaybackHasStopped())
\r
285 is_running_ = false;
\r
286 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
290 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
297 if(result == bmdOutputFrameDisplayedLate)
\r
299 graph_->add_tag("late-frame");
\r
300 ++frames_scheduled_;
\r
301 ++audio_scheduled_;
\r
303 else if(result == bmdOutputFrameDropped)
\r
304 graph_->add_tag("dropped-frame");
\r
305 else if(result == bmdOutputFrameFlushed)
\r
306 graph_->add_tag("flushed-frame");
\r
308 frame_container_.erase(std::find_if(frame_container_.begin(), frame_container_.end(), [&](const std::shared_ptr<IDeckLinkVideoFrame>& frame)
\r
310 return frame.get() == completed_frame;
\r
313 std::shared_ptr<core::read_frame> frame;
\r
314 video_frame_buffer_.pop(frame);
\r
315 schedule_next_video(make_safe(frame));
\r
319 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
320 exception_ = std::current_exception();
\r
327 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
336 if(++preroll_count_ >= buffer_size_)
\r
338 output_->EndAudioPreroll();
\r
342 schedule_next_audio(make_safe<core::read_frame>());
\r
346 std::shared_ptr<core::read_frame> frame;
\r
347 audio_frame_buffer_.pop(frame);
\r
348 schedule_next_audio(make_safe(frame));
\r
353 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
354 exception_ = std::current_exception();
\r
361 void schedule_next_audio(const safe_ptr<core::read_frame>& frame)
\r
363 const int sample_frame_count = frame->audio_data().size()/format_desc_.audio_channels;
\r
365 audio_container_.push_back(std::vector<int16_t>(frame->audio_data().begin(), frame->audio_data().end()));
\r
367 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))
\r
368 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
371 void schedule_next_video(const safe_ptr<core::read_frame>& frame)
\r
373 frame_container_.push_back(std::make_shared<decklink_frame>(frame, format_desc_, config_.key_only));
\r
374 if(FAILED(output_->ScheduleVideoFrame(frame_container_.back().get(), (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))
\r
375 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
377 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
378 tick_timer_.restart();
\r
381 void send(const safe_ptr<core::read_frame>& frame)
\r
384 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
385 if(exception_ != nullptr)
\r
386 std::rethrow_exception(exception_);
\r
390 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
392 if(config_.embedded_audio)
\r
393 audio_frame_buffer_.push(frame);
\r
394 video_frame_buffer_.push(frame);
\r
397 std::wstring print() const
\r
399 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
403 struct decklink_consumer_proxy : public core::frame_consumer
\r
405 const configuration config_;
\r
406 com_context<decklink_consumer> context_;
\r
407 core::video_format_desc format_desc_;
\r
408 size_t fail_count_;
\r
411 decklink_consumer_proxy(const configuration& config)
\r
413 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
\r
418 ~decklink_consumer_proxy()
\r
420 auto str = print();
\r
422 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";
\r
425 virtual void initialize(const core::video_format_desc& format_desc)
\r
427 format_desc_ = format_desc;
\r
428 context_.reset([&]{return new decklink_consumer(config_, format_desc_);});
\r
430 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
433 virtual bool send(const safe_ptr<core::read_frame>& frame)
\r
436 context_.reset([&]{return new decklink_consumer(config_, format_desc_);});
\r
440 context_->send(frame);
\r
447 if(fail_count_++ > 3)
\r
448 return false; // Outside didn't handle exception properly, just give up.
\r
456 virtual std::wstring print() const
\r
458 return context_ ? context_->print() : L"decklink_consumer";
\r
461 virtual bool key_only() const
\r
463 return config_.key_only;
\r
466 virtual const core::video_format_desc& get_video_format_desc() const
\r
468 return format_desc_;
\r
472 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params)
\r
474 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
475 return core::frame_consumer::empty();
\r
477 configuration config;
\r
479 if(params.size() > 1)
\r
480 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
482 config.internal_key = std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end();
\r
483 config.low_latency = std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end();
\r
484 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
485 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
487 return make_safe<decklink_consumer_proxy>(config);
\r
490 safe_ptr<core::frame_consumer> create_decklink_consumer(const boost::property_tree::ptree& ptree)
\r
492 configuration config;
\r
494 config.internal_key = ptree.get("internal-key", config.internal_key);
\r
495 config.low_latency = ptree.get("low-latency", config.low_latency);
\r
496 config.key_only = ptree.get("key-only", config.key_only);
\r
497 config.device_index = ptree.get("device", config.device_index);
\r
498 config.embedded_audio = ptree.get("embedded-audio", config.embedded_audio);
\r
500 return make_safe<decklink_consumer_proxy>(config);
\r
506 ##############################################################################
\r
512 BMD Developer Support
\r
513 developer@blackmagic-design.com
\r
515 -----------------------------------------------------------------------------
\r
517 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
518 for scheduled playback is three frames for video and four frames for audio.
\r
519 As you mentioned if you preroll less frames then playback will not start or
\r
520 playback will be very sporadic. From our experience with Media Express, we
\r
521 recommended that at least seven frames are prerolled for smooth playback.
\r
523 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
524 There can be around 3 frames worth of latency on scheduled output.
\r
525 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
526 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
527 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
528 guarantee that the provided frame will be output as soon the previous
\r
529 frame output has been completed.
\r
530 ################################################################################
\r
534 ##############################################################################
\r
535 Async DMA Transfer without redundant copying
\r
540 BMD Developer Support
\r
541 developer@blackmagic-design.com
\r
543 -----------------------------------------------------------------------------
\r
545 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
546 and providing a pointer to your video buffer when GetBytes() is called.
\r
547 This may help to keep copying to a minimum. Please ensure that the pixel
\r
548 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
549 have to colourspace convert which may result in additional copying.
\r
550 ################################################################################
\r