Updating XTBs based on .GRDs from branch master
[chromium-blink-merge.git] / media / capture / video / mac / video_capture_device_decklink_mac.mm
blobddf69adfdd782c75abc3aad8a2b86f15a3dcbddf
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.
5 #include "media/capture/video/mac/video_capture_device_decklink_mac.h"
7 #include "base/logging.h"
8 #include "base/memory/ref_counted.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "base/synchronization/lock.h"
11 #include "third_party/decklink/mac/include/DeckLinkAPI.h"
13 namespace {
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_;
23  public:
24   T** Receive() {
25     DCHECK(!ptr_) << "Object leak. Pointer must be NULL";
26     return &ptr_;
27   }
29   void** ReceiveVoid() { return reinterpret_cast<void**>(Receive()); }
31   void Release() {
32     if (ptr_ != NULL) {
33       ptr_->Release();
34       ptr_ = NULL;
35     }
36   }
39 // This class is used to interact directly with DeckLink SDK for video capture.
40 // Implements the reference counted interface IUnknown. Has a weak reference to
41 // VideoCaptureDeviceDeckLinkMac for sending captured frames, error messages and
42 // logs.
43 class DeckLinkCaptureDelegate
44     : public IDeckLinkInputCallback,
45       public base::RefCountedThreadSafe<DeckLinkCaptureDelegate> {
46  public:
47   DeckLinkCaptureDelegate(const media::VideoCaptureDevice::Name& device_name,
48                           media::VideoCaptureDeviceDeckLinkMac* frame_receiver);
50   void AllocateAndStart(const media::VideoCaptureParams& params);
51   void StopAndDeAllocate();
53   // Remove the VideoCaptureDeviceDeckLinkMac's weak reference.
54   void ResetVideoCaptureDeviceReference();
56  private:
57   // IDeckLinkInputCallback interface implementation.
58   HRESULT VideoInputFormatChanged(
59       BMDVideoInputFormatChangedEvents notification_events,
60       IDeckLinkDisplayMode* new_display_mode,
61       BMDDetectedVideoInputFormatFlags detected_signal_flags) override;
62   HRESULT VideoInputFrameArrived(
63       IDeckLinkVideoInputFrame* video_frame,
64       IDeckLinkAudioInputPacket* audio_packet) override;
66   // IUnknown interface implementation.
67   HRESULT QueryInterface(REFIID iid, void** ppv) override;
68   ULONG AddRef() override;
69   ULONG Release() override;
71   // Forwarder to VideoCaptureDeviceDeckLinkMac::SendErrorString().
72   void SendErrorString(const std::string& reason);
74   // Forwarder to VideoCaptureDeviceDeckLinkMac::SendLogString().
75   void SendLogString(const std::string& message);
77   const media::VideoCaptureDevice::Name device_name_;
79   // Protects concurrent setting and using of |frame_receiver_|.
80   base::Lock lock_;
81   // Weak reference to the captured frames client, used also for error messages
82   // and logging. Initialized on construction and used until cleared by calling
83   // ResetVideoCaptureDeviceReference().
84   media::VideoCaptureDeviceDeckLinkMac* frame_receiver_;
86   // This is used to control the video capturing device input interface.
87   ScopedDeckLinkPtr<IDeckLinkInput> decklink_input_;
88   // |decklink_| represents a physical device attached to the host.
89   ScopedDeckLinkPtr<IDeckLink> decklink_;
91   // Checks for Device (a.k.a. Audio) thread.
92   base::ThreadChecker thread_checker_;
94   friend class scoped_refptr<DeckLinkCaptureDelegate>;
95   friend class base::RefCountedThreadSafe<DeckLinkCaptureDelegate>;
97   ~DeckLinkCaptureDelegate() override;
99   DISALLOW_COPY_AND_ASSIGN(DeckLinkCaptureDelegate);
102 static float GetDisplayModeFrameRate(
103     const ScopedDeckLinkPtr<IDeckLinkDisplayMode>& display_mode) {
104   BMDTimeValue time_value, time_scale;
105   float display_mode_frame_rate = 0.0f;
106   if (display_mode->GetFrameRate(&time_value, &time_scale) == S_OK &&
107       time_value > 0) {
108     display_mode_frame_rate = static_cast<float>(time_scale) / time_value;
109   }
110   // Interlaced formats are going to be marked as double the frame rate,
111   // which follows the general naming convention.
112   if (display_mode->GetFieldDominance() == bmdLowerFieldFirst ||
113       display_mode->GetFieldDominance() == bmdUpperFieldFirst) {
114     display_mode_frame_rate *= 2.0f;
115   }
116   return display_mode_frame_rate;
119 DeckLinkCaptureDelegate::DeckLinkCaptureDelegate(
120     const media::VideoCaptureDevice::Name& device_name,
121     media::VideoCaptureDeviceDeckLinkMac* frame_receiver)
122     : device_name_(device_name), frame_receiver_(frame_receiver) {
125 DeckLinkCaptureDelegate::~DeckLinkCaptureDelegate() {
128 void DeckLinkCaptureDelegate::AllocateAndStart(
129     const media::VideoCaptureParams& params) {
130   DCHECK(thread_checker_.CalledOnValidThread());
131   scoped_refptr<IDeckLinkIterator> decklink_iter(
132       CreateDeckLinkIteratorInstance());
133   DLOG_IF(ERROR, !decklink_iter.get()) << "Error creating DeckLink iterator";
134   if (!decklink_iter.get())
135     return;
137   ScopedDeckLinkPtr<IDeckLink> decklink_local;
138   while (decklink_iter->Next(decklink_local.Receive()) == S_OK) {
139     CFStringRef device_model_name = NULL;
140     if ((decklink_local->GetModelName(&device_model_name) == S_OK) ||
141         (device_name_.id() == base::SysCFStringRefToUTF8(device_model_name))) {
142       break;
143     }
144   }
145   if (!decklink_local.get()) {
146     SendErrorString("Device id not found in the system");
147     return;
148   }
150   ScopedDeckLinkPtr<IDeckLinkInput> decklink_input_local;
151   if (decklink_local->QueryInterface(
152           IID_IDeckLinkInput, decklink_input_local.ReceiveVoid()) != S_OK) {
153     SendErrorString("Error querying input interface.");
154     return;
155   }
157   ScopedDeckLinkPtr<IDeckLinkDisplayModeIterator> display_mode_iter;
158   if (decklink_input_local->GetDisplayModeIterator(
159           display_mode_iter.Receive()) != S_OK) {
160     SendErrorString("Error creating Display Mode Iterator");
161     return;
162   }
164   ScopedDeckLinkPtr<IDeckLinkDisplayMode> chosen_display_mode;
165   ScopedDeckLinkPtr<IDeckLinkDisplayMode> display_mode;
166   float min_diff = FLT_MAX;
167   while (display_mode_iter->Next(display_mode.Receive()) == S_OK) {
168     const float diff = labs(display_mode->GetWidth() -
169                             params.requested_format.frame_size.width()) +
170                        labs(params.requested_format.frame_size.height() -
171                             display_mode->GetHeight()) +
172                        fabs(params.requested_format.frame_rate -
173                             GetDisplayModeFrameRate(display_mode));
174     if (diff < min_diff) {
175       chosen_display_mode = display_mode;
176       min_diff = diff;
177     }
178     display_mode.Release();
179   }
180   if (!chosen_display_mode.get()) {
181     SendErrorString("Could not find a display mode");
182     return;
183   }
184 #if !defined(NDEBUG)
185   DVLOG(1) << "Requested format: "
186            << media::VideoCaptureFormat::ToString(params.requested_format);
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
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   }
201   decklink_input_local->SetCallback(this);
202   if (decklink_input_local->StartStreams() != S_OK)
203     SendErrorString("Could not start capturing");
205   decklink_.swap(decklink_local);
206   decklink_input_.swap(decklink_input_local);
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();
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;
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));
237   media::VideoCapturePixelFormat pixel_format =
238       media::VIDEO_CAPTURE_PIXEL_FORMAT_UNKNOWN;
239   switch (video_frame->GetPixelFormat()) {
240     case bmdFormat8BitYUV:  // A.k.a. '2vuy';
241       pixel_format = media::VIDEO_CAPTURE_PIXEL_FORMAT_UYVY;
242       break;
243     case bmdFormat8BitARGB:
244       pixel_format = media::VIDEO_CAPTURE_PIXEL_FORMAT_ARGB;
245       break;
246     default:
247       SendErrorString("Unsupported pixel format");
248       break;
249   }
251   const media::VideoCaptureFormat capture_format(
252       gfx::Size(video_frame->GetWidth(), video_frame->GetHeight()),
253       0.0f,  // Frame rate is not needed for captured data callback.
254       pixel_format);
255   base::AutoLock lock(lock_);
256   if (frame_receiver_) {
257     frame_receiver_->OnIncomingCapturedData(
258         video_data, video_frame->GetRowBytes() * video_frame->GetHeight(),
259         capture_format,
260         0,  // Rotation.
261         base::TimeTicks::Now());
262   }
263   return S_OK;
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;
278 ULONG DeckLinkCaptureDelegate::AddRef() {
279   DCHECK(thread_checker_.CalledOnValidThread());
280   base::RefCountedThreadSafe<DeckLinkCaptureDelegate>::AddRef();
281   return 1;
284 ULONG DeckLinkCaptureDelegate::Release() {
285   DCHECK(thread_checker_.CalledOnValidThread());
286   bool ret_value = !HasOneRef();
287   base::RefCountedThreadSafe<DeckLinkCaptureDelegate>::Release();
288   return ret_value;
291 void DeckLinkCaptureDelegate::SendErrorString(const std::string& reason) {
292   base::AutoLock lock(lock_);
293   if (frame_receiver_)
294     frame_receiver_->SendErrorString(reason);
297 void DeckLinkCaptureDelegate::SendLogString(const std::string& message) {
298   base::AutoLock lock(lock_);
299   if (frame_receiver_)
300     frame_receiver_->SendLogString(message);
303 void DeckLinkCaptureDelegate::ResetVideoCaptureDeviceReference() {
304   DCHECK(thread_checker_.CalledOnValidThread());
305   base::AutoLock lock(lock_);
306   frame_receiver_ = NULL;
309 }  // namespace
311 namespace media {
313 static std::string JoinDeviceNameAndFormat(CFStringRef name,
314                                            CFStringRef format) {
315   return base::SysCFStringRefToUTF8(name) + " - " +
316          base::SysCFStringRefToUTF8(format);
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;
330   ScopedDeckLinkPtr<IDeckLink> decklink;
331   while (decklink_iter->Next(decklink.Receive()) == S_OK) {
332     ScopedDeckLinkPtr<IDeckLink> decklink_local;
333     decklink_local.swap(decklink);
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);
344     if (!device_model_name && !device_display_name)
345       continue;
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     }
354     ScopedDeckLinkPtr<IDeckLinkDisplayModeIterator> display_mode_iter;
355     if (decklink_input->GetDisplayModeIterator(display_mode_iter.Receive()) !=
356         S_OK) {
357       continue;
358     }
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   }
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;
387   ScopedDeckLinkPtr<IDeckLink> decklink;
388   while (decklink_iter->Next(decklink.Receive()) == S_OK) {
389     ScopedDeckLinkPtr<IDeckLink> decklink_local;
390     decklink_local.swap(decklink);
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     }
399     ScopedDeckLinkPtr<IDeckLinkDisplayModeIterator> display_mode_iter;
400     if (decklink_input->GetDisplayModeIterator(display_mode_iter.Receive()) !=
401         S_OK) {
402       continue;
403     }
405     CFStringRef device_model_name = NULL;
406     if (decklink_local->GetModelName(&device_model_name) != S_OK)
407       continue;
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 &&
413           device.id() !=
414               JoinDeviceNameAndFormat(device_model_name, format_name)) {
415         display_mode.Release();
416         continue;
417       }
419       // IDeckLinkDisplayMode does not have information on pixel format, this
420       // is only available on capture.
421       const media::VideoCaptureFormat format(
422           gfx::Size(display_mode->GetWidth(), display_mode->GetHeight()),
423           GetDisplayModeFrameRate(display_mode),
424           VIDEO_CAPTURE_PIXEL_FORMAT_UNKNOWN);
425       supported_formats->push_back(format);
426       DVLOG(2) << device.name() << " " << VideoCaptureFormat::ToString(format);
427       display_mode.Release();
428     }
429     return;
430   }
433 VideoCaptureDeviceDeckLinkMac::VideoCaptureDeviceDeckLinkMac(
434     const Name& device_name)
435     : decklink_capture_delegate_(
436           new DeckLinkCaptureDelegate(device_name, this)) {
439 VideoCaptureDeviceDeckLinkMac::~VideoCaptureDeviceDeckLinkMac() {
440   decklink_capture_delegate_->ResetVideoCaptureDeviceReference();
443 void VideoCaptureDeviceDeckLinkMac::OnIncomingCapturedData(
444     const uint8* data,
445     size_t length,
446     const VideoCaptureFormat& frame_format,
447     int rotation,  // Clockwise.
448     base::TimeTicks timestamp) {
449   base::AutoLock lock(lock_);
450   if (client_) {
451     client_->OnIncomingCapturedData(data, length, frame_format, rotation,
452                                     timestamp);
453   }
456 void VideoCaptureDeviceDeckLinkMac::SendErrorString(const std::string& reason) {
457   DCHECK(thread_checker_.CalledOnValidThread());
458   base::AutoLock lock(lock_);
459   if (client_)
460     client_->OnError(reason);
463 void VideoCaptureDeviceDeckLinkMac::SendLogString(const std::string& message) {
464   DCHECK(thread_checker_.CalledOnValidThread());
465   base::AutoLock lock(lock_);
466   if (client_)
467     client_->OnLog(message);
470 void VideoCaptureDeviceDeckLinkMac::AllocateAndStart(
471     const VideoCaptureParams& params,
472     scoped_ptr<VideoCaptureDevice::Client> client) {
473   DCHECK(thread_checker_.CalledOnValidThread());
474   client_ = client.Pass();
475   if (decklink_capture_delegate_.get())
476     decklink_capture_delegate_->AllocateAndStart(params);
479 void VideoCaptureDeviceDeckLinkMac::StopAndDeAllocate() {
480   if (decklink_capture_delegate_.get())
481     decklink_capture_delegate_->StopAndDeAllocate();
484 }  // namespace media