Reland "Add file capturer to AppRTCMobile on simulator."
This is a reland of 5adcd198752b651f7b7e9199a91f9b873b7d7237
Original change's description:
> Add file capturer to AppRTCMobile on simulator.
>
> To achieve this, the CL does the following
> - Adds sample mp4 video
> - Refactors the existing RTCFileVideoCapturer to achieve continious
> capture and adds tests.
>
> Bug: webrtc:8406
> Change-Id: Ibc0891176c58ec9053b42e340d2113036e7199ec
> Reviewed-on: https://webrtc-review.googlesource.com/12180
> Reviewed-by: Anders Carlsson <andersc@webrtc.org>
> Reviewed-by: Magnus Jedvert <magjed@webrtc.org>
> Commit-Queue: Daniela Jovanoska Petrenko <denicija@webrtc.org>
> Cr-Commit-Position: refs/heads/master@{#20598}
Bug: webrtc:8406
Change-Id: I93be89b86e342a9a8195e19ebaf4aef1410d2c20
Reviewed-on: https://webrtc-review.googlesource.com/23200
Reviewed-by: Daniela Jovanoska Petrenko <denicija@webrtc.org>
Reviewed-by: Magnus Jedvert <magjed@webrtc.org>
Commit-Queue: Daniela Jovanoska Petrenko <denicija@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20870}
diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn
index 3d68dd9..0d03dc2 100644
--- a/sdk/BUILD.gn
+++ b/sdk/BUILD.gn
@@ -282,8 +282,8 @@
]
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 (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 @@
]
}
+ 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 @@
"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.h b/sdk/objc/Framework/Classes/PeerConnection/RTCFileVideoCapturer.h
deleted file mode 100644
index c8793ca..0000000
--- a/sdk/objc/Framework/Classes/PeerConnection/RTCFileVideoCapturer.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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 <Foundation/Foundation.h>
-#import <WebRTC/RTCVideoCapturer.h>
-
-/**
- * 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.
- */
-NS_CLASS_AVAILABLE_IOS(10)
-@interface RTCFileVideoCapturer : RTCVideoCapturer
-
-- (void)startCapturingFromFileNamed:(NSString *)nameOfFile;
-- (void)stopCapture;
-
-@end
diff --git a/sdk/objc/Framework/Classes/PeerConnection/RTCFileVideoCapturer.m b/sdk/objc/Framework/Classes/PeerConnection/RTCFileVideoCapturer.m
index 178a958..07cb2c6 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 0000000..7b898e4
--- /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 <Foundation/Foundation.h>
+#import <WebRTC/RTCVideoCapturer.h>
+
+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 0000000..f0c1739
--- /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 <XCTest/XCTest.h>
+
+#include "rtc_base/gunit.h"
+
+NSString *const kTestFileName = @"foreman.mp4";
+static const int kTestTimeoutMs = 5 * 1000; // 5secs.
+
+@interface MockCapturerDelegate : NSObject <RTCVideoCapturerDelegate>
+
+@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 0000000..ccffbf4
--- /dev/null
+++ b/sdk/objc/Framework/UnitTests/foreman.mp4
Binary files differ