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
42 #include <boost/circular_buffer.hpp>
\r
43 #include <boost/timer.hpp>
\r
47 struct configuration
\r
49 size_t device_index;
\r
50 bool embedded_audio;
\r
54 size_t buffer_depth;
\r
58 , embedded_audio(false)
\r
59 , external_key(false)
\r
60 , low_latency(false)
\r
62 , buffer_depth(core::consumer_buffer_depth()){}
\r
65 class decklink_frame : public IDeckLinkVideoFrame
\r
67 const safe_ptr<core::read_frame> frame_;
\r
68 const core::video_format_desc format_desc_;
\r
70 decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc)
\r
72 , format_desc_(format_desc){}
\r
74 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
75 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
76 STDMETHOD_(ULONG, Release()) {return 1;}
\r
78 STDMETHOD_(long, GetWidth()) {return format_desc_.width;}
\r
79 STDMETHOD_(long, GetHeight()) {return format_desc_.height;}
\r
80 STDMETHOD_(long, GetRowBytes()) {return format_desc_.width*4;}
\r
81 STDMETHOD_(BMDPixelFormat, GetPixelFormat()){return bmdFormat8BitBGRA;}
\r
82 STDMETHOD_(BMDFrameFlags, GetFlags()) {return bmdFrameFlagDefault;}
\r
84 STDMETHOD(GetBytes(void** buffer))
\r
86 static std::vector<uint8_t> zeros(1920*1080*4, 0);
\r
87 *buffer = const_cast<uint8_t*>(frame_->image_data().begin());
\r
88 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)
\r
89 *buffer = zeros.data();
\r
93 STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}
\r
94 STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}
\r
97 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
99 const configuration config_;
\r
101 CComPtr<IDeckLink> decklink_;
\r
102 CComQIPtr<IDeckLinkOutput> output_;
\r
103 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
104 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
106 tbb::spin_mutex exception_mutex_;
\r
107 std::exception_ptr exception_;
\r
109 tbb::atomic<bool> is_running_;
\r
111 const std::wstring model_name_;
\r
112 const core::video_format_desc format_desc_;
\r
113 const size_t buffer_size_;
\r
115 long long frames_scheduled_;
\r
116 long long audio_scheduled_;
\r
118 size_t preroll_count_;
\r
120 std::list<std::shared_ptr<IDeckLinkVideoFrame>> frame_container_; // Must be std::list in order to guarantee that pointers are always valid.
\r
121 boost::circular_buffer<std::vector<int16_t>> audio_container_;
\r
123 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;
\r
124 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;
\r
126 std::shared_ptr<diagnostics::graph> graph_;
\r
127 boost::timer tick_timer_;
\r
130 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc)
\r
132 , decklink_(get_device(config.device_index))
\r
133 , output_(decklink_)
\r
134 , configuration_(decklink_)
\r
135 , keyer_(decklink_)
\r
136 , model_name_(get_model_name(decklink_))
\r
137 , format_desc_(format_desc)
\r
138 , buffer_size_(config.embedded_audio ? config.buffer_depth + 1 : config.buffer_depth) // Minimum buffer-size 3.
\r
139 , frames_scheduled_(0)
\r
140 , audio_scheduled_(0)
\r
141 , preroll_count_(0)
\r
142 , audio_container_(buffer_size_+1)
\r
144 is_running_ = true;
\r
146 video_frame_buffer_.set_capacity(1);
\r
147 audio_frame_buffer_.set_capacity(1);
\r
149 graph_ = diagnostics::create_graph(narrow(print()));
\r
150 graph_->add_guide("tick-time", 0.5);
\r
151 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
\r
152 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
153 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
154 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
\r
156 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
158 if(config.embedded_audio)
\r
161 set_latency(config.low_latency);
\r
162 set_keyer(config.external_key);
\r
164 if(config.embedded_audio)
\r
165 output_->BeginAudioPreroll();
\r
167 for(size_t n = 0; n < buffer_size_; ++n)
\r
168 schedule_next_video(make_safe<core::read_frame>());
\r
170 if(!config.embedded_audio)
\r
173 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
176 ~decklink_consumer()
\r
178 is_running_ = false;
\r
179 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
180 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
182 if(output_ != nullptr)
\r
184 output_->StopScheduledPlayback(0, nullptr, 0);
\r
185 if(config_.embedded_audio)
\r
186 output_->DisableAudioOutput();
\r
187 output_->DisableVideoOutput();
\r
191 const core::video_format_desc& get_video_format_desc() const
\r
193 return format_desc_;
\r
196 void set_latency(bool low_latency)
\r
200 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
201 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
205 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
206 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
210 void set_keyer(bool external_key)
\r
214 if(FAILED(keyer_->Enable(FALSE)))
\r
215 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
216 else if(FAILED(keyer_->SetLevel(255)))
\r
217 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
219 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
223 if(FAILED(keyer_->Enable(TRUE)))
\r
224 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
225 else if(FAILED(keyer_->SetLevel(255)))
\r
226 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
228 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
232 void enable_audio()
\r
234 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
235 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
237 if(FAILED(output_->SetAudioCallback(this)))
\r
238 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
240 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
243 void enable_video(BMDDisplayMode display_mode)
\r
245 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
246 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
248 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
249 BOOST_THROW_EXCEPTION(caspar_exception()
\r
250 << msg_info(narrow(print()) + " Failed to set playback completion callback.")
\r
251 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
254 void start_playback()
\r
256 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
257 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
260 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
261 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
262 STDMETHOD_(ULONG, Release()) {return 1;}
\r
264 STDMETHOD(ScheduledPlaybackHasStopped())
\r
266 is_running_ = false;
\r
267 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
271 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
278 if(result == bmdOutputFrameDisplayedLate)
\r
280 graph_->add_tag("late-frame");
\r
281 ++frames_scheduled_;
\r
282 ++audio_scheduled_;
\r
284 else if(result == bmdOutputFrameDropped)
\r
285 graph_->add_tag("dropped-frame");
\r
286 else if(result == bmdOutputFrameFlushed)
\r
287 graph_->add_tag("flushed-frame");
\r
289 frame_container_.erase(std::find_if(frame_container_.begin(), frame_container_.end(), [&](const std::shared_ptr<IDeckLinkVideoFrame>& frame)
\r
291 return frame.get() == completed_frame;
\r
294 std::shared_ptr<core::read_frame> frame;
\r
295 video_frame_buffer_.pop(frame);
\r
296 schedule_next_video(make_safe(frame));
\r
300 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
301 exception_ = std::current_exception();
\r
308 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
317 if(++preroll_count_ >= buffer_size_)
\r
319 output_->EndAudioPreroll();
\r
323 schedule_next_audio(make_safe<core::read_frame>());
\r
327 std::shared_ptr<core::read_frame> frame;
\r
328 audio_frame_buffer_.pop(frame);
\r
329 schedule_next_audio(make_safe(frame));
\r
334 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
335 exception_ = std::current_exception();
\r
342 void schedule_next_audio(const safe_ptr<core::read_frame>& frame)
\r
344 const int sample_frame_count = frame->audio_data().size()/format_desc_.audio_channels;
\r
346 audio_container_.push_back(std::vector<int16_t>(frame->audio_data().begin(), frame->audio_data().end()));
\r
348 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))
\r
349 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
352 void schedule_next_video(const safe_ptr<core::read_frame>& frame)
\r
354 frame_container_.push_back(std::make_shared<decklink_frame>(frame, format_desc_));
\r
355 if(FAILED(output_->ScheduleVideoFrame(frame_container_.back().get(), (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))
\r
356 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
358 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
359 tick_timer_.restart();
\r
362 void send(const safe_ptr<core::read_frame>& frame)
\r
365 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
366 if(exception_ != nullptr)
\r
367 std::rethrow_exception(exception_);
\r
371 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
373 if(config_.embedded_audio)
\r
374 audio_frame_buffer_.push(frame);
\r
375 video_frame_buffer_.push(frame);
\r
378 std::wstring print() const
\r
380 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
384 struct decklink_consumer_proxy : public core::frame_consumer
\r
386 const configuration config_;
\r
388 com_context<decklink_consumer> context_;
\r
391 decklink_consumer_proxy(const configuration& config)
\r
393 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]"){}
\r
395 virtual void initialize(const core::video_format_desc& format_desc)
\r
397 context_.reset([&]{return new decklink_consumer(config_, format_desc);});
\r
400 virtual bool send(const safe_ptr<core::read_frame>& frame)
\r
402 context_->send(frame);
\r
406 virtual std::wstring print() const
\r
408 return context_->print();
\r
411 virtual bool key_only() const
\r
413 return config_.key_only;
\r
416 virtual const core::video_format_desc& get_video_format_desc() const
\r
418 return context_->get_video_format_desc();
\r
422 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params)
\r
424 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
425 return core::frame_consumer::empty();
\r
427 configuration config;
\r
429 if(params.size() > 1)
\r
430 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
432 config.external_key = std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end();
\r
433 config.low_latency = std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end();
\r
434 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
435 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
437 return make_safe<decklink_consumer_proxy>(config);
\r
440 safe_ptr<core::frame_consumer> create_decklink_consumer(const boost::property_tree::ptree& ptree)
\r
442 configuration config;
\r
444 config.external_key = ptree.get("external-key", config.external_key);
\r
445 config.low_latency = ptree.get("low-latency", config.low_latency);
\r
446 config.key_only = ptree.get("key-only", config.key_only);
\r
447 config.device_index = ptree.get("device", config.device_index);
\r
448 config.embedded_audio = ptree.get("embedded-audio", config.embedded_audio);
\r
450 return make_safe<decklink_consumer_proxy>(config);
\r
456 ##############################################################################
\r
462 BMD Developer Support
\r
463 developer@blackmagic-design.com
\r
465 -----------------------------------------------------------------------------
\r
467 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
468 for scheduled playback is three frames for video and four frames for audio.
\r
469 As you mentioned if you preroll less frames then playback will not start or
\r
470 playback will be very sporadic. From our experience with Media Express, we
\r
471 recommended that at least seven frames are prerolled for smooth playback.
\r
473 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
474 There can be around 3 frames worth of latency on scheduled output.
\r
475 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
476 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
477 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
478 guarantee that the provided frame will be output as soon the previous
\r
479 frame output has been completed.
\r
480 ################################################################################
\r
484 ##############################################################################
\r
485 Async DMA Transfer without redundant copying
\r
490 BMD Developer Support
\r
491 developer@blackmagic-design.com
\r
493 -----------------------------------------------------------------------------
\r
495 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
496 and providing a pointer to your video buffer when GetBytes() is called.
\r
497 This may help to keep copying to a minimum. Please ensure that the pixel
\r
498 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
499 have to colourspace convert which may result in additional copying.
\r
500 ################################################################################
\r