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
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
437 auto str = print();
\r
439 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";
\r
442 virtual void initialize(const core::video_format_desc& format_desc, int channel_index, int sub_index)
\r
444 context_.reset([&]{return new decklink_consumer(config_, format_desc, channel_index, sub_index);});
\r
446 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
449 virtual bool send(const safe_ptr<core::read_frame>& frame)
\r
451 context_->send(frame);
\r
455 virtual std::wstring print() const
\r
457 return context_->print();
\r
460 virtual size_t buffer_depth() const
\r
462 return config_.buffer_depth;
\r
466 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params)
\r
468 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
469 return core::frame_consumer::empty();
\r
471 configuration config;
\r
473 if(params.size() > 1)
\r
474 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
476 config.internal_key = std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end();
\r
477 config.low_latency = std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end();
\r
478 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
479 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
481 return make_safe<decklink_consumer_proxy>(config);
\r
484 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::ptree& ptree)
\r
486 configuration config;
\r
488 config.internal_key = ptree.get("internal-key", config.internal_key);
\r
489 config.low_latency = ptree.get("low-latency", config.low_latency);
\r
490 config.key_only = ptree.get("key-only", config.key_only);
\r
491 config.device_index = ptree.get("device", config.device_index);
\r
492 config.embedded_audio = ptree.get("embedded-audio", config.embedded_audio);
\r
493 config.base_buffer_depth = ptree.get("buffer-depth", config.base_buffer_depth);
\r
495 return make_safe<decklink_consumer_proxy>(config);
\r
501 ##############################################################################
\r
507 BMD Developer Support
\r
508 developer@blackmagic-design.com
\r
510 -----------------------------------------------------------------------------
\r
512 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
513 for scheduled playback is three frames for video and four frames for audio.
\r
514 As you mentioned if you preroll less frames then playback will not start or
\r
515 playback will be very sporadic. From our experience with Media Express, we
\r
516 recommended that at least seven frames are prerolled for smooth playback.
\r
518 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
519 There can be around 3 frames worth of latency on scheduled output.
\r
520 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
521 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
522 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
523 guarantee that the provided frame will be output as soon the previous
\r
524 frame output has been completed.
\r
525 ################################################################################
\r
529 ##############################################################################
\r
530 Async DMA Transfer without redundant copying
\r
535 BMD Developer Support
\r
536 developer@blackmagic-design.com
\r
538 -----------------------------------------------------------------------------
\r
540 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
541 and providing a pointer to your video buffer when GetBytes() is called.
\r
542 This may help to keep copying to a minimum. Please ensure that the pixel
\r
543 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
544 have to colourspace convert which may result in additional copying.
\r
545 ################################################################################
\r