Chromium Code Reviews| Index: media/video/capture/mac/video_capture_device_decklink_mac.mm |
| diff --git a/media/video/capture/mac/video_capture_device_decklink_mac.mm b/media/video/capture/mac/video_capture_device_decklink_mac.mm |
| index 57c2c020ef0c0646240b0a3009ddf22de58b31a9..213cfb86c1f116cd500cc0eb7e47938add132b10 100644 |
| --- a/media/video/capture/mac/video_capture_device_decklink_mac.mm |
| +++ b/media/video/capture/mac/video_capture_device_decklink_mac.mm |
| @@ -6,6 +6,7 @@ |
| #include "base/logging.h" |
| #include "base/memory/ref_counted.h" |
| +#include "base/synchronization/lock.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "third_party/decklink/mac/include/DeckLinkAPI.h" |
| @@ -16,9 +17,10 @@ namespace { |
| // parts of base::win::ScopedComPtr<> for ref counting. |
| template <class T> |
| class ScopedDeckLinkPtr : public scoped_refptr<T> { |
| - public: |
| + private: |
| using scoped_refptr<T>::ptr_; |
| + public: |
| T** Receive() { |
| DCHECK(!ptr_) << "Object leak. Pointer must be NULL"; |
| return &ptr_; |
| @@ -36,11 +38,270 @@ class ScopedDeckLinkPtr : public scoped_refptr<T> { |
| } |
| }; |
| +// This class is used to interact directly with DeckLink SDK for video capture. |
| +// Implements the reference counted interface IUnknown. Has a weak reference to |
| +// VideoCaptureDeviceDeckLinkMac for sending captured frames, error messages and |
| +// logs. |
| +class DeckLinkCaptureDelegate : public IDeckLinkInputCallback { |
| + public: |
| + DeckLinkCaptureDelegate(const media::VideoCaptureDevice::Name& device_name, |
| + media::VideoCaptureDeviceDeckLinkMac* frame_receiver); |
| + virtual ~DeckLinkCaptureDelegate(); |
| + |
| + void AllocateAndStart(const media::VideoCaptureParams& params); |
| + void StopAndDeAllocate(); |
| + |
| + // Remove the VideoCaptureDeviceDeckLinkMac's weak reference. |
| + void ResetVideoCaptureDeviceReference(); |
| + |
| + private: |
| + // IDeckLinkInputCallback interface implementation. |
| + virtual HRESULT VideoInputFormatChanged( |
| + BMDVideoInputFormatChangedEvents notification_events, |
| + IDeckLinkDisplayMode *new_display_mode, |
| + BMDDetectedVideoInputFormatFlags detected_signal_flags) OVERRIDE; |
| + virtual HRESULT VideoInputFrameArrived( |
| + IDeckLinkVideoInputFrame* video_frame, |
| + IDeckLinkAudioInputPacket* audio_packet) OVERRIDE; |
| + |
| + // IUnknown interface implementation. |
| + virtual HRESULT QueryInterface(REFIID iid, void** ppv) OVERRIDE; |
| + virtual ULONG AddRef() OVERRIDE; |
| + virtual ULONG Release() OVERRIDE; |
| + |
| + // Forwarder to VideoCaptureDeviceDeckLinkMac::SendErrorString(). |
| + void SendErrorString(const std::string& reason); |
| + |
| + // Forwarder to VideoCaptureDeviceDeckLinkMac::SendLogString(). |
| + void SendLogString(const std::string& message); |
| + |
| + const media::VideoCaptureDevice::Name device_name_; |
| + |
| + // Protects concurrent setting and using of |frame_receiver_|. |
| + base::Lock lock_; |
| + // Weak reference to the captured frames client, used also for error messages |
| + // and logging. Initialized on construction and used until cleared by calling |
| + // ResetVideoCaptureDeviceReference(). |
| + media::VideoCaptureDeviceDeckLinkMac* frame_receiver_; |
| + |
| + // This is used to control the video capturing device input interface. |
| + ScopedDeckLinkPtr<IDeckLinkInput> decklink_input_; |
| + // |decklink_| represents a physical device attached to the host. |
| + ScopedDeckLinkPtr<IDeckLink> decklink_; |
| + // Internal counter for IUnknown interface implementation. |
| + int32_t ref_count_; |
| + |
| + base::ThreadChecker thread_checker_; |
|
Robert Sesek
2014/09/29 21:08:51
Same, what thread does this run on?
mcasas
2014/09/30 14:18:49
Done.
|
| + |
| + friend class scoped_refptr<DeckLinkCaptureDelegate>; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(DeckLinkCaptureDelegate); |
| +}; |
| + |
| +static float GetDisplayModeFrameRate( |
| + const ScopedDeckLinkPtr<IDeckLinkDisplayMode>& display_mode) { |
| + BMDTimeValue time_value, time_scale; |
| + float display_mode_frame_rate = 0.0f; |
| + if (display_mode->GetFrameRate(&time_value, &time_scale) == S_OK && |
| + time_value > 0) { |
| + display_mode_frame_rate = static_cast<float>(time_scale) / time_value; |
| + } |
| + // Interlaced formats are going to be marked as double the frame rate, |
| + // which follows the general naming convention. |
| + if (display_mode->GetFieldDominance() == bmdLowerFieldFirst || |
| + display_mode->GetFieldDominance() == bmdUpperFieldFirst) { |
| + display_mode_frame_rate *= 2.0f; |
| + } |
| + return display_mode_frame_rate; |
| +} |
| + |
| +DeckLinkCaptureDelegate::DeckLinkCaptureDelegate( |
| + const media::VideoCaptureDevice::Name& device_name, |
| + media::VideoCaptureDeviceDeckLinkMac* frame_receiver) |
| + : device_name_(device_name), |
| + frame_receiver_(frame_receiver) { |
| +} |
| + |
| +DeckLinkCaptureDelegate::~DeckLinkCaptureDelegate() {} |
| + |
| +void DeckLinkCaptureDelegate::AllocateAndStart( |
| + const media::VideoCaptureParams& params) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + scoped_refptr<IDeckLinkIterator> decklink_iter( |
| + CreateDeckLinkIteratorInstance()); |
| + DLOG_IF(ERROR, !decklink_iter.get()) << "Error creating DeckLink iterator"; |
| + if (!decklink_iter.get()) |
| + return; |
| + |
| + while (decklink_iter->Next(decklink_.Receive()) == S_OK) { |
| + CFStringRef device_model_name = NULL; |
| + if ((decklink_->GetModelName(&device_model_name) == S_OK) || |
| + (device_name_.id() == base::SysCFStringRefToUTF8(device_model_name))) { |
| + break; |
| + } |
| + decklink_.Release(); |
| + } |
| + if (!decklink_.get()) { |
| + SendErrorString("Device id not found in the system"); |
| + return; |
| + } |
| + |
| + if (decklink_->QueryInterface(IID_IDeckLinkInput, |
| + decklink_input_.ReceiveVoid()) != S_OK) { |
| + SendErrorString("Error querying input interface."); |
| + return; |
| + } |
| + |
| + ScopedDeckLinkPtr<IDeckLinkDisplayModeIterator> display_mode_iter; |
| + if (decklink_input_->GetDisplayModeIterator(display_mode_iter.Receive()) != |
| + S_OK) { |
| + SendErrorString("Error creating Display Mode Iterator"); |
| + return; |
| + } |
| + |
| + ScopedDeckLinkPtr<IDeckLinkDisplayMode> chosen_display_mode; |
| + ScopedDeckLinkPtr<IDeckLinkDisplayMode> display_mode; |
| + float min_diff = FLT_MAX; |
| + while (display_mode_iter->Next(display_mode.Receive()) == S_OK) { |
| + const float diff = labs(display_mode->GetWidth() - |
| + params.requested_format.frame_size.width()) + |
| + labs(params.requested_format.frame_size.height() - |
| + display_mode->GetHeight()) + fabs(params.requested_format.frame_rate - |
| + GetDisplayModeFrameRate(display_mode)); |
| + if (diff < min_diff) { |
| + chosen_display_mode = display_mode; |
| + min_diff = diff; |
| + } |
| + display_mode.Release(); |
| + } |
| + if (!chosen_display_mode.get()) { |
| + SendErrorString("Could not find a display mode"); |
| + return; |
| + } |
| +#if !defined(NDEBUG) |
| + DVLOG(1) << "Requested format: " << params.requested_format.ToString(); |
| + CFStringRef format_name = NULL; |
| + if (chosen_display_mode->GetName(&format_name) == S_OK) |
| + DVLOG(1) << "Chosen format: " << base::SysCFStringRefToUTF8(format_name); |
| +#endif |
| + |
| + // Enable video input. Configure for no input video format change detection, |
| + // this in turn will disable calls to VideoInputFormatChanged(). |
| + if (decklink_input_->EnableVideoInput(chosen_display_mode->GetDisplayMode(), |
| + bmdFormat8BitYUV, bmdVideoInputFlagDefault) != S_OK) { |
| + SendErrorString("Could not select the video format we like."); |
| + return; |
| + } |
| + |
| + decklink_input_->SetCallback(this); |
| + if (decklink_input_->StartStreams() != S_OK) |
| + SendErrorString("Could not start capturing"); |
| +} |
| + |
| +void DeckLinkCaptureDelegate::StopAndDeAllocate() { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + if (!decklink_input_.get()) |
| + return; |
| + if (decklink_input_->StopStreams() != S_OK) |
| + SendLogString("Problem stopping capture."); |
| + decklink_input_->SetCallback(NULL); |
| + decklink_input_->DisableVideoInput(); |
| + ResetVideoCaptureDeviceReference(); |
| +} |
| + |
| +HRESULT DeckLinkCaptureDelegate::VideoInputFormatChanged( |
| + BMDVideoInputFormatChangedEvents notification_events, |
| + IDeckLinkDisplayMode *new_display_mode, |
| + BMDDetectedVideoInputFormatFlags detected_signal_flags) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + return S_OK; |
| +} |
| + |
| +HRESULT DeckLinkCaptureDelegate::VideoInputFrameArrived( |
| + IDeckLinkVideoInputFrame* video_frame, |
| + IDeckLinkAudioInputPacket* /* audio_packet */) { |
| + // Capture frames are manipulated as an IDeckLinkVideoFrame. |
| + uint8* video_data = NULL; |
| + video_frame->GetBytes(reinterpret_cast<void**>(&video_data)); |
| + |
| + media::VideoPixelFormat pixel_format = media::PIXEL_FORMAT_UNKNOWN; |
| + switch (video_frame->GetPixelFormat()) { |
| + case bmdFormat8BitYUV: // A.k.a. '2vuy'; |
| + pixel_format = media::PIXEL_FORMAT_UYVY; |
| + break; |
| + case bmdFormat8BitARGB: |
| + pixel_format = media::PIXEL_FORMAT_ARGB; |
| + break; |
| + default: |
| + SendErrorString("Unsupported pixel format"); |
| + break; |
| + } |
| + |
| + const media::VideoCaptureFormat capture_format( |
| + gfx::Size(video_frame->GetWidth(), video_frame->GetHeight()), |
| + 0.0f, // Frame rate is not needed for captured data callback. |
| + pixel_format); |
| + base::AutoLock lock(lock_); |
| + if (frame_receiver_) { |
| + frame_receiver_->OnIncomingCapturedData( |
| + video_data, |
| + video_frame->GetRowBytes() * video_frame->GetHeight(), |
| + capture_format, |
| + 0, // Rotation. |
| + base::TimeTicks::Now()); |
| + } |
| + return S_OK; |
| +} |
| + |
| +HRESULT DeckLinkCaptureDelegate::QueryInterface(REFIID iid, void** ppv) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + CFUUIDBytes iunknown = CFUUIDGetUUIDBytes(IUnknownUUID); |
| + if (memcmp(&iid, &iunknown, sizeof(REFIID)) == 0 || |
| + memcmp(&iid, &IID_IDeckLinkInputCallback, sizeof(REFIID)) == 0) { |
| + *ppv = static_cast<IDeckLinkInputCallback*>(this); |
| + AddRef(); |
| + return S_OK; |
| + } |
| + return E_NOINTERFACE; |
| +} |
| + |
| +ULONG DeckLinkCaptureDelegate::AddRef() { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + return OSAtomicIncrement32(&ref_count_); |
|
Robert Sesek
2014/09/29 21:08:51
ref_count_ is an int32_t yet it's being returned h
mcasas
2014/09/30 14:18:49
Done.
|
| +} |
| + |
| +ULONG DeckLinkCaptureDelegate::Release() { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + int32_t new_ref_value = OSAtomicDecrement32(&ref_count_); |
| + if (new_ref_value == 0) |
| + delete this; |
| + return new_ref_value; |
| +} |
| + |
| +void DeckLinkCaptureDelegate::SendErrorString(const std::string& reason) { |
| + base::AutoLock lock(lock_); |
| + if (frame_receiver_) |
| + frame_receiver_->SendErrorString(reason); |
| +} |
| + |
| +void DeckLinkCaptureDelegate::SendLogString(const std::string& message) { |
| + base::AutoLock lock(lock_); |
| + if (frame_receiver_) |
| + frame_receiver_->SendLogString(message); |
| +} |
| + |
| +void DeckLinkCaptureDelegate::ResetVideoCaptureDeviceReference() { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + base::AutoLock lock(lock_); |
| + frame_receiver_ = NULL; |
| +} |
| + |
| } // namespace |
| namespace media { |
| -std::string JoinDeviceNameAndFormat(CFStringRef name, CFStringRef format) { |
| +static std::string JoinDeviceNameAndFormat(CFStringRef name, |
| + CFStringRef format) { |
| return base::SysCFStringRefToUTF8(name) + " - " + |
| base::SysCFStringRefToUTF8(format); |
| } |
| @@ -51,7 +312,7 @@ void VideoCaptureDeviceDeckLinkMac::EnumerateDevices( |
| scoped_refptr<IDeckLinkIterator> decklink_iter( |
| CreateDeckLinkIteratorInstance()); |
| // At this point, not being able to create a DeckLink iterator means that |
| - // there are no Blackmagic devices in the system but this isn't an error. |
| + // there are no Blackmagic DeckLink devices in the system, don't print error. |
| DVLOG_IF(1, !decklink_iter.get()) << "Could not create DeckLink iterator"; |
| if (!decklink_iter.get()) |
| return; |
| @@ -146,17 +407,10 @@ void VideoCaptureDeviceDeckLinkMac::EnumerateDeviceCapabilities( |
| // IDeckLinkDisplayMode does not have information on pixel format, this |
| // is only available on capture. |
| - media::VideoPixelFormat pixel_format = media::PIXEL_FORMAT_UNKNOWN; |
| - BMDTimeValue time_value, time_scale; |
| - float frame_rate = 0.0f; |
| - if (display_mode->GetFrameRate(&time_value, &time_scale) == S_OK && |
| - time_value > 0) { |
| - frame_rate = static_cast<float>(time_scale) / time_value; |
| - } |
| - media::VideoCaptureFormat format( |
| + const media::VideoCaptureFormat format( |
| gfx::Size(display_mode->GetWidth(), display_mode->GetHeight()), |
| - frame_rate, |
| - pixel_format); |
| + GetDisplayModeFrameRate(display_mode), |
| + PIXEL_FORMAT_UNKNOWN); |
| supported_formats->push_back(format); |
| DVLOG(2) << device.name() << " " << format.ToString(); |
| display_mode.Release(); |
| @@ -166,18 +420,54 @@ void VideoCaptureDeviceDeckLinkMac::EnumerateDeviceCapabilities( |
| } |
| VideoCaptureDeviceDeckLinkMac::VideoCaptureDeviceDeckLinkMac( |
| - const Name& device_name) {} |
| + const Name& device_name) |
| + : decklink_capture_delegate_( |
| + new DeckLinkCaptureDelegate(device_name, this)) { |
| +} |
| + |
| +VideoCaptureDeviceDeckLinkMac::~VideoCaptureDeviceDeckLinkMac() { |
| + decklink_capture_delegate_->ResetVideoCaptureDeviceReference(); |
| +} |
| + |
| +void VideoCaptureDeviceDeckLinkMac::OnIncomingCapturedData( |
| + const uint8* data, |
| + size_t length, |
| + const VideoCaptureFormat& frame_format, |
| + int rotation, // Clockwise. |
| + base::TimeTicks timestamp) { |
| + base::AutoLock lock(lock_); |
| + if (client_) { |
| + client_->OnIncomingCapturedData(data, length, frame_format, rotation, |
| + timestamp); |
| + } |
| +} |
| -VideoCaptureDeviceDeckLinkMac::~VideoCaptureDeviceDeckLinkMac() {} |
| +void VideoCaptureDeviceDeckLinkMac::SendErrorString(const std::string& reason) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + base::AutoLock lock(lock_); |
| + if (client_) |
| + client_->OnError(reason); |
| +} |
| + |
| +void VideoCaptureDeviceDeckLinkMac::SendLogString(const std::string& message) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + base::AutoLock lock(lock_); |
| + if (client_) |
| + client_->OnLog(message); |
| +} |
| void VideoCaptureDeviceDeckLinkMac::AllocateAndStart( |
| - const VideoCaptureParams& params, |
| - scoped_ptr<VideoCaptureDevice::Client> client) { |
| - NOTIMPLEMENTED(); |
| + const VideoCaptureParams& params, |
| + scoped_ptr<VideoCaptureDevice::Client> client) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + client_ = client.Pass(); |
| + if (decklink_capture_delegate_.get()) |
| + decklink_capture_delegate_->AllocateAndStart(params); |
| } |
| void VideoCaptureDeviceDeckLinkMac::StopAndDeAllocate() { |
| - NOTIMPLEMENTED(); |
| + if (decklink_capture_delegate_.get()) |
| + decklink_capture_delegate_->StopAndDeAllocate(); |
| } |
| } // namespace media |