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