tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

video_capture_avfoundation.mm (10579B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "video_capture_avfoundation.h"
      8 
      9 #import "api/video_frame_buffer/RTCNativeI420Buffer+Private.h"
     10 #import "base/RTCI420Buffer.h"
     11 #import "base/RTCVideoFrame.h"
     12 #import "base/RTCVideoFrameBuffer.h"
     13 #import "components/capturer/RTCCameraVideoCapturer.h"
     14 #import "helpers/NSString+StdString.h"
     15 
     16 #include "CallbackThreadRegistry.h"
     17 #include "api/scoped_refptr.h"
     18 #include "api/video/video_rotation.h"
     19 #include "device_info_avfoundation.h"
     20 #include "modules/video_capture/video_capture_defines.h"
     21 #include "mozilla/Assertions.h"
     22 #include "mozilla/Monitor.h"
     23 #include "rtc_base/time_utils.h"
     24 
     25 using namespace mozilla;
     26 using namespace webrtc::videocapturemodule;
     27 
     28 namespace {
     29 webrtc::VideoRotation ToNativeRotation(RTCVideoRotation aRotation) {
     30  switch (aRotation) {
     31    case RTCVideoRotation_0:
     32      return webrtc::kVideoRotation_0;
     33    case RTCVideoRotation_90:
     34      return webrtc::kVideoRotation_90;
     35    case RTCVideoRotation_180:
     36      return webrtc::kVideoRotation_180;
     37    case RTCVideoRotation_270:
     38      return webrtc::kVideoRotation_270;
     39    default:
     40      MOZ_CRASH_UNSAFE_PRINTF("Unexpected rotation %d",
     41                              static_cast<int>(aRotation));
     42      return webrtc::kVideoRotation_0;
     43  }
     44 }
     45 
     46 AVCaptureDeviceFormat* _Nullable FindFormat(
     47    AVCaptureDevice* _Nonnull aDevice,
     48    webrtc::VideoCaptureCapability aCapability) {
     49  for (AVCaptureDeviceFormat* format in [aDevice formats]) {
     50    CMVideoDimensions dimensions =
     51        CMVideoFormatDescriptionGetDimensions(format.formatDescription);
     52    if (dimensions.width != aCapability.width) {
     53      continue;
     54    }
     55    if (dimensions.height != aCapability.height) {
     56      continue;
     57    }
     58    FourCharCode fourcc =
     59        CMFormatDescriptionGetMediaSubType(format.formatDescription);
     60    if (aCapability.videoType !=
     61        DeviceInfoAvFoundation::ConvertFourCCToVideoType(fourcc)) {
     62      continue;
     63    }
     64    if ([format.videoSupportedFrameRateRanges
     65            indexOfObjectPassingTest:^BOOL(AVFrameRateRange* _Nonnull obj,
     66                                           NSUInteger idx,
     67                                           BOOL* _Nonnull stop) {
     68              return static_cast<BOOL>(
     69                  DeviceInfoAvFoundation::ConvertAVFrameRateToCapabilityFPS(
     70                      obj.maxFrameRate) == aCapability.maxFPS);
     71            }] == NSNotFound) {
     72      continue;
     73    }
     74 
     75    return format;
     76  }
     77  return nullptr;
     78 }
     79 }  // namespace
     80 
     81 @implementation VideoCaptureAdapter
     82 - (void)setCapturer:
     83    (webrtc::videocapturemodule::VideoCaptureAvFoundation* _Nullable)capturer {
     84  webrtc::MutexLock lock(&_mutex);
     85  _capturer = capturer;
     86 }
     87 
     88 - (void)capturer:(RTCVideoCapturer* _Nonnull)capturer
     89    didCaptureVideoFrame:(RTCVideoFrame* _Nonnull)frame {
     90  webrtc::scoped_refptr<webrtc::videocapturemodule::VideoCaptureAvFoundation>
     91      cap;
     92  {
     93    webrtc::MutexLock lock(&_mutex);
     94    cap = webrtc::scoped_refptr(_capturer);
     95  }
     96  if (!cap) return;
     97  cap->OnFrame(frame);
     98 }
     99 @end
    100 
    101 namespace webrtc::videocapturemodule {
    102 VideoCaptureAvFoundation::VideoCaptureAvFoundation(
    103    Clock* _Nonnull clock, AVCaptureDevice* _Nonnull aDevice)
    104    : VideoCaptureImpl(clock),
    105      mDevice(aDevice),
    106      mAdapter([[VideoCaptureAdapter alloc] init]),
    107      mCapturer([[RTC_OBJC_TYPE(RTCCameraVideoCapturer) alloc]
    108          initWithDelegate:mAdapter]),
    109      mCallbackThreadId() {
    110  const char* uniqueId = [[aDevice uniqueID] UTF8String];
    111  size_t len = strlen(uniqueId);
    112  _deviceUniqueId = new (std::nothrow) char[len + 1];
    113  if (_deviceUniqueId) {
    114    memcpy(_deviceUniqueId, uniqueId, len + 1);
    115  }
    116 }
    117 
    118 VideoCaptureAvFoundation::~VideoCaptureAvFoundation() {
    119  // Must block until capture has fully stopped, including async operations.
    120  StopCapture();
    121 }
    122 
    123 /* static */
    124 webrtc::scoped_refptr<VideoCaptureModule> VideoCaptureAvFoundation::Create(
    125    Clock* _Nonnull clock, const char* _Nullable aDeviceUniqueIdUTF8) {
    126  std::string uniqueId(aDeviceUniqueIdUTF8);
    127 
    128  for (AVCaptureDevice* device in [RTCCameraVideoCapturer
    129           captureDevicesWithDeviceTypes:[RTCCameraVideoCapturer
    130                                             defaultCaptureDeviceTypes]]) {
    131    if ([NSString stdStringForString:device.uniqueID] == uniqueId) {
    132      webrtc::scoped_refptr<VideoCaptureModule> module(
    133          new webrtc::RefCountedObject<VideoCaptureAvFoundation>(clock,
    134                                                                 device));
    135      return module;
    136    }
    137  }
    138  return nullptr;
    139 }
    140 
    141 int32_t VideoCaptureAvFoundation::StartCapture(
    142    const VideoCaptureCapability& aCapability) {
    143  RTC_DCHECK_RUN_ON(&mChecker);
    144  AVCaptureDeviceFormat* format = FindFormat(mDevice, aCapability);
    145  if (!format) {
    146    return -1;
    147  }
    148 
    149  {
    150    MutexLock lock(&api_lock_);
    151    if (mCapability) {
    152      if (mCapability->width == aCapability.width &&
    153          mCapability->height == aCapability.height &&
    154          mCapability->maxFPS == aCapability.maxFPS &&
    155          mCapability->videoType == aCapability.videoType) {
    156        return 0;
    157      }
    158 
    159      api_lock_.Unlock();
    160      int32_t rv = StopCapture();
    161      api_lock_.Lock();
    162 
    163      if (rv != 0) {
    164        return rv;
    165      }
    166    }
    167  }
    168 
    169  [mAdapter setCapturer:this];
    170 
    171  {
    172    Monitor monitor("VideoCaptureAVFoundation::StartCapture");
    173    Monitor* copyableMonitor = &monitor;
    174    MonitorAutoLock lock(monitor);
    175    __block Maybe<int32_t> rv;
    176 
    177    [mCapturer startCaptureWithDevice:mDevice
    178                               format:format
    179                                  fps:aCapability.maxFPS
    180                    completionHandler:^(NSError* error) {
    181                      MonitorAutoLock lock2(*copyableMonitor);
    182                      MOZ_RELEASE_ASSERT(!rv);
    183                      rv = Some(error ? -1 : 0);
    184                      copyableMonitor->Notify();
    185                    }];
    186 
    187    while (!rv) {
    188      monitor.Wait();
    189    }
    190 
    191    if (*rv != 0) {
    192      return *rv;
    193    }
    194  }
    195 
    196  MutexLock lock(&api_lock_);
    197  mCapability = Some(aCapability);
    198  mImageType = Some([type = aCapability.videoType] {
    199    switch (type) {
    200      case webrtc::VideoType::kI420:
    201        return CaptureStage::ImageType::I420;
    202      case webrtc::VideoType::kYUY2:
    203        return CaptureStage::ImageType::YUY2;
    204      case webrtc::VideoType::kYV12:
    205      case webrtc::VideoType::kIYUV:
    206        return CaptureStage::ImageType::YV12;
    207      case webrtc::VideoType::kUYVY:
    208        return CaptureStage::ImageType::UYVY;
    209      case webrtc::VideoType::kNV12:
    210        return CaptureStage::ImageType::NV12;
    211      case webrtc::VideoType::kNV21:
    212        return CaptureStage::ImageType::NV21;
    213      case webrtc::VideoType::kMJPEG:
    214        return CaptureStage::ImageType::MJPEG;
    215      case webrtc::VideoType::kRGB24:
    216      case webrtc::VideoType::kBGR24:
    217      case webrtc::VideoType::kABGR:
    218      case webrtc::VideoType::kARGB:
    219      case webrtc::VideoType::kARGB4444:
    220      case webrtc::VideoType::kRGB565:
    221      case webrtc::VideoType::kARGB1555:
    222      case webrtc::VideoType::kBGRA:
    223      case webrtc::VideoType::kUnknown:
    224        // Unlikely, and not represented by CaptureStage::ImageType.
    225        return CaptureStage::ImageType::Unknown;
    226    }
    227    return CaptureStage::ImageType::Unknown;
    228  }());
    229 
    230  return 0;
    231 }
    232 
    233 int32_t VideoCaptureAvFoundation::StopCapture() {
    234  RTC_DCHECK_RUN_ON(&mChecker);
    235  {
    236    MutexLock lock(&api_lock_);
    237    if (!mCapability) {
    238      return 0;
    239    }
    240    mCapability = Nothing();
    241  }
    242 
    243  Monitor monitor("VideoCaptureAVFoundation::StopCapture");
    244  Monitor* copyableMonitor = &monitor;
    245  MonitorAutoLock lock(monitor);
    246  __block bool done = false;
    247 
    248  [mCapturer stopCaptureWithCompletionHandler:^(void) {
    249    MonitorAutoLock lock2(*copyableMonitor);
    250    MOZ_RELEASE_ASSERT(!done);
    251    done = true;
    252    copyableMonitor->Notify();
    253  }];
    254 
    255  while (!done) {
    256    monitor.Wait();
    257  }
    258 
    259  [mAdapter setCapturer:nil];
    260 
    261  return 0;
    262 }
    263 
    264 bool VideoCaptureAvFoundation::CaptureStarted() {
    265  RTC_DCHECK_RUN_ON(&mChecker);
    266  MutexLock lock(&api_lock_);
    267  return mCapability.isSome();
    268 }
    269 
    270 int32_t VideoCaptureAvFoundation::CaptureSettings(
    271    VideoCaptureCapability& aSettings) {
    272  MOZ_CRASH("Unexpected call");
    273  return -1;
    274 }
    275 
    276 int32_t VideoCaptureAvFoundation::OnFrame(
    277    __strong RTCVideoFrame* _Nonnull aFrame) {
    278  MaybeRegisterCallbackThread();
    279  if (MutexLock lock(&api_lock_); MOZ_LIKELY(mTrackingId)) {
    280    mCaptureRecorder.Start(
    281        0, "VideoCaptureAVFoundation"_ns, *mTrackingId, aFrame.width,
    282        aFrame.height, mImageType.valueOr(CaptureStage::ImageType::Unknown));
    283    if (mCapability && mCapability->videoType != webrtc::VideoType::kI420) {
    284      mConversionRecorder.Start(0, "VideoCaptureAVFoundation"_ns, *mTrackingId,
    285                                aFrame.width, aFrame.height);
    286    }
    287  }
    288 
    289  const int64_t timestamp_us =
    290      aFrame.timeStampNs / webrtc::kNumNanosecsPerMicrosec;
    291  RTCI420Buffer* buffer = [aFrame.buffer toI420];
    292  mConversionRecorder.Record(0);
    293  // Accessing the (intended-to-be-private) native buffer directly is hacky but
    294  // lets us skip two copies
    295  webrtc::scoped_refptr<webrtc::I420BufferInterface> nativeBuffer =
    296      buffer.nativeI420Buffer;
    297  auto frame = webrtc::VideoFrame::Builder()
    298                   .set_video_frame_buffer(nativeBuffer)
    299                   .set_rotation(ToNativeRotation(aFrame.rotation))
    300                   .set_timestamp_us(timestamp_us)
    301                   .build();
    302 
    303  MutexLock lock(&api_lock_);
    304  int32_t rv = DeliverCapturedFrame(frame);
    305  mCaptureRecorder.Record(0);
    306  return rv;
    307 }
    308 
    309 void VideoCaptureAvFoundation::SetTrackingId(uint32_t aTrackingIdProcId) {
    310  RTC_DCHECK_RUN_ON(&mChecker);
    311  MutexLock lock(&api_lock_);
    312  if (NS_WARN_IF(mTrackingId.isSome())) {
    313    // This capture instance must be shared across multiple camera requests. For
    314    // now ignore other requests than the first.
    315    return;
    316  }
    317  mTrackingId.emplace(TrackingId::Source::Camera, aTrackingIdProcId);
    318 }
    319 
    320 void VideoCaptureAvFoundation::MaybeRegisterCallbackThread() {
    321  ProfilerThreadId id = profiler_current_thread_id();
    322  if (MOZ_LIKELY(id == mCallbackThreadId)) {
    323    return;
    324  }
    325  mCallbackThreadId = id;
    326  CallbackThreadRegistry::Get()->Register(mCallbackThreadId,
    327                                          "VideoCaptureAVFoundationCallback");
    328 }
    329 }  // namespace webrtc::videocapturemodule