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 <tbb/concurrent_queue.h>
\r
40 #include <boost/circular_buffer.hpp>
\r
41 #include <boost/timer.hpp>
\r
65 struct configuration
\r
67 size_t device_index;
\r
68 bool embedded_audio;
\r
71 output_pixels output;
\r
75 , embedded_audio(false)
\r
76 , keyer(default_key)
\r
77 , latency(default_latency)
\r
78 , output(fill_and_key){}
\r
81 class decklink_frame_adapter : public IDeckLinkVideoFrame
\r
83 const safe_ptr<const core::read_frame> frame_;
\r
84 const core::video_format_desc format_desc_;
\r
86 decklink_frame_adapter(const safe_ptr<const core::read_frame>& frame, const core::video_format_desc& format_desc)
\r
88 , format_desc_(format_desc){}
\r
90 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
91 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
92 STDMETHOD_(ULONG, Release()) {return 1;}
\r
94 STDMETHOD_(long, GetWidth()) {return format_desc_.width;}
\r
95 STDMETHOD_(long, GetHeight()) {return format_desc_.height;}
\r
96 STDMETHOD_(long, GetRowBytes()) {return format_desc_.width*4;}
\r
97 STDMETHOD_(BMDPixelFormat, GetPixelFormat()){return bmdFormat8BitBGRA;}
\r
98 STDMETHOD_(BMDFrameFlags, GetFlags()) {return bmdFrameFlagDefault;}
\r
100 STDMETHOD(GetBytes(void** buffer))
\r
102 static std::vector<unsigned char> zeros(1920*1080*4, 0);
\r
103 *buffer = const_cast<unsigned char*>(frame_->image_data().begin());
\r
104 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)
\r
105 *buffer = zeros.data();
\r
109 STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}
\r
110 STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}
\r
113 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
115 const configuration config_;
\r
117 CComPtr<IDeckLink> decklink_;
\r
118 CComQIPtr<IDeckLinkOutput> output_;
\r
119 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
120 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
122 std::exception_ptr exception_;
\r
124 tbb::atomic<bool> is_running_;
\r
126 const std::wstring model_name_;
\r
127 const core::video_format_desc format_desc_;
\r
128 const size_t buffer_size_;
\r
130 unsigned long frames_scheduled_;
\r
131 unsigned long audio_scheduled_;
\r
133 std::list<std::shared_ptr<IDeckLinkVideoFrame>> frame_container_; // Must be std::list in order to guarantee that pointers are always valid.
\r
134 boost::circular_buffer<std::vector<short>> audio_container_;
\r
136 tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> video_frame_buffer_;
\r
137 tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> audio_frame_buffer_;
\r
139 std::shared_ptr<diagnostics::graph> graph_;
\r
140 boost::timer tick_timer_;
\r
143 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc)
\r
145 , decklink_(get_device(config.device_index))
\r
146 , output_(decklink_)
\r
147 , configuration_(decklink_)
\r
148 , keyer_(decklink_)
\r
149 , model_name_(get_model_name(decklink_))
\r
150 , format_desc_(format_desc)
\r
151 , buffer_size_(config.embedded_audio ? 5 : 4) // Minimum buffer-size (3 + 1 tolerance).
\r
152 , frames_scheduled_(0)
\r
153 , audio_scheduled_(0)
\r
154 , audio_container_(buffer_size_+1)
\r
156 is_running_ = true;
\r
158 video_frame_buffer_.set_capacity(1);
\r
159 audio_frame_buffer_.set_capacity(1);
\r
161 graph_ = diagnostics::create_graph(narrow(print()));
\r
162 graph_->add_guide("tick-time", 0.5);
\r
163 graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f));
\r
164 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
165 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
166 graph_->set_color("flushed-frame", diagnostics::color(0.3f, 0.3f, 0.6f));
\r
168 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
170 if(config.embedded_audio)
\r
173 set_latency(config.latency);
\r
174 set_keyer(config.keyer);
\r
176 for(size_t n = 0; n < buffer_size_; ++n)
\r
177 schedule_next_video(core::read_frame::empty());
\r
179 if(config.embedded_audio)
\r
180 output_->BeginAudioPreroll();
\r
184 CASPAR_LOG(info) << print() << L" Buffer depth: " << buffer_size_;
\r
185 CASPAR_LOG(info) << print() << L" Successfully Initialized.";
\r
188 ~decklink_consumer()
\r
190 is_running_ = false;
\r
191 video_frame_buffer_.try_push(core::read_frame::empty());
\r
192 audio_frame_buffer_.try_push(core::read_frame::empty());
\r
194 if(output_ != nullptr)
\r
196 output_->StopScheduledPlayback(0, nullptr, 0);
\r
197 if(config_.embedded_audio)
\r
198 output_->DisableAudioOutput();
\r
199 output_->DisableVideoOutput();
\r
203 void set_latency(latency latency)
\r
205 if(latency == normal_latency)
\r
207 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
208 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
210 else if(latency == low_latency)
\r
212 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
213 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
216 CASPAR_LOG(info) << print() << L" Uses driver latency settings.";
\r
219 void set_keyer(key keyer)
\r
221 if(keyer == internal_key)
\r
223 if(FAILED(keyer_->Enable(FALSE)))
\r
224 CASPAR_LOG(error) << print() << L" Failed to enable internal 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 internal keyer.";
\r
230 else if(keyer == external_key)
\r
232 if(FAILED(keyer_->Enable(TRUE)))
\r
233 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
234 else if(FAILED(keyer_->SetLevel(255)))
\r
235 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
237 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
240 CASPAR_LOG(info) << print() << L" Uses driver keyer settings.";
\r
243 void enable_audio()
\r
245 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
246 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
248 if(FAILED(output_->SetAudioCallback(this)))
\r
249 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
251 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
254 void enable_video(BMDDisplayMode display_mode)
\r
256 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
257 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
259 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
260 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to set playback completion callback."));
\r
263 void start_playback()
\r
265 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
266 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
269 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
270 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
271 STDMETHOD_(ULONG, Release()) {return 1;}
\r
273 STDMETHOD(ScheduledPlaybackHasStopped())
\r
275 is_running_ = false;
\r
276 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
280 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
287 if(result == bmdOutputFrameDisplayedLate)
\r
288 graph_->add_tag("late-frame");
\r
289 else if(result == bmdOutputFrameDropped)
\r
290 graph_->add_tag("dropped-frame");
\r
291 else if(result == bmdOutputFrameFlushed)
\r
292 graph_->add_tag("flushed-frame");
\r
294 frame_container_.erase(std::find_if(frame_container_.begin(), frame_container_.end(), [&](const std::shared_ptr<IDeckLinkVideoFrame> frame)
\r
296 return frame.get() == completed_frame;
\r
299 std::shared_ptr<const core::read_frame> frame;
\r
300 video_frame_buffer_.pop(frame);
\r
301 schedule_next_video(safe_ptr<const core::read_frame>(frame));
\r
305 exception_ = std::current_exception();
\r
312 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
319 std::shared_ptr<const core::read_frame> frame;
\r
320 audio_frame_buffer_.pop(frame);
\r
321 schedule_next_audio(safe_ptr<const core::read_frame>(frame));
\r
328 exception_ = std::current_exception();
\r
335 void schedule_next_audio(const safe_ptr<const core::read_frame>& frame)
\r
337 static std::vector<short> silence(48000, 0);
\r
339 const int sample_count = format_desc_.audio_samples_per_frame;
\r
340 const int sample_frame_count = sample_count/2;
\r
342 const short* frame_audio_data = frame->audio_data().size() == sample_count ? frame->audio_data().begin() : silence.data();
\r
343 audio_container_.push_back(std::vector<short>(frame_audio_data, frame_audio_data+sample_count));
\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<const core::read_frame>& frame)
\r
351 frame_container_.push_back(std::make_shared<decklink_frame_adapter>(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<const 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<const 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_.output == caspar::key_only);
\r
410 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params)
\r
412 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
413 return core::frame_consumer::empty();
\r
415 configuration config;
\r
417 if(params.size() > 1)
\r
418 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
420 if(std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end())
\r
421 config.keyer = internal_key;
\r
422 else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end())
\r
423 config.keyer = external_key;
\r
425 if(std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end())
\r
426 config.latency = low_latency;
\r
427 else if(std::find(params.begin(), params.end(), L"NORMAL_LATENCY") != params.end())
\r
428 config.latency = normal_latency;
\r
430 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
431 config.output = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end() ? key_only : fill_and_key;
\r
433 return make_safe<decklink_consumer_proxy>(config);
\r
436 safe_ptr<core::frame_consumer> create_decklink_consumer(const boost::property_tree::ptree& ptree)
\r
438 configuration config;
\r
440 auto key_str = ptree.get("key", "default");
\r
441 if(key_str == "internal")
\r
442 config.keyer = internal_key;
\r
443 else if(key_str == "external")
\r
444 config.keyer = external_key;
\r
446 auto latency_str = ptree.get("latency", "default");
\r
447 if(latency_str == "normal")
\r
448 config.latency = normal_latency;
\r
449 else if(latency_str == "low")
\r
450 config.latency = low_latency;
\r
452 auto output_str = ptree.get("output", "fill_and_key");
\r
453 if(output_str == "key_only")
\r
454 config.output = key_only;
\r
456 config.device_index = ptree.get("device", 0);
\r
457 config.embedded_audio = ptree.get("embedded-audio", false);
\r
459 return make_safe<decklink_consumer_proxy>(config);
\r
465 ##############################################################################
\r
471 BMD Developer Support
\r
472 developer@blackmagic-design.com
\r
474 -----------------------------------------------------------------------------
\r
476 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
477 for scheduled playback is three frames for video and four frames for audio.
\r
478 As you mentioned if you preroll less frames then playback will not start or
\r
479 playback will be very sporadic. From our experience with Media Express, we
\r
480 recommended that at least seven frames are prerolled for smooth playback.
\r
482 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
483 There can be around 3 frames worth of latency on scheduled output.
\r
484 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
485 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
486 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
487 guarantee that the provided frame will be output as soon the previous
\r
488 frame output has been completed.
\r
489 ################################################################################
\r
493 ##############################################################################
\r
494 Async DMA Transfer without redundant copying
\r
499 BMD Developer Support
\r
500 developer@blackmagic-design.com
\r
502 -----------------------------------------------------------------------------
\r
504 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
505 and providing a pointer to your video buffer when GetBytes() is called.
\r
506 This may help to keep copying to a minimum. Please ensure that the pixel
\r
507 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
508 have to colourspace convert which may result in additional copying.
\r
509 ################################################################################
\r