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