blob: 1962a6b74579b7c172548d628bc64100633c61eb [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@import camera_avfoundation;
@import camera_avfoundation.Test;
@import XCTest;
@import AVFoundation;
#import <OCMock/OCMock.h>
#import "CameraTestUtils.h"
static const FCPPlatformResolutionPreset gTestResolutionPreset = FCPPlatformResolutionPresetMedium;
static const int gTestFramesPerSecond = 15;
static const int gTestVideoBitrate = 200000;
static const int gTestAudioBitrate = 32000;
static const BOOL gTestEnableAudio = YES;
@interface CameraSettingsTests : XCTestCase
@end
/**
* A test implemetation of `FLTCamMediaSettingsAVWrapper`
*
* This xctest-expectation-checking implementation of `FLTCamMediaSettingsAVWrapper` is injected
* into `camera-avfoundation` plugin instead of real AVFoundation-based realization.
* Such kind of Dependency Injection (DI) allows to run media-settings tests without
* any additional mocking of AVFoundation classes.
*/
@interface TestMediaSettingsAVWrapper : FLTCamMediaSettingsAVWrapper
@property(nonatomic, readonly) XCTestExpectation *lockExpectation;
@property(nonatomic, readonly) XCTestExpectation *unlockExpectation;
@property(nonatomic, readonly) XCTestExpectation *minFrameDurationExpectation;
@property(nonatomic, readonly) XCTestExpectation *maxFrameDurationExpectation;
@property(nonatomic, readonly) XCTestExpectation *beginConfigurationExpectation;
@property(nonatomic, readonly) XCTestExpectation *commitConfigurationExpectation;
@property(nonatomic, readonly) XCTestExpectation *audioSettingsExpectation;
@property(nonatomic, readonly) XCTestExpectation *videoSettingsExpectation;
@end
@implementation TestMediaSettingsAVWrapper
- (instancetype)initWithTestCase:(XCTestCase *)test {
_lockExpectation = [test expectationWithDescription:@"lockExpectation"];
_unlockExpectation = [test expectationWithDescription:@"unlockExpectation"];
_minFrameDurationExpectation = [test expectationWithDescription:@"minFrameDurationExpectation"];
_maxFrameDurationExpectation = [test expectationWithDescription:@"maxFrameDurationExpectation"];
_beginConfigurationExpectation =
[test expectationWithDescription:@"beginConfigurationExpectation"];
_commitConfigurationExpectation =
[test expectationWithDescription:@"commitConfigurationExpectation"];
_audioSettingsExpectation = [test expectationWithDescription:@"audioSettingsExpectation"];
_videoSettingsExpectation = [test expectationWithDescription:@"videoSettingsExpectation"];
return self;
}
- (BOOL)lockDevice:(AVCaptureDevice *)captureDevice error:(NSError **)outError {
[_lockExpectation fulfill];
return YES;
}
- (void)unlockDevice:(AVCaptureDevice *)captureDevice {
[_unlockExpectation fulfill];
}
- (void)beginConfigurationForSession:(AVCaptureSession *)videoCaptureSession {
[_beginConfigurationExpectation fulfill];
}
- (void)commitConfigurationForSession:(AVCaptureSession *)videoCaptureSession {
[_commitConfigurationExpectation fulfill];
}
- (void)setMinFrameDuration:(CMTime)duration onDevice:(AVCaptureDevice *)captureDevice {
// FLTCam allows to set frame rate with 1/10 precision.
CMTime expectedDuration = CMTimeMake(10, gTestFramesPerSecond * 10);
if (duration.value == expectedDuration.value &&
duration.timescale == expectedDuration.timescale) {
[_minFrameDurationExpectation fulfill];
}
}
- (void)setMaxFrameDuration:(CMTime)duration onDevice:(AVCaptureDevice *)captureDevice {
// FLTCam allows to set frame rate with 1/10 precision.
CMTime expectedDuration = CMTimeMake(10, gTestFramesPerSecond * 10);
if (duration.value == expectedDuration.value &&
duration.timescale == expectedDuration.timescale) {
[_maxFrameDurationExpectation fulfill];
}
}
- (AVAssetWriterInput *)assetWriterAudioInputWithOutputSettings:
(nullable NSDictionary<NSString *, id> *)outputSettings {
if ([outputSettings[AVEncoderBitRateKey] isEqual:@(gTestAudioBitrate)]) {
[_audioSettingsExpectation fulfill];
}
return [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio
outputSettings:outputSettings];
}
- (AVAssetWriterInput *)assetWriterVideoInputWithOutputSettings:
(nullable NSDictionary<NSString *, id> *)outputSettings {
if ([outputSettings[AVVideoCompressionPropertiesKey] isKindOfClass:[NSMutableDictionary class]]) {
NSDictionary *compressionProperties = outputSettings[AVVideoCompressionPropertiesKey];
if ([compressionProperties[AVVideoAverageBitRateKey] isEqual:@(gTestVideoBitrate)] &&
[compressionProperties[AVVideoExpectedSourceFrameRateKey]
isEqual:@(gTestFramesPerSecond)]) {
[_videoSettingsExpectation fulfill];
}
}
return [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
outputSettings:outputSettings];
}
- (void)addInput:(AVAssetWriterInput *)writerInput toAssetWriter:(AVAssetWriter *)writer {
}
- (NSDictionary<NSString *, id> *)
recommendedVideoSettingsForAssetWriterWithFileType:(AVFileType)fileType
forOutput:(AVCaptureVideoDataOutput *)output {
return @{};
}
@end
@implementation CameraSettingsTests
/// Expect that FPS, video and audio bitrate are passed to camera device and asset writer.
- (void)testSettings_shouldPassConfigurationToCameraDeviceAndWriter {
FCPPlatformMediaSettings *settings =
[FCPPlatformMediaSettings makeWithResolutionPreset:gTestResolutionPreset
framesPerSecond:@(gTestFramesPerSecond)
videoBitrate:@(gTestVideoBitrate)
audioBitrate:@(gTestAudioBitrate)
enableAudio:gTestEnableAudio];
TestMediaSettingsAVWrapper *injectedWrapper =
[[TestMediaSettingsAVWrapper alloc] initWithTestCase:self];
FLTCam *camera = FLTCreateCamWithCaptureSessionQueueAndMediaSettings(
dispatch_queue_create("test", NULL), settings, injectedWrapper);
// Expect FPS configuration is passed to camera device.
[self waitForExpectations:@[
injectedWrapper.lockExpectation, injectedWrapper.beginConfigurationExpectation,
injectedWrapper.minFrameDurationExpectation, injectedWrapper.maxFrameDurationExpectation,
injectedWrapper.commitConfigurationExpectation, injectedWrapper.unlockExpectation
]
timeout:1
enforceOrder:YES];
[camera
startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) {
}
messengerForStreaming:nil];
[self waitForExpectations:@[
injectedWrapper.audioSettingsExpectation, injectedWrapper.videoSettingsExpectation
]
timeout:1];
}
- (void)testSettings_ShouldBeSupportedByMethodCall {
CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil];
XCTestExpectation *expectation = [self expectationWithDescription:@"Result finished"];
// Set up mocks for initWithCameraName method
id avCaptureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]);
OCMStub([avCaptureDeviceInputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg anyObjectRef]])
.andReturn([AVCaptureInput alloc]);
id avCaptureSessionMock = OCMClassMock([AVCaptureSession class]);
OCMStub([avCaptureSessionMock alloc]).andReturn(avCaptureSessionMock);
OCMStub([avCaptureSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES);
// Set up method call
FCPPlatformMediaSettings *mediaSettings =
[FCPPlatformMediaSettings makeWithResolutionPreset:gTestResolutionPreset
framesPerSecond:@(gTestFramesPerSecond)
videoBitrate:@(gTestVideoBitrate)
audioBitrate:@(gTestAudioBitrate)
enableAudio:gTestEnableAudio];
__block NSNumber *resultValue;
[camera createCameraOnSessionQueueWithName:@"acamera"
settings:mediaSettings
completion:^(NSNumber *result, FlutterError *error) {
XCTAssertNil(error);
resultValue = result;
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
// Verify the result
XCTAssertNotNil(resultValue);
}
@end