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
137 CComPtr<IDeckLink> decklink_;
\r
138 CComQIPtr<IDeckLinkOutput> output_;
\r
139 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
140 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
142 tbb::spin_mutex exception_mutex_;
\r
143 std::exception_ptr exception_;
\r
145 tbb::atomic<bool> is_running_;
\r
147 const std::wstring model_name_;
\r
148 const core::video_format_desc format_desc_;
\r
149 const size_t buffer_size_;
\r
151 long long frames_scheduled_;
\r
152 long long audio_scheduled_;
\r
154 size_t preroll_count_;
\r
156 boost::circular_buffer<std::vector<int32_t>> audio_container_;
\r
158 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;
\r
159 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;
\r
161 safe_ptr<diagnostics::graph> graph_;
\r
162 boost::timer tick_timer_;
\r
165 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc)
\r
167 , decklink_(get_device(config.device_index))
\r
168 , output_(decklink_)
\r
169 , configuration_(decklink_)
\r
170 , keyer_(decklink_)
\r
171 , model_name_(get_model_name(decklink_))
\r
172 , format_desc_(format_desc)
\r
173 , buffer_size_(config.buffer_depth) // Minimum buffer-size 3.
\r
174 , frames_scheduled_(0)
\r
175 , audio_scheduled_(0)
\r
176 , preroll_count_(0)
\r
177 , audio_container_(buffer_size_+1)
\r
179 is_running_ = true;
\r
181 video_frame_buffer_.set_capacity(1);
\r
182 audio_frame_buffer_.set_capacity(1);
\r
184 graph_->add_guide("tick-time", 0.5);
\r
185 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
\r
186 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
187 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
188 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
\r
189 graph_->set_text(print());
\r
190 diagnostics::register_graph(graph_);
\r
192 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
194 if(config.embedded_audio)
\r
197 set_latency(config.low_latency);
\r
198 set_keyer(config.internal_key);
\r
200 if(config.embedded_audio)
\r
201 output_->BeginAudioPreroll();
\r
203 for(size_t n = 0; n < buffer_size_; ++n)
\r
204 schedule_next_video(make_safe<core::read_frame>());
\r
206 if(!config.embedded_audio)
\r
210 ~decklink_consumer()
\r
212 is_running_ = false;
\r
213 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
214 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
216 if(output_ != nullptr)
\r
218 output_->StopScheduledPlayback(0, nullptr, 0);
\r
219 if(config_.embedded_audio)
\r
220 output_->DisableAudioOutput();
\r
221 output_->DisableVideoOutput();
\r
225 const core::video_format_desc& get_video_format_desc() const
\r
227 return format_desc_;
\r
230 void set_latency(bool low_latency)
\r
234 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
235 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
239 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
240 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
244 void set_keyer(bool internal_key)
\r
248 if(FAILED(keyer_->Enable(FALSE)))
\r
249 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
250 else if(FAILED(keyer_->SetLevel(255)))
\r
251 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
253 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
257 if(FAILED(keyer_->Enable(TRUE)))
\r
258 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
259 else if(FAILED(keyer_->SetLevel(255)))
\r
260 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
262 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
266 void enable_audio()
\r
268 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
269 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
271 if(FAILED(output_->SetAudioCallback(this)))
\r
272 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
274 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
277 void enable_video(BMDDisplayMode display_mode)
\r
279 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
280 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
282 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
283 BOOST_THROW_EXCEPTION(caspar_exception()
\r
284 << msg_info(narrow(print()) + " Failed to set playback completion callback.")
\r
285 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
288 void start_playback()
\r
290 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
291 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
294 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
295 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
296 STDMETHOD_(ULONG, Release()) {return 1;}
\r
298 STDMETHOD(ScheduledPlaybackHasStopped())
\r
300 is_running_ = false;
\r
301 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
305 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
312 if(result == bmdOutputFrameDisplayedLate)
\r
314 graph_->add_tag("late-frame");
\r
315 ++frames_scheduled_;
\r
316 ++audio_scheduled_;
\r
318 else if(result == bmdOutputFrameDropped)
\r
319 graph_->add_tag("dropped-frame");
\r
320 else if(result == bmdOutputFrameFlushed)
\r
321 graph_->add_tag("flushed-frame");
\r
323 std::shared_ptr<core::read_frame> frame;
\r
324 video_frame_buffer_.pop(frame);
\r
325 schedule_next_video(make_safe_ptr(frame));
\r
329 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
330 exception_ = std::current_exception();
\r
337 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
346 if(++preroll_count_ >= buffer_size_)
\r
348 output_->EndAudioPreroll();
\r
352 schedule_next_audio(make_safe<core::read_frame>());
\r
356 std::shared_ptr<core::read_frame> frame;
\r
357 audio_frame_buffer_.pop(frame);
\r
358 schedule_next_audio(make_safe_ptr(frame));
\r
363 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
364 exception_ = std::current_exception();
\r
371 void schedule_next_audio(const safe_ptr<core::read_frame>& frame)
\r
373 const int sample_frame_count = frame->audio_data().size()/format_desc_.audio_channels;
\r
375 audio_container_.push_back(std::vector<int32_t>(frame->audio_data().begin(), frame->audio_data().end()));
\r
377 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))
\r
378 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
381 void schedule_next_video(const safe_ptr<core::read_frame>& frame)
\r
383 CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));
\r
384 if(FAILED(output_->ScheduleVideoFrame(frame2, (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))
\r
385 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
387 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
388 tick_timer_.restart();
\r
391 void send(const safe_ptr<core::read_frame>& frame)
\r
394 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
395 if(exception_ != nullptr)
\r
396 std::rethrow_exception(exception_);
\r
400 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
402 if(config_.embedded_audio)
\r
403 audio_frame_buffer_.push(frame);
\r
404 video_frame_buffer_.push(frame);
\r
407 std::wstring print() const
\r
409 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
413 struct decklink_consumer_proxy : public core::frame_consumer
\r
415 const configuration config_;
\r
416 com_context<decklink_consumer> context_;
\r
417 core::video_format_desc format_desc_;
\r
420 decklink_consumer_proxy(const configuration& config)
\r
422 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
\r
426 ~decklink_consumer_proxy()
\r
428 auto str = print();
\r
430 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";
\r
433 virtual void initialize(const core::video_format_desc& format_desc)
\r
435 format_desc_ = format_desc;
\r
436 context_.reset([&]{return new decklink_consumer(config_, format_desc_);});
\r
438 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
441 virtual bool send(const safe_ptr<core::read_frame>& frame)
\r
443 context_->send(frame);
\r
447 virtual std::wstring print() const
\r
449 return context_ ? context_->print() : L"decklink_consumer";
\r
452 virtual const core::video_format_desc& get_video_format_desc() const
\r
454 return format_desc_;
\r
457 virtual size_t buffer_depth() const
\r
459 return config_.buffer_depth;
\r
463 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params)
\r
465 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
466 return core::frame_consumer::empty();
\r
468 configuration config;
\r
470 if(params.size() > 1)
\r
471 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
473 config.internal_key = std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end();
\r
474 config.low_latency = std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end();
\r
475 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
476 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
478 return make_safe<decklink_consumer_proxy>(config);
\r
481 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::ptree& ptree)
\r
483 configuration config;
\r
485 config.internal_key = ptree.get("internal-key", config.internal_key);
\r
486 config.low_latency = ptree.get("low-latency", config.low_latency);
\r
487 config.key_only = ptree.get("key-only", config.key_only);
\r
488 config.device_index = ptree.get("device", config.device_index);
\r
489 config.embedded_audio = ptree.get("embedded-audio", config.embedded_audio);
\r
490 config.base_buffer_depth = ptree.get("buffer-depth", config.base_buffer_depth);
\r
492 return make_safe<decklink_consumer_proxy>(config);
\r
498 ##############################################################################
\r
504 BMD Developer Support
\r
505 developer@blackmagic-design.com
\r
507 -----------------------------------------------------------------------------
\r
509 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
510 for scheduled playback is three frames for video and four frames for audio.
\r
511 As you mentioned if you preroll less frames then playback will not start or
\r
512 playback will be very sporadic. From our experience with Media Express, we
\r
513 recommended that at least seven frames are prerolled for smooth playback.
\r
515 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
516 There can be around 3 frames worth of latency on scheduled output.
\r
517 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
518 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
519 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
520 guarantee that the provided frame will be output as soon the previous
\r
521 frame output has been completed.
\r
522 ################################################################################
\r
526 ##############################################################################
\r
527 Async DMA Transfer without redundant copying
\r
532 BMD Developer Support
\r
533 developer@blackmagic-design.com
\r
535 -----------------------------------------------------------------------------
\r
537 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
538 and providing a pointer to your video buffer when GetBytes() is called.
\r
539 This may help to keep copying to a minimum. Please ensure that the pixel
\r
540 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
541 have to colourspace convert which may result in additional copying.
\r
542 ################################################################################
\r