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
55 , embedded_audio(false)
\r
56 , external_key(false)
\r
57 , low_latency(false)
\r
61 class decklink_frame_adapter : public IDeckLinkVideoFrame
\r
63 const safe_ptr<const core::read_frame> frame_;
\r
64 const core::video_format_desc format_desc_;
\r
66 decklink_frame_adapter(const safe_ptr<const core::read_frame>& frame, const core::video_format_desc& format_desc)
\r
68 , format_desc_(format_desc){}
\r
70 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
71 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
72 STDMETHOD_(ULONG, Release()) {return 1;}
\r
74 STDMETHOD_(long, GetWidth()) {return format_desc_.width;}
\r
75 STDMETHOD_(long, GetHeight()) {return format_desc_.height;}
\r
76 STDMETHOD_(long, GetRowBytes()) {return format_desc_.width*4;}
\r
77 STDMETHOD_(BMDPixelFormat, GetPixelFormat()){return bmdFormat8BitBGRA;}
\r
78 STDMETHOD_(BMDFrameFlags, GetFlags()) {return bmdFrameFlagDefault;}
\r
80 STDMETHOD(GetBytes(void** buffer))
\r
82 static std::vector<unsigned char> zeros(1920*1080*4, 0);
\r
83 *buffer = const_cast<unsigned char*>(frame_->image_data().begin());
\r
84 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)
\r
85 *buffer = zeros.data();
\r
89 STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}
\r
90 STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}
\r
93 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
95 const configuration config_;
\r
97 CComPtr<IDeckLink> decklink_;
\r
98 CComQIPtr<IDeckLinkOutput> output_;
\r
99 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
100 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
102 std::exception_ptr exception_;
\r
104 tbb::atomic<bool> is_running_;
\r
106 const std::wstring model_name_;
\r
107 const core::video_format_desc format_desc_;
\r
108 const size_t buffer_size_;
\r
110 unsigned long frames_scheduled_;
\r
111 unsigned long audio_scheduled_;
\r
113 std::list<std::shared_ptr<IDeckLinkVideoFrame>> frame_container_; // Must be std::list in order to guarantee that pointers are always valid.
\r
114 boost::circular_buffer<std::vector<short>> audio_container_;
\r
116 tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> video_frame_buffer_;
\r
117 tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> audio_frame_buffer_;
\r
119 std::shared_ptr<diagnostics::graph> graph_;
\r
120 boost::timer tick_timer_;
\r
123 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc)
\r
125 , decklink_(get_device(config.device_index))
\r
126 , output_(decklink_)
\r
127 , configuration_(decklink_)
\r
128 , keyer_(decklink_)
\r
129 , model_name_(get_model_name(decklink_))
\r
130 , format_desc_(format_desc)
\r
131 , buffer_size_(config.embedded_audio ? 5 : 4) // Minimum buffer-size (3 + 1 tolerance).
\r
132 , frames_scheduled_(0)
\r
133 , audio_scheduled_(0)
\r
134 , audio_container_(buffer_size_+1)
\r
136 is_running_ = true;
\r
138 video_frame_buffer_.set_capacity(1);
\r
139 audio_frame_buffer_.set_capacity(1);
\r
141 graph_ = diagnostics::create_graph(narrow(print()));
\r
142 graph_->add_guide("tick-time", 0.5);
\r
143 graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f));
\r
144 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
145 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
146 graph_->set_color("flushed-frame", diagnostics::color(0.3f, 0.3f, 0.6f));
\r
148 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
150 if(config.embedded_audio)
\r
153 set_latency(config.low_latency);
\r
154 set_keyer(config.external_key);
\r
156 for(size_t n = 0; n < buffer_size_; ++n)
\r
157 schedule_next_video(core::read_frame::empty());
\r
159 if(config.embedded_audio)
\r
160 output_->BeginAudioPreroll();
\r
164 CASPAR_LOG(info) << print() << L" Buffer depth: " << buffer_size_;
\r
165 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
168 ~decklink_consumer()
\r
170 is_running_ = false;
\r
171 video_frame_buffer_.try_push(core::read_frame::empty());
\r
172 audio_frame_buffer_.try_push(core::read_frame::empty());
\r
174 if(output_ != nullptr)
\r
176 output_->StopScheduledPlayback(0, nullptr, 0);
\r
177 if(config_.embedded_audio)
\r
178 output_->DisableAudioOutput();
\r
179 output_->DisableVideoOutput();
\r
183 const core::video_format_desc& get_video_format_desc() const
\r
185 return format_desc_;
\r
188 void set_latency(bool low_latency)
\r
192 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
193 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
197 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
198 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
202 void set_keyer(bool external_key)
\r
206 if(FAILED(keyer_->Enable(FALSE)))
\r
207 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
208 else if(FAILED(keyer_->SetLevel(255)))
\r
209 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
211 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
215 if(FAILED(keyer_->Enable(TRUE)))
\r
216 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
217 else if(FAILED(keyer_->SetLevel(255)))
\r
218 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
220 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
224 void enable_audio()
\r
226 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
227 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
229 if(FAILED(output_->SetAudioCallback(this)))
\r
230 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
232 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
235 void enable_video(BMDDisplayMode display_mode)
\r
237 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
238 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
240 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
241 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to set playback completion callback."));
\r
244 void start_playback()
\r
246 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
247 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
250 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
251 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
252 STDMETHOD_(ULONG, Release()) {return 1;}
\r
254 STDMETHOD(ScheduledPlaybackHasStopped())
\r
256 is_running_ = false;
\r
257 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
261 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
268 if(result == bmdOutputFrameDisplayedLate)
\r
269 graph_->add_tag("late-frame");
\r
270 else if(result == bmdOutputFrameDropped)
\r
271 graph_->add_tag("dropped-frame");
\r
272 else if(result == bmdOutputFrameFlushed)
\r
273 graph_->add_tag("flushed-frame");
\r
275 frame_container_.erase(std::find_if(frame_container_.begin(), frame_container_.end(), [&](const std::shared_ptr<IDeckLinkVideoFrame> frame)
\r
277 return frame.get() == completed_frame;
\r
280 std::shared_ptr<const core::read_frame> frame;
\r
281 video_frame_buffer_.pop(frame);
\r
282 schedule_next_video(safe_ptr<const core::read_frame>(frame));
\r
286 exception_ = std::current_exception();
\r
293 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
300 std::shared_ptr<const core::read_frame> frame;
\r
301 audio_frame_buffer_.pop(frame);
\r
302 schedule_next_audio(safe_ptr<const core::read_frame>(frame));
\r
309 exception_ = std::current_exception();
\r
316 void schedule_next_audio(const safe_ptr<const core::read_frame>& frame)
\r
318 static std::vector<short> silence(48000, 0);
\r
320 const int sample_count = format_desc_.audio_samples_per_frame;
\r
321 const int sample_frame_count = sample_count/2;
\r
323 const short* frame_audio_data = frame->audio_data().size() == sample_count ? frame->audio_data().begin() : silence.data();
\r
324 audio_container_.push_back(std::vector<short>(frame_audio_data, frame_audio_data+sample_count));
\r
326 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))
\r
327 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
330 void schedule_next_video(const safe_ptr<const core::read_frame>& frame)
\r
332 frame_container_.push_back(std::make_shared<decklink_frame_adapter>(frame, format_desc_));
\r
333 if(FAILED(output_->ScheduleVideoFrame(frame_container_.back().get(), (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))
\r
334 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
336 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
337 tick_timer_.restart();
\r
340 void send(const safe_ptr<const core::read_frame>& frame)
\r
342 if(exception_ != nullptr)
\r
343 std::rethrow_exception(exception_);
\r
346 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
348 if(config_.embedded_audio)
\r
349 audio_frame_buffer_.push(frame);
\r
350 video_frame_buffer_.push(frame);
\r
353 std::wstring print() const
\r
355 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
359 struct decklink_consumer_proxy : public core::frame_consumer
\r
361 const configuration config_;
\r
363 com_context<decklink_consumer> context_;
\r
366 decklink_consumer_proxy(const configuration& config)
\r
368 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]"){}
\r
370 virtual void initialize(const core::video_format_desc& format_desc)
\r
372 context_.reset([&]{return new decklink_consumer(config_, format_desc);});
\r
375 virtual void send(const safe_ptr<const core::read_frame>& frame)
\r
377 context_->send(frame);
\r
380 virtual std::wstring print() const
\r
382 return context_->print();
\r
385 virtual bool key_only() const
\r
387 return config_.key_only;
\r
390 virtual const core::video_format_desc& get_video_format_desc() const
\r
392 return context_->get_video_format_desc();
\r
396 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params)
\r
398 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
399 return core::frame_consumer::empty();
\r
401 configuration config;
\r
403 if(params.size() > 1)
\r
404 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
406 config.external_key = std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end();
\r
407 config.low_latency = std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end();
\r
408 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
409 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
411 return make_safe<decklink_consumer_proxy>(config);
\r
414 safe_ptr<core::frame_consumer> create_decklink_consumer(const boost::property_tree::ptree& ptree)
\r
416 configuration config;
\r
418 config.external_key = ptree.get("external-key", false);
\r
419 config.low_latency = ptree.get("low-latency", false);
\r
420 config.key_only = ptree.get("key-only", false);
\r
421 config.device_index = ptree.get("device", 0);
\r
422 config.embedded_audio = ptree.get("embedded-audio", false);
\r
424 return make_safe<decklink_consumer_proxy>(config);
\r
430 ##############################################################################
\r
436 BMD Developer Support
\r
437 developer@blackmagic-design.com
\r
439 -----------------------------------------------------------------------------
\r
441 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
442 for scheduled playback is three frames for video and four frames for audio.
\r
443 As you mentioned if you preroll less frames then playback will not start or
\r
444 playback will be very sporadic. From our experience with Media Express, we
\r
445 recommended that at least seven frames are prerolled for smooth playback.
\r
447 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
448 There can be around 3 frames worth of latency on scheduled output.
\r
449 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
450 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
451 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
452 guarantee that the provided frame will be output as soon the previous
\r
453 frame output has been completed.
\r
454 ################################################################################
\r
458 ##############################################################################
\r
459 Async DMA Transfer without redundant copying
\r
464 BMD Developer Support
\r
465 developer@blackmagic-design.com
\r
467 -----------------------------------------------------------------------------
\r
469 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
470 and providing a pointer to your video buffer when GetBytes() is called.
\r
471 This may help to keep copying to a minimum. Please ensure that the pixel
\r
472 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
473 have to colourspace convert which may result in additional copying.
\r
474 ################################################################################
\r