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 class decklink_frame_adapter : public IDeckLinkVideoFrame
\r
88 safe_ptr<const core::read_frame> frame_;
\r
89 core::video_format_desc format_desc_;
\r
91 decklink_frame_adapter(const safe_ptr<const core::read_frame>& frame, const core::video_format_desc& format_desc)
\r
93 , format_desc_(format_desc){}
\r
95 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
96 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
97 STDMETHOD_(ULONG, Release()) {return 1;}
\r
99 STDMETHOD_(long, GetWidth()) {return format_desc_.width;}
\r
100 STDMETHOD_(long, GetHeight()) {return format_desc_.height;}
\r
101 STDMETHOD_(long, GetRowBytes()) {return format_desc_.width*4;}
\r
102 STDMETHOD_(BMDPixelFormat, GetPixelFormat()){return bmdFormat8BitBGRA;}
\r
103 STDMETHOD_(BMDFrameFlags, GetFlags()) {return bmdFrameFlagDefault;}
\r
105 STDMETHOD(GetBytes(void** buffer))
\r
107 static std::vector<unsigned char> zeros(1920*1080*4, 0);
\r
108 *buffer = const_cast<unsigned char*>(frame_->image_data().begin());
\r
109 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)
\r
110 *buffer = zeros.data();
\r
114 STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}
\r
115 STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}
\r
118 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
\r
120 const configuration config_;
\r
122 CComPtr<IDeckLink> decklink_;
\r
123 CComQIPtr<IDeckLinkOutput> output_;
\r
124 CComQIPtr<IDeckLinkConfiguration> configuration_;
\r
125 CComQIPtr<IDeckLinkKeyer> keyer_;
\r
127 std::exception_ptr exception_;
\r
129 tbb::atomic<bool> is_running_;
\r
131 const std::wstring model_name_;
\r
132 const core::video_format_desc format_desc_;
\r
133 const size_t buffer_size_;
\r
135 unsigned long frames_scheduled_;
\r
136 unsigned long audio_scheduled_;
\r
138 std::list<decklink_frame_adapter> frame_container_; // Must be std::list in order to guarantee that pointers are always valid.
\r
139 boost::circular_buffer<std::vector<short>> audio_container_;
\r
141 tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> video_frame_buffer_;
\r
142 tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> audio_frame_buffer_;
\r
144 std::shared_ptr<diagnostics::graph> graph_;
\r
145 boost::timer tick_timer_;
\r
149 decklink_consumer(const configuration& config, const core::video_format_desc& format_desc)
\r
151 , decklink_(get_device(config.device_index))
\r
152 , output_(decklink_)
\r
153 , configuration_(decklink_)
\r
154 , keyer_(decklink_)
\r
155 , model_name_(get_model_name(decklink_))
\r
156 , format_desc_(format_desc)
\r
157 , buffer_size_(config.embedded_audio ? 5 : 4) // Minimum buffer-size (3 + 1 tolerance).
\r
158 , frames_scheduled_(0)
\r
159 , audio_scheduled_(0)
\r
160 , audio_container_(buffer_size_+1)
\r
162 is_running_ = true;
\r
164 video_frame_buffer_.set_capacity(1);
\r
165 audio_frame_buffer_.set_capacity(1);
\r
167 graph_ = diagnostics::create_graph(narrow(print()));
\r
168 graph_->add_guide("tick-time", 0.5);
\r
169 graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f));
\r
170 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
\r
171 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
\r
172 graph_->set_color("flushed-frame", diagnostics::color(0.3f, 0.3f, 0.6f));
\r
174 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
\r
176 if(config.embedded_audio)
\r
179 set_latency(config.latency);
\r
180 set_keyer(config.keyer);
\r
182 for(size_t n = 0; n < buffer_size_; ++n)
\r
183 schedule_next_video(core::read_frame::empty());
\r
185 if(config.embedded_audio)
\r
186 output_->BeginAudioPreroll();
\r
189 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
190 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
193 CASPAR_LOG(info) << print() << L" Buffer depth: " << buffer_size_;
\r
194 CASPAR_LOG(info) << print() << L" Successfully initialized for " << format_desc_.name;
\r
197 ~decklink_consumer()
\r
199 is_running_ = false;
\r
200 video_frame_buffer_.clear();
\r
201 audio_frame_buffer_.clear();
\r
202 video_frame_buffer_.try_push(core::read_frame::empty());
\r
203 audio_frame_buffer_.try_push(core::read_frame::empty());
\r
205 if(output_ != nullptr)
\r
207 output_->StopScheduledPlayback(0, nullptr, 0);
\r
208 if(config_.embedded_audio)
\r
209 output_->DisableAudioOutput();
\r
210 output_->DisableVideoOutput();
\r
214 void set_latency(latency latency)
\r
216 if(latency == normal_latency)
\r
218 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
\r
219 CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";
\r
221 else if(latency == low_latency)
\r
223 configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
\r
224 CASPAR_LOG(info) << print() << L" Enabled low-latency mode";
\r
227 CASPAR_LOG(info) << print() << L" Uses driver latency settings.";
\r
230 void set_keyer(key keyer)
\r
232 if(keyer == internal_key)
\r
234 if(FAILED(keyer_->Enable(FALSE)))
\r
235 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";
\r
236 else if(FAILED(keyer_->SetLevel(255)))
\r
237 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
239 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";
\r
241 else if(keyer == external_key)
\r
243 if(FAILED(keyer_->Enable(TRUE)))
\r
244 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";
\r
245 else if(FAILED(keyer_->SetLevel(255)))
\r
246 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
\r
248 CASPAR_LOG(info) << print() << L" Enabled external keyer.";
\r
251 CASPAR_LOG(info) << print() << L" Uses driver keyer settings.";
\r
254 void enable_audio()
\r
256 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))
\r
257 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));
\r
259 if(FAILED(output_->SetAudioCallback(this)))
\r
260 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));
\r
262 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
\r
265 void enable_video(BMDDisplayMode display_mode)
\r
267 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
\r
268 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));
\r
270 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
\r
271 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to set playback completion callback."));
\r
274 STDMETHOD (QueryInterface(REFIID, LPVOID*)) {return E_NOINTERFACE;}
\r
275 STDMETHOD_(ULONG, AddRef()) {return 1;}
\r
276 STDMETHOD_(ULONG, Release()) {return 1;}
\r
278 STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))
\r
285 if(result == bmdOutputFrameDisplayedLate)
\r
286 graph_->add_tag("late-frame");
\r
287 else if(result == bmdOutputFrameDropped)
\r
288 graph_->add_tag("dropped-frame");
\r
289 else if(result == bmdOutputFrameFlushed)
\r
290 graph_->add_tag("flushed-frame");
\r
292 frame_container_.erase(std::remove_if(frame_container_.begin(), frame_container_.end(), [&](const decklink_frame_adapter& frame)
\r
294 return &frame == completed_frame;
\r
295 }), frame_container_.end());
\r
297 std::shared_ptr<const core::read_frame> frame;
\r
298 video_frame_buffer_.pop(frame);
\r
299 schedule_next_video(safe_ptr<const core::read_frame>(frame));
\r
303 exception_ = std::current_exception();
\r
310 STDMETHOD(ScheduledPlaybackHasStopped())
\r
312 is_running_ = false;
\r
313 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
\r
317 STDMETHOD(RenderAudioSamples(BOOL preroll))
\r
326 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
\r
327 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));
\r
330 std::shared_ptr<const core::read_frame> frame;
\r
331 audio_frame_buffer_.pop(frame);
\r
332 schedule_next_audio(safe_ptr<const core::read_frame>(frame));
\r
336 exception_ = std::current_exception();
\r
343 void schedule_next_audio(const safe_ptr<const core::read_frame>& frame)
\r
345 static std::vector<short> silence(48000, 0);
\r
347 int audio_samples = static_cast<size_t>(48000.0 / format_desc_.fps)*2; // Audio samples per channel
\r
349 const short* frame_audio_data = frame->audio_data().size() == audio_samples ? frame->audio_data().begin() : silence.data();
\r
350 audio_container_.push_back(std::vector<short>(frame_audio_data, frame_audio_data+audio_samples));
\r
352 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), audio_samples/2, (audio_scheduled_++) * audio_samples/2, 48000, nullptr)))
\r
353 CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
\r
356 void schedule_next_video(const safe_ptr<const core::read_frame>& frame)
\r
358 frame_container_.push_back(decklink_frame_adapter(frame, format_desc_));
\r
359 if(FAILED(output_->ScheduleVideoFrame(&frame_container_.back(), (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))
\r
360 CASPAR_LOG(error) << print() << L" Failed to schedule video.";
\r
362 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
\r
363 tick_timer_.restart();
\r
366 void send(const safe_ptr<const core::read_frame>& frame)
\r
368 if(exception_ != nullptr)
\r
369 std::rethrow_exception(exception_);
\r
372 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));
\r
374 if(config_.embedded_audio)
\r
375 audio_frame_buffer_.push(frame);
\r
376 video_frame_buffer_.push(frame);
\r
379 std::wstring print() const
\r
381 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"]";
\r
385 struct decklink_consumer_proxy : public core::frame_consumer
\r
387 const configuration config_;
\r
389 com_context<decklink_consumer> context_;
\r
392 decklink_consumer_proxy(const configuration& config)
\r
394 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]"){}
\r
396 virtual void initialize(const core::video_format_desc& format_desc)
\r
398 context_.reset([&]{return new decklink_consumer(config_, format_desc);});
\r
401 virtual void send(const safe_ptr<const core::read_frame>& frame)
\r
403 context_->send(frame);
\r
406 virtual std::wstring print() const
\r
408 return context_->print();
\r
412 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params)
\r
414 if(params.size() < 1 || params[0] != L"DECKLINK")
\r
415 return core::frame_consumer::empty();
\r
417 configuration config;
\r
419 if(params.size() > 1)
\r
420 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);
\r
422 if(std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end())
\r
423 config.keyer = internal_key;
\r
424 else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end())
\r
425 config.keyer = external_key;
\r
427 if(std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end())
\r
428 config.latency = low_latency;
\r
429 else if(std::find(params.begin(), params.end(), L"NORMAL_LATENCY") != params.end())
\r
430 config.latency = normal_latency;
\r
432 config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();
\r
434 return make_safe<decklink_consumer_proxy>(config);
\r
437 safe_ptr<core::frame_consumer> create_decklink_consumer(const boost::property_tree::ptree& ptree)
\r
439 configuration config;
\r
441 auto key_str = ptree.get("key", "default");
\r
442 if(key_str == "internal")
\r
443 config.keyer = internal_key;
\r
444 else if(key_str == "external")
\r
445 config.keyer = external_key;
\r
447 auto latency_str = ptree.get("latency", "default");
\r
448 if(latency_str == "normal")
\r
449 config.latency = normal_latency;
\r
450 else if(latency_str == "low")
\r
451 config.latency = low_latency;
\r
453 config.device_index = ptree.get("device", 0);
\r
454 config.embedded_audio = ptree.get("embedded-audio", false);
\r
456 return make_safe<decklink_consumer_proxy>(config);
\r
462 ##############################################################################
\r
468 BMD Developer Support
\r
469 developer@blackmagic-design.com
\r
471 -----------------------------------------------------------------------------
\r
473 Thanks for your inquiry. The minimum number of frames that you can preroll
\r
474 for scheduled playback is three frames for video and four frames for audio.
\r
475 As you mentioned if you preroll less frames then playback will not start or
\r
476 playback will be very sporadic. From our experience with Media Express, we
\r
477 recommended that at least seven frames are prerolled for smooth playback.
\r
479 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
\r
480 There can be around 3 frames worth of latency on scheduled output.
\r
481 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
\r
482 reduced or removed for scheduled playback. If the DisplayVideoFrameSync()
\r
483 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will
\r
484 guarantee that the provided frame will be output as soon the previous
\r
485 frame output has been completed.
\r
486 ################################################################################
\r
490 ##############################################################################
\r
491 Async DMA Transfer without redundant copying
\r
496 BMD Developer Support
\r
497 developer@blackmagic-design.com
\r
499 -----------------------------------------------------------------------------
\r
501 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame
\r
502 and providing a pointer to your video buffer when GetBytes() is called.
\r
503 This may help to keep copying to a minimum. Please ensure that the pixel
\r
504 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will
\r
505 have to colourspace convert which may result in additional copying.
\r
506 ################################################################################
\r