serve-sim 0.1.24 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,635 @@
1
+ #import "SimCamFakes.h"
2
+ #import "SimCamLog.h"
3
+
4
+ #import <CoreImage/CoreImage.h>
5
+ #import <CoreMedia/CoreMedia.h>
6
+ #import <CoreVideo/CoreVideo.h>
7
+ #import <UIKit/UIKit.h>
8
+ #import <objc/runtime.h>
9
+ #import <objc/message.h>
10
+ #include <stdatomic.h>
11
+ #include <string.h>
12
+
13
+ #pragma mark - Mirror mode
14
+
15
+ static SimCamMirrorMode gMirrorMode = SimCamMirrorAuto;
16
+
17
+ SimCamMirrorMode SimCamGetMirrorMode(void) { return gMirrorMode; }
18
+ void SimCamSetMirrorMode(SimCamMirrorMode m) { gMirrorMode = m; }
19
+
20
+ BOOL SimCamShouldMirror(AVCaptureDevicePosition p) {
21
+ if (gMirrorMode == SimCamMirrorForceOn) return YES;
22
+ if (gMirrorMode == SimCamMirrorForceOff) return NO;
23
+ return p == AVCaptureDevicePositionFront;
24
+ }
25
+
26
+ void SimCamReadMirrorModeFromEnv(void) {
27
+ const char *m = getenv("SIMCAM_MIRROR_MODE");
28
+ if (!m) return;
29
+ if (!strcasecmp(m, "on") || !strcmp(m, "1") || !strcasecmp(m, "true")) {
30
+ gMirrorMode = SimCamMirrorForceOn;
31
+ simcam_log(@"mirror mode forced ON");
32
+ } else if (!strcasecmp(m, "off") || !strcmp(m, "0") || !strcasecmp(m, "false")) {
33
+ gMirrorMode = SimCamMirrorForceOff;
34
+ simcam_log(@"mirror mode forced OFF");
35
+ } else if (!strcasecmp(m, "auto")) {
36
+ gMirrorMode = SimCamMirrorAuto;
37
+ }
38
+ }
39
+
40
+ #pragma mark - Position tag tracking
41
+
42
+ static char kSimCamPositionKey;
43
+
44
+ AVCaptureDevicePosition SimCamPositionOf(id obj) {
45
+ if (!obj) return AVCaptureDevicePositionFront;
46
+ NSNumber *n = objc_getAssociatedObject(obj, &kSimCamPositionKey);
47
+ return n ? (AVCaptureDevicePosition)n.intValue : AVCaptureDevicePositionFront;
48
+ }
49
+ void SimCamSetPosition(id obj, AVCaptureDevicePosition p) {
50
+ objc_setAssociatedObject(obj, &kSimCamPositionKey, @(p), OBJC_ASSOCIATION_RETAIN);
51
+ }
52
+
53
+ #pragma mark - Camera-in-use sticky flag
54
+
55
+ static atomic_int gSimCamCameraInUse = 0;
56
+ static char kSimCamSessionUsingFakeCameraKey;
57
+
58
+ BOOL SimCamCameraIsInUse(void) {
59
+ return atomic_load_explicit(&gSimCamCameraInUse, memory_order_relaxed) > 0;
60
+ }
61
+ void SimCamMarkCameraInUse(void) {
62
+ atomic_store_explicit(&gSimCamCameraInUse, 1, memory_order_relaxed);
63
+ }
64
+ void SimCamMarkSessionUsingFakeCamera(id session, BOOL usingFakeCamera) {
65
+ if (!session) return;
66
+ objc_setAssociatedObject(session,
67
+ &kSimCamSessionUsingFakeCameraKey,
68
+ usingFakeCamera ? @YES : nil,
69
+ OBJC_ASSOCIATION_RETAIN);
70
+ }
71
+ static BOOL SimCamSessionUsesFakeCamera(id session) {
72
+ if (!session) return NO;
73
+ if (![session isKindOfClass:[AVCaptureSession class]]) return NO;
74
+ return [objc_getAssociatedObject(session, &kSimCamSessionUsingFakeCameraKey) boolValue];
75
+ }
76
+
77
+ #pragma mark - AVF runtime-error notification suppression
78
+
79
+ BOOL SimCamShouldSwallowAVFRuntimeError(NSNotificationName name, id object) {
80
+ if (!name) return NO;
81
+ if (![name isEqualToString:AVCaptureSessionRuntimeErrorNotification]) return NO;
82
+ return SimCamSessionUsesFakeCamera(object);
83
+ }
84
+
85
+ void SimCamLogSwallowedRuntimeError(NSString *via, id object, NSDictionary *userInfo) {
86
+ NSError *err = userInfo[AVCaptureSessionErrorKey];
87
+ simcam_log(@"SWALLOW AVCaptureSessionRuntimeError via %@ object=%@ code=%ld domain=%@ desc=%@ underlying=%@",
88
+ via,
89
+ object ? NSStringFromClass([object class]) : @"<nil>",
90
+ (long)err.code,
91
+ err.domain ?: @"<nil>",
92
+ err.localizedDescription ?: @"<nil>",
93
+ err.userInfo[NSUnderlyingErrorKey] ?: @"<nil>");
94
+ }
95
+
96
+ #pragma mark - Weak delegate ref
97
+
98
+ @implementation SimCamWeakRef
99
+ @end
100
+
101
+ #pragma mark - SimCamFakeFrameRateRange
102
+
103
+ @interface SimCamFakeFrameRateRange : AVFrameRateRange
104
+ @end
105
+ @implementation SimCamFakeFrameRateRange
106
+ - (Float64)minFrameRate { return 1.0; }
107
+ - (Float64)maxFrameRate { return 60.0; }
108
+ - (CMTime)minFrameDuration { return CMTimeMake(1, 60); }
109
+ - (CMTime)maxFrameDuration { return CMTimeMake(1, 1); }
110
+ @end
111
+
112
+ #pragma mark - SimCamFakeFormat
113
+
114
+ @implementation SimCamFakeFormat {
115
+ CMVideoFormatDescriptionRef _fd;
116
+ NSArray<AVFrameRateRange *> *_ranges;
117
+ }
118
+ - (CMFormatDescriptionRef)formatDescription {
119
+ if (!_fd) {
120
+ CMVideoFormatDescriptionCreate(kCFAllocatorDefault,
121
+ kCVPixelFormatType_32BGRA, 1280, 720, NULL, &_fd);
122
+ }
123
+ return _fd;
124
+ }
125
+ - (NSArray<AVFrameRateRange *> *)videoSupportedFrameRateRanges {
126
+ if (!_ranges) {
127
+ AVFrameRateRange *r = (AVFrameRateRange *)class_createInstance(
128
+ [SimCamFakeFrameRateRange class], 0);
129
+ _ranges = r ? @[r] : @[];
130
+ }
131
+ return _ranges;
132
+ }
133
+ - (NSString *)mediaType { return AVMediaTypeVideo; }
134
+ - (FourCharCode)mediaSubType { return kCVPixelFormatType_32BGRA; }
135
+ - (CMVideoDimensions)highResolutionStillImageDimensions {
136
+ return (CMVideoDimensions){ 1280, 720 };
137
+ }
138
+ - (NSArray<NSValue *> *)supportedMaxPhotoDimensions {
139
+ CMVideoDimensions dims = { 1920, 1080 };
140
+ return @[ [NSValue valueWithBytes:&dims objCType:@encode(CMVideoDimensions)] ];
141
+ }
142
+ - (BOOL)isHighestPhotoQualitySupported { return YES; }
143
+ - (BOOL)isVideoBinned { return NO; }
144
+ - (BOOL)isVideoStabilizationModeSupported:(AVCaptureVideoStabilizationMode)m { return NO; }
145
+ - (CGFloat)videoMaxZoomFactor { return 16.0; }
146
+ - (CGFloat)videoZoomFactorUpscaleThreshold { return 1.0; }
147
+ - (AVCaptureAutoFocusSystem)autoFocusSystem { return AVCaptureAutoFocusSystemNone; }
148
+ - (BOOL)isMultiCamSupported { return NO; }
149
+ - (NSArray *)supportedColorSpaces { return @[]; }
150
+ - (NSArray *)supportedDepthDataFormats { return @[]; }
151
+ - (BOOL)isPortraitEffectSupported { return NO; }
152
+ - (float)minISO { return 25.0f; }
153
+ - (float)maxISO { return 6400.0f; }
154
+ - (CMTime)minExposureDuration { return CMTimeMake(1, 8000); }
155
+ - (CMTime)maxExposureDuration { return CMTimeMake(1, 30); }
156
+ - (id)figCaptureSourceVideoFormat { return nil; }
157
+ - (void)dealloc { if (_fd) CFRelease(_fd); }
158
+ @end
159
+
160
+ AVCaptureDeviceFormat *SimCamSharedFakeFormat(void) {
161
+ static AVCaptureDeviceFormat *f = nil;
162
+ static dispatch_once_t once;
163
+ dispatch_once(&once, ^{
164
+ f = (AVCaptureDeviceFormat *)class_createInstance([SimCamFakeFormat class], 0);
165
+ });
166
+ return f;
167
+ }
168
+
169
+ #pragma mark - SimCamFakeDevice
170
+
171
+ static char kFakePositionKey;
172
+
173
+ @implementation SimCamFakeDevice
174
+ - (AVCaptureDevicePosition)position {
175
+ NSNumber *n = objc_getAssociatedObject(self, &kFakePositionKey);
176
+ return n ? (AVCaptureDevicePosition)n.intValue : AVCaptureDevicePositionFront;
177
+ }
178
+ - (NSString *)uniqueID {
179
+ return self.position == AVCaptureDevicePositionBack
180
+ ? @"sim-cam-fake-back-0" : @"sim-cam-fake-front-0";
181
+ }
182
+ - (NSString *)modelID { return @"SimCamFakeCamera"; }
183
+ - (NSString *)localizedName {
184
+ return self.position == AVCaptureDevicePositionBack
185
+ ? @"Simulated Camera Back (serve-sim)"
186
+ : @"Simulated Camera Front (serve-sim)";
187
+ }
188
+ - (NSString *)manufacturer { return @"serve-sim"; }
189
+ - (BOOL)hasMediaType:(AVMediaType)mediaType { return [mediaType isEqualToString:AVMediaTypeVideo]; }
190
+ - (BOOL)supportsAVCaptureSessionPreset:(AVCaptureSessionPreset)preset { return YES; }
191
+ - (AVCaptureDeviceType)deviceType { return AVCaptureDeviceTypeBuiltInWideAngleCamera; }
192
+ - (NSArray<AVCaptureDeviceFormat *> *)formats {
193
+ AVCaptureDeviceFormat *f = SimCamSharedFakeFormat();
194
+ return f ? @[f] : @[];
195
+ }
196
+ - (BOOL)isConnected { return YES; }
197
+ - (BOOL)isSuspended { return NO; }
198
+ - (BOOL)lockForConfiguration:(NSError **)e { return YES; }
199
+ - (void)unlockForConfiguration { }
200
+ - (AVCaptureDeviceFormat *)activeFormat { return SimCamSharedFakeFormat(); }
201
+ - (CMTime)activeVideoMinFrameDuration { return CMTimeMake(1, 30); }
202
+ - (CMTime)activeVideoMaxFrameDuration { return CMTimeMake(1, 30); }
203
+ - (CGFloat)videoZoomFactor { return 1.0; }
204
+ - (void)setVideoZoomFactor:(CGFloat)v { (void)v; }
205
+ - (void)rampToVideoZoomFactor:(CGFloat)f withRate:(float)r { (void)f; (void)r; }
206
+ - (void)cancelVideoZoomRamp { }
207
+ - (BOOL)isRampingVideoZoom { return NO; }
208
+ - (CGFloat)minAvailableVideoZoomFactor { return 1.0; }
209
+ - (CGFloat)maxAvailableVideoZoomFactor { return 16.0; }
210
+ - (CGFloat)dualCameraSwitchOverVideoZoomFactor { return 2.0; }
211
+ - (NSArray<NSNumber *> *)virtualDeviceSwitchOverVideoZoomFactors { return @[]; }
212
+ - (NSArray *)constituentDevices { return @[]; }
213
+ - (BOOL)isVirtualDevice { return NO; }
214
+ - (BOOL)hasTorch { return NO; }
215
+ - (BOOL)hasFlash { return NO; }
216
+ - (BOOL)isTorchAvailable { return NO; }
217
+ - (BOOL)isTorchActive { return NO; }
218
+ - (AVCaptureTorchMode)torchMode { return AVCaptureTorchModeOff; }
219
+ - (void)setTorchMode:(AVCaptureTorchMode)m { (void)m; }
220
+ - (BOOL)isTorchModeSupported:(AVCaptureTorchMode)m { (void)m; return NO; }
221
+ - (BOOL)setTorchModeOnWithLevel:(float)l error:(NSError **)e { (void)l; if (e) *e = nil; return YES; }
222
+ - (AVCaptureFocusMode)focusMode { return AVCaptureFocusModeContinuousAutoFocus; }
223
+ - (void)setFocusMode:(AVCaptureFocusMode)m { (void)m; }
224
+ - (BOOL)isFocusModeSupported:(AVCaptureFocusMode)m { (void)m; return YES; }
225
+ - (CGPoint)focusPointOfInterest { return CGPointMake(0.5, 0.5); }
226
+ - (void)setFocusPointOfInterest:(CGPoint)p { (void)p; }
227
+ - (BOOL)isFocusPointOfInterestSupported { return YES; }
228
+ - (BOOL)isAdjustingFocus { return NO; }
229
+ - (BOOL)isSmoothAutoFocusEnabled { return NO; }
230
+ - (void)setSmoothAutoFocusEnabled:(BOOL)b { (void)b; }
231
+ - (BOOL)isSmoothAutoFocusSupported { return NO; }
232
+ - (AVCaptureAutoFocusRangeRestriction)autoFocusRangeRestriction { return AVCaptureAutoFocusRangeRestrictionNone; }
233
+ - (void)setAutoFocusRangeRestriction:(AVCaptureAutoFocusRangeRestriction)r { (void)r; }
234
+ - (BOOL)isAutoFocusRangeRestrictionSupported { return NO; }
235
+ - (AVCaptureExposureMode)exposureMode { return AVCaptureExposureModeContinuousAutoExposure; }
236
+ - (void)setExposureMode:(AVCaptureExposureMode)m { (void)m; }
237
+ - (BOOL)isExposureModeSupported:(AVCaptureExposureMode)m { (void)m; return YES; }
238
+ - (CGPoint)exposurePointOfInterest { return CGPointMake(0.5, 0.5); }
239
+ - (void)setExposurePointOfInterest:(CGPoint)p { (void)p; }
240
+ - (BOOL)isExposurePointOfInterestSupported { return YES; }
241
+ - (BOOL)isAdjustingExposure { return NO; }
242
+ - (float)exposureTargetBias { return 0.0f; }
243
+ - (float)minExposureTargetBias { return -8.0f; }
244
+ - (float)maxExposureTargetBias { return 8.0f; }
245
+ - (CMTime)exposureDuration { return CMTimeMake(1, 30); }
246
+ - (float)ISO { return 100.0f; }
247
+ - (float)minISO { return 25.0f; }
248
+ - (float)maxISO { return 6400.0f; }
249
+ - (CMTime)activeMinExposureDuration { return CMTimeMake(1, 8000); }
250
+ - (CMTime)activeMaxExposureDuration { return CMTimeMake(1, 30); }
251
+ - (AVCaptureWhiteBalanceMode)whiteBalanceMode { return AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance; }
252
+ - (void)setWhiteBalanceMode:(AVCaptureWhiteBalanceMode)m { (void)m; }
253
+ - (BOOL)isWhiteBalanceModeSupported:(AVCaptureWhiteBalanceMode)m { (void)m; return YES; }
254
+ - (BOOL)isAdjustingWhiteBalance { return NO; }
255
+ - (BOOL)isFlashAvailable { return NO; }
256
+ - (BOOL)videoHDREnabled { return NO; }
257
+ - (void)setVideoHDREnabled:(BOOL)b { (void)b; }
258
+ - (BOOL)automaticallyAdjustsVideoHDREnabled { return NO; }
259
+ - (void)setAutomaticallyAdjustsVideoHDREnabled:(BOOL)b { (void)b; }
260
+ - (BOOL)isLowLightBoostSupported { return NO; }
261
+ - (BOOL)isLowLightBoostEnabled { return NO; }
262
+ - (BOOL)automaticallyEnablesLowLightBoostWhenAvailable { return NO; }
263
+ - (void)setAutomaticallyEnablesLowLightBoostWhenAvailable:(BOOL)b { (void)b; }
264
+ - (NSArray *)linkedDevices { return @[]; }
265
+ @end
266
+
267
+ AVCaptureDevice *SimCamFakeDeviceForPosition(AVCaptureDevicePosition p) {
268
+ static AVCaptureDevice *front = nil;
269
+ static AVCaptureDevice *back = nil;
270
+ static dispatch_once_t once;
271
+ dispatch_once(&once, ^{
272
+ front = (AVCaptureDevice *)class_createInstance([SimCamFakeDevice class], 0);
273
+ objc_setAssociatedObject(front, &kFakePositionKey,
274
+ @(AVCaptureDevicePositionFront), OBJC_ASSOCIATION_RETAIN);
275
+ back = (AVCaptureDevice *)class_createInstance([SimCamFakeDevice class], 0);
276
+ objc_setAssociatedObject(back, &kFakePositionKey,
277
+ @(AVCaptureDevicePositionBack), OBJC_ASSOCIATION_RETAIN);
278
+ });
279
+ return p == AVCaptureDevicePositionBack ? back : front;
280
+ }
281
+
282
+ #pragma mark - SimCamFakeConnection
283
+
284
+ @interface AVCaptureConnection (SimCamPrivate)
285
+ - (BOOL)sourcesFromExternalCamera;
286
+ - (AVCaptureVideoOrientation)_videoOrientation;
287
+ @end
288
+
289
+ @implementation SimCamFakeConnection {
290
+ __weak AVCaptureOutput *_outputRef;
291
+ AVCaptureDevicePosition _position;
292
+ AVCaptureVideoOrientation _orientation;
293
+ BOOL _videoMirrored;
294
+ BOOL _automaticallyAdjustsVideoMirroring;
295
+ BOOL _enabled;
296
+ }
297
+ + (instancetype)allocWithZone:(NSZone *)zone {
298
+ return class_createInstance([SimCamFakeConnection class], 0);
299
+ }
300
+ + (instancetype)connectionForOutput:(AVCaptureOutput *)output
301
+ position:(AVCaptureDevicePosition)pos {
302
+ SimCamFakeConnection *c = [self alloc];
303
+ if (c) {
304
+ c->_outputRef = output;
305
+ c->_position = pos;
306
+ c->_orientation = AVCaptureVideoOrientationPortrait;
307
+ c->_automaticallyAdjustsVideoMirroring = YES;
308
+ c->_videoMirrored = SimCamShouldMirror(pos);
309
+ c->_enabled = YES;
310
+ }
311
+ return c;
312
+ }
313
+ - (AVCaptureOutput *)output { return _outputRef; }
314
+ - (NSArray *)inputPorts { return @[]; }
315
+ - (AVCaptureInput *)input { return nil; }
316
+ - (AVCaptureVideoPreviewLayer *)videoPreviewLayer { return nil; }
317
+ - (BOOL)isEnabled { return _enabled; }
318
+ - (void)setEnabled:(BOOL)e { _enabled = e; }
319
+ - (BOOL)isActive { return YES; }
320
+ - (NSArray *)audioChannels { return @[]; }
321
+ - (AVMediaType)mediaType { return AVMediaTypeVideo; }
322
+
323
+ - (AVCaptureDevice *)sourceDevice { return SimCamFakeDeviceForPosition(_position); }
324
+ - (AVCaptureDeviceType)sourceDeviceType { return AVCaptureDeviceTypeBuiltInWideAngleCamera; }
325
+ - (AVCaptureDevicePosition)sourceDevicePosition { return _position; }
326
+ - (AVCaptureSession *)originatingSession { return nil; }
327
+
328
+ - (AVCaptureDeviceInput *)deviceInput {
329
+ return SimCamFakeInputForPosition(_position);
330
+ }
331
+
332
+ - (BOOL)isVideoOrientationSupported { return YES; }
333
+ - (AVCaptureVideoOrientation)videoOrientation { return _orientation; }
334
+ - (void)setVideoOrientation:(AVCaptureVideoOrientation)o { _orientation = o; }
335
+
336
+ - (BOOL)isVideoRotationAngleSupported:(CGFloat)angle { (void)angle; return YES; }
337
+ - (CGFloat)videoRotationAngle {
338
+ switch (_orientation) {
339
+ case AVCaptureVideoOrientationPortrait: return 90.0;
340
+ case AVCaptureVideoOrientationPortraitUpsideDown: return 270.0;
341
+ case AVCaptureVideoOrientationLandscapeRight: return 0.0;
342
+ case AVCaptureVideoOrientationLandscapeLeft: return 180.0;
343
+ default: return 90.0;
344
+ }
345
+ }
346
+ - (void)setVideoRotationAngle:(CGFloat)angle {
347
+ long a = ((long)angle % 360 + 360) % 360;
348
+ if (a == 0) _orientation = AVCaptureVideoOrientationLandscapeRight;
349
+ else if (a == 90) _orientation = AVCaptureVideoOrientationPortrait;
350
+ else if (a == 180) _orientation = AVCaptureVideoOrientationLandscapeLeft;
351
+ else if (a == 270) _orientation = AVCaptureVideoOrientationPortraitUpsideDown;
352
+ }
353
+
354
+ - (BOOL)isVideoMirroringSupported { return YES; }
355
+ - (BOOL)isVideoMirrored {
356
+ if (_automaticallyAdjustsVideoMirroring) return SimCamShouldMirror(_position);
357
+ return _videoMirrored;
358
+ }
359
+ - (void)setVideoMirrored:(BOOL)m {
360
+ _videoMirrored = m;
361
+ _automaticallyAdjustsVideoMirroring = NO;
362
+ }
363
+ - (BOOL)automaticallyAdjustsVideoMirroring { return _automaticallyAdjustsVideoMirroring; }
364
+ - (void)setAutomaticallyAdjustsVideoMirroring:(BOOL)b {
365
+ _automaticallyAdjustsVideoMirroring = b;
366
+ if (b) _videoMirrored = SimCamShouldMirror(_position);
367
+ }
368
+
369
+ - (BOOL)isVideoMinFrameDurationSupported { return NO; }
370
+ - (BOOL)isVideoMaxFrameDurationSupported { return NO; }
371
+ - (CMTime)videoMinFrameDuration { return kCMTimeInvalid; }
372
+ - (CMTime)videoMaxFrameDuration { return kCMTimeInvalid; }
373
+ - (void)setVideoMinFrameDuration:(CMTime)d { (void)d; }
374
+ - (void)setVideoMaxFrameDuration:(CMTime)d { (void)d; }
375
+
376
+ - (BOOL)isVideoStabilizationSupported { return NO; }
377
+ - (AVCaptureVideoStabilizationMode)preferredVideoStabilizationMode { return AVCaptureVideoStabilizationModeOff; }
378
+ - (void)setPreferredVideoStabilizationMode:(AVCaptureVideoStabilizationMode)m { (void)m; }
379
+ - (AVCaptureVideoStabilizationMode)activeVideoStabilizationMode { return AVCaptureVideoStabilizationModeOff; }
380
+ - (BOOL)isVideoStabilizationEnabled { return NO; }
381
+ - (BOOL)enablesVideoStabilizationWhenAvailable { return NO; }
382
+ - (void)setEnablesVideoStabilizationWhenAvailable:(BOOL)b { (void)b; }
383
+
384
+ - (BOOL)isCameraIntrinsicMatrixDeliverySupported { return NO; }
385
+ - (BOOL)isCameraIntrinsicMatrixDeliveryEnabled { return NO; }
386
+ - (void)setCameraIntrinsicMatrixDeliveryEnabled:(BOOL)b { (void)b; }
387
+
388
+ - (BOOL)isVideoFieldModeSupported { return NO; }
389
+ - (CGFloat)videoMaxScaleAndCropFactor { return 1.0; }
390
+ - (CGFloat)videoScaleAndCropFactor { return 1.0; }
391
+ - (void)setVideoScaleAndCropFactor:(CGFloat)v { (void)v; }
392
+
393
+ - (AVCaptureVideoOrientation)_videoOrientation { return _orientation; }
394
+ - (BOOL)sourcesFromExternalCamera { return NO; }
395
+ @end
396
+
397
+ static char kSimCamOutputConnectionKey;
398
+
399
+ AVCaptureConnection *SimCamFakeConnectionForOutput(AVCaptureOutput *out) {
400
+ if (!out) return nil;
401
+ AVCaptureConnection *conn = objc_getAssociatedObject(out, &kSimCamOutputConnectionKey);
402
+ if (!conn) {
403
+ conn = (AVCaptureConnection *)[SimCamFakeConnection
404
+ connectionForOutput:out
405
+ position:SimCamPositionOf(out)];
406
+ if (conn) {
407
+ objc_setAssociatedObject(out, &kSimCamOutputConnectionKey, conn,
408
+ OBJC_ASSOCIATION_RETAIN);
409
+ }
410
+ }
411
+ return conn;
412
+ }
413
+
414
+ #pragma mark - SimCamFakeInput marking
415
+
416
+ static char kSimCamFakeInputKey;
417
+ static char kSimCamFakeInputDeviceKey;
418
+
419
+ void SimCamMarkFakeInput(id input, AVCaptureDevice *fakeDevice) {
420
+ if (!input) return;
421
+ objc_setAssociatedObject(input, &kSimCamFakeInputKey, @YES, OBJC_ASSOCIATION_RETAIN);
422
+ if (fakeDevice) {
423
+ objc_setAssociatedObject(input, &kSimCamFakeInputDeviceKey, fakeDevice, OBJC_ASSOCIATION_RETAIN);
424
+ }
425
+ }
426
+ BOOL SimCamIsFakeInput(id input) {
427
+ if (!input) return NO;
428
+ return [objc_getAssociatedObject(input, &kSimCamFakeInputKey) boolValue];
429
+ }
430
+ AVCaptureDevice *SimCamFakeInputDevice(id input) {
431
+ if (!input) return nil;
432
+ return objc_getAssociatedObject(input, &kSimCamFakeInputDeviceKey);
433
+ }
434
+
435
+ AVCaptureDeviceInput *SimCamFakeInputForPosition(AVCaptureDevicePosition p) {
436
+ static AVCaptureDeviceInput *front = nil;
437
+ static AVCaptureDeviceInput *back = nil;
438
+ static dispatch_once_t once;
439
+ dispatch_once(&once, ^{
440
+ front = (AVCaptureDeviceInput *)class_createInstance([AVCaptureDeviceInput class], 0);
441
+ SimCamMarkFakeInput(front, SimCamFakeDeviceForPosition(AVCaptureDevicePositionFront));
442
+ SimCamSetPosition(front, AVCaptureDevicePositionFront);
443
+
444
+ back = (AVCaptureDeviceInput *)class_createInstance([AVCaptureDeviceInput class], 0);
445
+ SimCamMarkFakeInput(back, SimCamFakeDeviceForPosition(AVCaptureDevicePositionBack));
446
+ SimCamSetPosition(back, AVCaptureDevicePositionBack);
447
+ });
448
+ return p == AVCaptureDevicePositionBack ? back : front;
449
+ }
450
+
451
+ #pragma mark - SimCamFakeResolvedPhotoSettings
452
+
453
+ @interface SimCamFakeResolvedPhotoSettings (SimCamFactory)
454
+ + (instancetype)settingsWithDimensions:(CMVideoDimensions)dims;
455
+ @end
456
+
457
+ @implementation SimCamFakeResolvedPhotoSettings {
458
+ CMVideoDimensions _photoDims;
459
+ }
460
+ + (instancetype)allocWithZone:(NSZone *)zone {
461
+ return class_createInstance([SimCamFakeResolvedPhotoSettings class], 0);
462
+ }
463
+ + (instancetype)settingsWithDimensions:(CMVideoDimensions)dims {
464
+ SimCamFakeResolvedPhotoSettings *s = [self alloc];
465
+ if (s) s->_photoDims = dims;
466
+ return s;
467
+ }
468
+ - (CMVideoDimensions)photoDimensions { return _photoDims; }
469
+ - (CMVideoDimensions)rawPhotoDimensions { return (CMVideoDimensions){0, 0}; }
470
+ - (CMVideoDimensions)previewDimensions { return _photoDims; }
471
+ - (CMVideoDimensions)embeddedThumbnailDimensions { return (CMVideoDimensions){0, 0}; }
472
+ - (CMVideoDimensions)portraitEffectsMatteDimensions { return (CMVideoDimensions){0, 0}; }
473
+ - (CMVideoDimensions)rawEmbeddedThumbnailDimensions { return (CMVideoDimensions){0, 0}; }
474
+ - (int64_t)uniqueID { return 1; }
475
+ - (BOOL)isFlashEnabled { return NO; }
476
+ - (BOOL)isRedEyeReductionEnabled { return NO; }
477
+ - (BOOL)isContentAwareDistortionCorrectionEnabled { return NO; }
478
+ - (BOOL)isStillImageStabilizationEnabled { return NO; }
479
+ - (BOOL)isVirtualDeviceFusionEnabled { return NO; }
480
+ - (BOOL)isAutoVirtualDeviceFusionEnabled { return NO; }
481
+ - (BOOL)isDualCameraFusionEnabled { return NO; }
482
+ - (BOOL)isAutoDualCameraFusionEnabled { return NO; }
483
+ - (BOOL)isDepthDataDeliveryEnabled { return NO; }
484
+ - (BOOL)isPortraitEffectsMatteDeliveryEnabled { return NO; }
485
+ - (BOOL)isCameraCalibrationDataDeliveryEnabled { return NO; }
486
+ - (NSArray *)enabledSemanticSegmentationMatteTypes { return @[]; }
487
+ - (CMTimeRange)photoProcessingTimeRange {
488
+ return CMTimeRangeMake(kCMTimeZero, kCMTimeZero);
489
+ }
490
+ - (CMTime)expectedPhotoCaptureDuration { return kCMTimeInvalid; }
491
+ - (NSURL *)deferredPhotoProxyDataFileURL { return nil; }
492
+ - (NSDictionary *)dimensionsRepresentation { return @{}; }
493
+ @end
494
+
495
+ #pragma mark - SimCamFakePhoto
496
+
497
+ @implementation SimCamFakePhoto {
498
+ NSData *_jpegData;
499
+ CGImageRef _cgImage;
500
+ NSDictionary *_metadata;
501
+ AVCaptureResolvedPhotoSettings *_resolvedSettings;
502
+ }
503
+ + (instancetype)allocWithZone:(NSZone *)zone {
504
+ return class_createInstance([SimCamFakePhoto class], 0);
505
+ }
506
+ + (instancetype)photoFromImage:(CGImageRef)cgImage
507
+ jpegQuality:(CGFloat)q
508
+ mirrored:(BOOL)mirrored {
509
+ if (!cgImage) return nil;
510
+ SimCamFakePhoto *p = [SimCamFakePhoto alloc];
511
+ if (p) {
512
+ p->_cgImage = CGImageRetain(cgImage);
513
+ UIImage *ui = [UIImage imageWithCGImage:cgImage];
514
+ p->_jpegData = UIImageJPEGRepresentation(ui, q);
515
+ UInt32 exifOrient = mirrored ? 2u : 1u;
516
+ p->_metadata = @{
517
+ (NSString *)kCGImagePropertyOrientation: @(exifOrient),
518
+ };
519
+ CMVideoDimensions dims = {
520
+ (int32_t)CGImageGetWidth(cgImage),
521
+ (int32_t)CGImageGetHeight(cgImage),
522
+ };
523
+ p->_resolvedSettings = (AVCaptureResolvedPhotoSettings *)
524
+ [SimCamFakeResolvedPhotoSettings settingsWithDimensions:dims];
525
+ }
526
+ return p;
527
+ }
528
+ - (NSData *)fileDataRepresentation { return _jpegData; }
529
+ - (NSData *)fileDataRepresentationWithCustomizer:(id)c { return _jpegData; }
530
+ - (NSData *)fileDataRepresentationWithReplacementMetadata:(NSDictionary *)m
531
+ replacementEmbeddedThumbnailPhotoFormat:(NSDictionary *)t
532
+ replacementEmbeddedThumbnailPixelBuffer:(CVPixelBufferRef)pb
533
+ replacementDepthData:(id)d { return _jpegData; }
534
+ - (CGImageRef)CGImageRepresentation { return _cgImage; }
535
+ - (CGImageRef)previewCGImageRepresentation { return _cgImage; }
536
+ - (NSDictionary *)metadata { return _metadata; }
537
+ - (CVPixelBufferRef)pixelBuffer { return NULL; }
538
+ - (CVPixelBufferRef)previewPixelBuffer { return NULL; }
539
+ - (AVDepthData *)depthData { return nil; }
540
+ - (AVCameraCalibrationData *)cameraCalibrationData { return nil; }
541
+ - (NSData *)bracketSettings { return nil; }
542
+ - (AVCaptureBracketedStillImageSettings *)bracketedSettings { return nil; }
543
+ - (NSData *)embeddedThumbnailPhotoFormat { return nil; }
544
+ - (NSInteger)photoCount { return 1; }
545
+ - (NSInteger)sequenceCount { return 1; }
546
+ - (CMTime)timestamp { return CMTimeMake(0, 30); }
547
+ - (BOOL)isRawPhoto { return NO; }
548
+ - (AVCaptureResolvedPhotoSettings *)resolvedSettings { return _resolvedSettings; }
549
+ - (NSString *)sourceDeviceType { return AVCaptureDeviceTypeBuiltInWideAngleCamera; }
550
+ - (NSArray *)availableRawEmbeddedThumbnailPhotoCodecTypes { return @[]; }
551
+ - (NSArray *)availableEmbeddedThumbnailPhotoCodecTypes { return @[]; }
552
+ - (void)dealloc { if (_cgImage) CGImageRelease(_cgImage); }
553
+ @end
554
+
555
+ #pragma mark - CoreMotion fakes
556
+
557
+ @implementation SimCamAttitude
558
+ - (double)pitch { return 0.0; }
559
+ - (double)roll { return 0.0; }
560
+ - (double)yaw { return 0.0; }
561
+ - (CMRotationMatrix)rotationMatrix {
562
+ return (CMRotationMatrix){ 1, 0, 0, 0, 1, 0, 0, 0, 1 };
563
+ }
564
+ - (CMQuaternion)quaternion { return (CMQuaternion){ 0, 0, 0, 1 }; }
565
+ - (void)multiplyByInverseOfAttitude:(CMAttitude *)attitude { (void)attitude; }
566
+ @end
567
+
568
+ @implementation SimCamAccelerometerData
569
+ - (CMAcceleration)acceleration { return (CMAcceleration){ 0.0, -1.0, 0.0 }; }
570
+ - (NSTimeInterval)timestamp { return [NSProcessInfo processInfo].systemUptime; }
571
+ @end
572
+
573
+ @implementation SimCamGyroData
574
+ - (CMRotationRate)rotationRate { return (CMRotationRate){ 0.0, 0.0, 0.0 }; }
575
+ - (NSTimeInterval)timestamp { return [NSProcessInfo processInfo].systemUptime; }
576
+ @end
577
+
578
+ @implementation SimCamMagnetometerData
579
+ - (CMMagneticField)magneticField { return (CMMagneticField){ 0.0, 0.0, 0.0 }; }
580
+ - (NSTimeInterval)timestamp { return [NSProcessInfo processInfo].systemUptime; }
581
+ @end
582
+
583
+ @implementation SimCamDeviceMotion
584
+ - (CMAttitude *)attitude { return SimCamSharedAttitude(); }
585
+ - (CMAcceleration)gravity { return (CMAcceleration){ 0.0, -1.0, 0.0 }; }
586
+ - (CMAcceleration)userAcceleration { return (CMAcceleration){ 0.0, 0.0, 0.0 }; }
587
+ - (CMRotationRate)rotationRate { return (CMRotationRate){ 0.0, 0.0, 0.0 }; }
588
+ - (CMCalibratedMagneticField)magneticField {
589
+ return (CMCalibratedMagneticField){ { 0.0, 0.0, 0.0 },
590
+ CMMagneticFieldCalibrationAccuracyUncalibrated };
591
+ }
592
+ - (double)heading { return 0.0; }
593
+ - (NSTimeInterval)timestamp { return [NSProcessInfo processInfo].systemUptime; }
594
+ @end
595
+
596
+ CMAttitude *SimCamSharedAttitude(void) {
597
+ static CMAttitude *att = nil;
598
+ static dispatch_once_t once;
599
+ dispatch_once(&once, ^{
600
+ att = (CMAttitude *)class_createInstance([SimCamAttitude class], 0);
601
+ });
602
+ return att;
603
+ }
604
+ CMAccelerometerData *SimCamSharedAccelerometerData(void) {
605
+ static CMAccelerometerData *d = nil;
606
+ static dispatch_once_t once;
607
+ dispatch_once(&once, ^{
608
+ d = (CMAccelerometerData *)class_createInstance([SimCamAccelerometerData class], 0);
609
+ });
610
+ return d;
611
+ }
612
+ CMGyroData *SimCamSharedGyroData(void) {
613
+ static CMGyroData *d = nil;
614
+ static dispatch_once_t once;
615
+ dispatch_once(&once, ^{
616
+ d = (CMGyroData *)class_createInstance([SimCamGyroData class], 0);
617
+ });
618
+ return d;
619
+ }
620
+ CMMagnetometerData *SimCamSharedMagnetometerData(void) {
621
+ static CMMagnetometerData *d = nil;
622
+ static dispatch_once_t once;
623
+ dispatch_once(&once, ^{
624
+ d = (CMMagnetometerData *)class_createInstance([SimCamMagnetometerData class], 0);
625
+ });
626
+ return d;
627
+ }
628
+ CMDeviceMotion *SimCamSharedDeviceMotion(void) {
629
+ static CMDeviceMotion *d = nil;
630
+ static dispatch_once_t once;
631
+ dispatch_once(&once, ^{
632
+ d = (CMDeviceMotion *)class_createInstance([SimCamDeviceMotion class], 0);
633
+ });
634
+ return d;
635
+ }
@@ -0,0 +1,26 @@
1
+ #pragma once
2
+
3
+ #import <AVFoundation/AVFoundation.h>
4
+
5
+ @interface SimCamRegistry : NSObject
6
+ + (instancetype)shared;
7
+
8
+ - (void)addOutput:(AVCaptureVideoDataOutput *)out
9
+ delegate:(id<AVCaptureVideoDataOutputSampleBufferDelegate>)delegate
10
+ queue:(dispatch_queue_t)queue;
11
+ - (void)removeOutput:(AVCaptureVideoDataOutput *)out;
12
+ - (void)addPreviewLayer:(AVCaptureVideoPreviewLayer *)layer;
13
+ - (void)reapplyMirrorToLayers;
14
+
15
+ - (CVPixelBufferRef)currentPixelBuffer CF_RETURNS_RETAINED;
16
+
17
+ - (NSData *)currentSnapshotJPEGAtQuality:(CGFloat)q;
18
+
19
+ - (void)startPumpingIfNeeded;
20
+ - (void)stopPumping;
21
+ @end
22
+
23
+ void SimCamFrameSourceLoadImage(void);
24
+ void SimCamFrameSourceOpenShmIfRequested(void);
25
+
26
+ BOOL SimCamFrameSourceIsShmAttached(void);