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