]> 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/utility/timer.h>\r
37 \r
38 #include <tbb/concurrent_queue.h>\r
39 \r
40 #include <boost/circular_buffer.hpp>\r
41 #include <boost/timer.hpp>\r
42 \r
43 #include <array>\r
44 \r
45 #pragma warning(push)\r
46 #pragma warning(disable : 4996)\r
47 \r
48         #include <atlbase.h>\r
49 \r
50         #include <atlcom.h>\r
51         #include <atlhost.h>\r
52 \r
53 #pragma warning(push)\r
54 \r
55 namespace caspar { \r
56         \r
57 struct decklink_output : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
58 {               \r
59         struct co_init\r
60         {\r
61                 co_init(){CoInitialize(nullptr);}\r
62                 ~co_init(){CoUninitialize();}\r
63         } co_;\r
64         \r
65         const decklink_consumer::configuration config_;\r
66 \r
67         std::wstring    model_name_;\r
68         tbb::atomic<bool> is_running_;\r
69 \r
70         std::shared_ptr<diagnostics::graph> graph_;\r
71         boost::timer perf_timer_;\r
72 \r
73         std::array<std::pair<void*, CComPtr<IDeckLinkMutableVideoFrame>>, 3> reserved_frames_;\r
74         boost::circular_buffer<std::vector<short>> audio_container_;\r
75         \r
76         CComPtr<IDeckLink>                                      decklink_;\r
77         CComQIPtr<IDeckLinkOutput>                      output_;\r
78         CComQIPtr<IDeckLinkConfiguration>       configuration_;\r
79         \r
80         core::video_format_desc format_desc_;\r
81 \r
82         BMDTimeScale frame_time_scale_;\r
83         BMDTimeValue frame_duration_;\r
84         unsigned long frames_scheduled_;\r
85         unsigned long audio_scheduled_;\r
86         \r
87         tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> video_frame_buffer_;\r
88         tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> audio_frame_buffer_;\r
89 \r
90 public:\r
91         decklink_output(const decklink_consumer::configuration& config, const core::video_format_desc& format_desc) \r
92                 :  model_name_(L"DECKLINK")\r
93                 , config_(config)\r
94                 , audio_container_(5)\r
95                 , frames_scheduled_(0)\r
96                 , audio_scheduled_(0)\r
97                 , format_desc_(format_desc)\r
98         {\r
99                 is_running_ = true;\r
100                 format_desc_ = format_desc;\r
101                 CComPtr<IDeckLinkIterator> pDecklinkIterator;\r
102                 if(FAILED(pDecklinkIterator.CoCreateInstance(CLSID_CDeckLinkIterator)))\r
103                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " No Decklink drivers installed."));\r
104                 \r
105                 size_t n = 0;\r
106                 while(n < config_.device_index && pDecklinkIterator->Next(&decklink_) == S_OK){++n;}    \r
107 \r
108                 if(n != config_.device_index || !decklink_)\r
109                         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
110                 \r
111                 BSTR pModelName;\r
112                 decklink_->GetModelName(&pModelName);\r
113                 model_name_ = std::wstring(pModelName);\r
114                                 \r
115                 graph_ = diagnostics::create_graph(narrow(print()));\r
116                 graph_->add_guide("tick-time", 0.5);\r
117                 graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f));\r
118                 \r
119                 output_ = decklink_;\r
120                 configuration_ = decklink_;\r
121 \r
122                 auto display_mode = get_display_mode(output_.p, format_desc_.format);\r
123                 if(display_mode == nullptr) \r
124                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Card does not support requested videoformat."));\r
125                 \r
126                 display_mode->GetFrameRate(&frame_duration_, &frame_time_scale_);\r
127 \r
128                 BMDDisplayModeSupport displayModeSupport;\r
129                 if(FAILED(output_->DoesSupportVideoMode(display_mode->GetDisplayMode(), bmdFormat8BitBGRA, bmdVideoOutputFlagDefault, &displayModeSupport, nullptr)))\r
130                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Card does not support requested videoformat."));\r
131                 \r
132                 if(config_.embed_audio)\r
133                 {\r
134                         if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))\r
135                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));\r
136                                 \r
137                         if(FAILED(output_->SetAudioCallback(this)))\r
138                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));\r
139 \r
140                         CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";\r
141                 }\r
142 \r
143                 if(config_.low_latency)\r
144                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);\r
145                 \r
146                 if(FAILED(output_->EnableVideoOutput(display_mode->GetDisplayMode(), bmdVideoOutputFlagDefault))) \r
147                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));\r
148                 \r
149                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
150                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to set playback completion callback."));\r
151                         \r
152                 CComQIPtr<IDeckLinkKeyer> keyer = decklink_;\r
153                 if(config_.keyer == decklink_consumer::internal_key) \r
154                 {\r
155                         if(FAILED(keyer->Enable(FALSE)))                        \r
156                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   \r
157                         else if(FAILED(keyer->SetLevel(255)))                   \r
158                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
159                         else\r
160                                 CASPAR_LOG(info) << print() << L" Successfully configured internal keyer.";             \r
161                 }\r
162                 else if(config.keyer == decklink_consumer::external_key)\r
163                 {\r
164                         if(FAILED(keyer->Enable(TRUE)))                 \r
165                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   \r
166                         else if(FAILED(keyer->SetLevel(255)))                   \r
167                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
168                         else\r
169                                 CASPAR_LOG(info) << print() << L" Successfully configured external keyer.";                     \r
170                 }\r
171                 else\r
172                                 CASPAR_LOG(info) << print() << L" Uses default keyer settings.";        \r
173 \r
174                 \r
175                 for(size_t n = 0; n < reserved_frames_.size(); ++n)\r
176                 {\r
177                         if(FAILED(output_->CreateVideoFrame(format_desc_.width, format_desc_.height, format_desc_.size/format_desc_.height, bmdFormat8BitBGRA, bmdFrameFlagDefault, &reserved_frames_[n].second)))\r
178                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to create frame."));\r
179 \r
180                         if(FAILED(reserved_frames_[n].second->GetBytes(&reserved_frames_[n].first)))\r
181                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to get frame bytes."));\r
182                 }\r
183                                         \r
184                 auto buffer_size = static_cast<size_t>(frame_time_scale_/frame_duration_)/4;\r
185                 for(size_t n = 0; n < buffer_size; ++n)\r
186                         schedule_next_video(core::read_frame::empty());\r
187 \r
188                 video_frame_buffer_.set_capacity(buffer_size);\r
189                 audio_frame_buffer_.set_capacity(buffer_size);\r
190                 for(size_t n = 0; n < std::max<size_t>(2, buffer_size-2); ++n)\r
191                 {\r
192                         video_frame_buffer_.try_push(core::read_frame::empty());\r
193                         if(config_.embed_audio)\r
194                                 audio_frame_buffer_.try_push(core::read_frame::empty());\r
195                 }\r
196                 \r
197                 if(FAILED(output_->StartScheduledPlayback(0, frame_time_scale_, 1.0))) \r
198                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
199                 \r
200                 CASPAR_LOG(info) << print() << L" Successfully initialized for " << format_desc_.name;  \r
201         }\r
202 \r
203         ~decklink_output()\r
204         {               \r
205                 is_running_ = false;\r
206                 video_frame_buffer_.try_push(core::read_frame::empty());\r
207                 audio_frame_buffer_.try_push(core::read_frame::empty());\r
208 \r
209                 if(output_ != nullptr) \r
210                 {\r
211                         output_->StopScheduledPlayback(0, nullptr, 0);\r
212                         if(config_.embed_audio)\r
213                                 output_->DisableAudioOutput();\r
214                         output_->DisableVideoOutput();\r
215                 }\r
216                 CASPAR_LOG(info) << print() << L" Shutting down.";      \r
217         }\r
218                         \r
219         virtual HRESULT STDMETHODCALLTYPE       QueryInterface (REFIID, LPVOID*)        {return E_NOINTERFACE;}\r
220         virtual ULONG STDMETHODCALLTYPE         AddRef ()                                                       {return 1;}\r
221         virtual ULONG STDMETHODCALLTYPE         Release ()                                                      {return 1;}\r
222         \r
223         virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted (IDeckLinkVideoFrame* /*completedFrame*/, BMDOutputFrameCompletionResult /*result*/)\r
224         {\r
225                 if(!is_running_)\r
226                         return S_OK;\r
227 \r
228                 std::shared_ptr<const core::read_frame> frame;  \r
229                 video_frame_buffer_.pop(frame);         \r
230                 schedule_next_video(safe_ptr<const core::read_frame>(frame));\r
231 \r
232                 return S_OK;\r
233         }\r
234 \r
235         virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped (void)\r
236         {\r
237                 return S_OK;\r
238         }\r
239                 \r
240         virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples (BOOL /*preroll*/)\r
241         {\r
242                 if(!is_running_)\r
243                         return S_OK;\r
244 \r
245                 std::shared_ptr<const core::read_frame> frame;\r
246                 audio_frame_buffer_.pop(frame);\r
247                 schedule_next_audio(safe_ptr<const core::read_frame>(frame));\r
248 \r
249                 return S_OK;\r
250         }\r
251 \r
252         void schedule_next_audio(const safe_ptr<const core::read_frame>& frame)\r
253         {\r
254                 static std::vector<short> silence(48000, 0);\r
255 \r
256                 int audio_samples = static_cast<size_t>(48000.0 / format_desc_.fps);\r
257 \r
258                 auto frame_audio_data = frame->audio_data().empty() ? silence.data() : const_cast<short*>(frame->audio_data().begin());\r
259 \r
260                 audio_container_.push_back(std::vector<short>(frame_audio_data, frame_audio_data+audio_samples*2));\r
261 \r
262                 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), audio_samples, (audio_scheduled_++) * audio_samples, 48000, nullptr)))\r
263                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
264         }\r
265                         \r
266         void schedule_next_video(const safe_ptr<const core::read_frame>& frame)\r
267         {\r
268                 if(!frame->image_data().empty())\r
269                         std::copy(frame->image_data().begin(), frame->image_data().end(), static_cast<char*>(reserved_frames_.front().first));\r
270                 else\r
271                         std::fill_n(static_cast<int*>(reserved_frames_.front().first), 0, format_desc_.size/4);\r
272 \r
273                 if(FAILED(output_->ScheduleVideoFrame(reserved_frames_.front().second, (frames_scheduled_++) * frame_duration_, frame_duration_, frame_time_scale_)))\r
274                         CASPAR_LOG(error) << print() << L" Failed to schedule video.";\r
275 \r
276                 std::rotate(reserved_frames_.begin(), reserved_frames_.begin() + 1, reserved_frames_.end());\r
277                 graph_->update_value("tick-time", static_cast<float>(perf_timer_.elapsed()/format_desc_.interval*0.5));\r
278                 perf_timer_.restart();\r
279         }\r
280 \r
281         void send(const safe_ptr<const core::read_frame>& frame)\r
282         {\r
283                 video_frame_buffer_.push(frame);\r
284                 if(config_.embed_audio)\r
285                         audio_frame_buffer_.push(frame);\r
286         }\r
287 \r
288         std::wstring print() const\r
289         {\r
290                 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"]";\r
291         }\r
292 };\r
293 \r
294 struct decklink_consumer::implementation\r
295 {\r
296         std::unique_ptr<decklink_output> input_;\r
297         decklink_consumer::configuration config_;\r
298 \r
299         executor executor_;\r
300 public:\r
301 \r
302         implementation(const decklink_consumer::configuration& config)\r
303                 : config_(config)\r
304                 , executor_(L"DECKLINK[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")\r
305         {\r
306                 executor_.start();\r
307         }\r
308 \r
309         ~implementation()\r
310         {\r
311                 executor_.invoke([&]\r
312                 {\r
313                         input_ = nullptr;\r
314                 });\r
315         }\r
316 \r
317         void initialize(const core::video_format_desc& format_desc)\r
318         {\r
319                 executor_.invoke([&]\r
320                 {\r
321                         input_.reset(new decklink_output(config_, format_desc));\r
322                 });\r
323         }\r
324         \r
325         void send(const safe_ptr<const core::read_frame>& frame)\r
326         {\r
327                 input_->send(frame);\r
328         }\r
329 \r
330         size_t buffer_depth() const\r
331         {\r
332                 return 1;\r
333         }\r
334 \r
335         std::wstring print() const\r
336         {\r
337                 return input_->print();\r
338         }\r
339 };\r
340 \r
341 decklink_consumer::decklink_consumer(const configuration& config) : impl_(new implementation(config)){}\r
342 decklink_consumer::decklink_consumer(decklink_consumer&& other) : impl_(std::move(other.impl_)){}\r
343 void decklink_consumer::initialize(const core::video_format_desc& format_desc){impl_->initialize(format_desc);}\r
344 void decklink_consumer::send(const safe_ptr<const core::read_frame>& frame){impl_->send(frame);}\r
345 size_t decklink_consumer::buffer_depth() const{return impl_->buffer_depth();}\r
346 std::wstring decklink_consumer::print() const{return impl_->print();}\r
347         \r
348 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params) \r
349 {\r
350         if(params.size() < 1 || params[0] != L"DECKLINK")\r
351                 return core::frame_consumer::empty();\r
352         \r
353         decklink_consumer::configuration config;\r
354 \r
355         if(params.size() > 1) \r
356                 config.device_index = lexical_cast_or_default<int>(params[2], config.device_index);\r
357 \r
358         if(params.size() > 2)\r
359                 config.embed_audio = lexical_cast_or_default<bool>(params[3], config.embed_audio);\r
360         \r
361         if(params.size() > 3) \r
362         {\r
363                 if(params[4] == L"INTERNAL_KEY")\r
364                         config.keyer = decklink_consumer::internal_key;\r
365                 else if(params[4] == L"EXTERNAL_KEY")\r
366                         config.keyer = decklink_consumer::external_key;\r
367         }\r
368 \r
369         return make_safe<decklink_consumer>(config);\r
370 }\r
371 \r
372 }