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
46 namespace caspar { namespace decklink {
\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 tbb::atomic<int> ref_count_;
\r
69 std::shared_ptr<core::read_frame> frame_;
\r
70 const core::video_format_desc format_desc_;
\r
73 std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> key_data_;
\r
75 decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, bool key_only)
\r
77 , format_desc_(format_desc)
\r
78 , key_only_(key_only)
\r
83 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
84 STDMETHOD_(ULONG, AddRef())
\r
86 return ++ref_count_;
\r
88 STDMETHOD_(ULONG, Release())
\r
96 STDMETHOD_(long, GetWidth()) {return format_desc_.width;}
\r
97 STDMETHOD_(long, GetHeight()) {return format_desc_.height;}
\r
98 STDMETHOD_(long, GetRowBytes()) {return format_desc_.width*4;}
\r
99 STDMETHOD_(BMDPixelFormat, GetPixelFormat()) {return bmdFormat8BitBGRA;}
\r
100 STDMETHOD_(BMDFrameFlags, GetFlags()) {return bmdFrameFlagDefault;}
\r
102 STDMETHOD(GetBytes(void** buffer))
\r
104 static std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> zeros(1920*1080*4, 0);
\r
105 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)
\r
107 *buffer = zeros.data();
\r
112 *buffer = const_cast<uint8_t*>(frame_->image_data().begin());
\r
115 if(key_data_.empty())
\r
117 key_data_.resize(frame_->image_data().size());
\r
118 fast_memshfl(key_data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);
\r
121 *buffer = key_data_.data();
\r
127 STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}
\r
128 STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}
\r
131 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
133 const configuration config_;
\r
135 CComPtr<IDeckLink> decklink_;
\r
136 CComQIPtr<IDeckLinkOutput> output_;
\r
137 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
138 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
140 tbb::spin_mutex exception_mutex_;
\r
141 std::exception_ptr exception_;
\r
143 tbb::atomic<bool> is_running_;
\r
145 const std::wstring model_name_;
\r
146 const core::video_format_desc format_desc_;
\r
147 const size_t buffer_size_;
\r
149 long long frames_scheduled_;
\r
150 long long audio_scheduled_;
\r
152 size_t preroll_count_;
\r
154 boost::circular_buffer<std::vector<int32_t>> audio_container_;
\r
156 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;
\r
157 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;
\r
159 safe_ptr<diagnostics::graph> graph_;
\r
160 boost::timer tick_timer_;
\r
163 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc)
\r
165 , decklink_(get_device(config.device_index))
\r
166 , output_(decklink_)
\r
167 , configuration_(decklink_)
\r
168 , keyer_(decklink_)
\r
169 , model_name_(get_model_name(decklink_))
\r
170 , format_desc_(format_desc)
\r
171 , buffer_size_(config.embedded_audio ? config.buffer_depth + 1 : config.buffer_depth) // Minimum buffer-size 3.
\r
172 , frames_scheduled_(0)
\r
173 , audio_scheduled_(0)
\r
174 , preroll_count_(0)
\r
175 , audio_container_(buffer_size_+1)
\r
177 is_running_ = true;
\r
179 video_frame_buffer_.set_capacity(1);
\r
180 audio_frame_buffer_.set_capacity(1);
\r
182 graph_->add_guide("tick-time", 0.5);
\r
183 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
\r
184 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
185 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
186 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
\r
187 graph_->set_text(print());
\r
188 diagnostics::register_graph(graph_);
\r
190 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
192 if(config.embedded_audio)
\r
195 set_latency(config.low_latency);
\r
196 set_keyer(config.internal_key);
\r
198 if(config.embedded_audio)
\r
199 output_->BeginAudioPreroll();
\r
201 for(size_t n = 0; n < buffer_size_; ++n)
\r
202 schedule_next_video(make_safe<core::read_frame>());
\r
204 if(!config.embedded_audio)
\r
208 ~decklink_consumer()
\r
210 is_running_ = false;
\r
211 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
212 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
214 if(output_ != nullptr)
\r
216 output_->StopScheduledPlayback(0, nullptr, 0);
\r
217 if(config_.embedded_audio)
\r
218 output_->DisableAudioOutput();
\r
219 output_->DisableVideoOutput();
\r
223 const core::video_format_desc& get_video_format_desc() const
\r
225 return format_desc_;
\r
228 void set_latency(bool low_latency)
\r
232 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
233 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
237 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
238 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
242 void set_keyer(bool internal_key)
\r
246 if(FAILED(keyer_->Enable(FALSE)))
\r
247 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
248 else if(FAILED(keyer_->SetLevel(255)))
\r
249 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
251 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
255 if(FAILED(keyer_->Enable(TRUE)))
\r
256 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
257 else if(FAILED(keyer_->SetLevel(255)))
\r
258 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
260 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
264 void enable_audio()
\r
266 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
267 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
269 if(FAILED(output_->SetAudioCallback(this)))
\r
270 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
272 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
275 void enable_video(BMDDisplayMode display_mode)
\r
277 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
278 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
280 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
281 BOOST_THROW_EXCEPTION(caspar_exception()
\r
282 << msg_info(narrow(print()) + " Failed to set playback completion callback.")
\r
283 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
286 void start_playback()
\r
288 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
289 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
292 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
293 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
294 STDMETHOD_(ULONG, Release()) {return 1;}
\r
296 STDMETHOD(ScheduledPlaybackHasStopped())
\r
298 is_running_ = false;
\r
299 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
303 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
310 if(result == bmdOutputFrameDisplayedLate)
\r
312 graph_->add_tag("late-frame");
\r
313 ++frames_scheduled_;
\r
314 ++audio_scheduled_;
\r
316 else if(result == bmdOutputFrameDropped)
\r
317 graph_->add_tag("dropped-frame");
\r
318 else if(result == bmdOutputFrameFlushed)
\r
319 graph_->add_tag("flushed-frame");
\r
321 std::shared_ptr<core::read_frame> frame;
\r
322 video_frame_buffer_.pop(frame);
\r
323 schedule_next_video(make_safe_ptr(frame));
\r
327 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
328 exception_ = std::current_exception();
\r
335 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
344 if(++preroll_count_ >= buffer_size_)
\r
346 output_->EndAudioPreroll();
\r
350 schedule_next_audio(make_safe<core::read_frame>());
\r
354 std::shared_ptr<core::read_frame> frame;
\r
355 audio_frame_buffer_.pop(frame);
\r
356 schedule_next_audio(make_safe_ptr(frame));
\r
361 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
362 exception_ = std::current_exception();
\r
369 void schedule_next_audio(const safe_ptr<core::read_frame>& frame)
\r
371 const int sample_frame_count = frame->audio_data().size()/format_desc_.audio_channels;
\r
373 audio_container_.push_back(std::vector<int32_t>(frame->audio_data().begin(), frame->audio_data().end()));
\r
375 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))
\r
376 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
379 void schedule_next_video(const safe_ptr<core::read_frame>& frame)
\r
381 CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));
\r
382 if(FAILED(output_->ScheduleVideoFrame(frame2, (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))
\r
383 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
385 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
386 tick_timer_.restart();
\r
389 void send(const safe_ptr<core::read_frame>& frame)
\r
392 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
393 if(exception_ != nullptr)
\r
394 std::rethrow_exception(exception_);
\r
398 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
400 if(config_.embedded_audio)
\r
401 audio_frame_buffer_.push(frame);
\r
402 video_frame_buffer_.push(frame);
\r
405 std::wstring print() const
\r
407 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
411 struct decklink_consumer_proxy : public core::frame_consumer
\r
413 const configuration config_;
\r
414 com_context<decklink_consumer> context_;
\r
415 core::video_format_desc format_desc_;
\r
418 decklink_consumer_proxy(const configuration& config)
\r
420 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
\r
424 ~decklink_consumer_proxy()
\r
426 auto str = print();
\r
428 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";
\r
431 virtual void initialize(const core::video_format_desc& format_desc)
\r
433 format_desc_ = format_desc;
\r
434 context_.reset([&]{return new decklink_consumer(config_, format_desc_);});
\r
436 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
439 virtual bool send(const safe_ptr<core::read_frame>& frame)
\r
441 context_->send(frame);
\r
445 virtual std::wstring print() const
\r
447 return context_ ? context_->print() : L"decklink_consumer";
\r
450 virtual const core::video_format_desc& get_video_format_desc() const
\r
452 return format_desc_;
\r
456 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params)
\r
458 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
459 return core::frame_consumer::empty();
\r
461 configuration config;
\r
463 if(params.size() > 1)
\r
464 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
466 config.internal_key = std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end();
\r
467 config.low_latency = std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end();
\r
468 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
469 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
471 return make_safe<decklink_consumer_proxy>(config);
\r
474 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::ptree& ptree)
\r
476 configuration config;
\r
478 config.internal_key = ptree.get("internal-key", config.internal_key);
\r
479 config.low_latency = ptree.get("low-latency", config.low_latency);
\r
480 config.key_only = ptree.get("key-only", config.key_only);
\r
481 config.device_index = ptree.get("device", config.device_index);
\r
482 config.embedded_audio = ptree.get("embedded-audio", config.embedded_audio);
\r
484 return make_safe<decklink_consumer_proxy>(config);
\r
490 ##############################################################################
\r
496 BMD Developer Support
\r
497 developer@blackmagic-design.com
\r
499 -----------------------------------------------------------------------------
\r
501 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
502 for scheduled playback is three frames for video and four frames for audio.
\r
503 As you mentioned if you preroll less frames then playback will not start or
\r
504 playback will be very sporadic. From our experience with Media Express, we
\r
505 recommended that at least seven frames are prerolled for smooth playback.
\r
507 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
508 There can be around 3 frames worth of latency on scheduled output.
\r
509 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
510 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
511 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
512 guarantee that the provided frame will be output as soon the previous
\r
513 frame output has been completed.
\r
514 ################################################################################
\r
518 ##############################################################################
\r
519 Async DMA Transfer without redundant copying
\r
524 BMD Developer Support
\r
525 developer@blackmagic-design.com
\r
527 -----------------------------------------------------------------------------
\r
529 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
530 and providing a pointer to your video buffer when GetBytes() is called.
\r
531 This may help to keep copying to a minimum. Please ensure that the pixel
\r
532 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
533 have to colourspace convert which may result in additional copying.
\r
534 ################################################################################
\r