2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
\r
4 * This file is part of CasparCG (www.casparcg.com).
\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
19 * Author: Robert Nagy, ronag89@gmail.com
\r
22 #include "../StdAfx.h"
\r
24 #include "decklink_consumer.h"
\r
26 #include "../util/util.h"
\r
28 #include "../interop/DeckLinkAPI_h.h"
\r
30 #include <core/mixer/read_frame.h>
\r
32 #include <common/concurrency/com_context.h>
\r
33 #include <common/diagnostics/graph.h>
\r
34 #include <common/exception/exceptions.h>
\r
35 #include <common/memory/memcpy.h>
\r
36 #include <common/memory/memclr.h>
\r
37 #include <common/memory/memshfl.h>
\r
39 #include <core/consumer/frame_consumer.h>
\r
41 #include <tbb/concurrent_queue.h>
\r
42 #include <tbb/cache_aligned_allocator.h>
\r
44 #include <boost/circular_buffer.hpp>
\r
45 #include <boost/timer.hpp>
\r
46 #include <boost/property_tree/ptree.hpp>
\r
48 namespace caspar { namespace decklink {
\r
50 struct configuration
\r
52 size_t device_index;
\r
53 bool embedded_audio;
\r
57 size_t base_buffer_depth;
\r
58 size_t buffer_depth;
\r
62 , embedded_audio(false)
\r
63 , internal_key(false)
\r
66 , base_buffer_depth(3)
\r
67 , buffer_depth(base_buffer_depth + (low_latency ? 0 : 1) + (embedded_audio ? 1 : 0)){}
\r
70 class decklink_frame : public IDeckLinkVideoFrame
\r
72 tbb::atomic<int> ref_count_;
\r
73 std::shared_ptr<core::read_frame> frame_;
\r
74 const core::video_format_desc format_desc_;
\r
76 const bool key_only_;
\r
77 std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> data_;
\r
79 decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, bool key_only)
\r
81 , format_desc_(format_desc)
\r
82 , key_only_(key_only)
\r
89 STDMETHOD (QueryInterface(REFIID, LPVOID*))
\r
91 return E_NOINTERFACE;
\r
94 STDMETHOD_(ULONG, AddRef())
\r
96 return ++ref_count_;
\r
99 STDMETHOD_(ULONG, Release())
\r
101 if(--ref_count_ == 0)
\r
106 // IDecklinkVideoFrame
\r
108 STDMETHOD_(long, GetWidth()) {return format_desc_.width;}
\r
109 STDMETHOD_(long, GetHeight()) {return format_desc_.height;}
\r
110 STDMETHOD_(long, GetRowBytes()) {return format_desc_.width*4;}
\r
111 STDMETHOD_(BMDPixelFormat, GetPixelFormat()) {return bmdFormat8BitBGRA;}
\r
112 STDMETHOD_(BMDFrameFlags, GetFlags()) {return bmdFrameFlagDefault;}
\r
114 STDMETHOD(GetBytes(void** buffer))
\r
118 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)
\r
120 data_.resize(format_desc_.size, 0);
\r
121 *buffer = data_.data();
\r
127 data_.resize(frame_->image_data().size());
\r
128 fast_memshfl(data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);
\r
130 *buffer = data_.data();
\r
133 *buffer = const_cast<uint8_t*>(frame_->image_data().begin());
\r
137 CASPAR_LOG_CURRENT_EXCEPTION();
\r
144 STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}
\r
145 STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}
\r
149 const boost::iterator_range<const int32_t*> audio_data()
\r
151 return frame_->audio_data();
\r
155 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
157 const int channel_index_;
\r
158 const configuration config_;
\r
160 CComPtr<IDeckLink> decklink_;
\r
161 CComQIPtr<IDeckLinkOutput> output_;
\r
162 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
163 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
165 tbb::spin_mutex exception_mutex_;
\r
166 std::exception_ptr exception_;
\r
168 tbb::atomic<bool> is_running_;
\r
170 const std::wstring model_name_;
\r
171 const core::video_format_desc format_desc_;
\r
172 const size_t buffer_size_;
\r
174 long long video_scheduled_;
\r
175 long long audio_scheduled_;
\r
177 size_t preroll_count_;
\r
179 boost::circular_buffer<std::vector<int32_t>> audio_container_;
\r
181 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;
\r
182 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;
\r
184 safe_ptr<diagnostics::graph> graph_;
\r
185 boost::timer tick_timer_;
\r
188 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index)
\r
189 : channel_index_(channel_index)
\r
191 , decklink_(get_device(config.device_index))
\r
192 , output_(decklink_)
\r
193 , configuration_(decklink_)
\r
194 , keyer_(decklink_)
\r
195 , model_name_(get_model_name(decklink_))
\r
196 , format_desc_(format_desc)
\r
197 , buffer_size_(config.buffer_depth) // Minimum buffer-size 3.
\r
198 , video_scheduled_(0)
\r
199 , audio_scheduled_(0)
\r
200 , preroll_count_(0)
\r
201 , audio_container_(buffer_size_+1)
\r
203 is_running_ = true;
\r
205 video_frame_buffer_.set_capacity(1);
\r
206 audio_frame_buffer_.set_capacity(1);
\r
208 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
\r
209 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
210 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
211 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
\r
212 graph_->set_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));
\r
213 graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));
\r
214 graph_->set_text(print());
\r
215 diagnostics::register_graph(graph_);
\r
217 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
219 if(config.embedded_audio)
\r
222 set_latency(config.low_latency);
\r
223 set_keyer(config.internal_key);
\r
225 if(config.embedded_audio)
\r
226 output_->BeginAudioPreroll();
\r
228 for(size_t n = 0; n < buffer_size_; ++n)
\r
229 schedule_next_video(make_safe<core::read_frame>());
\r
231 if(!config.embedded_audio)
\r
235 ~decklink_consumer()
\r
237 is_running_ = false;
\r
238 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
239 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
241 if(output_ != nullptr)
\r
243 output_->StopScheduledPlayback(0, nullptr, 0);
\r
244 if(config_.embedded_audio)
\r
245 output_->DisableAudioOutput();
\r
246 output_->DisableVideoOutput();
\r
250 void set_latency(bool low_latency)
\r
254 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
255 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
259 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
260 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
264 void set_keyer(bool internal_key)
\r
268 if(FAILED(keyer_->Enable(FALSE)))
\r
269 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
270 else if(FAILED(keyer_->SetLevel(255)))
\r
271 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
273 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
277 if(FAILED(keyer_->Enable(TRUE)))
\r
278 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
279 else if(FAILED(keyer_->SetLevel(255)))
\r
280 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
282 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
286 void enable_audio()
\r
288 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
289 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
291 if(FAILED(output_->SetAudioCallback(this)))
\r
292 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
294 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
297 void enable_video(BMDDisplayMode display_mode)
\r
299 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
300 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
302 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
303 BOOST_THROW_EXCEPTION(caspar_exception()
\r
304 << msg_info(narrow(print()) + " Failed to set playback completion callback.")
\r
305 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
308 void start_playback()
\r
310 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
311 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
314 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
315 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
316 STDMETHOD_(ULONG, Release()) {return 1;}
\r
318 STDMETHOD(ScheduledPlaybackHasStopped())
\r
320 is_running_ = false;
\r
321 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
325 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
332 if(result == bmdOutputFrameDisplayedLate)
\r
334 graph_->set_tag("late-frame");
\r
335 video_scheduled_ += format_desc_.duration;
\r
336 audio_scheduled_ += reinterpret_cast<decklink_frame*>(completed_frame)->audio_data().size()/format_desc_.audio_channels;
\r
337 //++video_scheduled_;
\r
338 //audio_scheduled_ += format_desc_.audio_cadence[0];
\r
339 //++audio_scheduled_;
\r
341 else if(result == bmdOutputFrameDropped)
\r
342 graph_->set_tag("dropped-frame");
\r
343 else if(result == bmdOutputFrameFlushed)
\r
344 graph_->set_tag("flushed-frame");
\r
346 std::shared_ptr<core::read_frame> frame;
\r
347 video_frame_buffer_.pop(frame);
\r
348 schedule_next_video(make_safe_ptr(frame));
\r
350 unsigned long buffered;
\r
351 output_->GetBufferedVideoFrameCount(&buffered);
\r
352 graph_->set_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);
\r
356 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
357 exception_ = std::current_exception();
\r
364 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
373 if(++preroll_count_ >= buffer_size_)
\r
375 output_->EndAudioPreroll();
\r
379 schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()], 0));
\r
383 std::shared_ptr<core::read_frame> frame;
\r
384 audio_frame_buffer_.pop(frame);
\r
385 schedule_next_audio(frame->audio_data());
\r
388 unsigned long buffered;
\r
389 output_->GetBufferedAudioSampleFrameCount(&buffered);
\r
390 graph_->set_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_cadence[0]*2));
\r
394 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
395 exception_ = std::current_exception();
\r
402 template<typename T>
\r
403 void schedule_next_audio(const T& audio_data)
\r
405 const int sample_frame_count = audio_data.size()/format_desc_.audio_channels;
\r
407 audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));
\r
409 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, audio_scheduled_, format_desc_.audio_sample_rate, nullptr)))
\r
410 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
412 audio_scheduled_ += sample_frame_count;
\r
415 void schedule_next_video(const safe_ptr<core::read_frame>& frame)
\r
417 CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));
\r
418 if(FAILED(output_->ScheduleVideoFrame(frame2, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
\r
419 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
421 video_scheduled_ += format_desc_.duration;
\r
423 graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
424 tick_timer_.restart();
\r
427 void send(const safe_ptr<core::read_frame>& frame)
\r
430 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
431 if(exception_ != nullptr)
\r
432 std::rethrow_exception(exception_);
\r
436 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
438 if(config_.embedded_audio)
\r
439 audio_frame_buffer_.push(frame);
\r
440 video_frame_buffer_.push(frame);
\r
443 std::wstring print() const
\r
445 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +
\r
446 boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
450 struct decklink_consumer_proxy : public core::frame_consumer
\r
452 const configuration config_;
\r
453 com_context<decklink_consumer> context_;
\r
454 std::vector<size_t> audio_cadence_;
\r
457 decklink_consumer_proxy(const configuration& config)
\r
459 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
\r
463 ~decklink_consumer_proxy()
\r
467 auto str = print();
\r
469 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";
\r
475 virtual void initialize(const core::video_format_desc& format_desc, int channel_index) override
\r
477 context_.reset([&]{return new decklink_consumer(config_, format_desc, channel_index);});
\r
478 audio_cadence_ = format_desc.audio_cadence;
\r
480 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
483 virtual bool send(const safe_ptr<core::read_frame>& frame) override
\r
485 CASPAR_VERIFY(audio_cadence_.front() == static_cast<size_t>(frame->audio_data().size()));
\r
486 boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);
\r
488 context_->send(frame);
\r
492 virtual std::wstring print() const override
\r
494 return context_ ? context_->print() : L"[decklink_consumer]";
\r
497 virtual boost::property_tree::wptree info() const override
\r
499 boost::property_tree::wptree info;
\r
500 info.add(L"type", L"decklink-consumer");
\r
501 info.add(L"key-only", config_.key_only);
\r
502 info.add(L"device", config_.device_index);
\r
503 info.add(L"low-latency", config_.low_latency);
\r
504 info.add(L"embedded-audio", config_.embedded_audio);
\r
505 info.add(L"low-latency", config_.low_latency);
\r
506 info.add(L"internal-key", config_.internal_key);
\r
510 virtual size_t buffer_depth() const override
\r
512 return config_.buffer_depth;
\r
515 virtual int index() const override
\r
517 return 300 + config_.device_index;
\r
521 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params)
\r
523 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
524 return core::frame_consumer::empty();
\r
526 configuration config;
\r
528 if(params.size() > 1)
\r
529 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
531 config.internal_key = std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end();
\r
532 config.low_latency = std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end();
\r
533 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
534 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
536 return make_safe<decklink_consumer_proxy>(config);
\r
539 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree)
\r
541 configuration config;
\r
543 config.internal_key = ptree.get(L"internal-key", config.internal_key);
\r
544 config.low_latency = ptree.get(L"low-latency", config.low_latency);
\r
545 config.key_only = ptree.get(L"key-only", config.key_only);
\r
546 config.device_index = ptree.get(L"device", config.device_index);
\r
547 config.embedded_audio = ptree.get(L"embedded-audio", config.embedded_audio);
\r
548 config.base_buffer_depth = ptree.get(L"buffer-depth", config.base_buffer_depth);
\r
550 return make_safe<decklink_consumer_proxy>(config);
\r
556 ##############################################################################
\r
562 BMD Developer Support
\r
563 developer@blackmagic-design.com
\r
565 -----------------------------------------------------------------------------
\r
567 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
568 for scheduled playback is three frames for video and four frames for audio.
\r
569 As you mentioned if you preroll less frames then playback will not start or
\r
570 playback will be very sporadic. From our experience with Media Express, we
\r
571 recommended that at least seven frames are prerolled for smooth playback.
\r
573 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
574 There can be around 3 frames worth of latency on scheduled output.
\r
575 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
576 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
577 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
578 guarantee that the provided frame will be output as soon the previous
\r
579 frame output has been completed.
\r
580 ################################################################################
\r
584 ##############################################################################
\r
585 Async DMA Transfer without redundant copying
\r
590 BMD Developer Support
\r
591 developer@blackmagic-design.com
\r
593 -----------------------------------------------------------------------------
\r
595 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
596 and providing a pointer to your video buffer when GetBytes() is called.
\r
597 This may help to keep copying to a minimum. Please ensure that the pixel
\r
598 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
599 have to colourspace convert which may result in additional copying.
\r
600 ################################################################################
\r