]> 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/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 #include <common/utility/timer.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 #include <array>\r
46 \r
47 #pragma warning(push)\r
48 #pragma warning(disable : 4996)\r
49 \r
50         #include <atlbase.h>\r
51 \r
52         #include <atlcom.h>\r
53         #include <atlhost.h>\r
54 \r
55 #pragma warning(push)\r
56 \r
57 namespace caspar { \r
58         \r
59 enum key\r
60 {\r
61         external_key,\r
62         internal_key,\r
63         default_key\r
64 };\r
65 \r
66 enum latency\r
67 {\r
68         low_latency,\r
69         normal_latency,\r
70         default_latency\r
71 };\r
72 \r
73 struct configuration\r
74 {\r
75         size_t device_index;\r
76         bool embedded_audio;\r
77         key keyer;\r
78         latency latency;\r
79         \r
80         configuration()\r
81                 : device_index(1)\r
82                 , embedded_audio(false)\r
83                 , keyer(default_key)\r
84                 , latency(default_latency){}\r
85 };\r
86 \r
87 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
88 {               \r
89         const configuration config_;\r
90 \r
91         CComPtr<IDeckLink>                                      decklink_;\r
92         CComQIPtr<IDeckLinkOutput>                      output_;\r
93         CComQIPtr<IDeckLinkConfiguration>       configuration_;\r
94         CComQIPtr<IDeckLinkKeyer>                       keyer_;\r
95 \r
96         std::exception_ptr exception_;\r
97 \r
98         tbb::atomic<bool> is_running_;\r
99                 \r
100         const std::wstring model_name_;\r
101         const core::video_format_desc format_desc_;\r
102 \r
103         BMDTimeScale frame_time_scale_;\r
104         BMDTimeValue frame_duration_;\r
105         unsigned long frames_scheduled_;\r
106         unsigned long audio_scheduled_;\r
107                 \r
108         std::vector<std::pair<void*, CComPtr<IDeckLinkMutableVideoFrame>>> reserved_frames_;\r
109         boost::circular_buffer<std::vector<short>> audio_container_;\r
110 \r
111         tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> video_frame_buffer_;\r
112         tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> audio_frame_buffer_;\r
113         \r
114         std::shared_ptr<diagnostics::graph> graph_;\r
115         boost::timer tick_timer_;\r
116 \r
117         size_t buffer_size_;\r
118 \r
119 public:\r
120         decklink_consumer(const configuration& config, const core::video_format_desc& format_desc) \r
121                 : config_(config)\r
122                 , decklink_(get_device(config.device_index))\r
123                 , output_(decklink_)\r
124                 , configuration_(decklink_)\r
125                 , keyer_(decklink_)\r
126                 , model_name_(get_model_name(decklink_))\r
127                 , format_desc_(format_desc)\r
128                 , audio_container_(5)\r
129                 , frames_scheduled_(0)\r
130                 , audio_scheduled_(0)\r
131                 , buffer_size_(5) // Minimum buffer-size (4 + 1 tolerance).\r
132         {\r
133                 is_running_ = true;\r
134                                                 \r
135                 graph_ = diagnostics::create_graph(narrow(print()));\r
136                 graph_->add_guide("tick-time", 0.5);\r
137                 graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f));\r
138                 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));\r
139                 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));\r
140                 graph_->set_color("flushed-frame", diagnostics::color(0.3f, 0.3f, 0.6f));\r
141                 \r
142                 auto display_mode = get_display_mode(output_, format_desc_.format);\r
143                 if(display_mode == nullptr) \r
144                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Card does not support requested videoformat."));\r
145                 \r
146                 display_mode->GetFrameRate(&frame_duration_, &frame_time_scale_);\r
147 \r
148                 BMDDisplayModeSupport displayModeSupport;\r
149                 if(FAILED(output_->DoesSupportVideoMode(display_mode->GetDisplayMode(), bmdFormat8BitBGRA, bmdVideoOutputFlagDefault, &displayModeSupport, nullptr)) || displayModeSupport == bmdDisplayModeNotSupported)\r
150                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Card does not support requested videoformat."));\r
151                 else if(displayModeSupport == bmdDisplayModeSupportedWithConversion)\r
152                         CASPAR_LOG(warning) << print() << " Display mode is supported with conversion.";\r
153 \r
154                 if(config.embedded_audio)\r
155                 {\r
156                         if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))\r
157                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));\r
158                                 \r
159                         if(FAILED(output_->SetAudioCallback(this)))\r
160                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));\r
161 \r
162                         CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";\r
163 \r
164                         buffer_size_ = 6; //  Minimum buffer-size with embedded-audio (5 + 1 tolerance).\r
165                 }\r
166 \r
167                 if(config.latency == normal_latency)\r
168                 {\r
169                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);\r
170                         CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";\r
171                 }\r
172                 else if(config.latency == low_latency)\r
173                 {                       \r
174                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);\r
175                         CASPAR_LOG(info) << print() << L" Enabled low-latency mode";\r
176                 }\r
177                 else\r
178                         CASPAR_LOG(info) << print() << L" Uses driver latency settings.";       \r
179                                 \r
180                 if(config.keyer == internal_key) \r
181                 {\r
182                         if(FAILED(keyer_->Enable(FALSE)))                       \r
183                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   \r
184                         else if(FAILED(keyer_->SetLevel(255)))                  \r
185                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
186                         else\r
187                                 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             \r
188                 }\r
189                 else if(config.keyer == external_key)\r
190                 {\r
191                         if(FAILED(keyer_->Enable(TRUE)))                        \r
192                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   \r
193                         else if(FAILED(keyer_->SetLevel(255)))                  \r
194                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
195                         else\r
196                                 CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     \r
197                 }\r
198                 else\r
199                         CASPAR_LOG(info) << print() << L" Uses driver keyer settings."; \r
200 \r
201                 if(FAILED(output_->EnableVideoOutput(display_mode->GetDisplayMode(), bmdVideoOutputFlagDefault))) \r
202                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));\r
203                 \r
204                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
205                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to set playback completion callback."));\r
206                                         \r
207                 for(size_t n = 0; n < buffer_size_+1; ++n)\r
208                 {\r
209                         CComPtr<IDeckLinkMutableVideoFrame> frame;\r
210 \r
211                         if(FAILED(output_->CreateVideoFrame(format_desc_.width, format_desc_.height, format_desc_.size/format_desc_.height, bmdFormat8BitBGRA, bmdFrameFlagDefault, &frame)))\r
212                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to create frame."));\r
213 \r
214                         void* bytes = nullptr;\r
215 \r
216                         if(FAILED(frame->GetBytes(&bytes)) || bytes == nullptr)\r
217                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to get frame bytes."));\r
218 \r
219                         reserved_frames_.push_back(std::make_pair(bytes, frame));\r
220                 }\r
221                 \r
222                 CASPAR_LOG(info) << print() << L" Buffer-depth: " << buffer_size_;\r
223                 \r
224                 for(size_t n = 0; n < buffer_size_; ++n)\r
225                         schedule_next_video(core::read_frame::empty());\r
226 \r
227                 video_frame_buffer_.set_capacity(2);\r
228                 audio_frame_buffer_.set_capacity(2);\r
229                 \r
230                 if(config.embedded_audio)\r
231                         output_->BeginAudioPreroll();\r
232                 else\r
233                 {\r
234                         if(FAILED(output_->StartScheduledPlayback(0, frame_time_scale_, 1.0))) \r
235                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
236                 }\r
237                 \r
238                 CASPAR_LOG(info) << print() << L" Successfully initialized for " << format_desc_.name;  \r
239         }\r
240 \r
241         ~decklink_consumer()\r
242         {               \r
243                 is_running_ = false;\r
244                 video_frame_buffer_.clear();\r
245                 audio_frame_buffer_.clear();\r
246                 video_frame_buffer_.try_push(core::read_frame::empty());\r
247                 audio_frame_buffer_.try_push(core::read_frame::empty());\r
248 \r
249                 if(output_ != nullptr) \r
250                 {\r
251                         output_->StopScheduledPlayback(0, nullptr, 0);\r
252                         if(config_.embedded_audio)\r
253                                 output_->DisableAudioOutput();\r
254                         output_->DisableVideoOutput();\r
255                 }\r
256         }\r
257                         \r
258         virtual HRESULT STDMETHODCALLTYPE       QueryInterface (REFIID, LPVOID*)        {return E_NOINTERFACE;}\r
259         virtual ULONG STDMETHODCALLTYPE         AddRef ()                                                       {return 1;}\r
260         virtual ULONG STDMETHODCALLTYPE         Release ()                                                      {return 1;}\r
261         \r
262         virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted(IDeckLinkVideoFrame* /*completedFrame*/, BMDOutputFrameCompletionResult result)\r
263         {\r
264                 if(!is_running_)\r
265                         return E_FAIL;\r
266 \r
267                 if(result == bmdOutputFrameDisplayedLate)\r
268                         graph_->add_tag("late-frame");\r
269                 else if(result == bmdOutputFrameDropped)\r
270                         graph_->add_tag("dropped-frame");\r
271                 else if(result == bmdOutputFrameFlushed)\r
272                         graph_->add_tag("flushed-frame");\r
273 \r
274                 std::shared_ptr<const core::read_frame> frame;  \r
275                 video_frame_buffer_.pop(frame);         \r
276                 schedule_next_video(safe_ptr<const core::read_frame>(frame));\r
277 \r
278                 return S_OK;\r
279         }\r
280 \r
281         virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped (void)\r
282         {\r
283                 is_running_ = false;\r
284                 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";\r
285                 return S_OK;\r
286         }\r
287                 \r
288         virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples(BOOL preroll)\r
289         {\r
290                 if(!is_running_)\r
291                         return E_FAIL;\r
292                 \r
293                 try\r
294                 {\r
295                         if(preroll)\r
296                         {\r
297                                 if(FAILED(output_->StartScheduledPlayback(0, frame_time_scale_, 1.0)))\r
298                                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
299                         }\r
300 \r
301                         std::shared_ptr<const core::read_frame> frame;\r
302                         audio_frame_buffer_.pop(frame);\r
303                         schedule_next_audio(safe_ptr<const core::read_frame>(frame));\r
304                 \r
305                 }\r
306                 catch(...)\r
307                 {\r
308                         exception_ = std::current_exception();\r
309                         return E_FAIL;\r
310                 }\r
311 \r
312                 return S_OK;\r
313         }\r
314 \r
315         void schedule_next_audio(const safe_ptr<const core::read_frame>& frame)\r
316         {\r
317                 static std::vector<short> silence(48000, 0);\r
318 \r
319                 int audio_samples = static_cast<size_t>(48000.0 / format_desc_.fps)*2; // Audio samples per channel\r
320 \r
321                 const short* frame_audio_data = frame->audio_data().size() == audio_samples ? frame->audio_data().begin() : silence.data();\r
322                 audio_container_.push_back(std::vector<short>(frame_audio_data, frame_audio_data+audio_samples));\r
323 \r
324                 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), audio_samples/2, (audio_scheduled_++) * audio_samples/2, 48000, nullptr)))\r
325                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
326         }\r
327                         \r
328         void schedule_next_video(const safe_ptr<const core::read_frame>& frame)\r
329         {\r
330                 if(static_cast<size_t>(frame->image_data().size()) == format_desc_.size)\r
331                         fast_memcpy(reserved_frames_.front().first, frame->image_data().begin(), frame->image_data().size());\r
332                 else\r
333                         fast_memclr(reserved_frames_.front().first, format_desc_.size);\r
334 \r
335                 if(FAILED(output_->ScheduleVideoFrame(reserved_frames_.front().second, (frames_scheduled_++) * frame_duration_, frame_duration_, frame_time_scale_)))\r
336                         CASPAR_LOG(error) << print() << L" Failed to schedule video.";\r
337 \r
338                 std::rotate(reserved_frames_.begin(), reserved_frames_.begin() + 1, reserved_frames_.end());\r
339                 graph_->update_value("tick-time", static_cast<float>(tick_timer_.elapsed()/format_desc_.interval)*0.5f);\r
340                 tick_timer_.restart();\r
341         }\r
342 \r
343         void send(const safe_ptr<const core::read_frame>& frame)\r
344         {\r
345                 if(!is_running_)\r
346                         return;\r
347 \r
348                 if(exception_ != nullptr)\r
349                         std::rethrow_exception(exception_);\r
350 \r
351                 video_frame_buffer_.push(frame);\r
352                 if(config_.embedded_audio)\r
353                         audio_frame_buffer_.push(frame);\r
354                 \r
355         }\r
356 \r
357         size_t buffer_depth() const {return 1;}\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         virtual size_t buffer_depth() const \r
392         {\r
393                 return context_->buffer_depth();\r
394         }\r
395 };      \r
396 \r
397 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params) \r
398 {\r
399         if(params.size() < 1 || params[0] != L"DECKLINK")\r
400                 return core::frame_consumer::empty();\r
401         \r
402         configuration config;\r
403                 \r
404         if(params.size() > 1)\r
405                 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);\r
406                 \r
407         if(std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end())\r
408                 config.keyer = internal_key;\r
409         else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end())\r
410                 config.keyer = external_key;\r
411         \r
412         if(std::find(params.begin(), params.end(), L"LOW_LATENCY") != params.end())\r
413                 config.latency = low_latency;\r
414         else if(std::find(params.begin(), params.end(), L"NORMAL_LATENCY") != params.end())\r
415                 config.latency = normal_latency;\r
416                 \r
417         config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
418 \r
419         return make_safe<decklink_consumer_proxy>(config);\r
420 }\r
421 \r
422 safe_ptr<core::frame_consumer> create_decklink_consumer(const boost::property_tree::ptree& ptree) \r
423 {\r
424         configuration config;\r
425 \r
426         auto key_str = ptree.get("key", "default");\r
427         if(key_str == "internal")\r
428                 config.keyer = internal_key;\r
429         else if(key_str == "external")\r
430                 config.keyer = external_key;\r
431 \r
432         auto latency_str = ptree.get("latency", "default");\r
433         if(latency_str == "normal")\r
434                 config.latency = normal_latency;\r
435         else if(latency_str == "low")\r
436                 config.latency = low_latency;\r
437 \r
438         config.device_index = ptree.get("device", 0);\r
439         config.embedded_audio  = ptree.get("embedded-audio", false);\r
440 \r
441         return make_safe<decklink_consumer_proxy>(config);\r
442 }\r
443 \r
444 }