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 const boost::iterator_range<const int32_t*> audio_data()
\r
87 return frame_->audio_data();
\r
90 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
91 STDMETHOD_(ULONG, AddRef())
\r
93 return ++ref_count_;
\r
95 STDMETHOD_(ULONG, Release())
\r
103 STDMETHOD_(long, GetWidth()) {return format_desc_.width;}
\r
104 STDMETHOD_(long, GetHeight()) {return format_desc_.height;}
\r
105 STDMETHOD_(long, GetRowBytes()) {return format_desc_.width*4;}
\r
106 STDMETHOD_(BMDPixelFormat, GetPixelFormat()) {return bmdFormat8BitBGRA;}
\r
107 STDMETHOD_(BMDFrameFlags, GetFlags()) {return bmdFrameFlagDefault;}
\r
109 STDMETHOD(GetBytes(void** buffer))
\r
111 static std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> zeros(1920*1080*4, 0);
\r
112 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)
\r
114 *buffer = zeros.data();
\r
119 *buffer = const_cast<uint8_t*>(frame_->image_data().begin());
\r
122 if(key_data_.empty())
\r
124 key_data_.resize(frame_->image_data().size());
\r
125 fast_memshfl(key_data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);
\r
128 *buffer = key_data_.data();
\r
134 STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}
\r
135 STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}
\r
138 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
140 const int channel_index_;
\r
141 const int sub_index_;
\r
142 const configuration config_;
\r
144 CComPtr<IDeckLink> decklink_;
\r
145 CComQIPtr<IDeckLinkOutput> output_;
\r
146 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
147 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
149 tbb::spin_mutex exception_mutex_;
\r
150 std::exception_ptr exception_;
\r
152 tbb::atomic<bool> is_running_;
\r
154 const std::wstring model_name_;
\r
155 const core::video_format_desc format_desc_;
\r
156 const size_t buffer_size_;
\r
158 long long video_scheduled_;
\r
159 long long audio_scheduled_;
\r
161 size_t preroll_count_;
\r
163 boost::circular_buffer<std::vector<int32_t>> audio_container_;
\r
165 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;
\r
166 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;
\r
168 safe_ptr<diagnostics::graph> graph_;
\r
169 boost::timer tick_timer_;
\r
172 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index, int sub_index)
\r
173 : channel_index_(channel_index)
\r
174 , sub_index_(sub_index)
\r
176 , decklink_(get_device(config.device_index))
\r
177 , output_(decklink_)
\r
178 , configuration_(decklink_)
\r
179 , keyer_(decklink_)
\r
180 , model_name_(get_model_name(decklink_))
\r
181 , format_desc_(format_desc)
\r
182 , buffer_size_(config.buffer_depth) // Minimum buffer-size 3.
\r
183 , video_scheduled_(0)
\r
184 , audio_scheduled_(0)
\r
185 , preroll_count_(0)
\r
186 , audio_container_(buffer_size_+1)
\r
188 is_running_ = true;
\r
190 video_frame_buffer_.set_capacity(1);
\r
191 audio_frame_buffer_.set_capacity(1);
\r
193 graph_->add_guide("tick-time", 0.5);
\r
194 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
\r
195 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
196 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
197 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
\r
198 graph_->set_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));
\r
199 graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));
\r
200 graph_->set_text(print());
\r
201 diagnostics::register_graph(graph_);
\r
203 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
205 if(config.embedded_audio)
\r
208 set_latency(config.low_latency);
\r
209 set_keyer(config.internal_key);
\r
211 if(config.embedded_audio)
\r
212 output_->BeginAudioPreroll();
\r
214 for(size_t n = 0; n < buffer_size_; ++n)
\r
215 schedule_next_video(make_safe<core::read_frame>());
\r
217 if(!config.embedded_audio)
\r
221 ~decklink_consumer()
\r
223 is_running_ = false;
\r
224 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
225 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
227 if(output_ != nullptr)
\r
229 output_->StopScheduledPlayback(0, nullptr, 0);
\r
230 if(config_.embedded_audio)
\r
231 output_->DisableAudioOutput();
\r
232 output_->DisableVideoOutput();
\r
236 void set_latency(bool low_latency)
\r
240 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
241 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
245 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
246 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
250 void set_keyer(bool internal_key)
\r
254 if(FAILED(keyer_->Enable(FALSE)))
\r
255 CASPAR_LOG(error) << print() << L" Failed to enable internal 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 internal keyer.";
\r
263 if(FAILED(keyer_->Enable(TRUE)))
\r
264 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
265 else if(FAILED(keyer_->SetLevel(255)))
\r
266 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
268 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
272 void enable_audio()
\r
274 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
275 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
277 if(FAILED(output_->SetAudioCallback(this)))
\r
278 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
280 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
283 void enable_video(BMDDisplayMode display_mode)
\r
285 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
286 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
288 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
289 BOOST_THROW_EXCEPTION(caspar_exception()
\r
290 << msg_info(narrow(print()) + " Failed to set playback completion callback.")
\r
291 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
294 void start_playback()
\r
296 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
297 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
300 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
301 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
302 STDMETHOD_(ULONG, Release()) {return 1;}
\r
304 STDMETHOD(ScheduledPlaybackHasStopped())
\r
306 is_running_ = false;
\r
307 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
311 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
318 if(result == bmdOutputFrameDisplayedLate)
\r
320 graph_->add_tag("late-frame");
\r
321 video_scheduled_ += format_desc_.duration;
\r
322 audio_scheduled_ += reinterpret_cast<decklink_frame*>(completed_frame)->audio_data().size()/format_desc_.audio_channels;
\r
323 //++video_scheduled_;
\r
324 //audio_scheduled_ += format_desc_.audio_cadence[0];
\r
325 //++audio_scheduled_;
\r
327 else if(result == bmdOutputFrameDropped)
\r
328 graph_->add_tag("dropped-frame");
\r
329 else if(result == bmdOutputFrameFlushed)
\r
330 graph_->add_tag("flushed-frame");
\r
332 std::shared_ptr<core::read_frame> frame;
\r
333 video_frame_buffer_.pop(frame);
\r
334 schedule_next_video(make_safe_ptr(frame));
\r
336 unsigned long buffered;
\r
337 output_->GetBufferedVideoFrameCount(&buffered);
\r
338 graph_->update_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);
\r
342 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
343 exception_ = std::current_exception();
\r
350 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
359 if(++preroll_count_ >= buffer_size_)
\r
361 output_->EndAudioPreroll();
\r
365 schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()], 0));
\r
369 std::shared_ptr<core::read_frame> frame;
\r
370 audio_frame_buffer_.pop(frame);
\r
371 schedule_next_audio(frame->audio_data());
\r
374 unsigned long buffered;
\r
375 output_->GetBufferedAudioSampleFrameCount(&buffered);
\r
376 graph_->update_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_cadence[0]*2));
\r
380 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
381 exception_ = std::current_exception();
\r
388 template<typename T>
\r
389 void schedule_next_audio(const T& audio_data)
\r
391 const int sample_frame_count = audio_data.size()/format_desc_.audio_channels;
\r
393 audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));
\r
395 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, audio_scheduled_, format_desc_.audio_sample_rate, nullptr)))
\r
396 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
398 audio_scheduled_ += sample_frame_count;
\r
401 void schedule_next_video(const safe_ptr<core::read_frame>& frame)
\r
403 CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));
\r
404 if(FAILED(output_->ScheduleVideoFrame(frame2, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
\r
405 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
407 video_scheduled_ += format_desc_.duration;
\r
409 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
410 tick_timer_.restart();
\r
413 void send(const safe_ptr<core::read_frame>& frame)
\r
416 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
417 if(exception_ != nullptr)
\r
418 std::rethrow_exception(exception_);
\r
422 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
424 if(config_.embedded_audio)
\r
425 audio_frame_buffer_.push(frame);
\r
426 video_frame_buffer_.push(frame);
\r
429 std::wstring print() const
\r
431 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" + boost::lexical_cast<std::wstring>(sub_index_) + L"|device " +
\r
432 boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
436 struct decklink_consumer_proxy : public core::frame_consumer
\r
438 const configuration config_;
\r
439 com_context<decklink_consumer> context_;
\r
440 std::vector<size_t> audio_cadence_;
\r
443 decklink_consumer_proxy(const configuration& config)
\r
445 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
\r
449 ~decklink_consumer_proxy()
\r
453 auto str = print();
\r
455 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";
\r
461 virtual void initialize(const core::video_format_desc& format_desc, int channel_index, int sub_index) override
\r
463 context_.reset([&]{return new decklink_consumer(config_, format_desc, channel_index, sub_index);});
\r
464 audio_cadence_ = format_desc.audio_cadence;
\r
466 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
469 virtual bool send(const safe_ptr<core::read_frame>& frame) override
\r
471 CASPAR_VERIFY(audio_cadence_.front() == static_cast<size_t>(frame->audio_data().size()));
\r
472 boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);
\r
474 context_->send(frame);
\r
478 virtual std::wstring print() const override
\r
480 return context_ ? context_->print() : L"[decklink_consumer]";
\r
483 virtual size_t buffer_depth() const override
\r
485 return config_.buffer_depth;
\r
489 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params)
\r
491 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
492 return core::frame_consumer::empty();
\r
494 configuration config;
\r
496 if(params.size() > 1)
\r
497 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
499 config.internal_key = std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end();
\r
500 config.low_latency = std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end();
\r
501 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
502 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
504 return make_safe<decklink_consumer_proxy>(config);
\r
507 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::ptree& ptree)
\r
509 configuration config;
\r
511 config.internal_key = ptree.get("internal-key", config.internal_key);
\r
512 config.low_latency = ptree.get("low-latency", config.low_latency);
\r
513 config.key_only = ptree.get("key-only", config.key_only);
\r
514 config.device_index = ptree.get("device", config.device_index);
\r
515 config.embedded_audio = ptree.get("embedded-audio", config.embedded_audio);
\r
516 config.base_buffer_depth = ptree.get("buffer-depth", config.base_buffer_depth);
\r
518 return make_safe<decklink_consumer_proxy>(config);
\r
524 ##############################################################################
\r
530 BMD Developer Support
\r
531 developer@blackmagic-design.com
\r
533 -----------------------------------------------------------------------------
\r
535 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
536 for scheduled playback is three frames for video and four frames for audio.
\r
537 As you mentioned if you preroll less frames then playback will not start or
\r
538 playback will be very sporadic. From our experience with Media Express, we
\r
539 recommended that at least seven frames are prerolled for smooth playback.
\r
541 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
542 There can be around 3 frames worth of latency on scheduled output.
\r
543 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
544 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
545 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
546 guarantee that the provided frame will be output as soon the previous
\r
547 frame output has been completed.
\r
548 ################################################################################
\r
552 ##############################################################################
\r
553 Async DMA Transfer without redundant copying
\r
558 BMD Developer Support
\r
559 developer@blackmagic-design.com
\r
561 -----------------------------------------------------------------------------
\r
563 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
564 and providing a pointer to your video buffer when GetBytes() is called.
\r
565 This may help to keep copying to a minimum. Please ensure that the pixel
\r
566 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
567 have to colourspace convert which may result in additional copying.
\r
568 ################################################################################
\r