]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
Fixed compilation problem in Linux
[casparcg] / modules / decklink / consumer / decklink_consumer.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Robert Nagy, ronag89@gmail.com
20 */
21
22 #include "../StdAfx.h"
23  
24 #include "decklink_consumer.h"
25
26 #include "../util/util.h"
27
28 #include "../decklink_api.h"
29
30 #include <core/frame/frame.h>
31 #include <core/mixer/audio/audio_mixer.h>
32 #include <core/consumer/frame_consumer.h>
33 #include <core/diagnostics/call_context.h>
34 #include <core/help/help_sink.h>
35 #include <core/help/help_repository.h>
36
37 #include <common/executor.h>
38 #include <common/lock.h>
39 #include <common/diagnostics/graph.h>
40 #include <common/except.h>
41 #include <common/memshfl.h>
42 #include <common/array.h>
43 #include <common/future.h>
44 #include <common/cache_aligned_vector.h>
45 #include <common/timer.h>
46 #include <common/param.h>
47
48 #include <tbb/concurrent_queue.h>
49
50 #include <common/assert.h>
51 #include <boost/lexical_cast.hpp>
52 #include <boost/circular_buffer.hpp>
53 #include <boost/property_tree/ptree.hpp>
54
55 namespace caspar { namespace decklink { 
56         
57 struct configuration
58 {
59         enum class keyer_t
60         {
61                 internal_keyer,
62                 external_keyer,
63                 default_keyer
64         };
65
66         enum class latency_t
67         {
68                 low_latency,
69                 normal_latency,
70                 default_latency
71         };
72
73         int                     device_index            = 1;
74         bool            embedded_audio          = true;
75         keyer_t         keyer                           = keyer_t::default_keyer;
76         latency_t       latency                         = latency_t::default_latency;
77         bool            key_only                        = false;
78         int                     base_buffer_depth       = 3;
79         
80         int buffer_depth() const
81         {
82                 return base_buffer_depth + (latency == latency_t::low_latency ? 0 : 1) + (embedded_audio ? 1 : 0);
83         }
84 };
85
86 class decklink_frame : public IDeckLinkVideoFrame
87 {
88         tbb::atomic<int>                                ref_count_;
89         core::const_frame                               frame_;
90         const core::video_format_desc   format_desc_;
91
92         const bool                                              key_only_;
93         cache_aligned_vector<uint8_t>   data_;
94 public:
95         decklink_frame(core::const_frame frame, const core::video_format_desc& format_desc, bool key_only)
96                 : frame_(frame)
97                 , format_desc_(format_desc)
98                 , key_only_(key_only)
99         {
100                 ref_count_ = 0;
101         }
102         
103         // IUnknown
104
105         virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)
106         {
107                 return E_NOINTERFACE;
108         }
109         
110         virtual ULONG STDMETHODCALLTYPE AddRef()
111         {
112                 return ++ref_count_;
113         }
114
115         virtual ULONG STDMETHODCALLTYPE Release()
116         {
117                 if(--ref_count_ == 0)
118                         delete this;
119                 return ref_count_;
120         }
121
122         // IDecklinkVideoFrame
123
124         virtual long STDMETHODCALLTYPE GetWidth()                   {return static_cast<long>(format_desc_.width);}
125         virtual long STDMETHODCALLTYPE GetHeight()                  {return static_cast<long>(format_desc_.height);}
126         virtual long STDMETHODCALLTYPE GetRowBytes()                {return static_cast<long>(format_desc_.width*4);}
127         virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat()   {return bmdFormat8BitBGRA;}
128         virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags()                      {return bmdFrameFlagDefault;}
129                 
130         virtual HRESULT STDMETHODCALLTYPE GetBytes(void** buffer)
131         {
132                 try
133                 {
134                         if(static_cast<int>(frame_.image_data().size()) != format_desc_.size)
135                         {
136                                 data_.resize(format_desc_.size, 0);
137                                 *buffer = data_.data();
138                         }
139                         else if(key_only_)
140                         {
141                                 if(data_.empty())
142                                 {
143                                         data_.resize(frame_.image_data().size());
144                                         aligned_memshfl(data_.data(), frame_.image_data().begin(), frame_.image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);
145                                 }
146                                 *buffer = data_.data();
147                         }
148                         else
149                                 *buffer = const_cast<uint8_t*>(frame_.image_data().begin());
150                 }
151                 catch(...)
152                 {
153                         CASPAR_LOG_CURRENT_EXCEPTION();
154                         return E_FAIL;
155                 }
156
157                 return S_OK;
158         }
159                 
160         virtual HRESULT STDMETHODCALLTYPE GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode) {return S_FALSE;}
161         virtual HRESULT STDMETHODCALLTYPE GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)              {return S_FALSE;}
162
163         // decklink_frame       
164
165         const core::audio_buffer& audio_data()
166         {
167                 return frame_.audio_data();
168         }
169
170         int64_t get_age_millis() const
171         {
172                 return frame_.get_age_millis();
173         }
174 };
175
176 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
177 {               
178         const int                                                                                       channel_index_;
179         const configuration                                                                     config_;
180
181         com_ptr<IDeckLink>                                                                      decklink_                               = get_device(config_.device_index);
182         com_iface_ptr<IDeckLinkOutput>                                          output_                                 = iface_cast<IDeckLinkOutput>(decklink_);
183         com_iface_ptr<IDeckLinkConfiguration>                           configuration_                  = iface_cast<IDeckLinkConfiguration>(decklink_);
184         com_iface_ptr<IDeckLinkKeyer>                                           keyer_                                  = iface_cast<IDeckLinkKeyer>(decklink_);
185         com_iface_ptr<IDeckLinkAttributes>                                      attributes_                             = iface_cast<IDeckLinkAttributes>(decklink_);
186
187         tbb::spin_mutex                                     exception_mutex_;
188         std::exception_ptr                                  exception_;
189
190         tbb::atomic<bool>                                   is_running_;
191                 
192         const std::wstring                                  model_name_                         = get_model_name(decklink_);
193         const core::video_format_desc                       format_desc_;
194         const int                                           buffer_size_                        = config_.buffer_depth(); // Minimum buffer-size 3.
195
196         long long                                           video_scheduled_            = 0;
197         long long                                           audio_scheduled_            = 0;
198
199         int                                                 preroll_count_                      = 0;
200                 
201         boost::circular_buffer<std::vector<int32_t>>        audio_container_            { buffer_size_ + 1 };
202
203         tbb::concurrent_bounded_queue<core::const_frame>    video_frame_buffer_;
204         tbb::concurrent_bounded_queue<core::const_frame>    audio_frame_buffer_;
205         
206         spl::shared_ptr<diagnostics::graph>                 graph_;
207         tbb::atomic<int64_t>                                                            current_presentation_delay_;
208         caspar::timer                                                                           tick_timer_;
209         retry_task<bool>                                    send_completion_;
210
211 public:
212         decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index) 
213                 : channel_index_(channel_index)
214                 , config_(config)
215                 , format_desc_(format_desc)
216         {
217                 is_running_ = true;
218                 current_presentation_delay_ = 0;
219                                 
220                 video_frame_buffer_.set_capacity(1);
221
222                 // Blackmagic calls RenderAudioSamples() 50 times per second
223                 // regardless of video mode so we sometimes need to give them
224                 // samples from 2 frames in order to keep up
225                 audio_frame_buffer_.set_capacity((format_desc.fps > 50.0) ? 2 : 1);
226
227                 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));   
228                 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
229                 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
230                 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
231                 graph_->set_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));
232                 graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));
233                 graph_->set_text(print());
234                 diagnostics::register_graph(graph_);
235                 
236                 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
237                                 
238                 if(config.embedded_audio)
239                         enable_audio();
240                 
241                 set_latency(config.latency);                            
242                 set_keyer(config.keyer);
243                                 
244                 if(config.embedded_audio)               
245                         output_->BeginAudioPreroll();           
246                 
247                 for(int n = 0; n < buffer_size_; ++n)
248                         schedule_next_video(core::const_frame::empty());
249
250                 if(!config.embedded_audio)
251                         start_playback();
252         }
253
254         ~decklink_consumer()
255         {               
256                 is_running_ = false;
257                 video_frame_buffer_.try_push(core::const_frame::empty());
258                 audio_frame_buffer_.try_push(core::const_frame::empty());
259
260                 if(output_ != nullptr) 
261                 {
262                         output_->StopScheduledPlayback(0, nullptr, 0);
263                         if(config_.embedded_audio)
264                                 output_->DisableAudioOutput();
265                         output_->DisableVideoOutput();
266                 }
267         }
268                         
269         void set_latency(configuration::latency_t latency)
270         {               
271                 if(latency == configuration::latency_t::low_latency)
272                 {
273                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
274                         CASPAR_LOG(info) << print() << L" Enabled low-latency mode.";
275                 }
276                 else if(latency == configuration::latency_t::normal_latency)
277                 {                       
278                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
279                         CASPAR_LOG(info) << print() << L" Disabled low-latency mode.";
280                 }
281         }
282
283         void set_keyer(configuration::keyer_t keyer)
284         {
285                 if(keyer == configuration::keyer_t::internal_keyer) 
286                 {
287                         BOOL value = true;
288                         if(SUCCEEDED(attributes_->GetFlag(BMDDeckLinkSupportsInternalKeying, &value)) && !value)
289                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";   
290                         else if(FAILED(keyer_->Enable(FALSE)))                  
291                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   
292                         else if(FAILED(keyer_->SetLevel(255)))                  
293                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
294                         else
295                                 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             
296                 }
297                 else if(keyer == configuration::keyer_t::external_keyer)
298                 {
299                         BOOL value = true;
300                         if(SUCCEEDED(attributes_->GetFlag(BMDDeckLinkSupportsExternalKeying, &value)) && !value)
301                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   
302                         else if(FAILED(keyer_->Enable(TRUE)))                   
303                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   
304                         else if(FAILED(keyer_->SetLevel(255)))                  
305                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
306                         else
307                                 CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     
308                 }
309         }
310         
311         void enable_audio()
312         {
313                 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))
314                                 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable audio output."));
315                                 
316                 if(FAILED(output_->SetAudioCallback(this)))
317                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not set audio callback."));
318
319                 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
320         }
321
322         void enable_video(BMDDisplayMode display_mode)
323         {
324                 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) 
325                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable video output."));
326                 
327                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
328                         CASPAR_THROW_EXCEPTION(caspar_exception() 
329                                                                         << msg_info(u8(print()) + " Failed to set playback completion callback.")
330                                                                         << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
331         }
332
333         void start_playback()
334         {
335                 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) 
336                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Failed to schedule playback."));
337         }
338         
339         virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)       {return E_NOINTERFACE;}
340         virtual ULONG STDMETHODCALLTYPE AddRef()                                        {return 1;}
341         virtual ULONG STDMETHODCALLTYPE Release()                               {return 1;}
342         
343         virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped()
344         {
345                 is_running_ = false;
346                 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";
347                 return S_OK;
348         }
349
350         virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result)
351         {
352                 if(!is_running_)
353                         return E_FAIL;
354                 
355                 try
356                 {
357                         auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);
358                         current_presentation_delay_ = dframe->get_age_millis();
359                         if(result == bmdOutputFrameDisplayedLate)
360                         {
361                                 graph_->set_tag("late-frame");
362                                 video_scheduled_ += format_desc_.duration;
363                                 audio_scheduled_ += dframe->audio_data().size() / format_desc_.audio_channels;
364                                 //++video_scheduled_;
365                                 //audio_scheduled_ += format_desc_.audio_cadence[0];
366                                 //++audio_scheduled_;
367                         }
368                         else if(result == bmdOutputFrameDropped)
369                                 graph_->set_tag("dropped-frame");
370                         else if(result == bmdOutputFrameFlushed)
371                                 graph_->set_tag("flushed-frame");
372
373                         auto frame = core::const_frame::empty();        
374                         video_frame_buffer_.pop(frame);
375                         send_completion_.try_completion();
376                         schedule_next_video(frame);     
377                         
378                         UINT32 buffered;
379                         output_->GetBufferedVideoFrameCount(&buffered);
380                         graph_->set_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);
381                 }
382                 catch(...)
383                 {
384                         lock(exception_mutex_, [&]
385                         {
386                                 exception_ = std::current_exception();
387                         });
388                         return E_FAIL;
389                 }
390
391                 return S_OK;
392         }
393                 
394         virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples(BOOL preroll)
395         {
396                 if(!is_running_)
397                         return E_FAIL;
398                 
399                 try
400                 {       
401                         if(preroll)
402                         {
403                                 if(++preroll_count_ >= buffer_size_)
404                                 {
405                                         output_->EndAudioPreroll();
406                                         start_playback();                               
407                                 }
408                                 else
409                                 {
410                                         schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()] * format_desc_.audio_channels, 0));
411                                 }
412                         }
413                         else
414                         {
415                                 auto frame = core::const_frame::empty();
416
417                                 while(audio_frame_buffer_.try_pop(frame))
418                                 {
419                                         send_completion_.try_completion();
420                                         schedule_next_audio(frame.audio_data());
421                                 }
422                         }
423
424                         UINT32 buffered;
425                         output_->GetBufferedAudioSampleFrameCount(&buffered);
426                         graph_->set_value("buffered-audio", static_cast<double>(buffered) / (format_desc_.audio_cadence[0] * format_desc_.audio_channels * 2));
427                 }
428                 catch(...)
429                 {
430                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);
431                         exception_ = std::current_exception();
432                         return E_FAIL;
433                 }
434
435                 return S_OK;
436         }
437
438         template<typename T>
439         void schedule_next_audio(const T& audio_data)
440         {
441                 auto sample_frame_count = static_cast<int>(audio_data.size()/format_desc_.audio_channels);
442
443                 audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));
444
445                 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, audio_scheduled_, format_desc_.audio_sample_rate, nullptr)))
446                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";
447
448                 audio_scheduled_ += sample_frame_count;
449         }
450                         
451         void schedule_next_video(core::const_frame frame)
452         {
453                 auto frame2 = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, config_.key_only));
454                 if(FAILED(output_->ScheduleVideoFrame(get_raw(frame2), video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
455                         CASPAR_LOG(error) << print() << L" Failed to schedule video.";
456
457                 video_scheduled_ += format_desc_.duration;
458
459                 graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
460                 tick_timer_.restart();
461         }
462
463         std::future<bool> send(core::const_frame frame)
464         {
465                 auto exception = lock(exception_mutex_, [&]
466                 {
467                         return exception_;
468                 });
469
470                 if(exception != nullptr)
471                         std::rethrow_exception(exception);              
472
473                 if(!is_running_)
474                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Is not running."));
475                 
476                 bool audio_ready = !config_.embedded_audio;
477                 bool video_ready = false;
478
479                 auto enqueue_task = [audio_ready, video_ready, frame, this]() mutable -> boost::optional<bool>
480                 {
481                         if (!audio_ready)
482                                 audio_ready = audio_frame_buffer_.try_push(frame);
483
484                         if (!video_ready)
485                                 video_ready = video_frame_buffer_.try_push(frame);
486
487                         if (audio_ready && video_ready)
488                                 return true;
489                         else
490                                 return boost::optional<bool>();
491                 };
492                 
493                 if (enqueue_task())
494                         return make_ready_future(true);
495
496                 send_completion_.set_task(enqueue_task);
497
498                 return send_completion_.get_future();
499         }
500         
501         std::wstring print() const
502         {
503                 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +
504                         boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";
505         }
506 };
507
508 struct decklink_consumer_proxy : public core::frame_consumer
509 {
510         core::monitor::subject                          monitor_subject_;
511         const configuration                                     config_;
512         std::unique_ptr<decklink_consumer>      consumer_;
513         core::video_format_desc                         format_desc_;
514         executor                                                        executor_;
515 public:
516
517         decklink_consumer_proxy(const configuration& config)
518                 : config_(config)
519                 , executor_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")
520         {
521                 auto ctx = core::diagnostics::call_context::for_thread();
522                 executor_.begin_invoke([=]
523                 {
524                         core::diagnostics::call_context::for_thread() = ctx;
525                         com_initialize();
526                 });
527         }
528
529         ~decklink_consumer_proxy()
530         {
531                 executor_.invoke([=]
532                 {
533                         consumer_.reset();
534                         com_uninitialize();
535                 });
536         }
537
538         // frame_consumer
539         
540         void initialize(const core::video_format_desc& format_desc, int channel_index) override
541         {
542                 format_desc_ = format_desc;
543                 executor_.invoke([=]
544                 {
545                         consumer_.reset();
546                         consumer_.reset(new decklink_consumer(config_, format_desc, channel_index));                    
547                 });
548         }
549         
550         std::future<bool> send(core::const_frame frame) override
551         {
552                 return consumer_->send(frame);
553         }
554         
555         std::wstring print() const override
556         {
557                 return consumer_ ? consumer_->print() : L"[decklink_consumer]";
558         }               
559
560         std::wstring name() const override
561         {
562                 return L"decklink";
563         }
564
565         boost::property_tree::wptree info() const override
566         {
567                 boost::property_tree::wptree info;
568                 info.add(L"type", L"decklink");
569                 info.add(L"key-only", config_.key_only);
570                 info.add(L"device", config_.device_index);
571                 info.add(L"low-latency", config_.latency == configuration::latency_t::low_latency);
572                 info.add(L"embedded-audio", config_.embedded_audio);
573                 info.add(L"presentation-frame-age", presentation_frame_age_millis());
574                 //info.add(L"internal-key", config_.internal_key);
575                 return info;
576         }
577
578         int buffer_depth() const override
579         {
580                 return config_.buffer_depth() + 2;
581         }
582
583         int index() const override
584         {
585                 return 300 + config_.device_index;
586         }
587
588         int64_t presentation_frame_age_millis() const override
589         {
590                 return consumer_ ? static_cast<int64_t>(consumer_->current_presentation_delay_) : 0;
591         }
592
593         core::monitor::subject& monitor_output()
594         {
595                 return monitor_subject_;
596         }
597 };      
598
599 void describe_consumer(core::help_sink& sink, const core::help_repository& repo)
600 {
601         sink.short_description(L"Sends video on an SDI output using Blackmagic Decklink video cards.");
602         sink.syntax(L"DECKLINK "
603                                 L"{[device_index:int]|1} "
604                                 L"{[keyer:INTERNAL_KEY,EXTERNAL_KEY]} "
605                                 L"{[low_latency:LOW_LATENCY]} "
606                                 L"{[embedded_audio:EMBEDDED_AUDIO]} "
607                                 L"{[key_only:KEY_ONLY]}");
608         sink.para()->text(L"Sends video on an SDI output using Blackmagic Decklink video cards.");
609         sink.definitions()
610                 ->item(L"device_index", L"The Blackmagic video card to use (See Blackmagic control panel for card order). Default is 1.")
611                 ->item(L"keyer", L"If given tries to enable either internal or external keying. Not all Blackmagic cards supports this.")
612                 ->item(L"low_latency", L"Tries to enable low latency if given.")
613                 ->item(L"embedded_audio", L"Embeds the audio into the SDI signal if given.")
614                 ->item(L"key_only",
615                                 L" will extract only the alpha channel from the "
616                                 L"channel. This is useful when you have two SDI video cards, and neither has native support "
617                                 L"for separate fill/key output");
618         sink.para()->text(L"Examples:");
619         sink.example(L">> ADD 1 DECKLINK", L"for using the default device_index of 1.");
620         sink.example(L">> ADD 1 DECKLINK 2", L"uses device_index 2.");
621         sink.example(L">> ADD 1 DECKLINK 1 EXTERNAL_KEY EMBEDDED_AUDIO");
622         sink.example(
623                 L">> ADD 1 DECKLINK 1 EMBEDDED_AUDIO\n"
624                 L">> ADD 1 DECKLINK 2 KEY_ONLY", L"uses device with index 1 as fill output with audio and device with index 2 as key output.");
625 }
626
627 spl::shared_ptr<core::frame_consumer> create_consumer(
628                 const std::vector<std::wstring>& params, core::interaction_sink*)
629 {
630         if (params.size() < 1 || !boost::iequals(params.at(0), L"DECKLINK"))
631                 return core::frame_consumer::empty();
632         
633         configuration config;
634                 
635         if (params.size() > 1)
636                 config.device_index = boost::lexical_cast<int>(params.at(1));
637         
638         if (contains_param(L"INTERNAL_KEY", params))
639                 config.keyer = configuration::keyer_t::internal_keyer;
640         else if (contains_param(L"EXTERNAL_KEY", params))
641                 config.keyer = configuration::keyer_t::external_keyer;
642         else
643                 config.keyer = configuration::keyer_t::default_keyer;
644
645         if (contains_param(L"LOW_LATENCY", params))
646                 config.latency = configuration::latency_t::low_latency;
647
648         config.embedded_audio   = contains_param(L"EMBEDDED_AUDIO", params);
649         config.key_only                 = contains_param(L"KEY_ONLY", params);
650
651         return spl::make_shared<decklink_consumer_proxy>(config);
652 }
653
654 spl::shared_ptr<core::frame_consumer> create_preconfigured_consumer(
655                 const boost::property_tree::wptree& ptree, core::interaction_sink*)
656 {
657         configuration config;
658
659         auto keyer = ptree.get(L"keyer", L"default");
660         if(keyer == L"external")
661                 config.keyer = configuration::keyer_t::external_keyer;
662         else if(keyer == L"internal")
663                 config.keyer = configuration::keyer_t::internal_keyer;
664
665         auto latency = ptree.get(L"latency", L"normal");
666         if(latency == L"low")
667                 config.latency = configuration::latency_t::low_latency;
668         else if(latency == L"normal")
669                 config.latency = configuration::latency_t::normal_latency;
670
671         config.key_only                         = ptree.get(L"key-only",                config.key_only);
672         config.device_index                     = ptree.get(L"device",                  config.device_index);
673         config.embedded_audio           = ptree.get(L"embedded-audio",  config.embedded_audio);
674         config.base_buffer_depth        = ptree.get(L"buffer-depth",    config.base_buffer_depth);
675
676         return spl::make_shared<decklink_consumer_proxy>(config);
677 }
678
679 }}
680
681 /*
682 ##############################################################################
683 Pre-rolling
684
685 Mail: 2011-05-09
686
687 Yoshan
688 BMD Developer Support
689 developer@blackmagic-design.com
690
691 -----------------------------------------------------------------------------
692
693 Thanks for your inquiry. The minimum number of frames that you can preroll 
694 for scheduled playback is three frames for video and four frames for audio. 
695 As you mentioned if you preroll less frames then playback will not start or
696 playback will be very sporadic. From our experience with Media Express, we 
697 recommended that at least seven frames are prerolled for smooth playback. 
698
699 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:
700 There can be around 3 frames worth of latency on scheduled output.
701 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is
702 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() 
703 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will 
704 guarantee that the provided frame will be output as soon the previous 
705 frame output has been completed.
706 ################################################################################
707 */
708
709 /*
710 ##############################################################################
711 Async DMA Transfer without redundant copying
712
713 Mail: 2011-05-10
714
715 Yoshan
716 BMD Developer Support
717 developer@blackmagic-design.com
718
719 -----------------------------------------------------------------------------
720
721 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame 
722 and providing a pointer to your video buffer when GetBytes() is called. 
723 This may help to keep copying to a minimum. Please ensure that the pixel 
724 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will 
725 have to colourspace convert which may result in additional copying.
726 ################################################################################
727 */