Add files via upload

This commit is contained in:
Khaled Alshehri 2018-08-08 02:18:40 +03:00 committed by GitHub
parent 5ce2bf3b35
commit 47c0ba2719
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1195 additions and 0 deletions

View File

@ -0,0 +1,56 @@
//
// SCBlackCameraDetector.h
// Snapchat
//
// Created by Derek Wang on 24/01/2018.
//
#import "SCBlackCameraReporter.h"
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class SCBlackCameraNoOutputDetector;
@interface SCBlackCameraDetector : NSObject
@property (nonatomic, strong) SCBlackCameraNoOutputDetector *blackCameraNoOutputDetector;
SC_INIT_AND_NEW_UNAVAILABLE
- (instancetype)initWithTicketCreator:(id<SCManiphestTicketCreator>)ticketCreator;
// CameraView visible/invisible
- (void)onCameraViewVisible:(BOOL)visible;
- (void)onCameraViewVisibleWithTouch:(UIGestureRecognizer *)touch;
// Call this when [AVCaptureSession startRunning] is called
- (void)sessionWillCallStartRunning;
- (void)sessionDidCallStartRunning;
// Call this when [AVCaptureSession stopRunning] is called
- (void)sessionWillCallStopRunning;
- (void)sessionDidCallStopRunning;
// Call this when [AVCaptureSession commitConfiguration] is called
- (void)sessionWillCommitConfiguration;
- (void)sessionDidCommitConfiguration;
- (void)sessionDidChangeIsRunning:(BOOL)running;
// For CapturePreview visibility detector
- (void)capturePreviewDidBecomeVisible:(BOOL)visible;
/**
Mark the start of creating new session
When we fix black camera by creating new session, some detector may report black camera because we called
[AVCaptureSession stopRunning] on old AVCaptureSession, so we need to tell the detector the session is recreating, so
it is fine to call [AVCaptureSession stopRunning] on old AVCaptureSession.
*/
- (void)sessionWillRecreate;
/**
Mark the end of creating new session
*/
- (void)sessionDidRecreate;
@end

View File

@ -0,0 +1,134 @@
//
// SCBlackCameraDetector.m
// Snapchat
//
// Created by Derek Wang on 24/01/2018.
//
#import "SCBlackCameraDetector.h"
#import "SCBlackCameraNoOutputDetector.h"
#import "SCBlackCameraPreviewDetector.h"
#import "SCBlackCameraRunningDetector.h"
#import "SCBlackCameraSessionBlockDetector.h"
#import "SCBlackCameraViewDetector.h"
#import <SCFoundation/SCQueuePerformer.h>
#if !TARGET_IPHONE_SIMULATOR
static char *const kSCBlackCameraDetectorQueueLabel = "com.snapchat.black-camera-detector";
#endif
@interface SCBlackCameraDetector () {
BOOL _sessionIsRunning;
BOOL _cameraIsVisible;
BOOL _previewIsVisible;
}
@property (nonatomic, strong) SCQueuePerformer *queuePerformer;
@property (nonatomic, strong) SCBlackCameraViewDetector *cameraViewDetector;
@property (nonatomic, strong) SCBlackCameraRunningDetector *sessionRunningDetector;
@property (nonatomic, strong) SCBlackCameraPreviewDetector *previewDetector;
@property (nonatomic, strong) SCBlackCameraSessionBlockDetector *sessionBlockDetector;
@end
@implementation SCBlackCameraDetector
- (instancetype)initWithTicketCreator:(id<SCManiphestTicketCreator>)ticketCreator
{
#if !TARGET_IPHONE_SIMULATOR
self = [super init];
if (self) {
_queuePerformer = [[SCQueuePerformer alloc] initWithLabel:kSCBlackCameraDetectorQueueLabel
qualityOfService:QOS_CLASS_BACKGROUND
queueType:DISPATCH_QUEUE_SERIAL
context:SCQueuePerformerContextCamera];
SCBlackCameraReporter *reporter = [[SCBlackCameraReporter alloc] initWithTicketCreator:ticketCreator];
_cameraViewDetector = [[SCBlackCameraViewDetector alloc] initWithPerformer:_queuePerformer reporter:reporter];
_sessionRunningDetector =
[[SCBlackCameraRunningDetector alloc] initWithPerformer:_queuePerformer reporter:reporter];
_previewDetector = [[SCBlackCameraPreviewDetector alloc] initWithPerformer:_queuePerformer reporter:reporter];
_sessionBlockDetector = [[SCBlackCameraSessionBlockDetector alloc] initWithReporter:reporter];
_blackCameraNoOutputDetector = [[SCBlackCameraNoOutputDetector alloc] initWithReporter:reporter];
}
return self;
#else
return nil;
#endif
}
#pragma mark - Camera view visibility detector
- (void)onCameraViewVisible:(BOOL)visible
{
SC_GUARD_ELSE_RETURN(visible != _cameraIsVisible);
_cameraIsVisible = visible;
[_cameraViewDetector onCameraViewVisible:visible];
}
- (void)onCameraViewVisibleWithTouch:(UIGestureRecognizer *)gesture
{
[_cameraViewDetector onCameraViewVisibleWithTouch:gesture];
}
#pragma mark - Track [AVCaptureSession startRunning] call
- (void)sessionWillCallStartRunning
{
[_cameraViewDetector sessionWillCallStartRunning];
[_sessionBlockDetector sessionWillCallStartRunning];
}
- (void)sessionDidCallStartRunning
{
[_sessionRunningDetector sessionDidCallStartRunning];
[_sessionBlockDetector sessionDidCallStartRunning];
}
#pragma mark - Track [AVCaptureSession stopRunning] call
- (void)sessionWillCallStopRunning
{
[_cameraViewDetector sessionWillCallStopRunning];
[_sessionRunningDetector sessionWillCallStopRunning];
}
- (void)sessionDidCallStopRunning
{
}
- (void)sessionDidChangeIsRunning:(BOOL)running
{
SC_GUARD_ELSE_RETURN(running != _sessionIsRunning);
_sessionIsRunning = running;
[_sessionRunningDetector sessionDidChangeIsRunning:running];
[_previewDetector sessionDidChangeIsRunning:running];
}
#pragma mark - Capture preview visibility detector
- (void)capturePreviewDidBecomeVisible:(BOOL)visible
{
SC_GUARD_ELSE_RETURN(visible != _previewIsVisible);
_previewIsVisible = visible;
[_previewDetector capturePreviewDidBecomeVisible:visible];
}
#pragma mark - AVCaptureSession block detector
- (void)sessionWillCommitConfiguration
{
[_sessionBlockDetector sessionWillCommitConfiguration];
}
- (void)sessionDidCommitConfiguration
{
[_sessionBlockDetector sessionDidCommitConfiguration];
}
- (void)sessionWillRecreate
{
[_cameraViewDetector sessionWillRecreate];
}
- (void)sessionDidRecreate
{
[_cameraViewDetector sessionDidRecreate];
}
@end

View File

@ -0,0 +1,26 @@
//
// SCBlackCameraNoOutputDetector.h
// Snapchat
//
// Created by Derek Wang on 05/12/2017.
//
#import "SCManagedCapturerListener.h"
#import <SCCameraFoundation/SCManagedVideoDataSourceListener.h>
#import <Foundation/Foundation.h>
@class SCBlackCameraNoOutputDetector, SCBlackCameraReporter;
@protocol SCManiphestTicketCreator;
@protocol SCBlackCameraDetectorDelegate
- (void)detector:(SCBlackCameraNoOutputDetector *)detector didDetectBlackCamera:(id<SCCapturer>)capture;
@end
@interface SCBlackCameraNoOutputDetector : NSObject <SCManagedVideoDataSourceListener, SCManagedCapturerListener>
@property (nonatomic, weak) id<SCBlackCameraDetectorDelegate> delegate;
- (instancetype)initWithReporter:(SCBlackCameraReporter *)reporter;
@end

View File

@ -0,0 +1,137 @@
//
// SCBlackCameraDetectorNoOutput.m
// Snapchat
//
// Created by Derek Wang on 05/12/2017.
//
// This detector is used to detect the case that session is running, but there is no sample buffer output
#import "SCBlackCameraNoOutputDetector.h"
#import "SCBlackCameraReporter.h"
#import <SCFoundation/SCAssertWrapper.h>
#import <SCFoundation/SCLog.h>
#import <SCFoundation/SCQueuePerformer.h>
#import <SCFoundation/SCTraceODPCompatible.h>
#import <SCFoundation/SCZeroDependencyExperiments.h>
#import <SCLogger/SCCameraMetrics.h>
#import <SCLogger/SCLogger.h>
static CGFloat const kShortCheckingDelay = 0.5f;
static CGFloat const kLongCheckingDelay = 3.0f;
static char *const kSCBlackCameraDetectorQueueLabel = "com.snapchat.black-camera-detector";
@interface SCBlackCameraNoOutputDetector () {
BOOL _sampleBufferReceived;
BOOL _blackCameraDetected;
// Whether we receive first frame after we detected black camera, that's maybe because the checking delay is too
// short, and we will switch to kLongCheckingDelay next time we do the checking
BOOL _blackCameraRecovered;
// Whether checking is scheduled, to avoid duplicated checking
BOOL _checkingScheduled;
// Whether AVCaptureSession is stopped, if stopped, we don't need to check black camera any more
// It is set on main thread, read on background queue
BOOL _sessionStoppedRunning;
}
@property (nonatomic) SCQueuePerformer *queuePerformer;
@property (nonatomic) SCBlackCameraReporter *reporter;
@end
@implementation SCBlackCameraNoOutputDetector
- (instancetype)initWithReporter:(SCBlackCameraReporter *)reporter
{
self = [super init];
if (self) {
_queuePerformer = [[SCQueuePerformer alloc] initWithLabel:kSCBlackCameraDetectorQueueLabel
qualityOfService:QOS_CLASS_BACKGROUND
queueType:DISPATCH_QUEUE_SERIAL
context:SCQueuePerformerContextCamera];
_reporter = reporter;
}
return self;
}
- (void)managedVideoDataSource:(id<SCManagedVideoDataSource>)managedVideoDataSource
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
devicePosition:(SCManagedCaptureDevicePosition)devicePosition
{
// The block is very light-weight
[self.queuePerformer perform:^{
if (_blackCameraDetected) {
// Detected a black camera case
_blackCameraDetected = NO;
_blackCameraRecovered = YES;
SCLogCoreCameraInfo(@"[BlackCamera] Black camera recovered");
if (SCExperimentWithBlackCameraReporting()) {
[[SCLogger sharedInstance] logUnsampledEvent:KSCCameraBlackCamera
parameters:@{
@"type" : @"RECOVERED"
}
secretParameters:nil
metrics:nil];
}
}
// Received buffer!
_sampleBufferReceived = YES;
}];
}
- (void)managedCapturer:(id<SCCapturer>)managedCapturer didStartRunning:(SCManagedCapturerState *)state
{
SCAssertMainThread();
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
SCLogCoreCameraInfo(@"[BlackCamera] In background, skip checking");
return;
}
_sessionStoppedRunning = NO;
[self.queuePerformer perform:^{
SCTraceODPCompatibleStart(2);
if (_checkingScheduled) {
SCLogCoreCameraInfo(@"[BlackCamera] Checking is scheduled, skip");
return;
}
if (_sessionStoppedRunning) {
SCLogCoreCameraInfo(@"[BlackCamera] AVCaptureSession stopped, should not check");
return;
}
_sampleBufferReceived = NO;
if (_blackCameraRecovered) {
SCLogCoreCameraInfo(@"[BlackCamera] Last black camera recovered, let's wait longer to check this time");
}
SCLogCoreCameraInfo(@"[BlackCamera] Schedule black camera checking");
[self.queuePerformer perform:^{
SCTraceODPCompatibleStart(2);
if (!_sessionStoppedRunning) {
if (!_sampleBufferReceived) {
_blackCameraDetected = YES;
[_reporter reportBlackCameraWithCause:SCBlackCameraNoOutputData];
[self.delegate detector:self didDetectBlackCamera:managedCapturer];
} else {
SCLogCoreCameraInfo(@"[BlackCamera] No black camera");
_blackCameraDetected = NO;
}
} else {
SCLogCoreCameraInfo(@"[BlackCamera] AVCaptureSession stopped");
_blackCameraDetected = NO;
}
_blackCameraRecovered = NO;
_checkingScheduled = NO;
}
after:_blackCameraRecovered ? kLongCheckingDelay : kShortCheckingDelay];
_checkingScheduled = YES;
}];
}
- (void)managedCapturer:(id<SCCapturer>)managedCapturer didStopRunning:(SCManagedCapturerState *)state
{
SCAssertMainThread();
_sessionStoppedRunning = YES;
[self.queuePerformer perform:^{
SCTraceODPCompatibleStart(2);
_sampleBufferReceived = NO;
}];
}
@end

View File

@ -0,0 +1,20 @@
//
// SCBlackCameraPreviewDetector.h
// Snapchat
//
// Created by Derek Wang on 25/01/2018.
//
#import <Foundation/Foundation.h>
@class SCQueuePerformer, SCBlackCameraReporter;
@protocol SCManiphestTicketCreator;
@interface SCBlackCameraPreviewDetector : NSObject
- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter;
- (void)sessionDidChangeIsRunning:(BOOL)running;
- (void)capturePreviewDidBecomeVisible:(BOOL)visible;
@end

View File

@ -0,0 +1,92 @@
//
// SCBlackCameraPreviewDetector.m
// Snapchat
//
// Created by Derek Wang on 25/01/2018.
//
#import "SCBlackCameraPreviewDetector.h"
#import "SCBlackCameraReporter.h"
#import "SCMetalUtils.h"
#import <SCCrashLogger/SCCrashLogger.h>
#import <SCFoundation/SCQueuePerformer.h>
#import <SCFoundation/SCThreadHelpers.h>
#import <SCFoundation/SCZeroDependencyExperiments.h>
// Check whether preview is visible when AVCaptureSession is running
static CGFloat const kSCBlackCameraCheckingDelay = 0.5;
@interface SCBlackCameraPreviewDetector () {
BOOL _previewVisible;
dispatch_block_t _checkingBlock;
}
@property (nonatomic) SCQueuePerformer *queuePerformer;
@property (nonatomic) SCBlackCameraReporter *reporter;
@end
@implementation SCBlackCameraPreviewDetector
- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter
{
self = [super init];
if (self) {
_queuePerformer = performer;
_reporter = reporter;
}
return self;
}
- (void)capturePreviewDidBecomeVisible:(BOOL)visible
{
[_queuePerformer perform:^{
_previewVisible = visible;
}];
}
- (void)sessionDidChangeIsRunning:(BOOL)running
{
if (running) {
[self _scheduleCheck];
} else {
[_queuePerformer perform:^{
if (_checkingBlock) {
dispatch_block_cancel(_checkingBlock);
_checkingBlock = nil;
}
}];
}
}
- (void)_scheduleCheck
{
[_queuePerformer perform:^{
@weakify(self);
_checkingBlock = dispatch_block_create(0, ^{
@strongify(self);
SC_GUARD_ELSE_RETURN(self);
self->_checkingBlock = nil;
[self _checkPreviewState];
});
[_queuePerformer perform:_checkingBlock after:kSCBlackCameraCheckingDelay];
}];
}
- (void)_checkPreviewState
{
if (!_previewVisible) {
runOnMainThreadAsynchronously(^{
// Make sure the app is in foreground
SC_GUARD_ELSE_RETURN([UIApplication sharedApplication].applicationState == UIApplicationStateActive);
SCBlackCameraCause cause =
SCDeviceSupportsMetal() ? SCBlackCameraRenderingPaused : SCBlackCameraPreviewIsHidden;
[_reporter reportBlackCameraWithCause:cause];
[_reporter fileShakeTicketWithCause:cause];
});
}
}
@end

View File

@ -0,0 +1,35 @@
//
// SCBlackCameraReporter.h
// Snapchat
//
// Created by Derek Wang on 09/01/2018.
//
#import <SCBase/SCMacros.h>
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, SCBlackCameraCause) {
SCBlackCameraStartRunningNotCalled, // 1. View is visible, but session startRunning is not called
SCBlackCameraSessionNotRunning, // 2. Session startRunning is called, but isRunning is still false
SCBlackCameraRenderingPaused, // 3.1 View is visible, but capture preview rendering is paused
SCBlackCameraPreviewIsHidden, // 3.2 For non-metal devices, capture preview is hidden
SCBlackCameraSessionStartRunningBlocked, // 4.1 AVCaptureSession is blocked at startRunning
SCBlackCameraSessionConfigurationBlocked, // 4.2 AVCaptureSession is blocked at commitConfiguration
SCBlackCameraNoOutputData, // 5. Session is running, but no data output
};
@protocol SCManiphestTicketCreator;
@interface SCBlackCameraReporter : NSObject
SC_INIT_AND_NEW_UNAVAILABLE
- (instancetype)initWithTicketCreator:(id<SCManiphestTicketCreator>)ticketCreator;
- (NSString *)causeNameFor:(SCBlackCameraCause)cause;
- (void)reportBlackCameraWithCause:(SCBlackCameraCause)cause;
- (void)fileShakeTicketWithCause:(SCBlackCameraCause)cause;
@end

View File

@ -0,0 +1,86 @@
//
// SCBlackCameraReporter.m
// Snapchat
//
// Created by Derek Wang on 09/01/2018.
//
#import "SCBlackCameraReporter.h"
#import "SCManiphestTicketCreator.h"
#import <SCFoundation/SCAssertWrapper.h>
#import <SCFoundation/SCLog.h>
#import <SCFoundation/SCLogHelper.h>
#import <SCFoundation/SCZeroDependencyExperiments.h>
#import <SCLogger/SCCameraMetrics.h>
#import <SCLogger/SCLogger.h>
@interface SCBlackCameraReporter ()
@property (nonatomic) id<SCManiphestTicketCreator> ticketCreator;
@end
@implementation SCBlackCameraReporter
- (instancetype)initWithTicketCreator:(id<SCManiphestTicketCreator>)ticketCreator
{
if (self = [super init]) {
_ticketCreator = ticketCreator;
}
return self;
}
- (NSString *)causeNameFor:(SCBlackCameraCause)cause
{
switch (cause) {
case SCBlackCameraStartRunningNotCalled:
return @"StartRunningNotCalled";
case SCBlackCameraSessionNotRunning:
return @"SessionNotRunning";
case SCBlackCameraRenderingPaused:
return @"RenderingPause";
case SCBlackCameraPreviewIsHidden:
return @"PreviewIsHidden";
case SCBlackCameraSessionStartRunningBlocked:
return @"SessionStartRunningBlocked";
case SCBlackCameraSessionConfigurationBlocked:
return @"SessionConfigurationBlocked";
case SCBlackCameraNoOutputData:
return @"NoOutputData";
default:
SCAssert(NO, @"illegate cause");
break;
}
return nil;
}
- (void)reportBlackCameraWithCause:(SCBlackCameraCause)cause
{
NSString *causeStr = [self causeNameFor:cause];
SCLogCoreCameraError(@"[BlackCamera] Detected black camera, cause: %@", causeStr);
NSDictionary *parameters = @{ @"type" : @"DETECTED", @"cause" : causeStr };
[_ticketCreator createAndFileBetaReport:JSONStringSerializeObjectForLogging(parameters)];
if (SCExperimentWithBlackCameraReporting()) {
[[SCLogger sharedInstance] logUnsampledEvent:KSCCameraBlackCamera
parameters:parameters
secretParameters:nil
metrics:nil];
}
}
- (void)fileShakeTicketWithCause:(SCBlackCameraCause)cause
{
if (SCExperimentWithBlackCameraExceptionLogging()) {
// Log exception with auto S2R
NSString *errMsg =
[NSString sc_stringWithFormat:@"[BlackCamera] Detected black camera, cause: %@", [self causeNameFor:cause]];
[_ticketCreator createAndFile:nil creationTime:0 description:errMsg email:nil project:@"Camera" subproject:nil];
}
}
@end

View File

@ -0,0 +1,27 @@
//
// SCBlackCameraRunningDetector.h
// Snapchat
//
// Created by Derek Wang on 30/01/2018.
//
#import <SCBase/SCMacros.h>
#import <Foundation/Foundation.h>
@class SCQueuePerformer, SCBlackCameraReporter;
@protocol SCManiphestTicketCreator;
@interface SCBlackCameraRunningDetector : NSObject
SC_INIT_AND_NEW_UNAVAILABLE
- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter;
// When session isRunning changed
- (void)sessionDidChangeIsRunning:(BOOL)running;
// Call this after [AVCaptureSession startRunning] is called
- (void)sessionDidCallStartRunning;
// Call this before [AVCaptureSession stopRunning] is called
- (void)sessionWillCallStopRunning;
@end

View File

@ -0,0 +1,84 @@
//
// SCBlackCameraRunningDetector.m
// Snapchat
//
// Created by Derek Wang on 30/01/2018.
//
#import "SCBlackCameraRunningDetector.h"
#import "SCBlackCameraReporter.h"
#import <SCFoundation/SCAssertWrapper.h>
#import <SCFoundation/SCQueuePerformer.h>
#import <SCFoundation/SCTraceODPCompatible.h>
#import <SCLogger/SCCameraMetrics.h>
// Check whether we called AVCaptureSession isRunning within this period
static CGFloat const kSCBlackCameraCheckingDelay = 5;
@interface SCBlackCameraRunningDetector () {
BOOL _isSessionRunning;
dispatch_block_t _checkSessionBlock;
}
@property (nonatomic) SCQueuePerformer *queuePerformer;
@property (nonatomic) SCBlackCameraReporter *reporter;
@end
@implementation SCBlackCameraRunningDetector
- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter
{
self = [super init];
if (self) {
_queuePerformer = performer;
_reporter = reporter;
}
return self;
}
- (void)sessionDidChangeIsRunning:(BOOL)running
{
[_queuePerformer perform:^{
_isSessionRunning = running;
}];
}
- (void)sessionDidCallStartRunning
{
[self _scheduleCheck];
}
- (void)sessionWillCallStopRunning
{
[_queuePerformer perform:^{
if (_checkSessionBlock) {
dispatch_block_cancel(_checkSessionBlock);
_checkSessionBlock = nil;
}
}];
}
- (void)_scheduleCheck
{
[_queuePerformer perform:^{
@weakify(self);
_checkSessionBlock = dispatch_block_create(0, ^{
@strongify(self);
SC_GUARD_ELSE_RETURN(self);
self->_checkSessionBlock = nil;
[self _checkSessionState];
});
[_queuePerformer perform:_checkSessionBlock after:kSCBlackCameraCheckingDelay];
}];
}
- (void)_checkSessionState
{
if (!_isSessionRunning) {
[_reporter reportBlackCameraWithCause:SCBlackCameraSessionNotRunning];
}
}
@end

View File

@ -0,0 +1,23 @@
//
// SCBlackCameraSessionBlockDetector.h
// Snapchat
//
// Created by Derek Wang on 25/01/2018.
//
#import "SCBlackCameraReporter.h"
#import <Foundation/Foundation.h>
@interface SCBlackCameraSessionBlockDetector : NSObject
SC_INIT_AND_NEW_UNAVAILABLE
- (instancetype)initWithReporter:(SCBlackCameraReporter *)reporter;
- (void)sessionWillCallStartRunning;
- (void)sessionDidCallStartRunning;
- (void)sessionWillCommitConfiguration;
- (void)sessionDidCommitConfiguration;
@end

View File

@ -0,0 +1,82 @@
//
// SCBlackCameraSessionBlockDetector.m
// Snapchat
//
// Created by Derek Wang on 25/01/2018.
//
#import "SCBlackCameraSessionBlockDetector.h"
#import "SCBlackCameraReporter.h"
#import <SCLogger/SCCameraMetrics.h>
#import <SCLogger/SCLogger.h>
@import CoreGraphics;
// Longer than 5 seconds is considerred as black camera
static CGFloat const kSCBlackCameraBlockingThreshold = 5;
// Will report if session blocks longer than 1 second
static CGFloat const kSCSessionBlockingLogThreshold = 1;
@interface SCBlackCameraSessionBlockDetector () {
NSTimeInterval _startTime;
}
@property (nonatomic) SCBlackCameraReporter *reporter;
@end
@implementation SCBlackCameraSessionBlockDetector
- (instancetype)initWithReporter:(SCBlackCameraReporter *)reporter
{
if (self = [super init]) {
_reporter = reporter;
}
return self;
}
- (void)sessionWillCallStartRunning
{
_startTime = [NSDate timeIntervalSinceReferenceDate];
}
- (void)sessionDidCallStartRunning
{
[self _reportBlackCameraIfNeededWithCause:SCBlackCameraSessionStartRunningBlocked];
[self _reportBlockingIfNeededWithCause:SCBlackCameraSessionStartRunningBlocked];
}
- (void)sessionWillCommitConfiguration
{
_startTime = [NSDate timeIntervalSinceReferenceDate];
}
- (void)sessionDidCommitConfiguration
{
[self _reportBlackCameraIfNeededWithCause:SCBlackCameraSessionConfigurationBlocked];
[self _reportBlockingIfNeededWithCause:SCBlackCameraSessionConfigurationBlocked];
}
- (void)_reportBlockingIfNeededWithCause:(SCBlackCameraCause)cause
{
NSTimeInterval duration = [NSDate timeIntervalSinceReferenceDate] - _startTime;
if (duration >= kSCSessionBlockingLogThreshold) {
NSString *causeStr = [_reporter causeNameFor:cause];
[[SCLogger sharedInstance] logEvent:KSCCameraCaptureSessionBlocked
parameters:@{
@"cause" : causeStr,
@"duration" : @(duration)
}];
}
}
- (void)_reportBlackCameraIfNeededWithCause:(SCBlackCameraCause)cause
{
NSTimeInterval endTime = [NSDate timeIntervalSinceReferenceDate];
if (endTime - _startTime >= kSCBlackCameraBlockingThreshold) {
[_reporter reportBlackCameraWithCause:cause];
}
}
@end

View File

@ -0,0 +1,31 @@
//
// SCBlackCameraDetectorCameraView.h
// Snapchat
//
// Created by Derek Wang on 24/01/2018.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class SCQueuePerformer, SCBlackCameraReporter;
@protocol SCManiphestTicketCreator;
@interface SCBlackCameraViewDetector : NSObject
- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter;
// CameraView visible/invisible
- (void)onCameraViewVisible:(BOOL)visible;
- (void)onCameraViewVisibleWithTouch:(UIGestureRecognizer *)gesture;
// Call this when [AVCaptureSession startRunning] is called
- (void)sessionWillCallStartRunning;
// Call this when [AVCaptureSession stopRunning] is called
- (void)sessionWillCallStopRunning;
- (void)sessionWillRecreate;
- (void)sessionDidRecreate;
@end

View File

@ -0,0 +1,136 @@
//
// SCBlackCameraDetectorCameraView.m
// Snapchat
//
// Created by Derek Wang on 24/01/2018.
//
#import "SCBlackCameraViewDetector.h"
#import "SCBlackCameraReporter.h"
#import "SCCaptureDeviceAuthorization.h"
#import <SCFoundation/SCAssertWrapper.h>
#import <SCFoundation/SCLog.h>
#import <SCFoundation/SCQueuePerformer.h>
#import <SCFoundation/SCTraceODPCompatible.h>
#import <SCLogger/SCCameraMetrics.h>
// Check whether we called [AVCaptureSession startRunning] within this period
static CGFloat const kSCBlackCameraCheckingDelay = 0.5;
@interface SCBlackCameraViewDetector () {
BOOL _startRunningCalled;
BOOL _sessionIsRecreating;
dispatch_block_t _checkSessionBlock;
}
@property (nonatomic) SCQueuePerformer *queuePerformer;
@property (nonatomic) SCBlackCameraReporter *reporter;
@property (nonatomic, weak) UIGestureRecognizer *cameraViewGesture;
@end
@implementation SCBlackCameraViewDetector
- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter
{
self = [super init];
if (self) {
_queuePerformer = performer;
_reporter = reporter;
}
return self;
}
#pragma mark - Camera view visibility change trigger
- (void)onCameraViewVisible:(BOOL)visible
{
SCTraceODPCompatibleStart(2);
SCLogCoreCameraInfo(@"[BlackCamera] onCameraViewVisible: %d", visible);
BOOL firstTimeAccess = [SCCaptureDeviceAuthorization notDeterminedForVideoCapture];
if (firstTimeAccess) {
// We don't want to check black camera for firstTimeAccess
return;
}
// Visible and application is active
if (visible && [UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
// Since this method is usually called before the view is actually visible, leave some margin to check
[self _scheduleCheckDelayed:YES];
} else {
[_queuePerformer perform:^{
if (_checkSessionBlock) {
dispatch_block_cancel(_checkSessionBlock);
_checkSessionBlock = nil;
}
}];
}
}
// Call this when [AVCaptureSession startRunning] is called
- (void)sessionWillCallStartRunning
{
[_queuePerformer perform:^{
_startRunningCalled = YES;
}];
}
- (void)sessionWillCallStopRunning
{
[_queuePerformer perform:^{
_startRunningCalled = NO;
}];
}
- (void)_scheduleCheckDelayed:(BOOL)delay
{
[_queuePerformer perform:^{
SC_GUARD_ELSE_RETURN(!_checkSessionBlock);
@weakify(self);
_checkSessionBlock = dispatch_block_create(0, ^{
@strongify(self);
SC_GUARD_ELSE_RETURN(self);
self->_checkSessionBlock = nil;
[self _checkSessionState];
});
if (delay) {
[_queuePerformer perform:_checkSessionBlock after:kSCBlackCameraCheckingDelay];
} else {
[_queuePerformer perform:_checkSessionBlock];
}
}];
}
- (void)_checkSessionState
{
SCLogCoreCameraInfo(@"[BlackCamera] checkSessionState startRunning: %d, sessionIsRecreating: %d",
_startRunningCalled, _sessionIsRecreating);
if (!_startRunningCalled && !_sessionIsRecreating) {
[_reporter reportBlackCameraWithCause:SCBlackCameraStartRunningNotCalled];
[_reporter fileShakeTicketWithCause:SCBlackCameraStartRunningNotCalled];
}
}
- (void)sessionWillRecreate
{
[_queuePerformer perform:^{
_sessionIsRecreating = YES;
}];
}
- (void)sessionDidRecreate
{
[_queuePerformer perform:^{
_sessionIsRecreating = NO;
}];
}
- (void)onCameraViewVisibleWithTouch:(UIGestureRecognizer *)gesture
{
if (gesture != _cameraViewGesture) {
// Skip repeating gesture
self.cameraViewGesture = gesture;
[self _scheduleCheckDelayed:NO];
}
}
@end

View File

@ -0,0 +1,14 @@
//
// SCCaptureSessionFixer.h
// Snapchat
//
// Created by Derek Wang on 05/12/2017.
//
#import "SCBlackCameraNoOutputDetector.h"
#import <Foundation/Foundation.h>
@interface SCCaptureSessionFixer : NSObject <SCBlackCameraDetectorDelegate>
@end

View File

@ -0,0 +1,21 @@
//
// SCCaptureSessionFixer.m
// Snapchat
//
// Created by Derek Wang on 05/12/2017.
//
#import "SCCaptureSessionFixer.h"
#import "SCCameraTweaks.h"
@implementation SCCaptureSessionFixer
- (void)detector:(SCBlackCameraNoOutputDetector *)detector didDetectBlackCamera:(id<SCCapturer>)capture
{
if (SCCameraTweaksBlackCameraRecoveryEnabled()) {
[capture recreateAVCaptureSession];
}
}
@end

View File

@ -0,0 +1,16 @@
//
// SCContextAwareSnapCreationThrottleRequest.h
// SCCamera
//
// Created by Cheng Jiang on 4/24/18.
//
#import <SCFoundation/SCContextAwareThrottleRequester.h>
#import <Foundation/Foundation.h>
@interface SCContextAwareSnapCreationThrottleRequest : NSObject <SCContextAwareThrottleRequest>
- (instancetype)init;
@end

View File

@ -0,0 +1,70 @@
//
// SCContextAwareSnapCreationThrottleRequest.m
// SCCamera
//
// Created by Cheng Jiang on 4/24/18.
//
#import "SCContextAwareSnapCreationThrottleRequest.h"
#import <SCFoundation/SCAssertWrapper.h>
#import <SCFoundation/SCContextAwareTaskManagementResourceProvider.h>
#import <SCFoundation/SCZeroDependencyExperiments.h>
#import <Tweaks/FBTweakInline.h>
BOOL SCCATMSnapCreationEnabled(void)
{
static dispatch_once_t capturingOnceToken;
static BOOL capturingImprovementEnabled;
dispatch_once(&capturingOnceToken, ^{
BOOL enabledWithAB = SCExperimentWithContextAwareTaskManagementCapturingImprovementEnabled();
NSInteger tweakOption = [FBTweakValue(@"CATM", @"Performance Improvement", @"Capturing", (id) @0,
(@{ @0 : @"Respect A/B",
@1 : @"YES",
@2 : @"NO" })) integerValue];
switch (tweakOption) {
case 0:
capturingImprovementEnabled = enabledWithAB;
break;
case 1:
capturingImprovementEnabled = YES;
break;
case 2:
capturingImprovementEnabled = NO;
break;
default:
SCCAssertFail(@"Illegal option");
}
});
return capturingImprovementEnabled;
}
@implementation SCContextAwareSnapCreationThrottleRequest {
NSString *_requestID;
}
- (instancetype)init
{
if (self = [super init]) {
_requestID = @"SCContextAwareSnapCreationThrottleRequest";
}
return self;
}
- (BOOL)shouldThrottle:(SCApplicationContextState)context
{
return SCCATMSnapCreationEnabled() && context != SCApplicationContextStateCamera;
}
- (NSString *)requestID
{
return _requestID;
}
- (BOOL)isEqual:(id<SCContextAwareThrottleRequest>)object
{
return [[object requestID] isEqualToString:_requestID];
}
@end

View File

@ -0,0 +1,22 @@
//
// SCSnapCreationTriggers.h
// Snapchat
//
// Created by Cheng Jiang on 4/1/18.
//
#import <Foundation/Foundation.h>
@interface SCSnapCreationTriggers : NSObject
- (void)markSnapCreationStart;
- (void)markSnapCreationPreviewAnimationFinish;
- (void)markSnapCreationPreviewImageSetupFinish;
- (void)markSnapCreationPreviewVideoFirstFrameRenderFinish;
- (void)markSnapCreationEndWithContext:(NSString *)context;
@end

View File

@ -0,0 +1,83 @@
//
// SCSnapCreationTriggers.m
// Snapchat
//
// Created by Cheng Jiang on 3/30/18.
//
#import "SCSnapCreationTriggers.h"
#import "SCContextAwareSnapCreationThrottleRequest.h"
#import <SCBase/SCMacros.h>
#import <SCFoundation/SCContextAwareThrottleRequester.h>
#import <SCFoundation/SCLog.h>
#import <SCFoundation/SCQueuePerformer.h>
@implementation SCSnapCreationTriggers {
BOOL _snapCreationStarted;
BOOL _previewAnimationFinished;
BOOL _previewImageSetupFinished;
BOOL _previewVideoFirstFrameRendered;
}
- (void)markSnapCreationStart
{
SC_GUARD_ELSE_RUN_AND_RETURN(
!_snapCreationStarted,
SCLogCoreCameraWarning(@"markSnapCreationStart skipped because previous SnapCreation session is not complete"));
@synchronized(self)
{
_snapCreationStarted = YES;
}
[[SCContextAwareThrottleRequester shared] submitSuspendRequest:[SCContextAwareSnapCreationThrottleRequest new]];
}
- (void)markSnapCreationPreviewAnimationFinish
{
@synchronized(self)
{
_previewAnimationFinished = YES;
if (_previewImageSetupFinished || _previewVideoFirstFrameRendered) {
[self markSnapCreationEndWithContext:@"markSnapCreationPreviewAnimationFinish"];
}
}
}
- (void)markSnapCreationPreviewImageSetupFinish
{
@synchronized(self)
{
_previewImageSetupFinished = YES;
if (_previewAnimationFinished) {
[self markSnapCreationEndWithContext:@"markSnapCreationPreviewImageSetupFinish"];
}
}
}
- (void)markSnapCreationPreviewVideoFirstFrameRenderFinish
{
@synchronized(self)
{
_previewVideoFirstFrameRendered = YES;
if (_previewAnimationFinished) {
[self markSnapCreationEndWithContext:@"markSnapCreationPreviewVideoFirstFrameRenderFinish"];
}
}
}
- (void)markSnapCreationEndWithContext:(NSString *)context
{
SC_GUARD_ELSE_RETURN(_snapCreationStarted);
SCLogCoreCameraInfo(@"markSnapCreationEnd triggered with context: %@", context);
@synchronized(self)
{
_snapCreationStarted = NO;
_previewAnimationFinished = NO;
_previewImageSetupFinished = NO;
_previewVideoFirstFrameRendered = NO;
}
[[SCContextAwareThrottleRequester shared] submitResumeRequest:[SCContextAwareSnapCreationThrottleRequest new]];
}
@end