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"
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.
19 class ScopedDeckLinkPtr : public scoped_refptr<T> {
21 using scoped_refptr<T>::ptr_;
25 DCHECK(!ptr_) << "Object leak. Pointer must be NULL";
29 void** ReceiveVoid() { return reinterpret_cast<void**>(Receive()); }
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
43 class DeckLinkCaptureDelegate
44 : public IDeckLinkInputCallback,
45 public base::RefCountedThreadSafe<DeckLinkCaptureDelegate> {
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();
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_|.
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 &&
108 display_mode_frame_rate = static_cast<float>(time_scale) / time_value;
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;
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())
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))) {
145 if (!decklink_local.get()) {
146 SendErrorString("Device id not found in the system");
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.");
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");
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;
178 display_mode.Release();
180 if (!chosen_display_mode.get()) {
181 SendErrorString("Could not find a display mode");
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);
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.");
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())
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();
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());
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;
243 case bmdFormat8BitARGB:
244 pixel_format = media::VIDEO_CAPTURE_PIXEL_FORMAT_ARGB;
247 SendErrorString("Unsupported pixel format");
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.
255 base::AutoLock lock(lock_);
256 if (frame_receiver_) {
257 frame_receiver_->OnIncomingCapturedData(
258 video_data, video_frame->GetRowBytes() * video_frame->GetHeight(),
261 base::TimeTicks::Now());
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);
275 return E_NOINTERFACE;
278 ULONG DeckLinkCaptureDelegate::AddRef() {
279 DCHECK(thread_checker_.CalledOnValidThread());
280 base::RefCountedThreadSafe<DeckLinkCaptureDelegate>::AddRef();
284 ULONG DeckLinkCaptureDelegate::Release() {
285 DCHECK(thread_checker_.CalledOnValidThread());
286 bool ret_value = !HasOneRef();
287 base::RefCountedThreadSafe<DeckLinkCaptureDelegate>::Release();
291 void DeckLinkCaptureDelegate::SendErrorString(const std::string& reason) {
292 base::AutoLock lock(lock_);
294 frame_receiver_->SendErrorString(reason);
297 void DeckLinkCaptureDelegate::SendLogString(const std::string& message) {
298 base::AutoLock lock(lock_);
300 frame_receiver_->SendLogString(message);
303 void DeckLinkCaptureDelegate::ResetVideoCaptureDeviceReference() {
304 DCHECK(thread_checker_.CalledOnValidThread());
305 base::AutoLock lock(lock_);
306 frame_receiver_ = NULL;
313 static std::string JoinDeviceNameAndFormat(CFStringRef name,
314 CFStringRef format) {
315 return base::SysCFStringRefToUTF8(name) + " - " +
316 base::SysCFStringRefToUTF8(format);
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())
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)
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.";
354 ScopedDeckLinkPtr<IDeckLinkDisplayModeIterator> display_mode_iter;
355 if (decklink_input->GetDisplayModeIterator(display_mode_iter.Receive()) !=
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();
372 display_mode.Release();
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())
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.";
399 ScopedDeckLinkPtr<IDeckLinkDisplayModeIterator> display_mode_iter;
400 if (decklink_input->GetDisplayModeIterator(display_mode_iter.Receive()) !=
405 CFStringRef device_model_name = NULL;
406 if (decklink_local->GetModelName(&device_model_name) != S_OK)
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 &&
414 JoinDeviceNameAndFormat(device_model_name, format_name)) {
415 display_mode.Release();
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();
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(
446 const VideoCaptureFormat& frame_format,
447 int rotation, // Clockwise.
448 base::TimeTicks timestamp) {
449 base::AutoLock lock(lock_);
451 client_->OnIncomingCapturedData(data, length, frame_format, rotation,
456 void VideoCaptureDeviceDeckLinkMac::SendErrorString(const std::string& reason) {
457 DCHECK(thread_checker_.CalledOnValidThread());
458 base::AutoLock lock(lock_);
460 client_->OnError(reason);
463 void VideoCaptureDeviceDeckLinkMac::SendLogString(const std::string& message) {
464 DCHECK(thread_checker_.CalledOnValidThread());
465 base::AutoLock lock(lock_);
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();