]> 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/executor.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         configuration(const boost::property_tree::ptree& ptree)\r
87                 : device_index(1)\r
88                 , embedded_audio(false)\r
89                 , keyer(default_key)\r
90                 , latency(default_latency)\r
91         {       \r
92                 auto key_str = ptree.get("key", "default");\r
93                 if(key_str == "internal")\r
94                         keyer = internal_key;\r
95                 else if(key_str == "external")\r
96                         keyer = external_key;\r
97 \r
98                 auto latency_str = ptree.get("latency", "default");\r
99                 if(latency_str == "normal")\r
100                         latency = normal_latency;\r
101                 else if(latency_str == "low")\r
102                         latency = low_latency;\r
103 \r
104                 device_index = ptree.get("device", 0);\r
105                 embedded_audio  = ptree.get("embedded-audio", false);\r
106         }\r
107 \r
108         configuration(const std::vector<std::wstring>& params)\r
109                 : device_index(1)\r
110                 , embedded_audio(false)\r
111                 , keyer(default_key)\r
112                 , latency(default_latency)\r
113         {\r
114                 if(params.size() > 0)\r
115                         device_index = lexical_cast_or_default<int>(params[0], device_index);\r
116 \r
117                 {\r
118                         auto it = std::find(params.begin(), params.end(), L"INTERNAL_KEY");\r
119                         if(it != params.end())\r
120                                 keyer = internal_key;\r
121                         else\r
122                         {\r
123                                 auto it = std::find(params.begin(), params.end(), L"EXTERNAL_KEY");\r
124                                 if(it != params.end())\r
125                                         keyer = external_key;\r
126                         }\r
127                 }\r
128                 \r
129                 embedded_audio = std::find(params.begin(), params.end(), L"EMBED_AUDIO") != params.end();       \r
130         }\r
131 };\r
132 \r
133 struct decklink_output : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
134 {               \r
135         static const size_t BUFFER_SIZE = 4;\r
136         \r
137         struct co_init\r
138         {\r
139                 co_init(){CoInitialize(nullptr);}\r
140                 ~co_init(){CoUninitialize();}\r
141         } co_;\r
142         \r
143         std::exception_ptr exception_;\r
144         const configuration config_;\r
145 \r
146         std::wstring model_name_;\r
147         tbb::atomic<bool> is_running_;\r
148 \r
149         std::shared_ptr<diagnostics::graph> graph_;\r
150         boost::timer perf_timer_;\r
151 \r
152         std::array<std::pair<void*, CComPtr<IDeckLinkMutableVideoFrame>>, BUFFER_SIZE+1> reserved_frames_;\r
153         boost::circular_buffer<std::vector<short>> audio_container_;\r
154         \r
155         CComPtr<IDeckLink>                                      decklink_;\r
156         CComQIPtr<IDeckLinkOutput>                      output_;\r
157         CComQIPtr<IDeckLinkConfiguration>       configuration_;\r
158         \r
159         core::video_format_desc format_desc_;\r
160 \r
161         BMDTimeScale frame_time_scale_;\r
162         BMDTimeValue frame_duration_;\r
163         unsigned long frames_scheduled_;\r
164         unsigned long audio_scheduled_;\r
165         \r
166         tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> video_frame_buffer_;\r
167         tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> audio_frame_buffer_;\r
168 \r
169 public:\r
170         decklink_output(const configuration& config, const core::video_format_desc& format_desc) \r
171                 : model_name_(L"DECKLINK")\r
172                 , config_(config)\r
173                 , audio_container_(5)\r
174                 , frames_scheduled_(0)\r
175                 , audio_scheduled_(0)\r
176                 , format_desc_(format_desc)\r
177         {\r
178                 is_running_ = true;\r
179                 format_desc_ = format_desc;\r
180                 CComPtr<IDeckLinkIterator> pDecklinkIterator;\r
181                 if(FAILED(pDecklinkIterator.CoCreateInstance(CLSID_CDeckLinkIterator)))\r
182                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " No Decklink drivers installed."));\r
183                 \r
184                 size_t n = 0;\r
185                 while(n < config_.device_index && pDecklinkIterator->Next(&decklink_) == S_OK){++n;}    \r
186 \r
187                 if(n != config_.device_index || !decklink_)\r
188                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Decklink card not found.") << arg_name_info("device_index") << arg_value_info(boost::lexical_cast<std::string>(config_.device_index)));\r
189                 \r
190                 BSTR pModelName;\r
191                 decklink_->GetModelName(&pModelName);\r
192                 model_name_ = std::wstring(pModelName);\r
193                                 \r
194                 graph_ = diagnostics::create_graph(narrow(print()));\r
195                 graph_->add_guide("tick-time", 0.5);\r
196                 graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f));\r
197                 \r
198                 output_ = decklink_;\r
199                 configuration_ = decklink_;\r
200 \r
201                 auto display_mode = get_display_mode(output_.p, format_desc_.format);\r
202                 if(display_mode == nullptr) \r
203                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Card does not support requested videoformat."));\r
204                 \r
205                 display_mode->GetFrameRate(&frame_duration_, &frame_time_scale_);\r
206 \r
207                 BMDDisplayModeSupport displayModeSupport;\r
208                 if(FAILED(output_->DoesSupportVideoMode(display_mode->GetDisplayMode(), bmdFormat8BitBGRA, bmdVideoOutputFlagDefault, &displayModeSupport, nullptr)))\r
209                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Card does not support requested videoformat."));\r
210                 \r
211                 if(config_.embedded_audio)\r
212                 {\r
213                         if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))\r
214                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));\r
215                                 \r
216                         if(FAILED(output_->SetAudioCallback(this)))\r
217                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));\r
218 \r
219                         CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";\r
220                 }\r
221 \r
222                 if(config_.latency == normal_latency)\r
223                 {\r
224                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);\r
225                         CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";\r
226                 }\r
227                 else if(config_.latency == low_latency)\r
228                 {                       \r
229                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);\r
230                         CASPAR_LOG(info) << print() << L" Enabled low-latency mode";\r
231                 }\r
232                 else\r
233                         CASPAR_LOG(info) << print() << L" Uses driver latency settings.";       \r
234                 \r
235                 CComQIPtr<IDeckLinkKeyer> keyer = decklink_;\r
236                 if(config_.keyer == internal_key) \r
237                 {\r
238                         if(FAILED(keyer->Enable(FALSE)))                        \r
239                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   \r
240                         else if(FAILED(keyer->SetLevel(255)))                   \r
241                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
242                         else\r
243                                 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             \r
244                 }\r
245                 else if(config.keyer == external_key)\r
246                 {\r
247                         if(FAILED(keyer->Enable(TRUE)))                 \r
248                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   \r
249                         else if(FAILED(keyer->SetLevel(255)))                   \r
250                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
251                         else\r
252                                 CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     \r
253                 }\r
254                 else\r
255                         CASPAR_LOG(info) << print() << L" Uses driver keyer settings."; \r
256 \r
257                 if(FAILED(output_->EnableVideoOutput(display_mode->GetDisplayMode(), bmdVideoOutputFlagDefault))) \r
258                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));\r
259                 \r
260                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
261                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to set playback completion callback."));\r
262                                         \r
263                 for(size_t n = 0; n < reserved_frames_.size(); ++n)\r
264                 {\r
265                         if(FAILED(output_->CreateVideoFrame(format_desc_.width, format_desc_.height, format_desc_.size/format_desc_.height, bmdFormat8BitBGRA, bmdFrameFlagDefault, &reserved_frames_[n].second)))\r
266                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to create frame."));\r
267 \r
268                         if(FAILED(reserved_frames_[n].second->GetBytes(&reserved_frames_[n].first)))\r
269                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to get frame bytes."));\r
270                 }\r
271                                         \r
272                 CASPAR_LOG(info) << print() << L" Buffer-depth: " << BUFFER_SIZE;\r
273                 \r
274                 for(size_t n = 0; n < BUFFER_SIZE; ++n)\r
275                         schedule_next_video(core::read_frame::empty());\r
276 \r
277                 video_frame_buffer_.set_capacity(2);\r
278                 audio_frame_buffer_.set_capacity(2);\r
279                 \r
280                 if(config_.embedded_audio)\r
281                         output_->BeginAudioPreroll();\r
282                 else\r
283                 {\r
284                         if(FAILED(output_->StartScheduledPlayback(0, frame_time_scale_, 1.0))) \r
285                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
286                 }\r
287                 \r
288                 CASPAR_LOG(info) << print() << L" Successfully initialized for " << format_desc_.name;  \r
289         }\r
290 \r
291         ~decklink_output()\r
292         {               \r
293                 is_running_ = false;\r
294                 video_frame_buffer_.try_push(core::read_frame::empty());\r
295                 audio_frame_buffer_.try_push(core::read_frame::empty());\r
296 \r
297                 if(output_ != nullptr) \r
298                 {\r
299                         output_->StopScheduledPlayback(0, nullptr, 0);\r
300                         if(config_.embedded_audio)\r
301                                 output_->DisableAudioOutput();\r
302                         output_->DisableVideoOutput();\r
303                 }\r
304                 CASPAR_LOG(info) << print() << L" Shutting down.";      \r
305         }\r
306                         \r
307         virtual HRESULT STDMETHODCALLTYPE       QueryInterface (REFIID, LPVOID*)        {return E_NOINTERFACE;}\r
308         virtual ULONG STDMETHODCALLTYPE         AddRef ()                                                       {return 1;}\r
309         virtual ULONG STDMETHODCALLTYPE         Release ()                                                      {return 1;}\r
310         \r
311         virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted (IDeckLinkVideoFrame* /*completedFrame*/, BMDOutputFrameCompletionResult /*result*/)\r
312         {\r
313                 if(!is_running_)\r
314                         return S_OK;\r
315 \r
316                 std::shared_ptr<const core::read_frame> frame;  \r
317                 video_frame_buffer_.pop(frame);         \r
318                 schedule_next_video(safe_ptr<const core::read_frame>(frame));\r
319 \r
320                 return S_OK;\r
321         }\r
322 \r
323         virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped (void)\r
324         {\r
325                 is_running_ = false;\r
326                 return S_OK;\r
327         }\r
328                 \r
329         virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples (BOOL preroll)\r
330         {\r
331                 if(!is_running_)\r
332                         return S_OK;\r
333                 \r
334                 try\r
335                 {\r
336                         if(preroll)\r
337                         {\r
338                                 if(FAILED(output_->StartScheduledPlayback(0, frame_time_scale_, 1.0)))\r
339                                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
340                         }\r
341 \r
342                         std::shared_ptr<const core::read_frame> frame;\r
343                         audio_frame_buffer_.pop(frame);\r
344                         schedule_next_audio(safe_ptr<const core::read_frame>(frame));\r
345                 \r
346                 }\r
347                 catch(...)\r
348                 {\r
349                         exception_ = std::current_exception();\r
350                         return E_FAIL;\r
351                 }\r
352 \r
353                 return S_OK;\r
354         }\r
355 \r
356         void schedule_next_audio(const safe_ptr<const core::read_frame>& frame)\r
357         {\r
358                 static std::vector<short> silence(48000, 0);\r
359 \r
360                 int audio_samples = static_cast<size_t>(48000.0 / format_desc_.fps);\r
361 \r
362                 auto frame_audio_data = frame->audio_data().empty() ? silence.data() : const_cast<short*>(frame->audio_data().begin());\r
363 \r
364                 audio_container_.push_back(std::vector<short>(frame_audio_data, frame_audio_data+audio_samples*2));\r
365 \r
366                 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), audio_samples, (audio_scheduled_++) * audio_samples, 48000, nullptr)))\r
367                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
368         }\r
369                         \r
370         void schedule_next_video(const safe_ptr<const core::read_frame>& frame)\r
371         {\r
372                 if(!frame->image_data().empty())\r
373                         fast_memcpy(reserved_frames_.front().first, frame->image_data().begin(), frame->image_data().size());\r
374                 else\r
375                         fast_memclr(reserved_frames_.front().first, format_desc_.size);\r
376 \r
377                 if(FAILED(output_->ScheduleVideoFrame(reserved_frames_.front().second, (frames_scheduled_++) * frame_duration_, frame_duration_, frame_time_scale_)))\r
378                         CASPAR_LOG(error) << print() << L" Failed to schedule video.";\r
379 \r
380                 std::rotate(reserved_frames_.begin(), reserved_frames_.begin() + 1, reserved_frames_.end());\r
381                 graph_->update_value("tick-time", static_cast<float>(perf_timer_.elapsed()/format_desc_.interval)*0.5f);\r
382                 perf_timer_.restart();\r
383         }\r
384 \r
385         void send(const safe_ptr<const core::read_frame>& frame)\r
386         {\r
387                 if(exception_ != nullptr)\r
388                         std::rethrow_exception(exception_);\r
389 \r
390                 video_frame_buffer_.push(frame);\r
391                 if(config_.embedded_audio)\r
392                         audio_frame_buffer_.push(frame);\r
393         }\r
394 \r
395         std::wstring print() const\r
396         {\r
397                 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"]";\r
398         }\r
399 };\r
400 \r
401 struct decklink_consumer : public core::frame_consumer\r
402 {\r
403         std::unique_ptr<decklink_output> input_;\r
404         configuration config_;\r
405 \r
406         executor executor_;\r
407 public:\r
408 \r
409         decklink_consumer(const configuration& config)\r
410                 : config_(config)\r
411                 , executor_(L"DECKLINK[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]", true){}\r
412 \r
413         ~decklink_consumer()\r
414         {\r
415                 executor_.invoke([&]\r
416                 {\r
417                         input_ = nullptr;\r
418                 });\r
419         }\r
420 \r
421         void initialize(const core::video_format_desc& format_desc)\r
422         {\r
423                 executor_.invoke([&]\r
424                 {\r
425                         input_.reset(new decklink_output(config_, format_desc));\r
426                 });\r
427         }\r
428         \r
429         void send(const safe_ptr<const core::read_frame>& frame)\r
430         {\r
431                 input_->send(frame);\r
432         }\r
433         \r
434         std::wstring print() const\r
435         {\r
436                 return input_->print();\r
437         }\r
438 };      \r
439 \r
440 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params) \r
441 {\r
442         if(params.size() < 1 || params[0] != L"DECKLINK")\r
443                 return core::frame_consumer::empty();\r
444         \r
445         return make_safe<decklink_consumer>(configuration(std::vector<std::wstring>(params.begin()+1, params.end())));\r
446 }\r
447 \r
448 safe_ptr<core::frame_consumer> create_decklink_consumer_ptree(const boost::property_tree::ptree& ptree) \r
449 {\r
450         return make_safe<decklink_consumer>(configuration(ptree));\r
451 }\r
452 \r
453 }