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_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));
\r
190 graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));
\r
191 graph_->set_text(print());
\r
192 diagnostics::register_graph(graph_);
\r
194 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
196 if(config.embedded_audio)
\r
199 set_latency(config.low_latency);
\r
200 set_keyer(config.internal_key);
\r
202 if(config.embedded_audio)
\r
203 output_->BeginAudioPreroll();
\r
205 for(size_t n = 0; n < buffer_size_; ++n)
\r
206 schedule_next_video(make_safe<core::read_frame>());
\r
208 if(!config.embedded_audio)
\r
212 ~decklink_consumer()
\r
214 is_running_ = false;
\r
215 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
216 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
218 if(output_ != nullptr)
\r
220 output_->StopScheduledPlayback(0, nullptr, 0);
\r
221 if(config_.embedded_audio)
\r
222 output_->DisableAudioOutput();
\r
223 output_->DisableVideoOutput();
\r
227 void set_latency(bool low_latency)
\r
231 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
232 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
236 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
237 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
241 void set_keyer(bool internal_key)
\r
245 if(FAILED(keyer_->Enable(FALSE)))
\r
246 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
247 else if(FAILED(keyer_->SetLevel(255)))
\r
248 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
250 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
254 if(FAILED(keyer_->Enable(TRUE)))
\r
255 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
256 else if(FAILED(keyer_->SetLevel(255)))
\r
257 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
259 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
263 void enable_audio()
\r
265 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
266 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
268 if(FAILED(output_->SetAudioCallback(this)))
\r
269 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
271 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
274 void enable_video(BMDDisplayMode display_mode)
\r
276 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
277 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
279 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
280 BOOST_THROW_EXCEPTION(caspar_exception()
\r
281 << msg_info(narrow(print()) + " Failed to set playback completion callback.")
\r
282 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
285 void start_playback()
\r
287 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
288 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
291 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
292 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
293 STDMETHOD_(ULONG, Release()) {return 1;}
\r
295 STDMETHOD(ScheduledPlaybackHasStopped())
\r
297 is_running_ = false;
\r
298 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
302 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
309 if(result == bmdOutputFrameDisplayedLate)
\r
311 graph_->add_tag("late-frame");
\r
312 ++frames_scheduled_;
\r
313 ++audio_scheduled_;
\r
315 else if(result == bmdOutputFrameDropped)
\r
316 graph_->add_tag("dropped-frame");
\r
317 else if(result == bmdOutputFrameFlushed)
\r
318 graph_->add_tag("flushed-frame");
\r
320 std::shared_ptr<core::read_frame> frame;
\r
321 video_frame_buffer_.pop(frame);
\r
322 schedule_next_video(make_safe_ptr(frame));
\r
324 unsigned long buffered;
\r
325 output_->GetBufferedVideoFrameCount(&buffered);
\r
326 graph_->update_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);
\r
330 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
331 exception_ = std::current_exception();
\r
338 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
347 if(++preroll_count_ >= buffer_size_)
\r
349 output_->EndAudioPreroll();
\r
353 schedule_next_audio(make_safe<core::read_frame>());
\r
357 std::shared_ptr<core::read_frame> frame;
\r
358 audio_frame_buffer_.pop(frame);
\r
359 schedule_next_audio(make_safe_ptr(frame));
\r
362 unsigned long buffered;
\r
363 output_->GetBufferedAudioSampleFrameCount(&buffered);
\r
364 graph_->update_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_samples_per_frame*2));
\r
368 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
369 exception_ = std::current_exception();
\r
376 void schedule_next_audio(const safe_ptr<core::read_frame>& frame)
\r
378 const int sample_frame_count = frame->audio_data().size()/format_desc_.audio_channels;
\r
380 audio_container_.push_back(std::vector<int32_t>(frame->audio_data().begin(), frame->audio_data().end()));
\r
382 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))
\r
383 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
386 void schedule_next_video(const safe_ptr<core::read_frame>& frame)
\r
388 CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));
\r
389 if(FAILED(output_->ScheduleVideoFrame(frame2, (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))
\r
390 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
392 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
393 tick_timer_.restart();
\r
396 void send(const safe_ptr<core::read_frame>& frame)
\r
399 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
400 if(exception_ != nullptr)
\r
401 std::rethrow_exception(exception_);
\r
405 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
407 if(config_.embedded_audio)
\r
408 audio_frame_buffer_.push(frame);
\r
409 video_frame_buffer_.push(frame);
\r
412 std::wstring print() const
\r
414 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
418 struct decklink_consumer_proxy : public core::frame_consumer
\r
420 const configuration config_;
\r
421 com_context<decklink_consumer> context_;
\r
424 decklink_consumer_proxy(const configuration& config)
\r
426 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
\r
430 ~decklink_consumer_proxy()
\r
432 auto str = print();
\r
434 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";
\r
437 virtual void initialize(const core::video_format_desc& format_desc)
\r
439 context_.reset([&]{return new decklink_consumer(config_, format_desc);});
\r
441 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
444 virtual bool send(const safe_ptr<core::read_frame>& frame)
\r
446 context_->send(frame);
\r
450 virtual std::wstring print() const
\r
452 return context_ ? context_->print() : L"decklink_consumer";
\r
455 virtual size_t buffer_depth() const
\r
457 return config_.buffer_depth;
\r
461 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params)
\r
463 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
464 return core::frame_consumer::empty();
\r
466 configuration config;
\r
468 if(params.size() > 1)
\r
469 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
471 config.internal_key = std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end();
\r
472 config.low_latency = std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end();
\r
473 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
474 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
476 return make_safe<decklink_consumer_proxy>(config);
\r
479 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::ptree& ptree)
\r
481 configuration config;
\r
483 config.internal_key = ptree.get("internal-key", config.internal_key);
\r
484 config.low_latency = ptree.get("low-latency", config.low_latency);
\r
485 config.key_only = ptree.get("key-only", config.key_only);
\r
486 config.device_index = ptree.get("device", config.device_index);
\r
487 config.embedded_audio = ptree.get("embedded-audio", config.embedded_audio);
\r
488 config.base_buffer_depth = ptree.get("buffer-depth", config.base_buffer_depth);
\r
490 return make_safe<decklink_consumer_proxy>(config);
\r
496 ##############################################################################
\r
502 BMD Developer Support
\r
503 developer@blackmagic-design.com
\r
505 -----------------------------------------------------------------------------
\r
507 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
508 for scheduled playback is three frames for video and four frames for audio.
\r
509 As you mentioned if you preroll less frames then playback will not start or
\r
510 playback will be very sporadic. From our experience with Media Express, we
\r
511 recommended that at least seven frames are prerolled for smooth playback.
\r
513 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
514 There can be around 3 frames worth of latency on scheduled output.
\r
515 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
516 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
517 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
518 guarantee that the provided frame will be output as soon the previous
\r
519 frame output has been completed.
\r
520 ################################################################################
\r
524 ##############################################################################
\r
525 Async DMA Transfer without redundant copying
\r
530 BMD Developer Support
\r
531 developer@blackmagic-design.com
\r
533 -----------------------------------------------------------------------------
\r
535 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
536 and providing a pointer to your video buffer when GetBytes() is called.
\r
537 This may help to keep copying to a minimum. Please ensure that the pixel
\r
538 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
539 have to colourspace convert which may result in additional copying.
\r
540 ################################################################################
\r