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
58 struct configuration
\r
60 size_t device_index;
\r
61 bool embedded_audio;
\r
67 , embedded_audio(false)
\r
68 , keyer(default_key)
\r
69 , latency(default_latency){}
\r
72 class decklink_frame_adapter : public IDeckLinkVideoFrame
\r
74 const safe_ptr<const core::read_frame> frame_;
\r
75 const core::video_format_desc format_desc_;
\r
77 decklink_frame_adapter(const safe_ptr<const core::read_frame>& frame, const core::video_format_desc& format_desc)
\r
79 , format_desc_(format_desc){}
\r
81 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
82 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
83 STDMETHOD_(ULONG, Release()) {return 1;}
\r
85 STDMETHOD_(long, GetWidth()) {return format_desc_.width;}
\r
86 STDMETHOD_(long, GetHeight()) {return format_desc_.height;}
\r
87 STDMETHOD_(long, GetRowBytes()) {return format_desc_.width*4;}
\r
88 STDMETHOD_(BMDPixelFormat, GetPixelFormat()){return bmdFormat8BitBGRA;}
\r
89 STDMETHOD_(BMDFrameFlags, GetFlags()) {return bmdFrameFlagDefault;}
\r
91 STDMETHOD(GetBytes(void** buffer))
\r
93 static std::vector<unsigned char> zeros(1920*1080*4, 0);
\r
94 *buffer = const_cast<unsigned char*>(frame_->image_data().begin());
\r
95 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)
\r
96 *buffer = zeros.data();
\r
100 STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}
\r
101 STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}
\r
104 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
106 const configuration config_;
\r
108 CComPtr<IDeckLink> decklink_;
\r
109 CComQIPtr<IDeckLinkOutput> output_;
\r
110 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
111 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
113 std::exception_ptr exception_;
\r
115 tbb::atomic<bool> is_running_;
\r
117 const std::wstring model_name_;
\r
118 const core::video_format_desc format_desc_;
\r
119 const size_t buffer_size_;
\r
121 unsigned long frames_scheduled_;
\r
122 unsigned long audio_scheduled_;
\r
124 std::list<decklink_frame_adapter> frame_container_; // Must be std::list in order to guarantee that pointers are always valid.
\r
125 boost::circular_buffer<std::vector<short>> audio_container_;
\r
127 tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> video_frame_buffer_;
\r
128 tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> audio_frame_buffer_;
\r
130 std::shared_ptr<diagnostics::graph> graph_;
\r
131 boost::timer tick_timer_;
\r
134 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc)
\r
136 , decklink_(get_device(config.device_index))
\r
137 , output_(decklink_)
\r
138 , configuration_(decklink_)
\r
139 , keyer_(decklink_)
\r
140 , model_name_(get_model_name(decklink_))
\r
141 , format_desc_(format_desc)
\r
142 , buffer_size_(config.embedded_audio ? 5 : 4) // Minimum buffer-size (3 + 1 tolerance).
\r
143 , frames_scheduled_(0)
\r
144 , audio_scheduled_(0)
\r
145 , audio_container_(buffer_size_+1)
\r
147 is_running_ = true;
\r
149 video_frame_buffer_.set_capacity(1);
\r
150 audio_frame_buffer_.set_capacity(1);
\r
152 graph_ = diagnostics::create_graph(narrow(print()));
\r
153 graph_->add_guide("tick-time", 0.5);
\r
154 graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f));
\r
155 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
156 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
157 graph_->set_color("flushed-frame", diagnostics::color(0.3f, 0.3f, 0.6f));
\r
159 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
161 if(config.embedded_audio)
\r
164 set_latency(config.latency);
\r
165 set_keyer(config.keyer);
\r
167 for(size_t n = 0; n < buffer_size_; ++n)
\r
168 schedule_next_video(core::read_frame::empty());
\r
170 if(config.embedded_audio)
\r
171 output_->BeginAudioPreroll();
\r
175 CASPAR_LOG(info) << print() << L" Buffer depth: " << buffer_size_;
\r
176 CASPAR_LOG(info) << print() << L" Successfully initialized for " << format_desc_.name;
\r
179 ~decklink_consumer()
\r
181 is_running_ = false;
\r
182 video_frame_buffer_.try_push(core::read_frame::empty());
\r
183 audio_frame_buffer_.try_push(core::read_frame::empty());
\r
185 if(output_ != nullptr)
\r
187 output_->StopScheduledPlayback(0, nullptr, 0);
\r
188 if(config_.embedded_audio)
\r
189 output_->DisableAudioOutput();
\r
190 output_->DisableVideoOutput();
\r
194 void set_latency(latency latency)
\r
196 if(latency == normal_latency)
\r
198 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
199 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
201 else if(latency == low_latency)
\r
203 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
204 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
207 CASPAR_LOG(info) << print() << L" Uses driver latency settings.";
\r
210 void set_keyer(key keyer)
\r
212 if(keyer == internal_key)
\r
214 if(FAILED(keyer_->Enable(FALSE)))
\r
215 CASPAR_LOG(error) << print() << L" Failed to enable internal 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 internal keyer.";
\r
221 else if(keyer == external_key)
\r
223 if(FAILED(keyer_->Enable(TRUE)))
\r
224 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
225 else if(FAILED(keyer_->SetLevel(255)))
\r
226 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
228 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
231 CASPAR_LOG(info) << print() << L" Uses driver keyer settings.";
\r
234 void enable_audio()
\r
236 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
237 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
239 if(FAILED(output_->SetAudioCallback(this)))
\r
240 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
242 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
245 void enable_video(BMDDisplayMode display_mode)
\r
247 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
248 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
250 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
251 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to set playback completion callback."));
\r
254 void start_playback()
\r
256 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
257 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
260 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
261 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
262 STDMETHOD_(ULONG, Release()) {return 1;}
\r
264 STDMETHOD(ScheduledPlaybackHasStopped())
\r
266 is_running_ = false;
\r
267 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
271 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
278 if(result == bmdOutputFrameDisplayedLate)
\r
279 graph_->add_tag("late-frame");
\r
280 else if(result == bmdOutputFrameDropped)
\r
281 graph_->add_tag("dropped-frame");
\r
282 else if(result == bmdOutputFrameFlushed)
\r
283 graph_->add_tag("flushed-frame");
\r
285 frame_container_.erase(std::find_if(frame_container_.begin(), frame_container_.end(), [&](const decklink_frame_adapter& frame)
\r
287 return &frame == completed_frame;
\r
290 std::shared_ptr<const core::read_frame> frame;
\r
291 video_frame_buffer_.pop(frame);
\r
292 schedule_next_video(safe_ptr<const core::read_frame>(frame));
\r
296 exception_ = std::current_exception();
\r
303 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
310 std::shared_ptr<const core::read_frame> frame;
\r
311 audio_frame_buffer_.pop(frame);
\r
312 schedule_next_audio(safe_ptr<const core::read_frame>(frame));
\r
319 exception_ = std::current_exception();
\r
326 void schedule_next_audio(const safe_ptr<const core::read_frame>& frame)
\r
328 static std::vector<short> silence(48000, 0);
\r
330 const int sample_count = format_desc_.audio_samples_per_frame;
\r
331 const int sample_frame_count = sample_count/2;
\r
333 const short* frame_audio_data = frame->audio_data().size() == sample_count ? frame->audio_data().begin() : silence.data();
\r
334 audio_container_.push_back(std::vector<short>(frame_audio_data, frame_audio_data+sample_count));
\r
336 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))
\r
337 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
340 void schedule_next_video(const safe_ptr<const core::read_frame>& frame)
\r
342 frame_container_.push_back(decklink_frame_adapter(frame, format_desc_));
\r
343 if(FAILED(output_->ScheduleVideoFrame(&frame_container_.back(), (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))
\r
344 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
346 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
347 tick_timer_.restart();
\r
350 void send(const safe_ptr<const core::read_frame>& frame)
\r
352 if(exception_ != nullptr)
\r
353 std::rethrow_exception(exception_);
\r
356 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
358 if(config_.embedded_audio)
\r
359 audio_frame_buffer_.push(frame);
\r
360 video_frame_buffer_.push(frame);
\r
363 std::wstring print() const
\r
365 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"]";
\r
369 struct decklink_consumer_proxy : public core::frame_consumer
\r
371 const configuration config_;
\r
373 com_context<decklink_consumer> context_;
\r
376 decklink_consumer_proxy(const configuration& config)
\r
378 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]"){}
\r
380 virtual void initialize(const core::video_format_desc& format_desc)
\r
382 context_.reset([&]{return new decklink_consumer(config_, format_desc);});
\r
385 virtual void send(const safe_ptr<const core::read_frame>& frame)
\r
387 context_->send(frame);
\r
390 virtual std::wstring print() const
\r
392 return context_->print();
\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 if(std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end())
\r
407 config.keyer = internal_key;
\r
408 else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end())
\r
409 config.keyer = external_key;
\r
411 if(std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end())
\r
412 config.latency = low_latency;
\r
413 else if(std::find(params.begin(), params.end(), L"NORMAL_LATENCY") != params.end())
\r
414 config.latency = normal_latency;
\r
416 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
418 return make_safe<decklink_consumer_proxy>(config);
\r
421 safe_ptr<core::frame_consumer> create_decklink_consumer(const boost::property_tree::ptree& ptree)
\r
423 configuration config;
\r
425 auto key_str = ptree.get("key", "default");
\r
426 if(key_str == "internal")
\r
427 config.keyer = internal_key;
\r
428 else if(key_str == "external")
\r
429 config.keyer = external_key;
\r
431 auto latency_str = ptree.get("latency", "default");
\r
432 if(latency_str == "normal")
\r
433 config.latency = normal_latency;
\r
434 else if(latency_str == "low")
\r
435 config.latency = low_latency;
\r
437 config.device_index = ptree.get("device", 0);
\r
438 config.embedded_audio = ptree.get("embedded-audio", false);
\r
440 return make_safe<decklink_consumer_proxy>(config);
\r
446 ##############################################################################
\r
452 BMD Developer Support
\r
453 developer@blackmagic-design.com
\r
455 -----------------------------------------------------------------------------
\r
457 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
458 for scheduled playback is three frames for video and four frames for audio.
\r
459 As you mentioned if you preroll less frames then playback will not start or
\r
460 playback will be very sporadic. From our experience with Media Express, we
\r
461 recommended that at least seven frames are prerolled for smooth playback.
\r
463 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
464 There can be around 3 frames worth of latency on scheduled output.
\r
465 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
466 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
467 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
468 guarantee that the provided frame will be output as soon the previous
\r
469 frame output has been completed.
\r
470 ################################################################################
\r
474 ##############################################################################
\r
475 Async DMA Transfer without redundant copying
\r
480 BMD Developer Support
\r
481 developer@blackmagic-design.com
\r
483 -----------------------------------------------------------------------------
\r
485 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
486 and providing a pointer to your video buffer when GetBytes() is called.
\r
487 This may help to keep copying to a minimum. Please ensure that the pixel
\r
488 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
489 have to colourspace convert which may result in additional copying.
\r
490 ################################################################################
\r