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_muxer : public IDeckLinkVideoFrame
\r
67 const safe_ptr<core::read_frame> frame_;
\r
68 const core::video_format_desc format_desc_;
\r
70 decklink_frame_muxer(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 std::exception_ptr exception_;
\r
108 tbb::atomic<bool> is_running_;
\r
110 const std::wstring model_name_;
\r
111 const core::video_format_desc format_desc_;
\r
112 const size_t buffer_size_;
\r
114 unsigned long frames_scheduled_;
\r
115 unsigned long audio_scheduled_;
\r
117 size_t preroll_count_;
\r
119 std::list<std::shared_ptr<IDeckLinkVideoFrame>> frame_container_; // Must be std::list in order to guarantee that pointers are always valid.
\r
120 boost::circular_buffer<std::vector<int16_t>> audio_container_;
\r
122 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;
\r
123 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;
\r
125 std::shared_ptr<diagnostics::graph> graph_;
\r
126 boost::timer tick_timer_;
\r
129 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc)
\r
131 , decklink_(get_device(config.device_index))
\r
132 , output_(decklink_)
\r
133 , configuration_(decklink_)
\r
134 , keyer_(decklink_)
\r
135 , model_name_(get_model_name(decklink_))
\r
136 , format_desc_(format_desc)
\r
137 , buffer_size_(config.embedded_audio ? config.buffer_depth + 1 : config.buffer_depth) // Minimum buffer-size 3.
\r
138 , frames_scheduled_(0)
\r
139 , audio_scheduled_(0)
\r
140 , preroll_count_(0)
\r
141 , audio_container_(buffer_size_+1)
\r
143 is_running_ = true;
\r
145 video_frame_buffer_.set_capacity(1);
\r
146 audio_frame_buffer_.set_capacity(1);
\r
148 graph_ = diagnostics::create_graph(narrow(print()));
\r
149 graph_->add_guide("tick-time", 0.5);
\r
150 graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f));
\r
151 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
152 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
153 graph_->set_color("flushed-frame", diagnostics::color(0.3f, 0.3f, 0.6f));
\r
155 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
157 if(config.embedded_audio)
\r
160 set_latency(config.low_latency);
\r
161 set_keyer(config.external_key);
\r
163 if(config.embedded_audio)
\r
164 output_->BeginAudioPreroll();
\r
166 for(size_t n = 0; n < buffer_size_; ++n)
\r
167 schedule_next_video(make_safe<core::read_frame>());
\r
169 if(!config.embedded_audio)
\r
172 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
175 ~decklink_consumer()
\r
177 is_running_ = false;
\r
178 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
179 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
181 if(output_ != nullptr)
\r
183 output_->StopScheduledPlayback(0, nullptr, 0);
\r
184 if(config_.embedded_audio)
\r
185 output_->DisableAudioOutput();
\r
186 output_->DisableVideoOutput();
\r
190 const core::video_format_desc& get_video_format_desc() const
\r
192 return format_desc_;
\r
195 void set_latency(bool low_latency)
\r
199 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
200 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
204 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
205 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
209 void set_keyer(bool external_key)
\r
213 if(FAILED(keyer_->Enable(FALSE)))
\r
214 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
215 else if(FAILED(keyer_->SetLevel(255)))
\r
216 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
218 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
222 if(FAILED(keyer_->Enable(TRUE)))
\r
223 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
224 else if(FAILED(keyer_->SetLevel(255)))
\r
225 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
227 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
231 void enable_audio()
\r
233 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
234 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
236 if(FAILED(output_->SetAudioCallback(this)))
\r
237 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
239 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
242 void enable_video(BMDDisplayMode display_mode)
\r
244 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
245 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
247 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
248 BOOST_THROW_EXCEPTION(caspar_exception()
\r
249 << msg_info(narrow(print()) + " Failed to set playback completion callback.")
\r
250 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
253 void start_playback()
\r
255 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
256 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
259 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
260 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
261 STDMETHOD_(ULONG, Release()) {return 1;}
\r
263 STDMETHOD(ScheduledPlaybackHasStopped())
\r
265 is_running_ = false;
\r
266 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
270 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
277 if(result == bmdOutputFrameDisplayedLate)
\r
279 graph_->add_tag("late-frame");
\r
280 ++frames_scheduled_;
\r
281 ++audio_scheduled_;
\r
283 else if(result == bmdOutputFrameDropped)
\r
284 graph_->add_tag("dropped-frame");
\r
285 else if(result == bmdOutputFrameFlushed)
\r
286 graph_->add_tag("flushed-frame");
\r
288 frame_container_.erase(std::find_if(frame_container_.begin(), frame_container_.end(), [&](const std::shared_ptr<IDeckLinkVideoFrame>& frame)
\r
290 return frame.get() == completed_frame;
\r
293 std::shared_ptr<core::read_frame> frame;
\r
294 video_frame_buffer_.pop(frame);
\r
295 schedule_next_video(make_safe(frame));
\r
299 exception_ = std::current_exception();
\r
306 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
315 if(++preroll_count_ >= buffer_size_)
\r
317 output_->EndAudioPreroll();
\r
321 schedule_next_audio(make_safe<core::read_frame>());
\r
325 std::shared_ptr<core::read_frame> frame;
\r
326 audio_frame_buffer_.pop(frame);
\r
327 schedule_next_audio(make_safe(frame));
\r
332 exception_ = std::current_exception();
\r
339 void schedule_next_audio(const safe_ptr<core::read_frame>& frame)
\r
341 const int sample_frame_count = frame->audio_data().size()/format_desc_.audio_channels;
\r
343 audio_container_.push_back(std::vector<int16_t>(frame->audio_data().begin(), frame->audio_data().end()));
\r
345 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))
\r
346 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
349 void schedule_next_video(const safe_ptr<core::read_frame>& frame)
\r
351 frame_container_.push_back(std::make_shared<decklink_frame_muxer>(frame, format_desc_));
\r
352 if(FAILED(output_->ScheduleVideoFrame(frame_container_.back().get(), (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))
\r
353 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
355 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
356 tick_timer_.restart();
\r
359 void send(const safe_ptr<core::read_frame>& frame)
\r
361 if(exception_ != nullptr)
\r
362 std::rethrow_exception(exception_);
\r
365 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
367 if(config_.embedded_audio)
\r
368 audio_frame_buffer_.push(frame);
\r
369 video_frame_buffer_.push(frame);
\r
372 std::wstring print() const
\r
374 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
378 struct decklink_consumer_proxy : public core::frame_consumer
\r
380 const configuration config_;
\r
382 com_context<decklink_consumer> context_;
\r
385 decklink_consumer_proxy(const configuration& config)
\r
387 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]"){}
\r
389 virtual void initialize(const core::video_format_desc& format_desc)
\r
391 context_.reset([&]{return new decklink_consumer(config_, format_desc);});
\r
394 virtual void send(const safe_ptr<core::read_frame>& frame)
\r
396 context_->send(frame);
\r
399 virtual std::wstring print() const
\r
401 return context_->print();
\r
404 virtual bool key_only() const
\r
406 return config_.key_only;
\r
409 virtual const core::video_format_desc& get_video_format_desc() const
\r
411 return context_->get_video_format_desc();
\r
414 virtual size_t buffer_depth() const
\r
416 return context_->buffer_size_;
\r
420 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params)
\r
422 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
423 return core::frame_consumer::empty();
\r
425 configuration config;
\r
427 if(params.size() > 1)
\r
428 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
430 config.external_key = std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end();
\r
431 config.low_latency = std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end();
\r
432 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
433 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
435 return make_safe<decklink_consumer_proxy>(config);
\r
438 safe_ptr<core::frame_consumer> create_decklink_consumer(const boost::property_tree::ptree& ptree)
\r
440 configuration config;
\r
442 config.external_key = ptree.get("external-key", config.external_key);
\r
443 config.low_latency = ptree.get("low-latency", config.low_latency);
\r
444 config.key_only = ptree.get("key-only", config.key_only);
\r
445 config.device_index = ptree.get("device", config.device_index);
\r
446 config.embedded_audio = ptree.get("embedded-audio", config.embedded_audio);
\r
448 return make_safe<decklink_consumer_proxy>(config);
\r
454 ##############################################################################
\r
460 BMD Developer Support
\r
461 developer@blackmagic-design.com
\r
463 -----------------------------------------------------------------------------
\r
465 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
466 for scheduled playback is three frames for video and four frames for audio.
\r
467 As you mentioned if you preroll less frames then playback will not start or
\r
468 playback will be very sporadic. From our experience with Media Express, we
\r
469 recommended that at least seven frames are prerolled for smooth playback.
\r
471 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
472 There can be around 3 frames worth of latency on scheduled output.
\r
473 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
474 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
475 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
476 guarantee that the provided frame will be output as soon the previous
\r
477 frame output has been completed.
\r
478 ################################################################################
\r
482 ##############################################################################
\r
483 Async DMA Transfer without redundant copying
\r
488 BMD Developer Support
\r
489 developer@blackmagic-design.com
\r
491 -----------------------------------------------------------------------------
\r
493 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
494 and providing a pointer to your video buffer when GetBytes() is called.
\r
495 This may help to keep copying to a minimum. Please ensure that the pixel
\r
496 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
497 have to colourspace convert which may result in additional copying.
\r
498 ################################################################################
\r