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