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/consumer/frame/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
37 #include <tbb/concurrent_queue.h>
\r
39 #include <boost/circular_buffer.hpp>
\r
40 #include <boost/timer.hpp>
\r
64 struct configuration
\r
66 size_t device_index;
\r
67 bool embedded_audio;
\r
70 output_pixels output;
\r
74 , embedded_audio(false)
\r
75 , keyer(default_key)
\r
76 , latency(default_latency)
\r
77 , output(fill_and_key){}
\r
80 class decklink_frame_adapter : public IDeckLinkVideoFrame
\r
82 const safe_ptr<const core::read_frame> frame_;
\r
83 const core::video_format_desc format_desc_;
\r
85 decklink_frame_adapter(const safe_ptr<const core::read_frame>& frame, const core::video_format_desc& format_desc)
\r
87 , format_desc_(format_desc){}
\r
89 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
90 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
91 STDMETHOD_(ULONG, Release()) {return 1;}
\r
93 STDMETHOD_(long, GetWidth()) {return format_desc_.width;}
\r
94 STDMETHOD_(long, GetHeight()) {return format_desc_.height;}
\r
95 STDMETHOD_(long, GetRowBytes()) {return format_desc_.width*4;}
\r
96 STDMETHOD_(BMDPixelFormat, GetPixelFormat()){return bmdFormat8BitBGRA;}
\r
97 STDMETHOD_(BMDFrameFlags, GetFlags()) {return bmdFrameFlagDefault;}
\r
99 STDMETHOD(GetBytes(void** buffer))
\r
101 static std::vector<unsigned char> zeros(1920*1080*4, 0);
\r
102 *buffer = const_cast<unsigned char*>(frame_->image_data().begin());
\r
103 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)
\r
104 *buffer = zeros.data();
\r
108 STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}
\r
109 STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}
\r
112 void make_alpha(void* dest, const void* source, size_t count)
\r
114 __m128i* dest128 = reinterpret_cast<__m128i*>(dest);
\r
115 const __m128i* source128 = reinterpret_cast<const __m128i*>(source);
\r
117 count /= 16; // 128 bit
\r
119 __m128i xmm0, xmm1, xmm2, xmm3;
\r
121 const __m128i mask128 = _mm_set_epi8(3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15);
\r
122 for(size_t n = 0; n < count/4; ++n)
\r
124 xmm0 = _mm_load_si128(source128++);
\r
125 xmm1 = _mm_load_si128(source128++);
\r
126 xmm2 = _mm_load_si128(source128++);
\r
127 xmm3 = _mm_load_si128(source128++);
\r
129 _mm_stream_si128(dest128++, _mm_shuffle_epi8(xmm0, mask128));
\r
130 _mm_stream_si128(dest128++, _mm_shuffle_epi8(xmm1, mask128));
\r
131 _mm_stream_si128(dest128++, _mm_shuffle_epi8(xmm2, mask128));
\r
132 _mm_stream_si128(dest128++, _mm_shuffle_epi8(xmm3, mask128));
\r
136 std::shared_ptr<IDeckLinkVideoFrame> make_alpha_only_frame(const CComQIPtr<IDeckLinkOutput>& decklink, const safe_ptr<const core::read_frame>& frame, const core::video_format_desc& format_desc)
\r
138 IDeckLinkMutableVideoFrame* result;
\r
140 if(FAILED(decklink->CreateVideoFrame(format_desc.width, format_desc.height, format_desc.size/format_desc.height, bmdFormat8BitBGRA, bmdFrameFlagDefault, &result)))
\r
141 BOOST_THROW_EXCEPTION(caspar_exception());
\r
143 void* bytes = nullptr;
\r
144 if(FAILED(result->GetBytes(&bytes)))
\r
145 BOOST_THROW_EXCEPTION(caspar_exception());
\r
147 unsigned char* data = reinterpret_cast<unsigned char*>(bytes);
\r
149 if(static_cast<size_t>(frame->image_data().size()) == format_desc.size)
\r
151 size_t count = frame->image_data().size();
\r
152 tbb::affinity_partitioner ap;
\r
153 tbb::parallel_for(tbb::blocked_range<size_t>(0, count/128), [&](const tbb::blocked_range<size_t>& r)
\r
155 make_alpha(reinterpret_cast<char*>(data) + r.begin()*128, reinterpret_cast<const char*>(frame->image_data().begin()) + r.begin()*128, r.size()*128);
\r
159 memset(data, 0, format_desc.size);
\r
161 return std::shared_ptr<IDeckLinkVideoFrame>(result, [](IDeckLinkMutableVideoFrame* p) {p->Release();});
\r
164 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
166 const configuration config_;
\r
168 CComPtr<IDeckLink> decklink_;
\r
169 CComQIPtr<IDeckLinkOutput> output_;
\r
170 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
171 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
173 std::exception_ptr exception_;
\r
175 tbb::atomic<bool> is_running_;
\r
177 const std::wstring model_name_;
\r
178 const core::video_format_desc format_desc_;
\r
179 const size_t buffer_size_;
\r
181 unsigned long frames_scheduled_;
\r
182 unsigned long audio_scheduled_;
\r
184 std::list<std::shared_ptr<IDeckLinkVideoFrame>> frame_container_; // Must be std::list in order to guarantee that pointers are always valid.
\r
185 boost::circular_buffer<std::vector<short>> audio_container_;
\r
187 tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> video_frame_buffer_;
\r
188 tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> audio_frame_buffer_;
\r
190 std::shared_ptr<diagnostics::graph> graph_;
\r
191 boost::timer tick_timer_;
\r
194 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc)
\r
196 , decklink_(get_device(config.device_index))
\r
197 , output_(decklink_)
\r
198 , configuration_(decklink_)
\r
199 , keyer_(decklink_)
\r
200 , model_name_(get_model_name(decklink_))
\r
201 , format_desc_(format_desc)
\r
202 , buffer_size_(config.embedded_audio ? 5 : 4) // Minimum buffer-size (3 + 1 tolerance).
\r
203 , frames_scheduled_(0)
\r
204 , audio_scheduled_(0)
\r
205 , audio_container_(buffer_size_+1)
\r
207 is_running_ = true;
\r
209 video_frame_buffer_.set_capacity(1);
\r
210 audio_frame_buffer_.set_capacity(1);
\r
212 graph_ = diagnostics::create_graph(narrow(print()));
\r
213 graph_->add_guide("tick-time", 0.5);
\r
214 graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f));
\r
215 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
216 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
217 graph_->set_color("flushed-frame", diagnostics::color(0.3f, 0.3f, 0.6f));
\r
219 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
221 if(config.embedded_audio)
\r
224 set_latency(config.latency);
\r
225 set_keyer(config.keyer);
\r
227 for(size_t n = 0; n < buffer_size_; ++n)
\r
228 schedule_next_video(core::read_frame::empty());
\r
230 if(config.embedded_audio)
\r
231 output_->BeginAudioPreroll();
\r
235 CASPAR_LOG(info) << print() << L" Buffer depth: " << buffer_size_;
\r
236 CASPAR_LOG(info) << print() << L" Successfully initialized for " << format_desc_.name;
\r
239 ~decklink_consumer()
\r
241 is_running_ = false;
\r
242 video_frame_buffer_.try_push(core::read_frame::empty());
\r
243 audio_frame_buffer_.try_push(core::read_frame::empty());
\r
245 if(output_ != nullptr)
\r
247 output_->StopScheduledPlayback(0, nullptr, 0);
\r
248 if(config_.embedded_audio)
\r
249 output_->DisableAudioOutput();
\r
250 output_->DisableVideoOutput();
\r
254 void set_latency(latency latency)
\r
256 if(latency == normal_latency)
\r
258 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
259 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
261 else if(latency == low_latency)
\r
263 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
264 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
267 CASPAR_LOG(info) << print() << L" Uses driver latency settings.";
\r
270 void set_keyer(key keyer)
\r
272 if(keyer == internal_key)
\r
274 if(FAILED(keyer_->Enable(FALSE)))
\r
275 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
276 else if(FAILED(keyer_->SetLevel(255)))
\r
277 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
279 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
281 else if(keyer == external_key)
\r
283 if(FAILED(keyer_->Enable(TRUE)))
\r
284 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
285 else if(FAILED(keyer_->SetLevel(255)))
\r
286 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
288 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
291 CASPAR_LOG(info) << print() << L" Uses driver keyer settings.";
\r
294 void enable_audio()
\r
296 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
297 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
299 if(FAILED(output_->SetAudioCallback(this)))
\r
300 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
302 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
305 void enable_video(BMDDisplayMode display_mode)
\r
307 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
308 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
310 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
311 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to set playback completion callback."));
\r
314 void start_playback()
\r
316 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
317 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
320 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
321 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
322 STDMETHOD_(ULONG, Release()) {return 1;}
\r
324 STDMETHOD(ScheduledPlaybackHasStopped())
\r
326 is_running_ = false;
\r
327 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
331 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
338 if(result == bmdOutputFrameDisplayedLate)
\r
339 graph_->add_tag("late-frame");
\r
340 else if(result == bmdOutputFrameDropped)
\r
341 graph_->add_tag("dropped-frame");
\r
342 else if(result == bmdOutputFrameFlushed)
\r
343 graph_->add_tag("flushed-frame");
\r
345 frame_container_.erase(std::find_if(frame_container_.begin(), frame_container_.end(), [&](const std::shared_ptr<IDeckLinkVideoFrame> frame)
\r
347 return frame.get() == completed_frame;
\r
350 std::shared_ptr<const core::read_frame> frame;
\r
351 video_frame_buffer_.pop(frame);
\r
352 schedule_next_video(safe_ptr<const core::read_frame>(frame));
\r
356 exception_ = std::current_exception();
\r
363 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
370 std::shared_ptr<const core::read_frame> frame;
\r
371 audio_frame_buffer_.pop(frame);
\r
372 schedule_next_audio(safe_ptr<const core::read_frame>(frame));
\r
379 exception_ = std::current_exception();
\r
386 void schedule_next_audio(const safe_ptr<const core::read_frame>& frame)
\r
388 static std::vector<short> silence(48000, 0);
\r
390 const int sample_count = format_desc_.audio_samples_per_frame;
\r
391 const int sample_frame_count = sample_count/2;
\r
393 const short* frame_audio_data = frame->audio_data().size() == sample_count ? frame->audio_data().begin() : silence.data();
\r
394 audio_container_.push_back(std::vector<short>(frame_audio_data, frame_audio_data+sample_count));
\r
396 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))
\r
397 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
400 void schedule_next_video(const safe_ptr<const core::read_frame>& frame)
\r
402 std::shared_ptr<IDeckLinkVideoFrame> deck_frame;
\r
403 if(config_.output == key_only)
\r
404 deck_frame = make_alpha_only_frame(output_, frame, format_desc_);
\r
406 deck_frame = std::make_shared<decklink_frame_adapter>(frame, format_desc_);
\r
408 frame_container_.push_back(deck_frame);
\r
409 if(FAILED(output_->ScheduleVideoFrame(frame_container_.back().get(), (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))
\r
410 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
412 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
413 tick_timer_.restart();
\r
416 void send(const safe_ptr<const core::read_frame>& frame)
\r
418 if(exception_ != nullptr)
\r
419 std::rethrow_exception(exception_);
\r
422 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
424 if(config_.embedded_audio)
\r
425 audio_frame_buffer_.push(frame);
\r
426 video_frame_buffer_.push(frame);
\r
429 std::wstring print() const
\r
431 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"]";
\r
435 struct decklink_consumer_proxy : public core::frame_consumer
\r
437 const configuration config_;
\r
439 com_context<decklink_consumer> context_;
\r
442 decklink_consumer_proxy(const configuration& config)
\r
444 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]"){}
\r
446 virtual void initialize(const core::video_format_desc& format_desc)
\r
448 context_.reset([&]{return new decklink_consumer(config_, format_desc);});
\r
451 virtual void send(const safe_ptr<const core::read_frame>& frame)
\r
453 context_->send(frame);
\r
456 virtual std::wstring print() const
\r
458 return context_->print();
\r
462 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params)
\r
464 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
465 return core::frame_consumer::empty();
\r
467 configuration config;
\r
469 if(params.size() > 1)
\r
470 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
472 if(std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end())
\r
473 config.keyer = internal_key;
\r
474 else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end())
\r
475 config.keyer = external_key;
\r
477 if(std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end())
\r
478 config.latency = low_latency;
\r
479 else if(std::find(params.begin(), params.end(), L"NORMAL_LATENCY") != params.end())
\r
480 config.latency = normal_latency;
\r
482 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
484 return make_safe<decklink_consumer_proxy>(config);
\r
487 safe_ptr<core::frame_consumer> create_decklink_consumer(const boost::property_tree::ptree& ptree)
\r
489 configuration config;
\r
491 auto key_str = ptree.get("key", "default");
\r
492 if(key_str == "internal")
\r
493 config.keyer = internal_key;
\r
494 else if(key_str == "external")
\r
495 config.keyer = external_key;
\r
497 auto latency_str = ptree.get("latency", "default");
\r
498 if(latency_str == "normal")
\r
499 config.latency = normal_latency;
\r
500 else if(latency_str == "low")
\r
501 config.latency = low_latency;
\r
503 auto output_str = ptree.get("output", "fill_and_key");
\r
504 if(output_str == "key_only")
\r
505 config.output = key_only;
\r
507 config.device_index = ptree.get("device", 0);
\r
508 config.embedded_audio = ptree.get("embedded-audio", false);
\r
510 return make_safe<decklink_consumer_proxy>(config);
\r
516 ##############################################################################
\r
522 BMD Developer Support
\r
523 developer@blackmagic-design.com
\r
525 -----------------------------------------------------------------------------
\r
527 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
528 for scheduled playback is three frames for video and four frames for audio.
\r
529 As you mentioned if you preroll less frames then playback will not start or
\r
530 playback will be very sporadic. From our experience with Media Express, we
\r
531 recommended that at least seven frames are prerolled for smooth playback.
\r
533 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
534 There can be around 3 frames worth of latency on scheduled output.
\r
535 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
536 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
537 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
538 guarantee that the provided frame will be output as soon the previous
\r
539 frame output has been completed.
\r
540 ################################################################################
\r
544 ##############################################################################
\r
545 Async DMA Transfer without redundant copying
\r
550 BMD Developer Support
\r
551 developer@blackmagic-design.com
\r
553 -----------------------------------------------------------------------------
\r
555 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
556 and providing a pointer to your video buffer when GetBytes() is called.
\r
557 This may help to keep copying to a minimum. Please ensure that the pixel
\r
558 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
559 have to colourspace convert which may result in additional copying.
\r
560 ################################################################################
\r