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/video_format.h>
\r
31 #include <core/consumer/frame/read_frame.h>
\r
33 #include <common/concurrency/com_context.h>
\r
34 #include <common/diagnostics/graph.h>
\r
35 #include <common/exception/exceptions.h>
\r
36 #include <common/memory/memcpy.h>
\r
37 #include <common/memory/memclr.h>
\r
39 #include <tbb/concurrent_queue.h>
\r
41 #include <boost/circular_buffer.hpp>
\r
42 #include <boost/timer.hpp>
\r
46 #pragma warning(push)
\r
47 #pragma warning(disable : 4996)
\r
49 #include <atlbase.h>
\r
52 #include <atlhost.h>
\r
54 #pragma warning(push)
\r
72 struct configuration
\r
74 size_t device_index;
\r
75 bool embedded_audio;
\r
81 , embedded_audio(false)
\r
82 , keyer(default_key)
\r
83 , latency(default_latency){}
\r
86 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
88 const configuration config_;
\r
90 CComPtr<IDeckLink> decklink_;
\r
91 CComQIPtr<IDeckLinkOutput> output_;
\r
92 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
93 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
95 std::exception_ptr exception_;
\r
97 tbb::atomic<bool> is_running_;
\r
99 const std::wstring model_name_;
\r
100 const core::video_format_desc format_desc_;
\r
102 unsigned long frames_scheduled_;
\r
103 unsigned long audio_scheduled_;
\r
105 std::vector<std::pair<void*, CComPtr<IDeckLinkMutableVideoFrame>>> reserved_frames_;
\r
106 boost::circular_buffer<std::vector<short>> audio_container_;
\r
108 tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> video_frame_buffer_;
\r
109 tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> audio_frame_buffer_;
\r
111 std::shared_ptr<diagnostics::graph> graph_;
\r
112 boost::timer tick_timer_;
\r
114 size_t buffer_size_;
\r
117 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc)
\r
119 , decklink_(get_device(config.device_index))
\r
120 , output_(decklink_)
\r
121 , configuration_(decklink_)
\r
122 , keyer_(decklink_)
\r
123 , model_name_(get_model_name(decklink_))
\r
124 , format_desc_(format_desc)
\r
125 , frames_scheduled_(0)
\r
126 , audio_scheduled_(0)
\r
127 , buffer_size_(4) // Minimum buffer-size (3 + 1 tolerance).
\r
129 is_running_ = true;
\r
131 graph_ = diagnostics::create_graph(narrow(print()));
\r
132 graph_->add_guide("tick-time", 0.5);
\r
133 graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f));
\r
134 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
135 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
136 graph_->set_color("flushed-frame", diagnostics::color(0.3f, 0.3f, 0.6f));
\r
138 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
140 if(config.embedded_audio)
\r
143 set_latency(config.latency);
\r
144 set_keyer(config.keyer);
\r
146 audio_container_.set_capacity(buffer_size_+1);
\r
147 allocate_frames(buffer_size_+1);
\r
149 CASPAR_LOG(info) << print() << L" Buffer-depth: " << buffer_size_;
\r
151 for(size_t n = 0; n < buffer_size_; ++n)
\r
152 schedule_next_video(core::read_frame::empty());
\r
154 video_frame_buffer_.set_capacity(2);
\r
155 audio_frame_buffer_.set_capacity(2);
\r
157 if(config.embedded_audio)
\r
158 output_->BeginAudioPreroll();
\r
161 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
162 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
165 CASPAR_LOG(info) << print() << L" Successfully initialized for " << format_desc_.name;
\r
168 ~decklink_consumer()
\r
170 is_running_ = false;
\r
171 video_frame_buffer_.clear();
\r
172 audio_frame_buffer_.clear();
\r
173 video_frame_buffer_.try_push(core::read_frame::empty());
\r
174 audio_frame_buffer_.try_push(core::read_frame::empty());
\r
176 if(output_ != nullptr)
\r
178 output_->StopScheduledPlayback(0, nullptr, 0);
\r
179 if(config_.embedded_audio)
\r
180 output_->DisableAudioOutput();
\r
181 output_->DisableVideoOutput();
\r
185 void set_latency(latency latency)
\r
187 if(latency == normal_latency)
\r
189 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
190 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
192 else if(latency == low_latency)
\r
194 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
195 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
198 CASPAR_LOG(info) << print() << L" Uses driver latency settings.";
\r
201 void set_keyer(key keyer)
\r
203 if(keyer == internal_key)
\r
205 if(FAILED(keyer_->Enable(FALSE)))
\r
206 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
207 else if(FAILED(keyer_->SetLevel(255)))
\r
208 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
210 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
212 else if(keyer == external_key)
\r
214 if(FAILED(keyer_->Enable(TRUE)))
\r
215 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
216 else if(FAILED(keyer_->SetLevel(255)))
\r
217 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
219 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
222 CASPAR_LOG(info) << print() << L" Uses driver keyer settings.";
\r
225 void enable_audio()
\r
227 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
228 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
230 if(FAILED(output_->SetAudioCallback(this)))
\r
231 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
233 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
235 buffer_size_ = 5; // Minimum buffer-size with embedded-audio (4 + 1 tolerance).
\r
238 void enable_video(BMDDisplayMode display_mode)
\r
240 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
241 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
243 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
244 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to set playback completion callback."));
\r
247 void allocate_frames(size_t count)
\r
249 std::pair<void*, CComPtr<IDeckLinkMutableVideoFrame>> frame;
\r
250 std::generate_n(std::back_inserter(reserved_frames_), count, [&]() -> decltype(frame)
\r
252 if(FAILED(output_->CreateVideoFrame(format_desc_.width, format_desc_.height, format_desc_.size/format_desc_.height, bmdFormat8BitBGRA, bmdFrameFlagDefault, &frame.second)))
\r
253 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to create frame."));
\r
255 if(FAILED(frame.second->GetBytes(&frame.first)) || frame.first == nullptr)
\r
256 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to get frame bytes."));
\r
262 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
263 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
264 STDMETHOD_(ULONG, Release()) {return 1;}
\r
266 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* /*completedFrame*/, BMDOutputFrameCompletionResult result))
\r
271 if(result == bmdOutputFrameDisplayedLate)
\r
272 graph_->add_tag("late-frame");
\r
273 else if(result == bmdOutputFrameDropped)
\r
274 graph_->add_tag("dropped-frame");
\r
275 else if(result == bmdOutputFrameFlushed)
\r
276 graph_->add_tag("flushed-frame");
\r
278 std::shared_ptr<const core::read_frame> frame;
\r
279 video_frame_buffer_.pop(frame);
\r
280 schedule_next_video(safe_ptr<const core::read_frame>(frame));
\r
285 STDMETHOD(ScheduledPlaybackHasStopped())
\r
287 is_running_ = false;
\r
288 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
292 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
301 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
302 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
305 std::shared_ptr<const core::read_frame> frame;
\r
306 audio_frame_buffer_.pop(frame);
\r
307 schedule_next_audio(safe_ptr<const core::read_frame>(frame));
\r
311 exception_ = std::current_exception();
\r
318 void schedule_next_audio(const safe_ptr<const core::read_frame>& frame)
\r
320 static std::vector<short> silence(48000, 0);
\r
322 int audio_samples = static_cast<size_t>(48000.0 / format_desc_.fps)*2; // Audio samples per channel
\r
324 const short* frame_audio_data = frame->audio_data().size() == audio_samples ? frame->audio_data().begin() : silence.data();
\r
325 audio_container_.push_back(std::vector<short>(frame_audio_data, frame_audio_data+audio_samples));
\r
327 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), audio_samples/2, (audio_scheduled_++) * audio_samples/2, 48000, nullptr)))
\r
328 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
331 void schedule_next_video(const safe_ptr<const core::read_frame>& frame)
\r
333 if(static_cast<size_t>(frame->image_data().size()) == format_desc_.size)
\r
334 fast_memcpy(reserved_frames_.front().first, frame->image_data().begin(), frame->image_data().size());
\r
336 fast_memclr(reserved_frames_.front().first, format_desc_.size);
\r
338 if(FAILED(output_->ScheduleVideoFrame(reserved_frames_.front().second, (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))
\r
339 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
341 std::rotate(reserved_frames_.begin(), reserved_frames_.begin() + 1, reserved_frames_.end());
\r
342 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
343 tick_timer_.restart();
\r
346 void send(const safe_ptr<const core::read_frame>& frame)
\r
348 if(exception_ != nullptr)
\r
349 std::rethrow_exception(exception_);
\r
352 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
354 video_frame_buffer_.push(frame);
\r
355 if(config_.embedded_audio)
\r
356 audio_frame_buffer_.push(frame);
\r
359 std::wstring print() const
\r
361 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"]";
\r
365 struct decklink_consumer_proxy : public core::frame_consumer
\r
367 const configuration config_;
\r
369 com_context<decklink_consumer> context_;
\r
372 decklink_consumer_proxy(const configuration& config)
\r
374 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]"){}
\r
376 virtual void initialize(const core::video_format_desc& format_desc)
\r
378 context_.reset([&]{return new decklink_consumer(config_, format_desc);});
\r
381 virtual void send(const safe_ptr<const core::read_frame>& frame)
\r
383 context_->send(frame);
\r
386 virtual std::wstring print() const
\r
388 return context_->print();
\r
392 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params)
\r
394 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
395 return core::frame_consumer::empty();
\r
397 configuration config;
\r
399 if(params.size() > 1)
\r
400 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
402 if(std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end())
\r
403 config.keyer = internal_key;
\r
404 else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end())
\r
405 config.keyer = external_key;
\r
407 if(std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end())
\r
408 config.latency = low_latency;
\r
409 else if(std::find(params.begin(), params.end(), L"NORMAL_LATENCY") != params.end())
\r
410 config.latency = normal_latency;
\r
412 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
414 return make_safe<decklink_consumer_proxy>(config);
\r
417 safe_ptr<core::frame_consumer> create_decklink_consumer(const boost::property_tree::ptree& ptree)
\r
419 configuration config;
\r
421 auto key_str = ptree.get("key", "default");
\r
422 if(key_str == "internal")
\r
423 config.keyer = internal_key;
\r
424 else if(key_str == "external")
\r
425 config.keyer = external_key;
\r
427 auto latency_str = ptree.get("latency", "default");
\r
428 if(latency_str == "normal")
\r
429 config.latency = normal_latency;
\r
430 else if(latency_str == "low")
\r
431 config.latency = low_latency;
\r
433 config.device_index = ptree.get("device", 0);
\r
434 config.embedded_audio = ptree.get("embedded-audio", false);
\r
436 return make_safe<decklink_consumer_proxy>(config);
\r
442 ##############################################################################
\r
448 BMD Developer Support
\r
449 developer@blackmagic-design.com
\r
451 -----------------------------------------------------------------------------
\r
453 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
454 for scheduled playback is three frames for video and four frames for audio.
\r
455 As you mentioned if you preroll less frames then playback will not start or
\r
456 playback will be very sporadic. From our experience with Media Express, we
\r
457 recommended that at least seven frames are prerolled for smooth playback.
\r
459 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
460 There can be around 3 frames worth of latency on scheduled output.
\r
461 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
462 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
463 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
464 guarantee that the provided frame will be output as soon the previous
\r
465 frame output has been completed.
\r
466 ################################################################################
\r
470 ##############################################################################
\r
471 Async DMA Transfer without redundant copying
\r
476 BMD Developer Support
\r
477 developer@blackmagic-design.com
\r
479 -----------------------------------------------------------------------------
\r
481 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
482 and providing a pointer to your video buffer when GetBytes() is called.
\r
483 This may help to keep copying to a minimum. Please ensure that the pixel
\r
484 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
485 have to colourspace convert which may result in additional copying.
\r
486 ################################################################################
\r