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