]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
git-svn-id: https://casparcg.svn.sourceforge.net/svnroot/casparcg/server/branches...
[casparcg] / modules / decklink / consumer / decklink_consumer.cpp
1 /*\r
2 * copyright (c) 2010 Sveriges Television AB <info@casparcg.com>\r
3 *\r
4 *  This file is part of CasparCG.\r
5 *\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
10 *\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
15 \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
18 *\r
19 */\r
20 \r
21 #include "../StdAfx.h"\r
22  \r
23 #include "decklink_consumer.h"\r
24 \r
25 #include "../util/util.h"\r
26 \r
27 #include "../interop/DeckLinkAPI_h.h"\r
28 \r
29 #include <core/mixer/read_frame.h>\r
30 \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
37 \r
38 #include <core/consumer/frame_consumer.h>\r
39 \r
40 #include <tbb/concurrent_queue.h>\r
41 #include <tbb/cache_aligned_allocator.h>\r
42 \r
43 #include <boost/circular_buffer.hpp>\r
44 #include <boost/timer.hpp>\r
45 \r
46 namespace caspar { namespace decklink { \r
47         \r
48 struct configuration\r
49 {\r
50         size_t  device_index;\r
51         bool    embedded_audio;\r
52         bool    internal_key;\r
53         bool    low_latency;\r
54         bool    key_only;\r
55         size_t  buffer_depth;\r
56         \r
57         configuration()\r
58                 : device_index(1)\r
59                 , embedded_audio(false)\r
60                 , internal_key(false)\r
61                 , low_latency(false)\r
62                 , key_only(false)\r
63                 , buffer_depth(core::consumer_buffer_depth()){}\r
64 };\r
65 \r
66 class decklink_frame : public IDeckLinkVideoFrame\r
67 {\r
68         tbb::atomic<int>                                                                                        ref_count_;\r
69         std::shared_ptr<core::read_frame>                                                       frame_;\r
70         const core::video_format_desc                                                           format_desc_;\r
71 \r
72         bool                                                                                                            key_only_;\r
73         std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> key_data_;\r
74 public:\r
75         decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, bool key_only)\r
76                 : frame_(frame)\r
77                 , format_desc_(format_desc)\r
78                 , key_only_(key_only)\r
79         {\r
80                 ref_count_ = 0;\r
81         }\r
82         \r
83         STDMETHOD (QueryInterface(REFIID, LPVOID*))             {return E_NOINTERFACE;}\r
84         STDMETHOD_(ULONG,                       AddRef())                       \r
85         {\r
86                 return ++ref_count_;\r
87         }\r
88         STDMETHOD_(ULONG,                       Release())                      \r
89         {\r
90                 --ref_count_;\r
91                 if(ref_count_ == 0)\r
92                         delete this;\r
93                 return ref_count_;\r
94         }\r
95 \r
96         STDMETHOD_(long,                        GetWidth())                     {return format_desc_.width;}        \r
97     STDMETHOD_(long,                    GetHeight())            {return format_desc_.height;}        \r
98     STDMETHOD_(long,                    GetRowBytes())          {return format_desc_.width*4;}        \r
99         STDMETHOD_(BMDPixelFormat,      GetPixelFormat())       {return bmdFormat8BitBGRA;}        \r
100     STDMETHOD_(BMDFrameFlags,   GetFlags())                     {return bmdFrameFlagDefault;}\r
101         \r
102     STDMETHOD(GetBytes(void** buffer))\r
103         {\r
104                 static std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> zeros(1920*1080*4, 0);\r
105                 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)\r
106                 {\r
107                         *buffer = zeros.data();\r
108                         return S_OK;\r
109                 }\r
110 \r
111                 if(!key_only_)\r
112                         *buffer = const_cast<uint8_t*>(frame_->image_data().begin());\r
113                 else\r
114                 {\r
115                         if(key_data_.empty())\r
116                         {\r
117                                 key_data_.resize(frame_->image_data().size());\r
118                                 fast_memshfl(key_data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);\r
119                                 frame_.reset();\r
120                         }\r
121                         *buffer = key_data_.data();\r
122                 }\r
123 \r
124                 return S_OK;\r
125         }\r
126         \r
127     STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}        \r
128     STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary))                 {return S_FALSE;}\r
129 };\r
130 \r
131 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
132 {               \r
133         const configuration                                     config_;\r
134 \r
135         CComPtr<IDeckLink>                                      decklink_;\r
136         CComQIPtr<IDeckLinkOutput>                      output_;\r
137         CComQIPtr<IDeckLinkConfiguration>       configuration_;\r
138         CComQIPtr<IDeckLinkKeyer>                       keyer_;\r
139 \r
140         tbb::spin_mutex                                         exception_mutex_;\r
141         std::exception_ptr                                      exception_;\r
142 \r
143         tbb::atomic<bool>                                       is_running_;\r
144                 \r
145         const std::wstring                                      model_name_;\r
146         const core::video_format_desc           format_desc_;\r
147         const size_t                                            buffer_size_;\r
148 \r
149         long long                                                       frames_scheduled_;\r
150         long long                                                       audio_scheduled_;\r
151 \r
152         size_t                                                          preroll_count_;\r
153                 \r
154         boost::circular_buffer<std::vector<int32_t>>    audio_container_;\r
155 \r
156         tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;\r
157         tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;\r
158         \r
159         safe_ptr<diagnostics::graph> graph_;\r
160         boost::timer tick_timer_;\r
161 \r
162 public:\r
163         decklink_consumer(const configuration& config, const core::video_format_desc& format_desc) \r
164                 : config_(config)\r
165                 , decklink_(get_device(config.device_index))\r
166                 , output_(decklink_)\r
167                 , configuration_(decklink_)\r
168                 , keyer_(decklink_)\r
169                 , model_name_(get_model_name(decklink_))\r
170                 , format_desc_(format_desc)\r
171                 , buffer_size_(config.embedded_audio ? config.buffer_depth + 1 : config.buffer_depth) // Minimum buffer-size 3.\r
172                 , frames_scheduled_(0)\r
173                 , audio_scheduled_(0)\r
174                 , preroll_count_(0)\r
175                 , audio_container_(buffer_size_+1)\r
176         {\r
177                 is_running_ = true;\r
178                                 \r
179                 video_frame_buffer_.set_capacity(1);\r
180                 audio_frame_buffer_.set_capacity(1);\r
181 \r
182                 graph_->add_guide("tick-time", 0.5);\r
183                 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));   \r
184                 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));\r
185                 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));\r
186                 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));\r
187                 graph_->set_text(print());\r
188                 diagnostics::register_graph(graph_);\r
189                 \r
190                 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));\r
191                                 \r
192                 if(config.embedded_audio)\r
193                         enable_audio();\r
194 \r
195                 set_latency(config.low_latency);                                \r
196                 set_keyer(config.internal_key);\r
197                                 \r
198                 if(config.embedded_audio)               \r
199                         output_->BeginAudioPreroll();           \r
200                 \r
201                 for(size_t n = 0; n < buffer_size_; ++n)\r
202                         schedule_next_video(make_safe<core::read_frame>());\r
203 \r
204                 if(!config.embedded_audio)\r
205                         start_playback();\r
206         }\r
207 \r
208         ~decklink_consumer()\r
209         {               \r
210                 is_running_ = false;\r
211                 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());\r
212                 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());\r
213 \r
214                 if(output_ != nullptr) \r
215                 {\r
216                         output_->StopScheduledPlayback(0, nullptr, 0);\r
217                         if(config_.embedded_audio)\r
218                                 output_->DisableAudioOutput();\r
219                         output_->DisableVideoOutput();\r
220                 }\r
221         }\r
222                         \r
223         const core::video_format_desc& get_video_format_desc() const\r
224         {\r
225                 return format_desc_;\r
226         }\r
227 \r
228         void set_latency(bool low_latency)\r
229         {               \r
230                 if(!low_latency)\r
231                 {\r
232                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);\r
233                         CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";\r
234                 }\r
235                 else\r
236                 {                       \r
237                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);\r
238                         CASPAR_LOG(info) << print() << L" Enabled low-latency mode";\r
239                 }\r
240         }\r
241 \r
242         void set_keyer(bool internal_key)\r
243         {\r
244                 if(internal_key) \r
245                 {\r
246                         if(FAILED(keyer_->Enable(FALSE)))                       \r
247                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   \r
248                         else if(FAILED(keyer_->SetLevel(255)))                  \r
249                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
250                         else\r
251                                 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             \r
252                 }\r
253                 else\r
254                 {\r
255                         if(FAILED(keyer_->Enable(TRUE)))                        \r
256                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   \r
257                         else if(FAILED(keyer_->SetLevel(255)))                  \r
258                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
259                         else\r
260                                 CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     \r
261                 }\r
262         }\r
263         \r
264         void enable_audio()\r
265         {\r
266                 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))\r
267                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));\r
268                                 \r
269                 if(FAILED(output_->SetAudioCallback(this)))\r
270                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));\r
271 \r
272                 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";\r
273         }\r
274 \r
275         void enable_video(BMDDisplayMode display_mode)\r
276         {\r
277                 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) \r
278                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));\r
279                 \r
280                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
281                         BOOST_THROW_EXCEPTION(caspar_exception() \r
282                                                                         << msg_info(narrow(print()) + " Failed to set playback completion callback.")\r
283                                                                         << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));\r
284         }\r
285 \r
286         void start_playback()\r
287         {\r
288                 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) \r
289                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
290         }\r
291         \r
292         STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}\r
293         STDMETHOD_(ULONG, AddRef())                                     {return 1;}\r
294         STDMETHOD_(ULONG, Release())                            {return 1;}\r
295         \r
296         STDMETHOD(ScheduledPlaybackHasStopped())\r
297         {\r
298                 is_running_ = false;\r
299                 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";\r
300                 return S_OK;\r
301         }\r
302 \r
303         STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))\r
304         {\r
305                 if(!is_running_)\r
306                         return E_FAIL;\r
307                 \r
308                 try\r
309                 {\r
310                         if(result == bmdOutputFrameDisplayedLate)\r
311                         {\r
312                                 graph_->add_tag("late-frame");\r
313                                 ++frames_scheduled_;\r
314                                 ++audio_scheduled_;\r
315                         }\r
316                         else if(result == bmdOutputFrameDropped)\r
317                                 graph_->add_tag("dropped-frame");\r
318                         else if(result == bmdOutputFrameFlushed)\r
319                                 graph_->add_tag("flushed-frame");\r
320 \r
321                         std::shared_ptr<core::read_frame> frame;        \r
322                         video_frame_buffer_.pop(frame);                                 \r
323                         schedule_next_video(make_safe_ptr(frame));      \r
324                 }\r
325                 catch(...)\r
326                 {\r
327                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
328                         exception_ = std::current_exception();\r
329                         return E_FAIL;\r
330                 }\r
331 \r
332                 return S_OK;\r
333         }\r
334                 \r
335         STDMETHOD(RenderAudioSamples(BOOL preroll))\r
336         {\r
337                 if(!is_running_)\r
338                         return E_FAIL;\r
339                 \r
340                 try\r
341                 {       \r
342                         if(preroll)\r
343                         {\r
344                                 if(++preroll_count_ >= buffer_size_)\r
345                                 {\r
346                                         output_->EndAudioPreroll();\r
347                                         start_playback();                               \r
348                                 }\r
349                                 else\r
350                                         schedule_next_audio(make_safe<core::read_frame>());     \r
351                         }\r
352                         else\r
353                         {\r
354                                 std::shared_ptr<core::read_frame> frame;\r
355                                 audio_frame_buffer_.pop(frame);\r
356                                 schedule_next_audio(make_safe_ptr(frame));      \r
357                         }\r
358                 }\r
359                 catch(...)\r
360                 {\r
361                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
362                         exception_ = std::current_exception();\r
363                         return E_FAIL;\r
364                 }\r
365 \r
366                 return S_OK;\r
367         }\r
368 \r
369         void schedule_next_audio(const safe_ptr<core::read_frame>& frame)\r
370         {\r
371                 const int sample_frame_count = frame->audio_data().size()/format_desc_.audio_channels;\r
372 \r
373                 audio_container_.push_back(std::vector<int32_t>(frame->audio_data().begin(), frame->audio_data().end()));\r
374 \r
375                 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))\r
376                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
377         }\r
378                         \r
379         void schedule_next_video(const safe_ptr<core::read_frame>& frame)\r
380         {\r
381                 CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));\r
382                 if(FAILED(output_->ScheduleVideoFrame(frame2, (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))\r
383                         CASPAR_LOG(error) << print() << L" Failed to schedule video.";\r
384 \r
385                 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);\r
386                 tick_timer_.restart();\r
387         }\r
388 \r
389         void send(const safe_ptr<core::read_frame>& frame)\r
390         {\r
391                 {\r
392                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
393                         if(exception_ != nullptr)\r
394                                 std::rethrow_exception(exception_);\r
395                 }\r
396 \r
397                 if(!is_running_)\r
398                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));\r
399                 \r
400                 if(config_.embedded_audio)\r
401                         audio_frame_buffer_.push(frame);        \r
402                 video_frame_buffer_.push(frame);        \r
403         }\r
404         \r
405         std::wstring print() const\r
406         {\r
407                 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";\r
408         }\r
409 };\r
410 \r
411 struct decklink_consumer_proxy : public core::frame_consumer\r
412 {\r
413         const configuration                             config_;\r
414         com_context<decklink_consumer>  context_;\r
415         core::video_format_desc                 format_desc_;\r
416 public:\r
417 \r
418         decklink_consumer_proxy(const configuration& config)\r
419                 : config_(config)\r
420                 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")\r
421         {\r
422         }\r
423 \r
424         ~decklink_consumer_proxy()\r
425         {\r
426                 auto str = print();\r
427                 context_.reset();\r
428                 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";     \r
429         }\r
430         \r
431         virtual void initialize(const core::video_format_desc& format_desc)\r
432         {\r
433                 format_desc_ = format_desc;\r
434                 context_.reset([&]{return new decklink_consumer(config_, format_desc_);});              \r
435                                 \r
436                 CASPAR_LOG(info) << print() << L" Successfully Initialized.";   \r
437         }\r
438         \r
439         virtual bool send(const safe_ptr<core::read_frame>& frame)\r
440         {\r
441                 context_->send(frame);\r
442                 return true;\r
443         }\r
444         \r
445         virtual std::wstring print() const\r
446         {\r
447                 return context_ ? context_->print() : L"decklink_consumer";\r
448         }\r
449                         \r
450         virtual const core::video_format_desc& get_video_format_desc() const\r
451         {\r
452                 return format_desc_;\r
453         }\r
454 };      \r
455 \r
456 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params) \r
457 {\r
458         if(params.size() < 1 || params[0] != L"DECKLINK")\r
459                 return core::frame_consumer::empty();\r
460         \r
461         configuration config;\r
462                 \r
463         if(params.size() > 1)\r
464                 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);\r
465         \r
466         config.internal_key             = std::find(params.begin(), params.end(), L"INTERNAL_KEY")       != params.end();\r
467         config.low_latency              = std::find(params.begin(), params.end(), L"LOW_LATENCY")        != params.end();\r
468         config.embedded_audio   = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
469         config.key_only                 = std::find(params.begin(), params.end(), L"KEY_ONLY")           != params.end();\r
470 \r
471         return make_safe<decklink_consumer_proxy>(config);\r
472 }\r
473 \r
474 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::ptree& ptree) \r
475 {\r
476         configuration config;\r
477 \r
478         config.internal_key             = ptree.get("internal-key",       config.internal_key);\r
479         config.low_latency              = ptree.get("low-latency",        config.low_latency);\r
480         config.key_only                 = ptree.get("key-only",           config.key_only);\r
481         config.device_index             = ptree.get("device",             config.device_index);\r
482         config.embedded_audio   = ptree.get("embedded-audio", config.embedded_audio);\r
483 \r
484         return make_safe<decklink_consumer_proxy>(config);\r
485 }\r
486 \r
487 }}\r
488 \r
489 /*\r
490 ##############################################################################\r
491 Pre-rolling\r
492 \r
493 Mail: 2011-05-09\r
494 \r
495 Yoshan\r
496 BMD Developer Support\r
497 developer@blackmagic-design.com\r
498 \r
499 -----------------------------------------------------------------------------\r
500 \r
501 Thanks for your inquiry. The minimum number of frames that you can preroll \r
502 for scheduled playback is three frames for video and four frames for audio. \r
503 As you mentioned if you preroll less frames then playback will not start or\r
504 playback will be very sporadic. From our experience with Media Express, we \r
505 recommended that at least seven frames are prerolled for smooth playback. \r
506 \r
507 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:\r
508 There can be around 3 frames worth of latency on scheduled output.\r
509 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is\r
510 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() \r
511 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will \r
512 guarantee that the provided frame will be output as soon the previous \r
513 frame output has been completed.\r
514 ################################################################################\r
515 */\r
516 \r
517 /*\r
518 ##############################################################################\r
519 Async DMA Transfer without redundant copying\r
520 \r
521 Mail: 2011-05-10\r
522 \r
523 Yoshan\r
524 BMD Developer Support\r
525 developer@blackmagic-design.com\r
526 \r
527 -----------------------------------------------------------------------------\r
528 \r
529 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame \r
530 and providing a pointer to your video buffer when GetBytes() is called. \r
531 This may help to keep copying to a minimum. Please ensure that the pixel \r
532 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will \r
533 have to colourspace convert which may result in additional copying.\r
534 ################################################################################\r
535 */