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