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