// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "media/capture/video/mac/video_capture_device_decklink_mac.h" #include #include "base/logging.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/strings/sys_string_conversions.h" #include "base/synchronization/lock.h" #include "media/capture/video/video_capture_device_info.h" #include "third_party/decklink/mac/include/DeckLinkAPI.h" namespace { // DeckLink SDK uses ComPtr-style APIs. Microsoft::WRL::ComPtr<> is only // available for Windows builds. This provides a subset of the methods required // for ref counting. template class ScopedDeckLinkPtr : public scoped_refptr { private: using scoped_refptr::ptr_; public: T** Receive() { DCHECK(!ptr_) << "Object leak. Pointer must be NULL"; return &ptr_; } void** ReceiveVoid() { return reinterpret_cast(Receive()); } void Release() { if (ptr_ != NULL) { ptr_->Release(); ptr_ = NULL; } } }; // 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 base::RefCountedThreadSafe { public: DeckLinkCaptureDelegate( const media::VideoCaptureDeviceDescriptor& device_descriptor, media::VideoCaptureDeviceDeckLinkMac* frame_receiver); void AllocateAndStart(const media::VideoCaptureParams& params); void StopAndDeAllocate(); // Remove the VideoCaptureDeviceDeckLinkMac's weak reference. void ResetVideoCaptureDeviceReference(); private: // IDeckLinkInputCallback interface implementation. HRESULT VideoInputFormatChanged( BMDVideoInputFormatChangedEvents notification_events, IDeckLinkDisplayMode* new_display_mode, BMDDetectedVideoInputFormatFlags detected_signal_flags) override; HRESULT VideoInputFrameArrived( IDeckLinkVideoInputFrame* video_frame, IDeckLinkAudioInputPacket* audio_packet) override; // IUnknown interface implementation. HRESULT QueryInterface(REFIID iid, void** ppv) override; ULONG AddRef() override; ULONG Release() override; // Forwarder to VideoCaptureDeviceDeckLinkMac::SendErrorString(). void SendErrorString(media::VideoCaptureError error, const base::Location& from_here, const std::string& reason); // Forwarder to VideoCaptureDeviceDeckLinkMac::SendLogString(). void SendLogString(const std::string& message); const media::VideoCaptureDeviceDescriptor device_descriptor_; // 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 decklink_input_; // |decklink_| represents a physical device attached to the host. ScopedDeckLinkPtr decklink_; base::TimeTicks first_ref_time_; // Checks for Device (a.k.a. Audio) thread. base::ThreadChecker thread_checker_; friend class scoped_refptr; friend class base::RefCountedThreadSafe; ~DeckLinkCaptureDelegate() override; DISALLOW_COPY_AND_ASSIGN(DeckLinkCaptureDelegate); }; static float GetDisplayModeFrameRate( const ScopedDeckLinkPtr& 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(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::VideoCaptureDeviceDescriptor& device_descriptor, media::VideoCaptureDeviceDeckLinkMac* frame_receiver) : device_descriptor_(device_descriptor), frame_receiver_(frame_receiver) {} DeckLinkCaptureDelegate::~DeckLinkCaptureDelegate() { } void DeckLinkCaptureDelegate::AllocateAndStart( const media::VideoCaptureParams& params) { DCHECK(thread_checker_.CalledOnValidThread()); scoped_refptr decklink_iter( CreateDeckLinkIteratorInstance()); DLOG_IF(ERROR, !decklink_iter.get()) << "Error creating DeckLink iterator"; if (!decklink_iter.get()) return; ScopedDeckLinkPtr decklink_local; while (decklink_iter->Next(decklink_local.Receive()) == S_OK) { CFStringRef device_model_name = NULL; if ((decklink_local->GetModelName(&device_model_name) == S_OK) || (device_descriptor_.device_id == base::SysCFStringRefToUTF8(device_model_name))) { break; } } if (!decklink_local.get()) { SendErrorString( media::VideoCaptureError::kMacDeckLinkDeviceIdNotFoundInTheSystem, FROM_HERE, "Device id not found in the system"); return; } ScopedDeckLinkPtr decklink_input_local; if (decklink_local->QueryInterface( IID_IDeckLinkInput, decklink_input_local.ReceiveVoid()) != S_OK) { SendErrorString( media::VideoCaptureError::kMacDeckLinkErrorQueryingInputInterface, FROM_HERE, "Error querying input interface."); return; } ScopedDeckLinkPtr display_mode_iter; if (decklink_input_local->GetDisplayModeIterator( display_mode_iter.Receive()) != S_OK) { SendErrorString( media::VideoCaptureError::kMacDeckLinkErrorCreatingDisplayModeIterator, FROM_HERE, "Error creating Display Mode Iterator"); return; } ScopedDeckLinkPtr chosen_display_mode; ScopedDeckLinkPtr 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( media::VideoCaptureError::kMacDeckLinkCouldNotFindADisplayMode, FROM_HERE, "Could not find a display mode"); return; } #if !defined(NDEBUG) DVLOG(1) << "Requested format: " << media::VideoCaptureFormat::ToString(params.requested_format); 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_local->EnableVideoInput( chosen_display_mode->GetDisplayMode(), bmdFormat8BitYUV, bmdVideoInputFlagDefault) != S_OK) { SendErrorString(media::VideoCaptureError:: kMacDeckLinkCouldNotSelectTheVideoFormatWeLike, FROM_HERE, "Could not select the video format we like."); return; } decklink_input_local->SetCallback(this); if (decklink_input_local->StartStreams() != S_OK) SendErrorString( media::VideoCaptureError::kMacDeckLinkCouldNotStartCapturing, FROM_HERE, "Could not start capturing"); if (frame_receiver_) frame_receiver_->ReportStarted(); decklink_.swap(decklink_local); decklink_input_.swap(decklink_input_local); } 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(); decklink_input_.Release(); decklink_.Release(); 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_t* video_data = NULL; video_frame->GetBytes(reinterpret_cast(&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( media::VideoCaptureError::kMacDeckLinkUnsupportedPixelFormat, FROM_HERE, "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::TimeTicks now = base::TimeTicks::Now(); if (first_ref_time_.is_null()) first_ref_time_ = now; base::AutoLock lock(lock_); if (frame_receiver_) { const BMDTimeScale micros_time_scale = base::Time::kMicrosecondsPerSecond; BMDTimeValue frame_time; BMDTimeValue frame_duration; base::TimeDelta timestamp; if (SUCCEEDED(video_frame->GetStreamTime(&frame_time, &frame_duration, micros_time_scale))) { timestamp = base::TimeDelta::FromMicroseconds(frame_time); } else { timestamp = now - first_ref_time_; } // TODO(julien.isorce): Build a gfx::ColorSpace from DeckLink API, .i.e // using BMDDisplayModeFlags or BMDDeckLinkFrameMetadataID. See // http://crbug.com/959953. frame_receiver_->OnIncomingCapturedData( video_data, video_frame->GetRowBytes() * video_frame->GetHeight(), capture_format, gfx::ColorSpace(), 0, // Rotation. false, // Vertical flip. now, timestamp); } 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(this); AddRef(); return S_OK; } return E_NOINTERFACE; } ULONG DeckLinkCaptureDelegate::AddRef() { DCHECK(thread_checker_.CalledOnValidThread()); base::RefCountedThreadSafe::AddRef(); return 1; } ULONG DeckLinkCaptureDelegate::Release() { DCHECK(thread_checker_.CalledOnValidThread()); bool ret_value = !HasOneRef(); base::RefCountedThreadSafe::Release(); return ret_value; } void DeckLinkCaptureDelegate::SendErrorString(media::VideoCaptureError error, const base::Location& from_here, const std::string& reason) { base::AutoLock lock(lock_); if (frame_receiver_) frame_receiver_->SendErrorString(error, from_here, 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 { static std::string JoinDeviceNameAndFormat(CFStringRef name, CFStringRef format) { return base::SysCFStringRefToUTF8(name) + " - " + base::SysCFStringRefToUTF8(format); } // static void VideoCaptureDeviceDeckLinkMac::EnumerateDevices( std::vector* devices_info) { scoped_refptr decklink_iter( CreateDeckLinkIteratorInstance()); // At this point, not being able to create a DeckLink iterator means that // 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; ScopedDeckLinkPtr decklink; while (decklink_iter->Next(decklink.Receive()) == S_OK) { ScopedDeckLinkPtr decklink_local; decklink_local.swap(decklink); CFStringRef device_model_name = NULL; HRESULT hr = decklink_local->GetModelName(&device_model_name); DVLOG_IF(1, hr != S_OK) << "Error reading Blackmagic device model name"; CFStringRef device_display_name = NULL; hr = decklink_local->GetDisplayName(&device_display_name); DVLOG_IF(1, hr != S_OK) << "Error reading Blackmagic device display name"; DVLOG_IF(1, hr == S_OK) << "Blackmagic device found with name: " << base::SysCFStringRefToUTF8(device_display_name); if (!device_model_name && !device_display_name) continue; ScopedDeckLinkPtr decklink_input; if (decklink_local->QueryInterface(IID_IDeckLinkInput, decklink_input.ReceiveVoid()) != S_OK) { DLOG(ERROR) << "Error Blackmagic querying input interface."; return; } ScopedDeckLinkPtr display_mode_iter; if (decklink_input->GetDisplayModeIterator(display_mode_iter.Receive()) != S_OK) { continue; } ScopedDeckLinkPtr display_mode; while (display_mode_iter->Next(display_mode.Receive()) == S_OK) { CFStringRef format_name = NULL; if (display_mode->GetName(&format_name) == S_OK) { VideoCaptureDeviceDescriptor descriptor; descriptor.set_display_name( JoinDeviceNameAndFormat(device_display_name, format_name)); descriptor.device_id = JoinDeviceNameAndFormat(device_model_name, format_name); descriptor.capture_api = VideoCaptureApi::MACOSX_DECKLINK; descriptor.transport_type = VideoCaptureTransportType::OTHER_TRANSPORT; descriptor.set_control_support(VideoCaptureControlSupport()); DVLOG(1) << "Blackmagic camera enumerated: " << descriptor.display_name(); devices_info->emplace_back(std::move(descriptor)); // IDeckLinkDisplayMode does not have information on pixel format, this // is only available on capture. const media::VideoCaptureFormat format( gfx::Size(display_mode->GetWidth(), display_mode->GetHeight()), GetDisplayModeFrameRate(display_mode), PIXEL_FORMAT_UNKNOWN); devices_info->back().supported_formats.push_back(format); DVLOG(2) << devices_info->back().descriptor.display_name() << " " << VideoCaptureFormat::ToString(format); } display_mode.Release(); } } } VideoCaptureDeviceDeckLinkMac::VideoCaptureDeviceDeckLinkMac( const VideoCaptureDeviceDescriptor& device_descriptor) : decklink_capture_delegate_( new DeckLinkCaptureDelegate(device_descriptor, this)) {} VideoCaptureDeviceDeckLinkMac::~VideoCaptureDeviceDeckLinkMac() { decklink_capture_delegate_->ResetVideoCaptureDeviceReference(); } void VideoCaptureDeviceDeckLinkMac::OnIncomingCapturedData( const uint8_t* data, size_t length, const VideoCaptureFormat& frame_format, const gfx::ColorSpace& color_space, int rotation, // Clockwise. bool flip_y, base::TimeTicks reference_time, base::TimeDelta timestamp) { base::AutoLock lock(lock_); if (!client_) return; client_->OnIncomingCapturedData(data, length, frame_format, color_space, rotation, flip_y, reference_time, timestamp); } void VideoCaptureDeviceDeckLinkMac::SendErrorString( VideoCaptureError error, const base::Location& from_here, const std::string& reason) { DCHECK(thread_checker_.CalledOnValidThread()); base::AutoLock lock(lock_); if (client_) client_->OnError(error, from_here, reason); } void VideoCaptureDeviceDeckLinkMac::SendLogString(const std::string& message) { DCHECK(thread_checker_.CalledOnValidThread()); base::AutoLock lock(lock_); if (client_) client_->OnLog(message); } void VideoCaptureDeviceDeckLinkMac::ReportStarted() { DCHECK(thread_checker_.CalledOnValidThread()); base::AutoLock lock(lock_); if (client_) client_->OnStarted(); } void VideoCaptureDeviceDeckLinkMac::AllocateAndStart( const VideoCaptureParams& params, std::unique_ptr client) { DCHECK(thread_checker_.CalledOnValidThread()); client_ = std::move(client); if (decklink_capture_delegate_.get()) decklink_capture_delegate_->AllocateAndStart(params); } void VideoCaptureDeviceDeckLinkMac::StopAndDeAllocate() { if (decklink_capture_delegate_.get()) decklink_capture_delegate_->StopAndDeAllocate(); } } // namespace media