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/video/capture/mac/video_capture_device_decklink_mac.h"
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"
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() {
30 return reinterpret_cast<void**>(Receive());
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
45 class DeckLinkCaptureDelegate :
46 public IDeckLinkInputCallback,
47 public base::RefCountedThreadSafe<DeckLinkCaptureDelegate> {
49 DeckLinkCaptureDelegate(const media::VideoCaptureDevice::Name& device_name,
50 media::VideoCaptureDeviceDeckLinkMac* frame_receiver);
52 void AllocateAndStart(const media::VideoCaptureParams& params);
53 void StopAndDeAllocate();
55 // Remove the VideoCaptureDeviceDeckLinkMac's weak reference.
56 void ResetVideoCaptureDeviceReference();
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;
68 // IUnknown interface implementation.
69 HRESULT QueryInterface(REFIID iid, void** ppv) override;
70 ULONG AddRef() override;
71 ULONG Release() override;
73 // Forwarder to VideoCaptureDeviceDeckLinkMac::SendErrorString().
74 void SendErrorString(const std::string& reason);
76 // Forwarder to VideoCaptureDeviceDeckLinkMac::SendLogString().
77 void SendLogString(const std::string& message);
79 const media::VideoCaptureDevice::Name device_name_;
81 // Protects concurrent setting and using of |frame_receiver_|.
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_;
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_;
93 // Checks for Device (a.k.a. Audio) thread.
94 base::ThreadChecker thread_checker_;
96 friend class scoped_refptr<DeckLinkCaptureDelegate>;
97 friend class base::RefCountedThreadSafe<DeckLinkCaptureDelegate>;
99 ~DeckLinkCaptureDelegate() override;
101 DISALLOW_COPY_AND_ASSIGN(DeckLinkCaptureDelegate);
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 &&
110 display_mode_frame_rate = static_cast<float>(time_scale) / time_value;
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;
118 return display_mode_frame_rate;
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) {
128 DeckLinkCaptureDelegate::~DeckLinkCaptureDelegate() {}
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())
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))) {
147 if (!decklink_local.get()) {
148 SendErrorString("Device id not found in the system");
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.");
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");
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;
179 display_mode.Release();
181 if (!chosen_display_mode.get()) {
182 SendErrorString("Could not find a display mode");
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);
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::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;
242 case bmdFormat8BitARGB:
243 pixel_format = media::PIXEL_FORMAT_ARGB;
246 SendErrorString("Unsupported pixel format");
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.
254 base::AutoLock lock(lock_);
255 if (frame_receiver_) {
256 frame_receiver_->OnIncomingCapturedData(
258 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 && device.id() !=
413 JoinDeviceNameAndFormat(device_model_name, format_name)) {
414 display_mode.Release();
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();
432 VideoCaptureDeviceDeckLinkMac::VideoCaptureDeviceDeckLinkMac(
433 const Name& device_name)
434 : decklink_capture_delegate_(
435 new DeckLinkCaptureDelegate(device_name, this)) {
438 VideoCaptureDeviceDeckLinkMac::~VideoCaptureDeviceDeckLinkMac() {
439 decklink_capture_delegate_->ResetVideoCaptureDeviceReference();
442 void VideoCaptureDeviceDeckLinkMac::OnIncomingCapturedData(
445 const VideoCaptureFormat& frame_format,
446 int rotation, // Clockwise.
447 base::TimeTicks timestamp) {
448 base::AutoLock lock(lock_);
450 client_->OnIncomingCapturedData(data, length, frame_format, rotation,
455 void VideoCaptureDeviceDeckLinkMac::SendErrorString(const std::string& reason) {
456 DCHECK(thread_checker_.CalledOnValidThread());
457 base::AutoLock lock(lock_);
459 client_->OnError(reason);
462 void VideoCaptureDeviceDeckLinkMac::SendLogString(const std::string& message) {
463 DCHECK(thread_checker_.CalledOnValidThread());
464 base::AutoLock lock(lock_);
466 client_->OnLog(message);
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);
478 void VideoCaptureDeviceDeckLinkMac::StopAndDeAllocate() {
479 if (decklink_capture_delegate_.get())
480 decklink_capture_delegate_->StopAndDeAllocate();