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