b61d93ae96529d2bddc5286b64fee910d2bc24ed
[platform/framework/web/crosswalk.git] / src / media / video / capture / mac / video_capture_device_decklink_mac.mm
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/video/capture/mac/video_capture_device_decklink_mac.h"
6
7 #include "base/logging.h"
8 #include "base/memory/ref_counted.h"
9 #include "base/synchronization/lock.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "third_party/decklink/mac/include/DeckLinkAPI.h"
12
13 namespace {
14
15 // DeckLink SDK uses ScopedComPtr-style APIs. Chrome ScopedComPtr is only
16 // available for Windows builds. This is a verbatim knock-off of the needed
17 // parts of base::win::ScopedComPtr<> for ref counting.
18 template <class T>
19 class ScopedDeckLinkPtr : public scoped_refptr<T> {
20  private:
21   using scoped_refptr<T>::ptr_;
22
23  public:
24   T** Receive() {
25     DCHECK(!ptr_) << "Object leak. Pointer must be NULL";
26     return &ptr_;
27   }
28
29   void** ReceiveVoid() {
30     return reinterpret_cast<void**>(Receive());
31   }
32
33   void Release() {
34     if (ptr_ != NULL) {
35       ptr_->Release();
36       ptr_ = NULL;
37     }
38   }
39 };
40
41 // This class is used to interact directly with DeckLink SDK for video capture.
42 // Implements the reference counted interface IUnknown. Has a weak reference to
43 // VideoCaptureDeviceDeckLinkMac for sending captured frames, error messages and
44 // logs.
45 class DeckLinkCaptureDelegate :
46     public IDeckLinkInputCallback,
47     public base::RefCountedThreadSafe<DeckLinkCaptureDelegate> {
48  public:
49   DeckLinkCaptureDelegate(const media::VideoCaptureDevice::Name& device_name,
50                           media::VideoCaptureDeviceDeckLinkMac* frame_receiver);
51
52   void AllocateAndStart(const media::VideoCaptureParams& params);
53   void StopAndDeAllocate();
54
55   // Remove the VideoCaptureDeviceDeckLinkMac's weak reference.
56   void ResetVideoCaptureDeviceReference();
57
58  private:
59   // IDeckLinkInputCallback interface implementation.
60   HRESULT VideoInputFormatChanged(
61       BMDVideoInputFormatChangedEvents notification_events,
62       IDeckLinkDisplayMode* new_display_mode,
63       BMDDetectedVideoInputFormatFlags detected_signal_flags) override;
64   HRESULT VideoInputFrameArrived(
65       IDeckLinkVideoInputFrame* video_frame,
66       IDeckLinkAudioInputPacket* audio_packet) override;
67
68   // IUnknown interface implementation.
69   HRESULT QueryInterface(REFIID iid, void** ppv) override;
70   ULONG AddRef() override;
71   ULONG Release() override;
72
73   // Forwarder to VideoCaptureDeviceDeckLinkMac::SendErrorString().
74   void SendErrorString(const std::string& reason);
75
76   // Forwarder to VideoCaptureDeviceDeckLinkMac::SendLogString().
77   void SendLogString(const std::string& message);
78
79   const media::VideoCaptureDevice::Name device_name_;
80
81   // Protects concurrent setting and using of |frame_receiver_|.
82   base::Lock lock_;
83   // Weak reference to the captured frames client, used also for error messages
84   // and logging. Initialized on construction and used until cleared by calling
85   // ResetVideoCaptureDeviceReference().
86   media::VideoCaptureDeviceDeckLinkMac* frame_receiver_;
87
88   // This is used to control the video capturing device input interface.
89   ScopedDeckLinkPtr<IDeckLinkInput> decklink_input_;
90   // |decklink_| represents a physical device attached to the host.
91   ScopedDeckLinkPtr<IDeckLink> decklink_;
92
93   // Checks for Device (a.k.a. Audio) thread.
94   base::ThreadChecker thread_checker_;
95
96   friend class scoped_refptr<DeckLinkCaptureDelegate>;
97   friend class base::RefCountedThreadSafe<DeckLinkCaptureDelegate>;
98
99   ~DeckLinkCaptureDelegate() override;
100
101   DISALLOW_COPY_AND_ASSIGN(DeckLinkCaptureDelegate);
102 };
103
104 static float GetDisplayModeFrameRate(
105     const ScopedDeckLinkPtr<IDeckLinkDisplayMode>& display_mode) {
106   BMDTimeValue time_value, time_scale;
107   float display_mode_frame_rate = 0.0f;
108   if (display_mode->GetFrameRate(&time_value, &time_scale) == S_OK &&
109       time_value > 0) {
110     display_mode_frame_rate = static_cast<float>(time_scale) / time_value;
111   }
112   // Interlaced formats are going to be marked as double the frame rate,
113   // which follows the general naming convention.
114   if (display_mode->GetFieldDominance() == bmdLowerFieldFirst ||
115       display_mode->GetFieldDominance() == bmdUpperFieldFirst) {
116     display_mode_frame_rate *= 2.0f;
117   }
118   return display_mode_frame_rate;
119 }
120
121 DeckLinkCaptureDelegate::DeckLinkCaptureDelegate(
122     const media::VideoCaptureDevice::Name& device_name,
123     media::VideoCaptureDeviceDeckLinkMac* frame_receiver)
124     : device_name_(device_name),
125       frame_receiver_(frame_receiver) {
126 }
127
128 DeckLinkCaptureDelegate::~DeckLinkCaptureDelegate() {}
129
130 void DeckLinkCaptureDelegate::AllocateAndStart(
131     const media::VideoCaptureParams& params) {
132   DCHECK(thread_checker_.CalledOnValidThread());
133   scoped_refptr<IDeckLinkIterator> decklink_iter(
134       CreateDeckLinkIteratorInstance());
135   DLOG_IF(ERROR, !decklink_iter.get()) << "Error creating DeckLink iterator";
136   if (!decklink_iter.get())
137     return;
138
139   ScopedDeckLinkPtr<IDeckLink> decklink_local;
140   while (decklink_iter->Next(decklink_local.Receive()) == S_OK) {
141     CFStringRef device_model_name = NULL;
142     if ((decklink_local->GetModelName(&device_model_name) == S_OK) ||
143         (device_name_.id() == base::SysCFStringRefToUTF8(device_model_name))) {
144       break;
145     }
146   }
147   if (!decklink_local.get()) {
148     SendErrorString("Device id not found in the system");
149     return;
150   }
151
152   ScopedDeckLinkPtr<IDeckLinkInput> decklink_input_local;
153   if (decklink_local->QueryInterface(IID_IDeckLinkInput,
154           decklink_input_local.ReceiveVoid()) != S_OK) {
155     SendErrorString("Error querying input interface.");
156     return;
157   }
158
159   ScopedDeckLinkPtr<IDeckLinkDisplayModeIterator> display_mode_iter;
160   if (decklink_input_local->GetDisplayModeIterator(
161       display_mode_iter.Receive()) != S_OK) {
162     SendErrorString("Error creating Display Mode Iterator");
163     return;
164   }
165
166   ScopedDeckLinkPtr<IDeckLinkDisplayMode> chosen_display_mode;
167   ScopedDeckLinkPtr<IDeckLinkDisplayMode> display_mode;
168   float min_diff = FLT_MAX;
169   while (display_mode_iter->Next(display_mode.Receive()) == S_OK) {
170     const float diff = labs(display_mode->GetWidth() -
171         params.requested_format.frame_size.width()) +
172         labs(params.requested_format.frame_size.height() -
173         display_mode->GetHeight()) + fabs(params.requested_format.frame_rate -
174         GetDisplayModeFrameRate(display_mode));
175     if (diff < min_diff) {
176       chosen_display_mode = display_mode;
177       min_diff = diff;
178     }
179     display_mode.Release();
180   }
181   if (!chosen_display_mode.get()) {
182     SendErrorString("Could not find a display mode");
183     return;
184   }
185 #if !defined(NDEBUG)
186   DVLOG(1) << "Requested format: " << params.requested_format.ToString();
187   CFStringRef format_name = NULL;
188   if (chosen_display_mode->GetName(&format_name) == S_OK)
189     DVLOG(1) << "Chosen format: " << base::SysCFStringRefToUTF8(format_name);
190 #endif
191
192   // Enable video input. Configure for no input video format change detection,
193   // this in turn will disable calls to VideoInputFormatChanged().
194   if (decklink_input_local->EnableVideoInput(
195           chosen_display_mode->GetDisplayMode(), bmdFormat8BitYUV,
196           bmdVideoInputFlagDefault) != S_OK) {
197     SendErrorString("Could not select the video format we like.");
198     return;
199   }
200
201   decklink_input_local->SetCallback(this);
202   if (decklink_input_local->StartStreams() != S_OK)
203     SendErrorString("Could not start capturing");
204
205   decklink_.swap(decklink_local);
206   decklink_input_.swap(decklink_input_local);
207 }
208
209 void DeckLinkCaptureDelegate::StopAndDeAllocate() {
210   DCHECK(thread_checker_.CalledOnValidThread());
211   if (!decklink_input_.get())
212     return;
213   if (decklink_input_->StopStreams() != S_OK)
214     SendLogString("Problem stopping capture.");
215   decklink_input_->SetCallback(NULL);
216   decklink_input_->DisableVideoInput();
217   decklink_input_.Release();
218   decklink_.Release();
219   ResetVideoCaptureDeviceReference();
220 }
221
222 HRESULT DeckLinkCaptureDelegate::VideoInputFormatChanged(
223     BMDVideoInputFormatChangedEvents notification_events,
224     IDeckLinkDisplayMode *new_display_mode,
225     BMDDetectedVideoInputFormatFlags detected_signal_flags) {
226   DCHECK(thread_checker_.CalledOnValidThread());
227   return S_OK;
228 }
229
230 HRESULT DeckLinkCaptureDelegate::VideoInputFrameArrived(
231     IDeckLinkVideoInputFrame* video_frame,
232     IDeckLinkAudioInputPacket* /* audio_packet */) {
233   // Capture frames are manipulated as an IDeckLinkVideoFrame.
234   uint8* video_data = NULL;
235   video_frame->GetBytes(reinterpret_cast<void**>(&video_data));
236
237   media::VideoPixelFormat pixel_format = media::PIXEL_FORMAT_UNKNOWN;
238   switch (video_frame->GetPixelFormat()) {
239     case bmdFormat8BitYUV:  // A.k.a. '2vuy';
240       pixel_format = media::PIXEL_FORMAT_UYVY;
241       break;
242     case bmdFormat8BitARGB:
243       pixel_format = media::PIXEL_FORMAT_ARGB;
244       break;
245     default:
246       SendErrorString("Unsupported pixel format");
247       break;
248   }
249
250   const media::VideoCaptureFormat capture_format(
251       gfx::Size(video_frame->GetWidth(), video_frame->GetHeight()),
252       0.0f,  // Frame rate is not needed for captured data callback.
253       pixel_format);
254   base::AutoLock lock(lock_);
255   if (frame_receiver_) {
256     frame_receiver_->OnIncomingCapturedData(
257         video_data,
258         video_frame->GetRowBytes() * video_frame->GetHeight(),
259         capture_format,
260         0,  // Rotation.
261         base::TimeTicks::Now());
262   }
263   return S_OK;
264 }
265
266 HRESULT DeckLinkCaptureDelegate::QueryInterface(REFIID iid, void** ppv) {
267   DCHECK(thread_checker_.CalledOnValidThread());
268   CFUUIDBytes iunknown = CFUUIDGetUUIDBytes(IUnknownUUID);
269   if (memcmp(&iid, &iunknown, sizeof(REFIID)) == 0 ||
270       memcmp(&iid, &IID_IDeckLinkInputCallback, sizeof(REFIID)) == 0) {
271     *ppv = static_cast<IDeckLinkInputCallback*>(this);
272     AddRef();
273     return S_OK;
274   }
275   return E_NOINTERFACE;
276 }
277
278 ULONG DeckLinkCaptureDelegate::AddRef() {
279   DCHECK(thread_checker_.CalledOnValidThread());
280   base::RefCountedThreadSafe<DeckLinkCaptureDelegate>::AddRef();
281   return 1;
282 }
283
284 ULONG DeckLinkCaptureDelegate::Release() {
285   DCHECK(thread_checker_.CalledOnValidThread());
286   bool ret_value = !HasOneRef();
287   base::RefCountedThreadSafe<DeckLinkCaptureDelegate>::Release();
288   return ret_value;
289 }
290
291 void DeckLinkCaptureDelegate::SendErrorString(const std::string& reason) {
292   base::AutoLock lock(lock_);
293   if (frame_receiver_)
294     frame_receiver_->SendErrorString(reason);
295 }
296
297 void DeckLinkCaptureDelegate::SendLogString(const std::string& message) {
298   base::AutoLock lock(lock_);
299   if (frame_receiver_)
300     frame_receiver_->SendLogString(message);
301 }
302
303 void DeckLinkCaptureDelegate::ResetVideoCaptureDeviceReference() {
304   DCHECK(thread_checker_.CalledOnValidThread());
305   base::AutoLock lock(lock_);
306   frame_receiver_ = NULL;
307 }
308
309 }  // namespace
310
311 namespace media {
312
313 static std::string JoinDeviceNameAndFormat(CFStringRef name,
314                                            CFStringRef format) {
315   return base::SysCFStringRefToUTF8(name) + " - " +
316       base::SysCFStringRefToUTF8(format);
317 }
318
319 //static
320 void VideoCaptureDeviceDeckLinkMac::EnumerateDevices(
321     VideoCaptureDevice::Names* device_names) {
322   scoped_refptr<IDeckLinkIterator> decklink_iter(
323       CreateDeckLinkIteratorInstance());
324   // At this point, not being able to create a DeckLink iterator means that
325   // there are no Blackmagic DeckLink devices in the system, don't print error.
326   DVLOG_IF(1, !decklink_iter.get()) << "Could not create DeckLink iterator";
327   if (!decklink_iter.get())
328     return;
329
330   ScopedDeckLinkPtr<IDeckLink> decklink;
331   while (decklink_iter->Next(decklink.Receive()) == S_OK) {
332     ScopedDeckLinkPtr<IDeckLink> decklink_local;
333     decklink_local.swap(decklink);
334
335     CFStringRef device_model_name = NULL;
336     HRESULT hr = decklink_local->GetModelName(&device_model_name);
337     DVLOG_IF(1, hr != S_OK) << "Error reading Blackmagic device model name";
338     CFStringRef device_display_name = NULL;
339     hr = decklink_local->GetDisplayName(&device_display_name);
340     DVLOG_IF(1, hr != S_OK) << "Error reading Blackmagic device display name";
341     DVLOG_IF(1, hr == S_OK) << "Blackmagic device found with name: " <<
342         base::SysCFStringRefToUTF8(device_display_name);
343
344     if (!device_model_name && !device_display_name)
345       continue;
346
347     ScopedDeckLinkPtr<IDeckLinkInput> decklink_input;
348     if (decklink_local->QueryInterface(IID_IDeckLinkInput,
349         decklink_input.ReceiveVoid()) != S_OK) {
350       DLOG(ERROR) << "Error Blackmagic querying input interface.";
351       return;
352     }
353
354     ScopedDeckLinkPtr<IDeckLinkDisplayModeIterator> display_mode_iter;
355     if (decklink_input->GetDisplayModeIterator(display_mode_iter.Receive()) !=
356         S_OK) {
357       continue;
358     }
359
360     ScopedDeckLinkPtr<IDeckLinkDisplayMode> display_mode;
361     while (display_mode_iter->Next(display_mode.Receive()) == S_OK) {
362       CFStringRef format_name = NULL;
363       if (display_mode->GetName(&format_name) == S_OK) {
364         VideoCaptureDevice::Name name(
365             JoinDeviceNameAndFormat(device_display_name, format_name),
366             JoinDeviceNameAndFormat(device_model_name, format_name),
367             VideoCaptureDevice::Name::DECKLINK,
368             VideoCaptureDevice::Name::OTHER_TRANSPORT);
369         device_names->push_back(name);
370         DVLOG(1) << "Blackmagic camera enumerated: " << name.name();
371       }
372       display_mode.Release();
373     }
374   }
375 }
376
377 // static
378 void VideoCaptureDeviceDeckLinkMac::EnumerateDeviceCapabilities(
379     const VideoCaptureDevice::Name& device,
380     VideoCaptureFormats* supported_formats) {
381   scoped_refptr<IDeckLinkIterator> decklink_iter(
382       CreateDeckLinkIteratorInstance());
383   DLOG_IF(ERROR, !decklink_iter.get()) << "Error creating DeckLink iterator";
384   if (!decklink_iter.get())
385     return;
386
387   ScopedDeckLinkPtr<IDeckLink> decklink;
388   while (decklink_iter->Next(decklink.Receive()) == S_OK) {
389     ScopedDeckLinkPtr<IDeckLink> decklink_local;
390     decklink_local.swap(decklink);
391
392     ScopedDeckLinkPtr<IDeckLinkInput> decklink_input;
393     if (decklink_local->QueryInterface(IID_IDeckLinkInput,
394             decklink_input.ReceiveVoid()) != S_OK) {
395       DLOG(ERROR) << "Error Blackmagic querying input interface.";
396       return;
397     }
398
399     ScopedDeckLinkPtr<IDeckLinkDisplayModeIterator> display_mode_iter;
400     if (decklink_input->GetDisplayModeIterator(display_mode_iter.Receive()) !=
401         S_OK) {
402       continue;
403     }
404
405     CFStringRef device_model_name = NULL;
406     if (decklink_local->GetModelName(&device_model_name) != S_OK)
407       continue;
408
409     ScopedDeckLinkPtr<IDeckLinkDisplayMode> display_mode;
410     while (display_mode_iter->Next(display_mode.Receive()) == S_OK) {
411       CFStringRef format_name = NULL;
412       if (display_mode->GetName(&format_name) == S_OK && device.id() !=
413           JoinDeviceNameAndFormat(device_model_name, format_name)) {
414         display_mode.Release();
415         continue;
416       }
417
418       // IDeckLinkDisplayMode does not have information on pixel format, this
419       // is only available on capture.
420       const media::VideoCaptureFormat format(
421           gfx::Size(display_mode->GetWidth(), display_mode->GetHeight()),
422           GetDisplayModeFrameRate(display_mode),
423           PIXEL_FORMAT_UNKNOWN);
424       supported_formats->push_back(format);
425       DVLOG(2) << device.name() << " " << format.ToString();
426       display_mode.Release();
427     }
428     return;
429   }
430 }
431
432 VideoCaptureDeviceDeckLinkMac::VideoCaptureDeviceDeckLinkMac(
433     const Name& device_name)
434     : decklink_capture_delegate_(
435           new DeckLinkCaptureDelegate(device_name, this)) {
436 }
437
438 VideoCaptureDeviceDeckLinkMac::~VideoCaptureDeviceDeckLinkMac() {
439   decklink_capture_delegate_->ResetVideoCaptureDeviceReference();
440 }
441
442 void VideoCaptureDeviceDeckLinkMac::OnIncomingCapturedData(
443     const uint8* data,
444     size_t length,
445     const VideoCaptureFormat& frame_format,
446     int rotation,  // Clockwise.
447     base::TimeTicks timestamp) {
448   base::AutoLock lock(lock_);
449   if (client_) {
450     client_->OnIncomingCapturedData(data, length, frame_format, rotation,
451         timestamp);
452   }
453 }
454
455 void VideoCaptureDeviceDeckLinkMac::SendErrorString(const std::string& reason) {
456   DCHECK(thread_checker_.CalledOnValidThread());
457   base::AutoLock lock(lock_);
458   if (client_)
459     client_->OnError(reason);
460 }
461
462 void VideoCaptureDeviceDeckLinkMac::SendLogString(const std::string& message) {
463   DCHECK(thread_checker_.CalledOnValidThread());
464   base::AutoLock lock(lock_);
465   if (client_)
466     client_->OnLog(message);
467 }
468
469 void VideoCaptureDeviceDeckLinkMac::AllocateAndStart(
470       const VideoCaptureParams& params,
471       scoped_ptr<VideoCaptureDevice::Client> client) {
472   DCHECK(thread_checker_.CalledOnValidThread());
473   client_ = client.Pass();
474   if (decklink_capture_delegate_.get())
475     decklink_capture_delegate_->AllocateAndStart(params);
476 }
477
478 void VideoCaptureDeviceDeckLinkMac::StopAndDeAllocate() {
479   if (decklink_capture_delegate_.get())
480     decklink_capture_delegate_->StopAndDeAllocate();
481 }
482
483 } // namespace media