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