OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "media/video/capture/mac/video_capture_device_decklink_mac.h" | 5 #include "media/video/capture/mac/video_capture_device_decklink_mac.h" |
6 | 6 |
7 #include "base/logging.h" | 7 #include "base/logging.h" |
8 #include "base/memory/ref_counted.h" | 8 #include "base/memory/ref_counted.h" |
| 9 #include "base/synchronization/lock.h" |
9 #include "base/strings/sys_string_conversions.h" | 10 #include "base/strings/sys_string_conversions.h" |
10 #include "third_party/decklink/mac/include/DeckLinkAPI.h" | 11 #include "third_party/decklink/mac/include/DeckLinkAPI.h" |
11 | 12 |
12 namespace { | 13 namespace { |
13 | 14 |
14 // DeckLink SDK uses ScopedComPtr-style APIs. Chrome ScopedComPtr is only | 15 // DeckLink SDK uses ScopedComPtr-style APIs. Chrome ScopedComPtr is only |
15 // available for Windows builds. This is a verbatim knock-off of the needed | 16 // available for Windows builds. This is a verbatim knock-off of the needed |
16 // parts of base::win::ScopedComPtr<> for ref counting. | 17 // parts of base::win::ScopedComPtr<> for ref counting. |
17 template <class T> | 18 template <class T> |
18 class ScopedDeckLinkPtr : public scoped_refptr<T> { | 19 class ScopedDeckLinkPtr : public scoped_refptr<T> { |
19 public: | 20 private: |
20 using scoped_refptr<T>::ptr_; | 21 using scoped_refptr<T>::ptr_; |
21 | 22 |
| 23 public: |
22 T** Receive() { | 24 T** Receive() { |
23 DCHECK(!ptr_) << "Object leak. Pointer must be NULL"; | 25 DCHECK(!ptr_) << "Object leak. Pointer must be NULL"; |
24 return &ptr_; | 26 return &ptr_; |
25 } | 27 } |
26 | 28 |
27 void** ReceiveVoid() { | 29 void** ReceiveVoid() { |
28 return reinterpret_cast<void**>(Receive()); | 30 return reinterpret_cast<void**>(Receive()); |
29 } | 31 } |
30 | 32 |
31 void Release() { | 33 void Release() { |
32 if (ptr_ != NULL) { | 34 if (ptr_ != NULL) { |
33 ptr_->Release(); | 35 ptr_->Release(); |
34 ptr_ = NULL; | 36 ptr_ = NULL; |
35 } | 37 } |
36 } | 38 } |
37 }; | 39 }; |
38 | 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 virtual HRESULT VideoInputFormatChanged( |
| 61 BMDVideoInputFormatChangedEvents notification_events, |
| 62 IDeckLinkDisplayMode *new_display_mode, |
| 63 BMDDetectedVideoInputFormatFlags detected_signal_flags) OVERRIDE; |
| 64 virtual HRESULT VideoInputFrameArrived( |
| 65 IDeckLinkVideoInputFrame* video_frame, |
| 66 IDeckLinkAudioInputPacket* audio_packet) OVERRIDE; |
| 67 |
| 68 // IUnknown interface implementation. |
| 69 virtual HRESULT QueryInterface(REFIID iid, void** ppv) OVERRIDE; |
| 70 virtual ULONG AddRef() OVERRIDE; |
| 71 virtual 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 virtual ~DeckLinkCaptureDelegate(); |
| 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 |
39 } // namespace | 309 } // namespace |
40 | 310 |
41 namespace media { | 311 namespace media { |
42 | 312 |
43 std::string JoinDeviceNameAndFormat(CFStringRef name, CFStringRef format) { | 313 static std::string JoinDeviceNameAndFormat(CFStringRef name, |
| 314 CFStringRef format) { |
44 return base::SysCFStringRefToUTF8(name) + " - " + | 315 return base::SysCFStringRefToUTF8(name) + " - " + |
45 base::SysCFStringRefToUTF8(format); | 316 base::SysCFStringRefToUTF8(format); |
46 } | 317 } |
47 | 318 |
48 //static | 319 //static |
49 void VideoCaptureDeviceDeckLinkMac::EnumerateDevices( | 320 void VideoCaptureDeviceDeckLinkMac::EnumerateDevices( |
50 VideoCaptureDevice::Names* device_names) { | 321 VideoCaptureDevice::Names* device_names) { |
51 scoped_refptr<IDeckLinkIterator> decklink_iter( | 322 scoped_refptr<IDeckLinkIterator> decklink_iter( |
52 CreateDeckLinkIteratorInstance()); | 323 CreateDeckLinkIteratorInstance()); |
53 // At this point, not being able to create a DeckLink iterator means that | 324 // At this point, not being able to create a DeckLink iterator means that |
54 // there are no Blackmagic devices in the system but this isn't an error. | 325 // there are no Blackmagic DeckLink devices in the system, don't print error. |
55 DVLOG_IF(1, !decklink_iter.get()) << "Could not create DeckLink iterator"; | 326 DVLOG_IF(1, !decklink_iter.get()) << "Could not create DeckLink iterator"; |
56 if (!decklink_iter.get()) | 327 if (!decklink_iter.get()) |
57 return; | 328 return; |
58 | 329 |
59 ScopedDeckLinkPtr<IDeckLink> decklink; | 330 ScopedDeckLinkPtr<IDeckLink> decklink; |
60 while (decklink_iter->Next(decklink.Receive()) == S_OK) { | 331 while (decklink_iter->Next(decklink.Receive()) == S_OK) { |
61 ScopedDeckLinkPtr<IDeckLink> decklink_local; | 332 ScopedDeckLinkPtr<IDeckLink> decklink_local; |
62 decklink_local.swap(decklink); | 333 decklink_local.swap(decklink); |
63 | 334 |
64 CFStringRef device_model_name = NULL; | 335 CFStringRef device_model_name = NULL; |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
139 while (display_mode_iter->Next(display_mode.Receive()) == S_OK) { | 410 while (display_mode_iter->Next(display_mode.Receive()) == S_OK) { |
140 CFStringRef format_name = NULL; | 411 CFStringRef format_name = NULL; |
141 if (display_mode->GetName(&format_name) == S_OK && device.id() != | 412 if (display_mode->GetName(&format_name) == S_OK && device.id() != |
142 JoinDeviceNameAndFormat(device_model_name, format_name)) { | 413 JoinDeviceNameAndFormat(device_model_name, format_name)) { |
143 display_mode.Release(); | 414 display_mode.Release(); |
144 continue; | 415 continue; |
145 } | 416 } |
146 | 417 |
147 // IDeckLinkDisplayMode does not have information on pixel format, this | 418 // IDeckLinkDisplayMode does not have information on pixel format, this |
148 // is only available on capture. | 419 // is only available on capture. |
149 media::VideoPixelFormat pixel_format = media::PIXEL_FORMAT_UNKNOWN; | 420 const media::VideoCaptureFormat format( |
150 BMDTimeValue time_value, time_scale; | |
151 float frame_rate = 0.0f; | |
152 if (display_mode->GetFrameRate(&time_value, &time_scale) == S_OK && | |
153 time_value > 0) { | |
154 frame_rate = static_cast<float>(time_scale) / time_value; | |
155 } | |
156 media::VideoCaptureFormat format( | |
157 gfx::Size(display_mode->GetWidth(), display_mode->GetHeight()), | 421 gfx::Size(display_mode->GetWidth(), display_mode->GetHeight()), |
158 frame_rate, | 422 GetDisplayModeFrameRate(display_mode), |
159 pixel_format); | 423 PIXEL_FORMAT_UNKNOWN); |
160 supported_formats->push_back(format); | 424 supported_formats->push_back(format); |
161 DVLOG(2) << device.name() << " " << format.ToString(); | 425 DVLOG(2) << device.name() << " " << format.ToString(); |
162 display_mode.Release(); | 426 display_mode.Release(); |
163 } | 427 } |
164 return; | 428 return; |
165 } | 429 } |
166 } | 430 } |
167 | 431 |
168 VideoCaptureDeviceDeckLinkMac::VideoCaptureDeviceDeckLinkMac( | 432 VideoCaptureDeviceDeckLinkMac::VideoCaptureDeviceDeckLinkMac( |
169 const Name& device_name) {} | 433 const Name& device_name) |
| 434 : decklink_capture_delegate_( |
| 435 new DeckLinkCaptureDelegate(device_name, this)) { |
| 436 } |
170 | 437 |
171 VideoCaptureDeviceDeckLinkMac::~VideoCaptureDeviceDeckLinkMac() {} | 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 } |
172 | 468 |
173 void VideoCaptureDeviceDeckLinkMac::AllocateAndStart( | 469 void VideoCaptureDeviceDeckLinkMac::AllocateAndStart( |
174 const VideoCaptureParams& params, | 470 const VideoCaptureParams& params, |
175 scoped_ptr<VideoCaptureDevice::Client> client) { | 471 scoped_ptr<VideoCaptureDevice::Client> client) { |
176 NOTIMPLEMENTED(); | 472 DCHECK(thread_checker_.CalledOnValidThread()); |
| 473 client_ = client.Pass(); |
| 474 if (decklink_capture_delegate_.get()) |
| 475 decklink_capture_delegate_->AllocateAndStart(params); |
177 } | 476 } |
178 | 477 |
179 void VideoCaptureDeviceDeckLinkMac::StopAndDeAllocate() { | 478 void VideoCaptureDeviceDeckLinkMac::StopAndDeAllocate() { |
180 NOTIMPLEMENTED(); | 479 if (decklink_capture_delegate_.get()) |
| 480 decklink_capture_delegate_->StopAndDeAllocate(); |
181 } | 481 } |
182 | 482 |
183 } // namespace media | 483 } // namespace media |
OLD | NEW |