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 base_buffer_depth;
\r
56 size_t buffer_depth;
\r
60 , embedded_audio(false)
\r
61 , internal_key(false)
\r
62 , low_latency(false)
\r
64 , base_buffer_depth(3)
\r
65 , buffer_depth(base_buffer_depth + (low_latency ? 0 : 1) + (embedded_audio ? 1 : 0)){}
\r
68 class decklink_frame : public IDeckLinkVideoFrame
\r
70 tbb::atomic<int> ref_count_;
\r
71 std::shared_ptr<core::read_frame> frame_;
\r
72 const core::video_format_desc format_desc_;
\r
75 std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> key_data_;
\r
77 decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, bool key_only)
\r
79 , format_desc_(format_desc)
\r
80 , key_only_(key_only)
\r
85 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
86 STDMETHOD_(ULONG, AddRef())
\r
88 return ++ref_count_;
\r
90 STDMETHOD_(ULONG, Release())
\r
98 STDMETHOD_(long, GetWidth()) {return format_desc_.width;}
\r
99 STDMETHOD_(long, GetHeight()) {return format_desc_.height;}
\r
100 STDMETHOD_(long, GetRowBytes()) {return format_desc_.width*4;}
\r
101 STDMETHOD_(BMDPixelFormat, GetPixelFormat()) {return bmdFormat8BitBGRA;}
\r
102 STDMETHOD_(BMDFrameFlags, GetFlags()) {return bmdFrameFlagDefault;}
\r
104 STDMETHOD(GetBytes(void** buffer))
\r
106 static std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> zeros(1920*1080*4, 0);
\r
107 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)
\r
109 *buffer = zeros.data();
\r
114 *buffer = const_cast<uint8_t*>(frame_->image_data().begin());
\r
117 if(key_data_.empty())
\r
119 key_data_.resize(frame_->image_data().size());
\r
120 fast_memshfl(key_data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);
\r
123 *buffer = key_data_.data();
\r
129 STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}
\r
130 STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}
\r
133 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
135 const configuration config_;
\r
136 const int channel_index_;
\r
137 const int sub_index_;
\r
139 CComPtr<IDeckLink> decklink_;
\r
140 CComQIPtr<IDeckLinkOutput> output_;
\r
141 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
142 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
144 tbb::spin_mutex exception_mutex_;
\r
145 std::exception_ptr exception_;
\r
147 tbb::atomic<bool> is_running_;
\r
149 const std::wstring model_name_;
\r
150 const core::video_format_desc format_desc_;
\r
151 const size_t buffer_size_;
\r
153 long long frames_scheduled_;
\r
154 long long audio_scheduled_;
\r
156 size_t preroll_count_;
\r
158 boost::circular_buffer<std::vector<int32_t>> audio_container_;
\r
160 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;
\r
161 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;
\r
163 safe_ptr<diagnostics::graph> graph_;
\r
164 boost::timer tick_timer_;
\r
167 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index, int sub_index)
\r
169 , channel_index_(channel_index)
\r
170 , sub_index_(sub_index)
\r
171 , decklink_(get_device(config.device_index))
\r
172 , output_(decklink_)
\r
173 , configuration_(decklink_)
\r
174 , keyer_(decklink_)
\r
175 , model_name_(get_model_name(decklink_))
\r
176 , format_desc_(format_desc)
\r
177 , buffer_size_(config.buffer_depth) // Minimum buffer-size 3.
\r
178 , frames_scheduled_(0)
\r
179 , audio_scheduled_(0)
\r
180 , preroll_count_(0)
\r
181 , audio_container_(buffer_size_+1)
\r
183 is_running_ = true;
\r
185 video_frame_buffer_.set_capacity(1);
\r
186 audio_frame_buffer_.set_capacity(1);
\r
188 graph_->add_guide("tick-time", 0.5);
\r
189 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
\r
190 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
191 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
192 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
\r
193 graph_->set_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));
\r
194 graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));
\r
195 graph_->set_text(print());
\r
196 diagnostics::register_graph(graph_);
\r
198 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
200 if(config.embedded_audio)
\r
203 set_latency(config.low_latency);
\r
204 set_keyer(config.internal_key);
\r
206 if(config.embedded_audio)
\r
207 output_->BeginAudioPreroll();
\r
209 for(size_t n = 0; n < buffer_size_; ++n)
\r
210 schedule_next_video(make_safe<core::read_frame>());
\r
212 if(!config.embedded_audio)
\r
216 ~decklink_consumer()
\r
218 is_running_ = false;
\r
219 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
220 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
222 if(output_ != nullptr)
\r
224 output_->StopScheduledPlayback(0, nullptr, 0);
\r
225 if(config_.embedded_audio)
\r
226 output_->DisableAudioOutput();
\r
227 output_->DisableVideoOutput();
\r
231 void set_latency(bool low_latency)
\r
235 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
236 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
240 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
241 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
245 void set_keyer(bool internal_key)
\r
249 if(FAILED(keyer_->Enable(FALSE)))
\r
250 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
251 else if(FAILED(keyer_->SetLevel(255)))
\r
252 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
254 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
258 if(FAILED(keyer_->Enable(TRUE)))
\r
259 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
260 else if(FAILED(keyer_->SetLevel(255)))
\r
261 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
263 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
267 void enable_audio()
\r
269 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
270 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
272 if(FAILED(output_->SetAudioCallback(this)))
\r
273 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
275 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
278 void enable_video(BMDDisplayMode display_mode)
\r
280 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
281 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
283 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
284 BOOST_THROW_EXCEPTION(caspar_exception()
\r
285 << msg_info(narrow(print()) + " Failed to set playback completion callback.")
\r
286 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
289 void start_playback()
\r
291 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
292 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
295 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
296 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
297 STDMETHOD_(ULONG, Release()) {return 1;}
\r
299 STDMETHOD(ScheduledPlaybackHasStopped())
\r
301 is_running_ = false;
\r
302 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
306 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
313 if(result == bmdOutputFrameDisplayedLate)
\r
315 graph_->add_tag("late-frame");
\r
316 ++frames_scheduled_;
\r
317 ++audio_scheduled_;
\r
319 else if(result == bmdOutputFrameDropped)
\r
320 graph_->add_tag("dropped-frame");
\r
321 else if(result == bmdOutputFrameFlushed)
\r
322 graph_->add_tag("flushed-frame");
\r
324 std::shared_ptr<core::read_frame> frame;
\r
325 video_frame_buffer_.pop(frame);
\r
326 schedule_next_video(make_safe_ptr(frame));
\r
328 unsigned long buffered;
\r
329 output_->GetBufferedVideoFrameCount(&buffered);
\r
330 graph_->update_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);
\r
334 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
335 exception_ = std::current_exception();
\r
342 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
351 if(++preroll_count_ >= buffer_size_)
\r
353 output_->EndAudioPreroll();
\r
357 schedule_next_audio(make_safe<core::read_frame>());
\r
361 std::shared_ptr<core::read_frame> frame;
\r
362 audio_frame_buffer_.pop(frame);
\r
363 schedule_next_audio(make_safe_ptr(frame));
\r
366 unsigned long buffered;
\r
367 output_->GetBufferedAudioSampleFrameCount(&buffered);
\r
368 graph_->update_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_samples_per_frame*2));
\r
372 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
373 exception_ = std::current_exception();
\r
380 void schedule_next_audio(const safe_ptr<core::read_frame>& frame)
\r
382 const int sample_frame_count = frame->audio_data().size()/format_desc_.audio_channels;
\r
384 audio_container_.push_back(std::vector<int32_t>(frame->audio_data().begin(), frame->audio_data().end()));
\r
386 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))
\r
387 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
390 void schedule_next_video(const safe_ptr<core::read_frame>& frame)
\r
392 CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));
\r
393 if(FAILED(output_->ScheduleVideoFrame(frame2, (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))
\r
394 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
396 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
397 tick_timer_.restart();
\r
400 void send(const safe_ptr<core::read_frame>& frame)
\r
403 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
404 if(exception_ != nullptr)
\r
405 std::rethrow_exception(exception_);
\r
409 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
411 if(config_.embedded_audio)
\r
412 audio_frame_buffer_.push(frame);
\r
413 video_frame_buffer_.push(frame);
\r
416 std::wstring print() const
\r
418 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" + boost::lexical_cast<std::wstring>(sub_index_) + L"|device " +
\r
419 boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
423 struct decklink_consumer_proxy : public core::frame_consumer
\r
425 const configuration config_;
\r
426 com_context<decklink_consumer> context_;
\r
429 decklink_consumer_proxy(const configuration& config)
\r
431 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
\r
435 ~decklink_consumer_proxy()
\r
439 auto str = print();
\r
441 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";
\r
447 virtual void initialize(const core::video_format_desc& format_desc, int channel_index, int sub_index) override
\r
449 context_.reset([&]{return new decklink_consumer(config_, format_desc, channel_index, sub_index);});
\r
451 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
454 virtual bool send(const safe_ptr<core::read_frame>& frame) override
\r
456 context_->send(frame);
\r
460 virtual std::wstring print() const override
\r
462 return context_ ? context_->print() : L"[decklink_consumer]";
\r
465 virtual size_t buffer_depth() const override
\r
467 return config_.buffer_depth;
\r
471 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params)
\r
473 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
474 return core::frame_consumer::empty();
\r
476 configuration config;
\r
478 if(params.size() > 1)
\r
479 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
481 config.internal_key = std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end();
\r
482 config.low_latency = std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end();
\r
483 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
484 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
486 return make_safe<decklink_consumer_proxy>(config);
\r
489 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::ptree& ptree)
\r
491 configuration config;
\r
493 config.internal_key = ptree.get("internal-key", config.internal_key);
\r
494 config.low_latency = ptree.get("low-latency", config.low_latency);
\r
495 config.key_only = ptree.get("key-only", config.key_only);
\r
496 config.device_index = ptree.get("device", config.device_index);
\r
497 config.embedded_audio = ptree.get("embedded-audio", config.embedded_audio);
\r
498 config.base_buffer_depth = ptree.get("buffer-depth", config.base_buffer_depth);
\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