diff --git a/examples/BUILD.gn b/examples/BUILD.gn index 740ebe5e90..eef20d4bb8 100644 --- a/examples/BUILD.gn +++ b/examples/BUILD.gn @@ -249,6 +249,8 @@ if (is_ios || (is_mac && target_cpu != "x86")) { testonly = true sources = [ "objc/AppRTCMobile/ios/ARDAppDelegate.m", + "objc/AppRTCMobile/ios/ARDFileCaptureController.h", + "objc/AppRTCMobile/ios/ARDFileCaptureController.m", "objc/AppRTCMobile/ios/ARDMainView.h", "objc/AppRTCMobile/ios/ARDMainView.m", "objc/AppRTCMobile/ios/ARDMainViewController.h", @@ -311,6 +313,9 @@ if (is_ios || (is_mac && target_cpu != "x86")) { bundle_data("AppRTCMobile_ios_bundle_data") { sources = [ "objc/AppRTCMobile/ios/resources/Roboto-Regular.ttf", + + # Sample video taken from https://media.xiph.org/video/derf/ + "objc/AppRTCMobile/ios/resources/foreman.mp4", "objc/AppRTCMobile/ios/resources/iPhone5@2x.png", "objc/AppRTCMobile/ios/resources/iPhone6@2x.png", "objc/AppRTCMobile/ios/resources/iPhone6p@3x.png", @@ -420,6 +425,7 @@ if (is_ios || (is_mac && target_cpu != "x86")) { testonly = true sources = [ "objc/AppRTCMobile/tests/ARDAppClient_xctest.mm", + "objc/AppRTCMobile/tests/ARDFileCaptureController_xctest.mm", "objc/AppRTCMobile/tests/ARDSettingsModel_xctest.mm", ] deps = [ diff --git a/examples/objc/AppRTCMobile/ARDAppClient.h b/examples/objc/AppRTCMobile/ARDAppClient.h index 8c27b345c6..5054c28676 100644 --- a/examples/objc/AppRTCMobile/ARDAppClient.h +++ b/examples/objc/AppRTCMobile/ARDAppClient.h @@ -9,8 +9,6 @@ */ #import - -#import "WebRTC/RTCCameraVideoCapturer.h" #import "WebRTC/RTCPeerConnection.h" #import "WebRTC/RTCVideoTrack.h" @@ -26,6 +24,8 @@ typedef NS_ENUM(NSInteger, ARDAppClientState) { @class ARDAppClient; @class ARDSettingsModel; @class RTCMediaConstraints; +@class RTCCameraVideoCapturer; +@class RTCFileVideoCapturer; // The delegate is informed of pertinent events and will be called on the // main queue. @@ -52,6 +52,10 @@ typedef NS_ENUM(NSInteger, ARDAppClientState) { - (void)appClient:(ARDAppClient *)client didGetStats:(NSArray *)stats; +@optional +- (void)appClient:(ARDAppClient *)client +didCreateLocalFileCapturer:(RTCFileVideoCapturer *)fileCapturer; + @end // Handles connections to the AppRTC server for a given room. Methods on this diff --git a/examples/objc/AppRTCMobile/ARDAppClient.m b/examples/objc/AppRTCMobile/ARDAppClient.m index f753828f75..10be40a89e 100644 --- a/examples/objc/AppRTCMobile/ARDAppClient.m +++ b/examples/objc/AppRTCMobile/ARDAppClient.m @@ -15,6 +15,7 @@ #import "WebRTC/RTCCameraVideoCapturer.h" #import "WebRTC/RTCConfiguration.h" #import "WebRTC/RTCFileLogger.h" +#import "WebRTC/RTCFileVideoCapturer.h" #import "WebRTC/RTCIceServer.h" #import "WebRTC/RTCLogging.h" #import "WebRTC/RTCMediaConstraints.h" @@ -690,21 +691,26 @@ static int const kKbpsMultiplier = 1000; } - (RTCVideoTrack *)createLocalVideoTrack { - RTCVideoTrack* localVideoTrack = nil; - // The iOS simulator doesn't provide any sort of camera capture - // support or emulation (http://goo.gl/rHAnC1) so don't bother - // trying to open a local stream. + if ([_settings currentAudioOnlySettingFromStore]) { + return nil; + } + + RTCVideoSource *source = [_factory videoSource]; + #if !TARGET_IPHONE_SIMULATOR - if (![_settings currentAudioOnlySettingFromStore]) { - RTCVideoSource *source = [_factory videoSource]; - RTCCameraVideoCapturer *capturer = [[RTCCameraVideoCapturer alloc] initWithDelegate:source]; - [_delegate appClient:self didCreateLocalCapturer:capturer]; - localVideoTrack = - [_factory videoTrackWithSource:source - trackId:kARDVideoTrackId]; + RTCCameraVideoCapturer *capturer = [[RTCCameraVideoCapturer alloc] initWithDelegate:source]; + [_delegate appClient:self didCreateLocalCapturer:capturer]; + +#else +#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0) + if (@available(iOS 10, *)) { + RTCFileVideoCapturer *fileCapturer = [[RTCFileVideoCapturer alloc] initWithDelegate:source]; + [_delegate appClient:self didCreateLocalFileCapturer:fileCapturer]; } #endif - return localVideoTrack; +#endif + + return [_factory videoTrackWithSource:source trackId:kARDVideoTrackId]; } #pragma mark - Collider methods diff --git a/examples/objc/AppRTCMobile/ARDSettingsModel.m b/examples/objc/AppRTCMobile/ARDSettingsModel.m index 0a2bee66e7..a4eea731d4 100644 --- a/examples/objc/AppRTCMobile/ARDSettingsModel.m +++ b/examples/objc/AppRTCMobile/ARDSettingsModel.m @@ -169,20 +169,12 @@ NS_ASSUME_NONNULL_BEGIN - (void)registerStoreDefaults { NSString *defaultVideoResolutionSetting = [self defaultVideoResolutionSetting]; - BOOL audioOnly = (defaultVideoResolutionSetting.length == 0); - -// The iOS simulator doesn't provide any sort of camera capture -// support or emulation (http://goo.gl/rHAnC1) so don't bother -// trying to open a local stream. -#if TARGET_IPHONE_SIMULATOR - audioOnly = YES; -#endif NSData *codecData = [NSKeyedArchiver archivedDataWithRootObject:[self defaultVideoCodecSetting]]; [ARDSettingsStore setDefaultsForVideoResolution:[self defaultVideoResolutionSetting] videoCodec:codecData bitrate:nil - audioOnly:audioOnly + audioOnly:NO createAecDump:NO useLevelController:NO useManualAudioConfig:YES]; diff --git a/sdk/objc/Framework/Classes/PeerConnection/RTCFileVideoCapturer.h b/examples/objc/AppRTCMobile/ios/ARDFileCaptureController.h similarity index 54% rename from sdk/objc/Framework/Classes/PeerConnection/RTCFileVideoCapturer.h rename to examples/objc/AppRTCMobile/ios/ARDFileCaptureController.h index c8793ca172..7e0387de3c 100644 --- a/sdk/objc/Framework/Classes/PeerConnection/RTCFileVideoCapturer.h +++ b/examples/objc/AppRTCMobile/ios/ARDFileCaptureController.h @@ -9,18 +9,32 @@ */ #import -#import + +@class RTCFileVideoCapturer; /** - * RTCVideoCapturer that reads buffers from file. - * - * Per design, the file capturer can only be run once and once stopped it cannot run again. - * To run another file capture session, create new instance of the class. + * Controls a file capturer. */ NS_CLASS_AVAILABLE_IOS(10) -@interface RTCFileVideoCapturer : RTCVideoCapturer +@interface ARDFileCaptureController : NSObject -- (void)startCapturingFromFileNamed:(NSString *)nameOfFile; +/** + * Creates instance of the controller. + * + * @param capturer The capturer to be controlled. + */ +- (instancetype)initWithCapturer:(RTCFileVideoCapturer *)capturer; + +/** + * Starts the file capturer. + * + * Possible errors produced by the capturer will be logged. + */ +- (void)startCapture; + +/** + * Immediately stops capturer. + */ - (void)stopCapture; @end diff --git a/examples/objc/AppRTCMobile/ios/ARDFileCaptureController.m b/examples/objc/AppRTCMobile/ios/ARDFileCaptureController.m new file mode 100644 index 0000000000..d61bfe2d98 --- /dev/null +++ b/examples/objc/AppRTCMobile/ios/ARDFileCaptureController.m @@ -0,0 +1,45 @@ +/* + * Copyright 2017 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import "ARDFileCaptureController.h" + +#import "WebRTC/RTCFileVideoCapturer.h" + +@interface ARDFileCaptureController () + +@property(nonatomic, strong) RTCFileVideoCapturer *fileCapturer; + +@end + +@implementation ARDFileCaptureController +@synthesize fileCapturer = _fileCapturer; + +- (instancetype)initWithCapturer:(RTCFileVideoCapturer *)capturer { + if (self = [super init]) { + _fileCapturer = capturer; + } + return self; +} + +- (void)startCapture { + [self startFileCapture]; +} + +- (void)startFileCapture { + [self.fileCapturer startCapturingFromFileNamed:@"foreman.mp4" + onError:^(NSError *_Nonnull error) { + NSLog(@"Error %@", error.userInfo); + }]; +} + +- (void)stopCapture { + [self.fileCapturer stopCapture]; +} +@end diff --git a/examples/objc/AppRTCMobile/ios/ARDVideoCallViewController.m b/examples/objc/AppRTCMobile/ios/ARDVideoCallViewController.m index 3deb067a68..bf349ceea9 100644 --- a/examples/objc/AppRTCMobile/ios/ARDVideoCallViewController.m +++ b/examples/objc/AppRTCMobile/ios/ARDVideoCallViewController.m @@ -11,9 +11,11 @@ #import "ARDVideoCallViewController.h" #import "WebRTC/RTCAudioSession.h" +#import "WebRTC/RTCCameraVideoCapturer.h" #import "ARDAppClient.h" #import "ARDCaptureController.h" +#import "ARDFileCaptureController.h" #import "ARDSettingsModel.h" #import "ARDVideoCallView.h" #import "WebRTC/RTCAVFoundationVideoSource.h" @@ -32,6 +34,7 @@ ARDAppClient *_client; RTCVideoTrack *_remoteVideoTrack; ARDCaptureController *_captureController; + ARDFileCaptureController *_fileCaptureController NS_AVAILABLE_IOS(10); AVAudioSessionPortOverride _portOverride; } @@ -105,6 +108,16 @@ [_captureController startCapture]; } +- (void)appClient:(ARDAppClient *)client + didCreateLocalFileCapturer:(RTCFileVideoCapturer *)fileCapturer { +#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0) + if (@available(iOS 10, *)) { + _fileCaptureController = [[ARDFileCaptureController alloc] initWithCapturer:fileCapturer]; + [_fileCaptureController startCapture]; + } +#endif +} + - (void)appClient:(ARDAppClient *)client didReceiveLocalVideoTrack:(RTCVideoTrack *)localVideoTrack { } @@ -191,6 +204,8 @@ _videoCallView.localVideoView.captureSession = nil; [_captureController stopCapture]; _captureController = nil; + [_fileCaptureController stopCapture]; + _fileCaptureController = nil; [_client disconnect]; [_delegate viewControllerDidFinish:self]; } diff --git a/examples/objc/AppRTCMobile/ios/resources/foreman.mp4 b/examples/objc/AppRTCMobile/ios/resources/foreman.mp4 new file mode 100644 index 0000000000..ccffbf4722 Binary files /dev/null and b/examples/objc/AppRTCMobile/ios/resources/foreman.mp4 differ diff --git a/examples/objc/AppRTCMobile/tests/ARDFileCaptureController_xctest.mm b/examples/objc/AppRTCMobile/tests/ARDFileCaptureController_xctest.mm new file mode 100644 index 0000000000..e734f102d4 --- /dev/null +++ b/examples/objc/AppRTCMobile/tests/ARDFileCaptureController_xctest.mm @@ -0,0 +1,62 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import +#import + +#import "ARDFileCaptureController.h" + +#import "WebRTC/RTCFileVideoCapturer.h" + +NS_CLASS_AVAILABLE_IOS(10) +@interface ARDFileCaptureControllerTests : XCTestCase + +@property(nonatomic, strong) ARDFileCaptureController *fileCaptureController; +@property(nonatomic, strong) id fileCapturerMock; + +@end + +@implementation ARDFileCaptureControllerTests + +@synthesize fileCaptureController = _fileCaptureController; +@synthesize fileCapturerMock = _fileCapturerMock; + +- (void)setUp { + [super setUp]; + self.fileCapturerMock = OCMClassMock([RTCFileVideoCapturer class]); + self.fileCaptureController = + [[ARDFileCaptureController alloc] initWithCapturer:self.fileCapturerMock]; +} + +- (void)tearDown { + self.fileCaptureController = nil; + [self.fileCapturerMock stopMocking]; + self.fileCapturerMock = nil; + [super tearDown]; +} + +- (void)testCaptureIsStarted { + [[self.fileCapturerMock expect] startCapturingFromFileNamed:[OCMArg any] onError:[OCMArg any]]; + + [self.fileCaptureController startCapture]; + + [self.fileCapturerMock verify]; +} + +- (void)testCaptureIsStoped { + [[self.fileCapturerMock expect] stopCapture]; + + [self.fileCaptureController stopCapture]; + + [self.fileCapturerMock verify]; +} + +@end diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index 3d68dd9b80..0d03dc2ea3 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -282,8 +282,8 @@ if (is_ios || is_mac) { ] if (is_ios) { sources += [ - "objc/Framework/Classes/PeerConnection/RTCFileVideoCapturer.h", "objc/Framework/Classes/PeerConnection/RTCFileVideoCapturer.m", + "objc/Framework/Headers/WebRTC/RTCFileVideoCapturer.h", ] } libs = [ "AVFoundation.framework" ] @@ -529,20 +529,22 @@ if (is_ios || is_mac) { } if (rtc_include_tests) { - # TODO(denicija):remove second part of this check. - if (is_ios && (current_cpu == "arm64" || use_ios_simulator)) { + if (is_ios) { rtc_source_set("sdk_unittests_sources") { testonly = true include_dirs = [ "objc/Framework/Headers", "objc/Framework/Classes", ] + sources = [ - # TODO(denicija): Once more sources are included, - # move the second part of the check on line 516 here - # when adding this file to the sources - "objc/Framework/UnitTests/RTCMTLVideoView_xctest.mm", + "objc/Framework/UnitTests/RTCFileVideoCapturer_xctest.mm", ] + + if (current_cpu == "arm64" || use_ios_simulator) { + sources += [ "objc/Framework/UnitTests/RTCMTLVideoView_xctest.mm" ] + } + if (use_ios_simulator) { # Only include this file on simulator, as it's already # included in device builds. @@ -568,14 +570,26 @@ if (is_ios || is_mac) { ] } + bundle_data("sdk_unittests_bundle_data") { + # Sample video taken from https://media.xiph.org/video/derf/ + sources = [ + "objc/Framework/UnitTests/foreman.mp4", + ] + outputs = [ + "{{bundle_resources_dir}}/{{source_file_part}}", + ] + } + rtc_ios_xctest_test("sdk_unittests") { info_plist = "//test/ios/Info.plist" sources = [ "objc/Framework/UnitTests/main.m", ] + _bundle_id_suffix = ios_generic_test_bundle_id_suffix extra_substitutions = [ "GTEST_BUNDLE_ID_SUFFIX=$_bundle_id_suffix" ] deps = [ + ":sdk_unittests_bundle_data", ":sdk_unittests_sources", ] ldflags = [ "-all_load" ] @@ -666,6 +680,7 @@ if (is_ios || is_mac) { "objc/Framework/Headers/WebRTC/RTCDispatcher.h", "objc/Framework/Headers/WebRTC/RTCEAGLVideoView.h", "objc/Framework/Headers/WebRTC/RTCFieldTrials.h", + "objc/Framework/Headers/WebRTC/RTCFileVideoCapturer.h", "objc/Framework/Headers/WebRTC/RTCIceCandidate.h", "objc/Framework/Headers/WebRTC/RTCIceServer.h", "objc/Framework/Headers/WebRTC/RTCIntervalRange.h", diff --git a/sdk/objc/Framework/Classes/PeerConnection/RTCFileVideoCapturer.m b/sdk/objc/Framework/Classes/PeerConnection/RTCFileVideoCapturer.m index 178a958041..07cb2c6668 100644 --- a/sdk/objc/Framework/Classes/PeerConnection/RTCFileVideoCapturer.m +++ b/sdk/objc/Framework/Classes/PeerConnection/RTCFileVideoCapturer.m @@ -8,60 +8,91 @@ * be found in the AUTHORS file in the root of the source tree. */ -#import "RTCFileVideoCapturer.h" +#import "WebRTC/RTCFileVideoCapturer.h" #import "WebRTC/RTCLogging.h" #import "WebRTC/RTCVideoFrameBuffer.h" +NSString *const kRTCFileVideoCapturerErrorDomain = @"org.webrtc.RTCFileVideoCapturer"; + +typedef NS_ENUM(NSInteger, RTCFileVideoCapturerErrorCode) { + RTCFileVideoCapturerErrorCode_CapturerRunning = 2000, + RTCFileVideoCapturerErrorCode_FileNotFound +}; + +typedef NS_ENUM(NSInteger, RTCFileVideoCapturerStatus) { + RTCFileVideoCapturerStatusNotInitialized, + RTCFileVideoCapturerStatusStarted, + RTCFileVideoCapturerStatusStopped +}; + @implementation RTCFileVideoCapturer { AVAssetReader *_reader; AVAssetReaderTrackOutput *_outTrack; - BOOL _capturerStopped; + RTCFileVideoCapturerStatus _status; CMTime _lastPresentationTime; dispatch_queue_t _frameQueue; + NSURL *_fileURL; } -- (void)startCapturingFromFileNamed:(NSString *)nameOfFile { +- (void)startCapturingFromFileNamed:(NSString *)nameOfFile + onError:(RTCFileVideoCapturerErrorBlock)errorBlock { + if (_status == RTCFileVideoCapturerStatusStarted) { + NSError *error = + [NSError errorWithDomain:kRTCFileVideoCapturerErrorDomain + code:RTCFileVideoCapturerErrorCode_CapturerRunning + userInfo:@{NSUnderlyingErrorKey : @"Capturer has been started."}]; + + errorBlock(error); + return; + } else { + _status = RTCFileVideoCapturerStatusStarted; + } + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - if (_reader && _reader.status == AVAssetReaderStatusReading) { - RTCLog("Capturer exists and reads another file. Start capture request failed."); - return; - } NSString *pathForFile = [self pathForFileName:nameOfFile]; if (!pathForFile) { - RTCLog("File %@ not found in bundle", nameOfFile); + NSString *errorString = + [NSString stringWithFormat:@"File %@ not found in bundle", nameOfFile]; + NSError *error = [NSError errorWithDomain:kRTCFileVideoCapturerErrorDomain + code:RTCFileVideoCapturerErrorCode_FileNotFound + userInfo:@{NSUnderlyingErrorKey : errorString}]; + errorBlock(error); return; } _lastPresentationTime = CMTimeMake(0, 0); - NSURL *URLForFile = [NSURL fileURLWithPath:pathForFile]; - AVURLAsset *asset = [AVURLAsset URLAssetWithURL:URLForFile options:nil]; - - NSArray *allTracks = [asset tracksWithMediaType:AVMediaTypeVideo]; - NSError *error = nil; - _reader = [[AVAssetReader alloc] initWithAsset:asset error:&error]; - if (error) { - RTCLog("File reader failed with error: %@", error); - return; - } - - NSDictionary *options = @{ - (NSString *) - kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) - }; - _outTrack = [[AVAssetReaderTrackOutput alloc] initWithTrack:allTracks.firstObject - outputSettings:options]; - [_reader addOutput:_outTrack]; - - [_reader startReading]; - RTCLog(@"File capturer started reading"); - [self readNextBuffer]; + _fileURL = [NSURL fileURLWithPath:pathForFile]; + [self setupReaderOnError:errorBlock]; }); } +- (void)setupReaderOnError:(RTCFileVideoCapturerErrorBlock)errorBlock { + AVURLAsset *asset = [AVURLAsset URLAssetWithURL:_fileURL options:nil]; + + NSArray *allTracks = [asset tracksWithMediaType:AVMediaTypeVideo]; + NSError *error = nil; + + _reader = [[AVAssetReader alloc] initWithAsset:asset error:&error]; + if (error) { + errorBlock(error); + return; + } + + NSDictionary *options = @{ + (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) + }; + _outTrack = + [[AVAssetReaderTrackOutput alloc] initWithTrack:allTracks.firstObject outputSettings:options]; + [_reader addOutput:_outTrack]; + + [_reader startReading]; + RTCLog(@"File capturer started reading"); + [self readNextBuffer]; +} - (void)stopCapture { - _capturerStopped = YES; + _status = RTCFileVideoCapturerStatusStopped; RTCLog(@"File capturer stopped."); } @@ -88,12 +119,19 @@ } - (void)readNextBuffer { - if (_reader.status != AVAssetReaderStatusReading || _capturerStopped) { + if (_status == RTCFileVideoCapturerStatusStopped) { [_reader cancelReading]; _reader = nil; return; } + if (_reader.status == AVAssetReaderStatusCompleted) { + [_reader cancelReading]; + _reader = nil; + [self setupReaderOnError:nil]; + return; + } + CMSampleBufferRef sampleBuffer = [_outTrack copyNextSampleBuffer]; if (!sampleBuffer) { [self readNextBuffer]; diff --git a/sdk/objc/Framework/Headers/WebRTC/RTCFileVideoCapturer.h b/sdk/objc/Framework/Headers/WebRTC/RTCFileVideoCapturer.h new file mode 100644 index 0000000000..7b898e494c --- /dev/null +++ b/sdk/objc/Framework/Headers/WebRTC/RTCFileVideoCapturer.h @@ -0,0 +1,50 @@ +/* + * Copyright 2017 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Error passing block. + */ +typedef void (^RTCFileVideoCapturerErrorBlock)(NSError *error); + +/** + * Captures buffers from bundled video file. + * + * See @c RTCVideoCapturer for more info on capturers. + */ +RTC_EXPORT + +NS_CLASS_AVAILABLE_IOS(10) +@interface RTCFileVideoCapturer : RTCVideoCapturer + +/** + * Starts asynchronous capture of frames from video file. + * + * Capturing is not started if error occurs. Underlying error will be + * relayed in the errorBlock if one is provided. + * Successfully captured video frames will be passed to the delegate. + * + * @param nameOfFile The name of the bundled video file to be read. + * @errorBlock block to be executed upon error. + */ +- (void)startCapturingFromFileNamed:(NSString *)nameOfFile + onError:(__nullable RTCFileVideoCapturerErrorBlock)errorBlock; + +/** + * Immediately stops capture. + */ +- (void)stopCapture; +@end + +NS_ASSUME_NONNULL_END diff --git a/sdk/objc/Framework/UnitTests/RTCFileVideoCapturer_xctest.mm b/sdk/objc/Framework/UnitTests/RTCFileVideoCapturer_xctest.mm new file mode 100644 index 0000000000..f0c173973f --- /dev/null +++ b/sdk/objc/Framework/UnitTests/RTCFileVideoCapturer_xctest.mm @@ -0,0 +1,113 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import "WebRTC/RTCFileVideoCapturer.h" + +#import + +#include "rtc_base/gunit.h" + +NSString *const kTestFileName = @"foreman.mp4"; +static const int kTestTimeoutMs = 5 * 1000; // 5secs. + +@interface MockCapturerDelegate : NSObject + +@property(nonatomic, assign) NSInteger capturedFramesCount; + +@end + +@implementation MockCapturerDelegate +@synthesize capturedFramesCount = _capturedFramesCount; + +- (void)capturer:(RTCVideoCapturer *)capturer didCaptureVideoFrame:(RTCVideoFrame *)frame { + self.capturedFramesCount++; +} + +@end + +NS_CLASS_AVAILABLE_IOS(10) +@interface RTCFileVideoCapturerTests : XCTestCase + +@property(nonatomic, strong) RTCFileVideoCapturer *capturer; +@property(nonatomic, strong) MockCapturerDelegate *mockDelegate; + +@end + +@implementation RTCFileVideoCapturerTests +@synthesize capturer = _capturer; +@synthesize mockDelegate = _mockDelegate; + +- (void)setUp { + self.mockDelegate = [[MockCapturerDelegate alloc] init]; + self.capturer = [[RTCFileVideoCapturer alloc] initWithDelegate:self.mockDelegate]; +} + +- (void)tearDown { + self.capturer = nil; + self.mockDelegate = nil; +} + +- (void)testCaptureWhenFileNotInBundle { + __block BOOL errorOccured = NO; + + RTCFileVideoCapturerErrorBlock errorBlock = ^void(NSError *error) { + errorOccured = YES; + }; + + [self.capturer startCapturingFromFileNamed:@"not_in_bundle.mov" onError:errorBlock]; + ASSERT_TRUE_WAIT(errorOccured, kTestTimeoutMs); +} + +- (void)testSecondStartCaptureCallFails { + __block BOOL secondError = NO; + + RTCFileVideoCapturerErrorBlock firstErrorBlock = ^void(NSError *error) { + // This block should never be called. + NSLog(@"Error: %@", [error userInfo]); + ASSERT_TRUE(false); + }; + + RTCFileVideoCapturerErrorBlock secondErrorBlock = ^void(NSError *error) { + secondError = YES; + }; + + [self.capturer startCapturingFromFileNamed:kTestFileName onError:firstErrorBlock]; + [self.capturer startCapturingFromFileNamed:kTestFileName onError:secondErrorBlock]; + + ASSERT_TRUE_WAIT(secondError, kTestTimeoutMs); +} + +- (void)testStartStopCapturer { +#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0) + if (@available(iOS 10, *)) { + [self.capturer startCapturingFromFileNamed:kTestFileName onError:nil]; + + __block BOOL done = NO; + __block NSInteger capturedFrames = -1; + NSInteger capturedFramesAfterStop = -1; + + // We're dispatching the `stopCapture` with delay to ensure the capturer has + // had the chance to capture several frames. + dispatch_time_t captureDelay = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); // 2secs. + dispatch_after(captureDelay, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + capturedFrames = self.mockDelegate.capturedFramesCount; + [self.capturer stopCapture]; + done = YES; + }); + WAIT(done, kTestTimeoutMs); + + capturedFramesAfterStop = self.mockDelegate.capturedFramesCount; + ASSERT_TRUE(capturedFrames != -1); + ASSERT_EQ(capturedFrames, capturedFramesAfterStop); + } +#endif +} + +@end diff --git a/sdk/objc/Framework/UnitTests/foreman.mp4 b/sdk/objc/Framework/UnitTests/foreman.mp4 new file mode 100644 index 0000000000..ccffbf4722 Binary files /dev/null and b/sdk/objc/Framework/UnitTests/foreman.mp4 differ