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
41 #include <tbb/cache_aligned_allocator.h>
\r
43 #include <boost/circular_buffer.hpp>
\r
44 #include <boost/timer.hpp>
\r
48 struct configuration
\r
50 size_t device_index;
\r
51 bool embedded_audio;
\r
55 size_t buffer_depth;
\r
59 , embedded_audio(false)
\r
60 , internal_key(false)
\r
61 , low_latency(false)
\r
63 , buffer_depth(core::consumer_buffer_depth()){}
\r
66 class decklink_frame : public IDeckLinkVideoFrame
\r
68 tbb::atomic<int> ref_count_;
\r
69 std::shared_ptr<core::read_frame> frame_;
\r
70 const core::video_format_desc format_desc_;
\r
73 std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> key_data_;
\r
75 decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, bool key_only)
\r
77 , format_desc_(format_desc)
\r
78 , key_only_(key_only)
\r
83 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
84 STDMETHOD_(ULONG, AddRef())
\r
86 return ++ref_count_;
\r
88 STDMETHOD_(ULONG, Release())
\r
96 STDMETHOD_(long, GetWidth()) {return format_desc_.width;}
\r
97 STDMETHOD_(long, GetHeight()) {return format_desc_.height;}
\r
98 STDMETHOD_(long, GetRowBytes()) {return format_desc_.width*4;}
\r
99 STDMETHOD_(BMDPixelFormat, GetPixelFormat()) {return bmdFormat8BitBGRA;}
\r
100 STDMETHOD_(BMDFrameFlags, GetFlags()) {return bmdFrameFlagDefault;}
\r
102 STDMETHOD(GetBytes(void** buffer))
\r
104 static std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> zeros(1920*1080*4, 0);
\r
105 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)
\r
107 *buffer = zeros.data();
\r
112 *buffer = const_cast<uint8_t*>(frame_->image_data().begin());
\r
115 if(key_data_.empty())
\r
117 key_data_.resize(frame_->image_data().size());
\r
118 fast_memshfl(key_data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);
\r
121 *buffer = key_data_.data();
\r
127 STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}
\r
128 STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}
\r
131 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
133 const configuration config_;
\r
135 CComPtr<IDeckLink> decklink_;
\r
136 CComQIPtr<IDeckLinkOutput> output_;
\r
137 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
138 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
140 tbb::spin_mutex exception_mutex_;
\r
141 std::exception_ptr exception_;
\r
143 tbb::atomic<bool> is_running_;
\r
145 const std::wstring model_name_;
\r
146 const core::video_format_desc format_desc_;
\r
147 const size_t buffer_size_;
\r
149 long long frames_scheduled_;
\r
150 long long audio_scheduled_;
\r
152 size_t preroll_count_;
\r
154 boost::circular_buffer<std::vector<int32_t>> audio_container_;
\r
156 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;
\r
157 tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;
\r
159 std::shared_ptr<diagnostics::graph> graph_;
\r
160 boost::timer tick_timer_;
\r
163 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc)
\r
165 , decklink_(get_device(config.device_index))
\r
166 , output_(decklink_)
\r
167 , configuration_(decklink_)
\r
168 , keyer_(decklink_)
\r
169 , model_name_(get_model_name(decklink_))
\r
170 , format_desc_(format_desc)
\r
171 , buffer_size_(config.embedded_audio ? config.buffer_depth + 1 : config.buffer_depth) // Minimum buffer-size 3.
\r
172 , frames_scheduled_(0)
\r
173 , audio_scheduled_(0)
\r
174 , preroll_count_(0)
\r
175 , audio_container_(buffer_size_+1)
\r
177 is_running_ = true;
\r
179 video_frame_buffer_.set_capacity(1);
\r
180 audio_frame_buffer_.set_capacity(1);
\r
182 graph_ = diagnostics::create_graph(narrow(print()));
\r
183 graph_->add_guide("tick-time", 0.5);
\r
184 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
\r
185 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
186 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
187 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
\r
189 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
191 if(config.embedded_audio)
\r
194 set_latency(config.low_latency);
\r
195 set_keyer(config.internal_key);
\r
197 if(config.embedded_audio)
\r
198 output_->BeginAudioPreroll();
\r
200 for(size_t n = 0; n < buffer_size_; ++n)
\r
201 schedule_next_video(make_safe<core::read_frame>());
\r
203 if(!config.embedded_audio)
\r
207 ~decklink_consumer()
\r
209 is_running_ = false;
\r
210 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
211 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());
\r
213 if(output_ != nullptr)
\r
215 output_->StopScheduledPlayback(0, nullptr, 0);
\r
216 if(config_.embedded_audio)
\r
217 output_->DisableAudioOutput();
\r
218 output_->DisableVideoOutput();
\r
222 const core::video_format_desc& get_video_format_desc() const
\r
224 return format_desc_;
\r
227 void set_latency(bool low_latency)
\r
231 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
232 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
236 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
237 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
241 void set_keyer(bool internal_key)
\r
245 if(FAILED(keyer_->Enable(FALSE)))
\r
246 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
247 else if(FAILED(keyer_->SetLevel(255)))
\r
248 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
250 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
254 if(FAILED(keyer_->Enable(TRUE)))
\r
255 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
256 else if(FAILED(keyer_->SetLevel(255)))
\r
257 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
259 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
263 void enable_audio()
\r
265 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
266 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
268 if(FAILED(output_->SetAudioCallback(this)))
\r
269 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
271 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
274 void enable_video(BMDDisplayMode display_mode)
\r
276 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
277 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
279 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
280 BOOST_THROW_EXCEPTION(caspar_exception()
\r
281 << msg_info(narrow(print()) + " Failed to set playback completion callback.")
\r
282 << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
\r
285 void start_playback()
\r
287 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
288 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
291 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
292 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
293 STDMETHOD_(ULONG, Release()) {return 1;}
\r
295 STDMETHOD(ScheduledPlaybackHasStopped())
\r
297 is_running_ = false;
\r
298 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
302 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
309 if(result == bmdOutputFrameDisplayedLate)
\r
311 graph_->add_tag("late-frame");
\r
312 ++frames_scheduled_;
\r
313 ++audio_scheduled_;
\r
315 else if(result == bmdOutputFrameDropped)
\r
316 graph_->add_tag("dropped-frame");
\r
317 else if(result == bmdOutputFrameFlushed)
\r
318 graph_->add_tag("flushed-frame");
\r
320 std::shared_ptr<core::read_frame> frame;
\r
321 video_frame_buffer_.pop(frame);
\r
322 schedule_next_video(make_safe(frame));
\r
326 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
327 exception_ = std::current_exception();
\r
334 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
343 if(++preroll_count_ >= buffer_size_)
\r
345 output_->EndAudioPreroll();
\r
349 schedule_next_audio(make_safe<core::read_frame>());
\r
353 std::shared_ptr<core::read_frame> frame;
\r
354 audio_frame_buffer_.pop(frame);
\r
355 schedule_next_audio(make_safe(frame));
\r
360 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
361 exception_ = std::current_exception();
\r
368 void schedule_next_audio(const safe_ptr<core::read_frame>& frame)
\r
370 const int sample_frame_count = frame->audio_data().size()/format_desc_.audio_channels;
\r
372 audio_container_.push_back(std::vector<int32_t>(frame->audio_data().begin(), frame->audio_data().end()));
\r
374 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))
\r
375 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
378 void schedule_next_video(const safe_ptr<core::read_frame>& frame)
\r
380 CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));
\r
381 if(FAILED(output_->ScheduleVideoFrame(frame2, (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))
\r
382 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
384 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
385 tick_timer_.restart();
\r
388 void send(const safe_ptr<core::read_frame>& frame)
\r
391 tbb::spin_mutex::scoped_lock lock(exception_mutex_);
\r
392 if(exception_ != nullptr)
\r
393 std::rethrow_exception(exception_);
\r
397 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
399 if(config_.embedded_audio)
\r
400 audio_frame_buffer_.push(frame);
\r
401 video_frame_buffer_.push(frame);
\r
404 std::wstring print() const
\r
406 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
\r
410 struct decklink_consumer_proxy : public core::frame_consumer
\r
412 const configuration config_;
\r
413 com_context<decklink_consumer> context_;
\r
414 core::video_format_desc format_desc_;
\r
417 decklink_consumer_proxy(const configuration& config)
\r
419 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
\r
423 ~decklink_consumer_proxy()
\r
425 auto str = print();
\r
427 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";
\r
430 virtual void initialize(const core::video_format_desc& format_desc)
\r
432 format_desc_ = format_desc;
\r
433 context_.reset([&]{return new decklink_consumer(config_, format_desc_);});
\r
435 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
438 virtual bool send(const safe_ptr<core::read_frame>& frame)
\r
440 context_->send(frame);
\r
444 virtual std::wstring print() const
\r
446 return context_ ? context_->print() : L"decklink_consumer";
\r
449 virtual const core::video_format_desc& get_video_format_desc() const
\r
451 return format_desc_;
\r
455 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params)
\r
457 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
458 return core::frame_consumer::empty();
\r
460 configuration config;
\r
462 if(params.size() > 1)
\r
463 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
465 config.internal_key = std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end();
\r
466 config.low_latency = std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end();
\r
467 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
468 config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
\r
470 return make_safe<decklink_consumer_proxy>(config);
\r
473 safe_ptr<core::frame_consumer> create_decklink_consumer(const boost::property_tree::ptree& ptree)
\r
475 configuration config;
\r
477 config.internal_key = ptree.get("internal-key", config.internal_key);
\r
478 config.low_latency = ptree.get("low-latency", config.low_latency);
\r
479 config.key_only = ptree.get("key-only", config.key_only);
\r
480 config.device_index = ptree.get("device", config.device_index);
\r
481 config.embedded_audio = ptree.get("embedded-audio", config.embedded_audio);
\r
483 return make_safe<decklink_consumer_proxy>(config);
\r
489 ##############################################################################
\r
495 BMD Developer Support
\r
496 developer@blackmagic-design.com
\r
498 -----------------------------------------------------------------------------
\r
500 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
501 for scheduled playback is three frames for video and four frames for audio.
\r
502 As you mentioned if you preroll less frames then playback will not start or
\r
503 playback will be very sporadic. From our experience with Media Express, we
\r
504 recommended that at least seven frames are prerolled for smooth playback.
\r
506 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
507 There can be around 3 frames worth of latency on scheduled output.
\r
508 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
509 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
510 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
511 guarantee that the provided frame will be output as soon the previous
\r
512 frame output has been completed.
\r
513 ################################################################################
\r
517 ##############################################################################
\r
518 Async DMA Transfer without redundant copying
\r
523 BMD Developer Support
\r
524 developer@blackmagic-design.com
\r
526 -----------------------------------------------------------------------------
\r
528 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
529 and providing a pointer to your video buffer when GetBytes() is called.
\r
530 This may help to keep copying to a minimum. Please ensure that the pixel
\r
531 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
532 have to colourspace convert which may result in additional copying.
\r
533 ################################################################################
\r