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 <tbb/concurrent_queue.h>
\r
40 #include <boost/circular_buffer.hpp>
\r
41 #include <boost/timer.hpp>
\r
45 struct configuration
\r
47 size_t device_index;
\r
48 bool embedded_audio;
\r
52 size_t buffer_depth;
\r
56 , embedded_audio(false)
\r
57 , external_key(false)
\r
58 , low_latency(false)
\r
60 , buffer_depth(CONSUMER_BUFFER_DEPTH){}
\r
63 class decklink_frame_adapter : public IDeckLinkVideoFrame
\r
65 const safe_ptr<const core::read_frame> frame_;
\r
66 const core::video_format_desc format_desc_;
\r
68 decklink_frame_adapter(const safe_ptr<const core::read_frame>& frame, const core::video_format_desc& format_desc)
\r
70 , format_desc_(format_desc){}
\r
72 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
73 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
74 STDMETHOD_(ULONG, Release()) {return 1;}
\r
76 STDMETHOD_(long, GetWidth()) {return format_desc_.width;}
\r
77 STDMETHOD_(long, GetHeight()) {return format_desc_.height;}
\r
78 STDMETHOD_(long, GetRowBytes()) {return format_desc_.width*4;}
\r
79 STDMETHOD_(BMDPixelFormat, GetPixelFormat()){return bmdFormat8BitBGRA;}
\r
80 STDMETHOD_(BMDFrameFlags, GetFlags()) {return bmdFrameFlagDefault;}
\r
82 STDMETHOD(GetBytes(void** buffer))
\r
84 static std::vector<uint8_t> zeros(1920*1080*4, 0);
\r
85 *buffer = const_cast<uint8_t*>(frame_->image_data().begin());
\r
86 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)
\r
87 *buffer = zeros.data();
\r
91 STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}
\r
92 STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}
\r
95 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
97 const configuration config_;
\r
99 CComPtr<IDeckLink> decklink_;
\r
100 CComQIPtr<IDeckLinkOutput> output_;
\r
101 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
102 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
104 std::exception_ptr exception_;
\r
106 tbb::atomic<bool> is_running_;
\r
108 const std::wstring model_name_;
\r
109 const core::video_format_desc format_desc_;
\r
110 const size_t buffer_size_;
\r
112 unsigned long frames_scheduled_;
\r
113 unsigned long audio_scheduled_;
\r
115 size_t preroll_count_;
\r
117 std::list<std::shared_ptr<IDeckLinkVideoFrame>> frame_container_; // Must be std::list in order to guarantee that pointers are always valid.
\r
118 boost::circular_buffer<std::vector<short>> audio_container_;
\r
120 tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> video_frame_buffer_;
\r
121 tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> audio_frame_buffer_;
\r
123 std::shared_ptr<diagnostics::graph> graph_;
\r
124 boost::timer tick_timer_;
\r
127 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc)
\r
129 , decklink_(get_device(config.device_index))
\r
130 , output_(decklink_)
\r
131 , configuration_(decklink_)
\r
132 , keyer_(decklink_)
\r
133 , model_name_(get_model_name(decklink_))
\r
134 , format_desc_(format_desc)
\r
135 , buffer_size_(config.embedded_audio ? config.buffer_depth + 1 : config.buffer_depth) // Minimum buffer-size 3.
\r
136 , frames_scheduled_(0)
\r
137 , audio_scheduled_(0)
\r
138 , preroll_count_(0)
\r
139 , audio_container_(buffer_size_+1)
\r
141 is_running_ = true;
\r
143 video_frame_buffer_.set_capacity(1);
\r
144 audio_frame_buffer_.set_capacity(1);
\r
146 graph_ = diagnostics::create_graph(narrow(print()));
\r
147 graph_->add_guide("tick-time", 0.5);
\r
148 graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f));
\r
149 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
150 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
151 graph_->set_color("flushed-frame", diagnostics::color(0.3f, 0.3f, 0.6f));
\r
153 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
155 if(config.embedded_audio)
\r
158 set_latency(config.low_latency);
\r
159 set_keyer(config.external_key);
\r
161 if(config.embedded_audio)
\r
162 output_->BeginAudioPreroll();
\r
164 for(size_t n = 0; n < buffer_size_; ++n)
\r
165 schedule_next_video(make_safe<core::read_frame>());
\r
167 if(!config.embedded_audio)
\r
170 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
173 ~decklink_consumer()
\r
175 is_running_ = false;
\r
176 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
177 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
179 if(output_ != nullptr)
\r
181 output_->StopScheduledPlayback(0, nullptr, 0);
\r
182 if(config_.embedded_audio)
\r
183 output_->DisableAudioOutput();
\r
184 output_->DisableVideoOutput();
\r
188 const core::video_format_desc& get_video_format_desc() const
\r
190 return format_desc_;
\r
193 void set_latency(bool low_latency)
\r
197 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
198 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
202 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
203 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
207 void set_keyer(bool external_key)
\r
211 if(FAILED(keyer_->Enable(FALSE)))
\r
212 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
213 else if(FAILED(keyer_->SetLevel(255)))
\r
214 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
216 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
220 if(FAILED(keyer_->Enable(TRUE)))
\r
221 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
222 else if(FAILED(keyer_->SetLevel(255)))
\r
223 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
225 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
229 void enable_audio()
\r
231 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
232 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
234 if(FAILED(output_->SetAudioCallback(this)))
\r
235 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
237 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
240 void enable_video(BMDDisplayMode display_mode)
\r
242 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
243 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
245 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
246 BOOST_THROW_EXCEPTION(caspar_exception()
\r
247 << msg_info(narrow(print()) + " Failed to set playback completion callback.")
\r
248 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
251 void start_playback()
\r
253 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
254 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
257 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
258 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
259 STDMETHOD_(ULONG, Release()) {return 1;}
\r
261 STDMETHOD(ScheduledPlaybackHasStopped())
\r
263 is_running_ = false;
\r
264 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
268 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
275 if(result == bmdOutputFrameDisplayedLate)
\r
277 graph_->add_tag("late-frame");
\r
278 ++frames_scheduled_;
\r
279 ++audio_scheduled_;
\r
281 else if(result == bmdOutputFrameDropped)
\r
282 graph_->add_tag("dropped-frame");
\r
283 else if(result == bmdOutputFrameFlushed)
\r
284 graph_->add_tag("flushed-frame");
\r
286 frame_container_.erase(std::find_if(frame_container_.begin(), frame_container_.end(), [&](const std::shared_ptr<IDeckLinkVideoFrame>& frame)
\r
288 return frame.get() == completed_frame;
\r
291 std::shared_ptr<const core::read_frame> frame;
\r
292 video_frame_buffer_.pop(frame);
\r
293 schedule_next_video(make_safe(frame));
\r
297 exception_ = std::current_exception();
\r
304 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
313 if(++preroll_count_ >= buffer_size_)
\r
315 output_->EndAudioPreroll();
\r
319 schedule_next_audio(make_safe<core::read_frame>());
\r
323 std::shared_ptr<const core::read_frame> frame;
\r
324 audio_frame_buffer_.pop(frame);
\r
325 schedule_next_audio(make_safe(frame));
\r
330 exception_ = std::current_exception();
\r
337 void schedule_next_audio(const safe_ptr<const core::read_frame>& frame)
\r
339 static std::vector<short> silence(48000, 0);
\r
341 const int sample_count = format_desc_.audio_samples_per_frame;
\r
342 const int sample_frame_count = sample_count/2;
\r
344 const int16_t* frame_audio_data = frame->audio_data().size() == sample_count ? frame->audio_data().begin() : silence.data();
\r
345 audio_container_.push_back(std::vector<int16_t>(frame_audio_data, frame_audio_data+sample_count));
\r
347 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))
\r
348 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
351 void schedule_next_video(const safe_ptr<const core::read_frame>& frame)
\r
353 frame_container_.push_back(std::make_shared<decklink_frame_adapter>(frame, format_desc_));
\r
354 if(FAILED(output_->ScheduleVideoFrame(frame_container_.back().get(), (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))
\r
355 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
357 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
358 tick_timer_.restart();
\r
361 void send(const safe_ptr<const core::read_frame>& frame)
\r
363 if(exception_ != nullptr)
\r
364 std::rethrow_exception(exception_);
\r
367 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
369 if(config_.embedded_audio)
\r
370 audio_frame_buffer_.push(frame);
\r
371 video_frame_buffer_.push(frame);
\r
374 std::wstring print() const
\r
376 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
380 struct decklink_consumer_proxy : public core::frame_consumer
\r
382 const configuration config_;
\r
384 com_context<decklink_consumer> context_;
\r
387 decklink_consumer_proxy(const configuration& config)
\r
389 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]"){}
\r
391 virtual void initialize(const core::video_format_desc& format_desc)
\r
393 context_.reset([&]{return new decklink_consumer(config_, format_desc);});
\r
396 virtual void send(const safe_ptr<const core::read_frame>& frame)
\r
398 context_->send(frame);
\r
401 virtual std::wstring print() const
\r
403 return context_->print();
\r
406 virtual bool key_only() const
\r
408 return config_.key_only;
\r
411 virtual const core::video_format_desc& get_video_format_desc() const
\r
413 return context_->get_video_format_desc();
\r
416 virtual size_t buffer_depth() const
\r
418 return context_->buffer_size_;
\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