From 01ad812dd0e5e5b2588673b4e931a8255f06f4c0 Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Sun, 30 Jul 2023 16:22:03 +0300 Subject: [PATCH 01/21] Set higher resolution when using max preset --- .../camera_avfoundation/ios/Classes/FLTCam.m | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index 31bffc91794..9ef6e9a25e2 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -340,6 +340,25 @@ - (NSString *)getTemporaryFilePathWithExtension:(NSString *)extension - (void)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset { switch (resolutionPreset) { case FLTResolutionPresetMax: + { + AVCaptureDeviceFormat *bestFormat = [self getHighestResolutionFormatFor:_captureDevice]; + if ( bestFormat ) { + _videoCaptureSession.sessionPreset = AVCaptureSessionPresetInputPriority; + if ( [_captureDevice lockForConfiguration:NULL] == YES ) { + _captureDevice.activeFormat = bestFormat; + [_captureDevice unlockForConfiguration]; + _previewSize = + CGSizeMake(_captureDevice.activeFormat.highResolutionStillImageDimensions.width, + _captureDevice.activeFormat.highResolutionStillImageDimensions.height); + break; + } + } + } + if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset3840x2160]) { + _videoCaptureSession.sessionPreset = AVCaptureSessionPreset3840x2160; + _previewSize = CGSizeMake(3840, 2160); + break; + } case FLTResolutionPresetUltraHigh: if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset3840x2160]) { _videoCaptureSession.sessionPreset = AVCaptureSessionPreset3840x2160; @@ -395,6 +414,22 @@ - (void)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset { _audioCaptureSession.sessionPreset = _videoCaptureSession.sessionPreset; } +- (AVCaptureDeviceFormat *)getHighestResolutionFormatFor:(AVCaptureDevice*)captureDevice { + AVCaptureDeviceFormat *bestFormat = nil; + NSUInteger maxPixelCount = 0; + for ( AVCaptureDeviceFormat *format in [_captureDevice formats] ) { + CMVideoDimensions res = CMVideoFormatDescriptionGetDimensions(format.formatDescription); + NSUInteger height = res.height; + NSUInteger width = res.width; + NSUInteger pixelCount = height * width; + if ( pixelCount > maxPixelCount ) { + maxPixelCount = pixelCount; + bestFormat = format; + } + } + return bestFormat; +} + - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { From f9f302f1c6b5e9b141ad79ffa1fc76d1d0d0a402 Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Fri, 27 Oct 2023 17:49:27 +0300 Subject: [PATCH 02/21] chore: update changelog --- packages/camera/camera/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 81e8fd45b17..76a887a2384 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.5+6 + +* Fixes bug where max resolution preset does not produce highest available resolution on iOS. + ## 0.10.5+5 * Fixes bug where old camera resources were not disposed when switching between camera descriptions. From e5a33f63291b100e412ec262f09a8bf03c5293e8 Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Fri, 27 Oct 2023 17:54:02 +0300 Subject: [PATCH 03/21] chore: update changelog and pubspec --- packages/camera/camera_avfoundation/CHANGELOG.md | 4 ++++ packages/camera/camera_avfoundation/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 8f221842e40..ca668621987 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.13+7 + +* Fixes bug where max resolution preset does not produce highest available resolution on iOS. + ## 0.9.13+6 * Fixes incorrect use of `NSError` that could cause crashes on launch. diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index b4a4396926d..3de45b68908 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.13+6 +version: 0.9.13+7 environment: sdk: ">=2.19.0 <4.0.0" From d3ce411b9aa88dfd32e5a7b7f5f921f0c13943e2 Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Fri, 27 Oct 2023 18:00:10 +0300 Subject: [PATCH 04/21] refactor: add comments --- packages/camera/camera_avfoundation/ios/Classes/FLTCam.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index 02a33412235..66745411c16 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -353,8 +353,12 @@ - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError: if ( bestFormat ) { _videoCaptureSession.sessionPreset = AVCaptureSessionPresetInputPriority; if ( [_captureDevice lockForConfiguration:NULL] == YES ) { + + // set best device format and finish device configuration _captureDevice.activeFormat = bestFormat; [_captureDevice unlockForConfiguration]; + + // set preview size based on values from the current _captureDevice _previewSize = CGSizeMake(_captureDevice.activeFormat.highResolutionStillImageDimensions.width, _captureDevice.activeFormat.highResolutionStillImageDimensions.height); @@ -422,6 +426,7 @@ - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError: return YES; } +/// Finds the highest available resolution in terms of pixel count for the given device - (AVCaptureDeviceFormat *)getHighestResolutionFormatFor:(AVCaptureDevice*)captureDevice { AVCaptureDeviceFormat *bestFormat = nil; NSUInteger maxPixelCount = 0; From 5813f252b56cacf736db64137369f8cd0394bea7 Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Fri, 27 Oct 2023 18:13:34 +0300 Subject: [PATCH 05/21] chore: update pubspec --- packages/camera/camera/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index c246e4c232a..c6392651d62 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing Dart. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.5+5 +version: 0.10.5+6 environment: sdk: ">=2.19.0 <4.0.0" From 60e043ad06532992758be4343f57ba873d19b04a Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Mon, 22 Jan 2024 09:53:13 +0300 Subject: [PATCH 06/21] feat(tests): add convenience method for camera init --- .../example/ios/RunnerTests/CameraTestUtils.h | 6 ++++++ .../example/ios/RunnerTests/CameraTestUtils.m | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h index 0c7e62f9fbb..a6f5226f8ed 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h @@ -11,6 +11,12 @@ NS_ASSUME_NONNULL_BEGIN /// @return an FLTCam object. extern FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessionQueue); +/// Creates an `FLTCam` with a given captureSession and resolutionPreset +/// @param captureSession AVCaptureSession for video +/// @param resolutionPreset preset for camera's captureSession resolution +/// @return an FLTCam object. +extern FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSession, NSString *resolutionPreset); + /// Creates a test sample buffer. /// @return a test sample buffer. extern CMSampleBufferRef FLTCreateTestSampleBuffer(void); diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index bb98f7cf71e..45f7d7279bf 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -29,6 +29,25 @@ error:nil]; } +FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSession, NSString *resolutionPreset) { + id inputMock = OCMClassMock([AVCaptureDeviceInput class]); + OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) + .andReturn(inputMock); + + id audioSessionMock = OCMClassMock([AVCaptureSession class]); + OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); + + return [[FLTCam alloc] initWithCameraName:@"camera" + resolutionPreset:resolutionPreset + enableAudio:true + orientation:UIDeviceOrientationPortrait + videoCaptureSession:captureSession + audioCaptureSession:audioSessionMock + captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) + error:nil]; +} + CMSampleBufferRef FLTCreateTestSampleBuffer(void) { CVPixelBufferRef pixelBuffer; CVPixelBufferCreate(kCFAllocatorDefault, 100, 100, kCVPixelFormatType_32BGRA, NULL, &pixelBuffer); From 983040421b43f17119fb7300b6adfae69254cff0 Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Mon, 22 Jan 2024 09:55:49 +0300 Subject: [PATCH 07/21] feat(tests): add tests for setting resolutionPresets --- .../RunnerTests/CameraSessionPresetsTest.m | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m new file mode 100644 index 00000000000..be568bfc7b9 --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m @@ -0,0 +1,52 @@ +// 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 AVFoundation; +@import XCTest; +#import +#import "CameraTestUtils.h" + +/// Includes test cases related to photo capture operations for FLTCam class. +@interface FLTCamSessionPresetsTest : XCTestCase + +@end + +@implementation FLTCamSessionPresetsTest + +- (void)testResolutionPresetWithCanSetSessionPresetMax_mustUpdateCaptureSessionPreset { + NSString *expectedUltraHighPreset = AVCaptureSessionPreset3840x2160; + NSString *expectedMaxPreset = AVCaptureSessionPresetInputPriority; + + id videoSessionMock = OCMClassMock([AVCaptureSession class]); + OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); + OCMExpect([videoSessionMock setSessionPreset:[OCMArg checkWithBlock:^BOOL(id value) { + // Return YES if the value is one of the expected presets + return [value isEqualToString:expectedUltraHighPreset] || [value isEqualToString:expectedMaxPreset]; + }]]); + + + FLTCam *cam = FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"max"); + + OCMVerifyAll(videoSessionMock); +} + +- (void)testResolutionPresetWithCanSetSessionPresetUltraHigh_mustUpdateCaptureSessionPreset { //TODO + NSString *expectedPreset = AVCaptureSessionPreset3840x2160; + + id videoSessionMock = OCMClassMock([AVCaptureSession class]); + OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); + + // expect that setting "ultraHigh" resolutionPreset correctly updates videoCaptureSession + OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); + + FLTCam *cam = FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"ultraHigh"); + + OCMVerifyAll(videoSessionMock); +} + +@end From 87b0c5906d9475d6f38ad418eb906f491b8f4ebd Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Mon, 22 Jan 2024 10:11:37 +0300 Subject: [PATCH 08/21] chore(tests): add tests file to .pbxproj --- .../example/ios/Runner.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index 6006b9f2b4b..6fe7edae559 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + CEF6611A2B5E36A500D33FD4 /* CameraSessionPresetsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTest.m */; }; E01EE4A82799F3A5008C1950 /* QueueUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */; }; E032F250279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E032F24F279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m */; }; E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */; }; @@ -89,6 +90,7 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9C5CC6CAD53AD388B2694F3A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; A24F9E418BA48BCC7409B117 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTest.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = CameraSessionPresetsTest.m; sourceTree = ""; }; E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QueueUtilsTests.m; sourceTree = ""; }; E032F24F279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CameraCaptureSessionQueueRaceConditionTests.m; sourceTree = ""; }; E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTSavePhotoDelegateTests.m; sourceTree = ""; }; @@ -151,6 +153,7 @@ E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */, 788A065927B0E02900533D74 /* StreamingTest.m */, 43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */, + CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTest.m */, ); path = RunnerTests; sourceTree = ""; @@ -429,6 +432,7 @@ F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */, E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */, 334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */, + CEF6611A2B5E36A500D33FD4 /* CameraSessionPresetsTest.m in Sources */, E032F250279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m in Sources */, 788A065A27B0E02900533D74 /* StreamingTest.m in Sources */, E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */, From 0c29019125242862fd73e8ab4ce282c2bfe04e82 Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Mon, 22 Jan 2024 10:41:01 +0300 Subject: [PATCH 09/21] chore: fix formatting for updated files --- .../RunnerTests/CameraSessionPresetsTest.m | 23 +++---- .../example/ios/RunnerTests/CameraTestUtils.h | 3 +- .../example/ios/RunnerTests/CameraTestUtils.m | 3 +- .../camera_avfoundation/ios/Classes/FLTCam.m | 60 +++++++++---------- 4 files changed, 45 insertions(+), 44 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m index be568bfc7b9..b11a04c1e68 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m @@ -24,28 +24,29 @@ - (void)testResolutionPresetWithCanSetSessionPresetMax_mustUpdateCaptureSessionP OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); OCMExpect([videoSessionMock setSessionPreset:[OCMArg checkWithBlock:^BOOL(id value) { - // Return YES if the value is one of the expected presets - return [value isEqualToString:expectedUltraHighPreset] || [value isEqualToString:expectedMaxPreset]; - }]]); + // Return YES if the value is one of the expected presets + return [value isEqualToString:expectedUltraHighPreset] || + [value isEqualToString:expectedMaxPreset]; + }]]); + + FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"max"); - - FLTCam *cam = FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"max"); - OCMVerifyAll(videoSessionMock); } -- (void)testResolutionPresetWithCanSetSessionPresetUltraHigh_mustUpdateCaptureSessionPreset { //TODO +- (void) + testResolutionPresetWithCanSetSessionPresetUltraHigh_mustUpdateCaptureSessionPreset { // TODO NSString *expectedPreset = AVCaptureSessionPreset3840x2160; id videoSessionMock = OCMClassMock([AVCaptureSession class]); OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); - + // expect that setting "ultraHigh" resolutionPreset correctly updates videoCaptureSession OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); - - FLTCam *cam = FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"ultraHigh"); - + + FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"ultraHigh"); + OCMVerifyAll(videoSessionMock); } diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h index a6f5226f8ed..18682b0d6c8 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h @@ -15,7 +15,8 @@ extern FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessi /// @param captureSession AVCaptureSession for video /// @param resolutionPreset preset for camera's captureSession resolution /// @return an FLTCam object. -extern FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSession, NSString *resolutionPreset); +extern FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSession, + NSString *resolutionPreset); /// Creates a test sample buffer. /// @return a test sample buffer. diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index 45f7d7279bf..6acdbe1a331 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -29,7 +29,8 @@ error:nil]; } -FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSession, NSString *resolutionPreset) { +FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSession, + NSString *resolutionPreset) { id inputMock = OCMClassMock([AVCaptureDeviceInput class]); OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) .andReturn(inputMock); diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index 66745411c16..34093eeb3f2 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -347,25 +347,23 @@ - (NSString *)getTemporaryFilePathWithExtension:(NSString *)extension - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError:(NSError **)error { switch (resolutionPreset) { - case FLTResolutionPresetMax: - { - AVCaptureDeviceFormat *bestFormat = [self getHighestResolutionFormatFor:_captureDevice]; - if ( bestFormat ) { - _videoCaptureSession.sessionPreset = AVCaptureSessionPresetInputPriority; - if ( [_captureDevice lockForConfiguration:NULL] == YES ) { - - // set best device format and finish device configuration - _captureDevice.activeFormat = bestFormat; - [_captureDevice unlockForConfiguration]; - - // set preview size based on values from the current _captureDevice - _previewSize = - CGSizeMake(_captureDevice.activeFormat.highResolutionStillImageDimensions.width, - _captureDevice.activeFormat.highResolutionStillImageDimensions.height); - break; - } - } + case FLTResolutionPresetMax: { + AVCaptureDeviceFormat *bestFormat = [self getHighestResolutionFormatFor:_captureDevice]; + if (bestFormat) { + _videoCaptureSession.sessionPreset = AVCaptureSessionPresetInputPriority; + if ([_captureDevice lockForConfiguration:NULL] == YES) { + // set best device format and finish device configuration + _captureDevice.activeFormat = bestFormat; + [_captureDevice unlockForConfiguration]; + + // set preview size based on values from the current _captureDevice + _previewSize = + CGSizeMake(_captureDevice.activeFormat.highResolutionStillImageDimensions.width, + _captureDevice.activeFormat.highResolutionStillImageDimensions.height); + break; + } } + } if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset3840x2160]) { _videoCaptureSession.sessionPreset = AVCaptureSessionPreset3840x2160; _previewSize = CGSizeMake(3840, 2160); @@ -427,20 +425,20 @@ - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError: } /// Finds the highest available resolution in terms of pixel count for the given device -- (AVCaptureDeviceFormat *)getHighestResolutionFormatFor:(AVCaptureDevice*)captureDevice { - AVCaptureDeviceFormat *bestFormat = nil; - NSUInteger maxPixelCount = 0; - for ( AVCaptureDeviceFormat *format in [_captureDevice formats] ) { - CMVideoDimensions res = CMVideoFormatDescriptionGetDimensions(format.formatDescription); - NSUInteger height = res.height; - NSUInteger width = res.width; - NSUInteger pixelCount = height * width; - if ( pixelCount > maxPixelCount ) { - maxPixelCount = pixelCount; - bestFormat = format; - } +- (AVCaptureDeviceFormat *)getHighestResolutionFormatFor:(AVCaptureDevice *)captureDevice { + AVCaptureDeviceFormat *bestFormat = nil; + NSUInteger maxPixelCount = 0; + for (AVCaptureDeviceFormat *format in [_captureDevice formats]) { + CMVideoDimensions res = CMVideoFormatDescriptionGetDimensions(format.formatDescription); + NSUInteger height = res.height; + NSUInteger width = res.width; + NSUInteger pixelCount = height * width; + if (pixelCount > maxPixelCount) { + maxPixelCount = pixelCount; + bestFormat = format; } - return bestFormat; + } + return bestFormat; } - (void)captureOutput:(AVCaptureOutput *)output From ae0210fd16cc6b08e83cca9410407640716fa62f Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Wed, 31 Jan 2024 09:20:29 +0300 Subject: [PATCH 10/21] fix: don't update the changelog --- packages/camera/camera/CHANGELOG.md | 4 ---- packages/camera/camera/pubspec.yaml | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 76a887a2384..81e8fd45b17 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,7 +1,3 @@ -## 0.10.5+6 - -* Fixes bug where max resolution preset does not produce highest available resolution on iOS. - ## 0.10.5+5 * Fixes bug where old camera resources were not disposed when switching between camera descriptions. diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index c6392651d62..c246e4c232a 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing Dart. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.5+6 +version: 0.10.5+5 environment: sdk: ">=2.19.0 <4.0.0" From 0c8b21a277b941e0daba03613db56a55dd2f5aee Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Wed, 31 Jan 2024 09:20:53 +0300 Subject: [PATCH 11/21] refactor: remove todo and renaming --- .../example/ios/RunnerTests/CameraSessionPresetsTest.m | 3 +-- packages/camera/camera_avfoundation/ios/Classes/FLTCam.m | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m index b11a04c1e68..491af17be75 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m @@ -34,8 +34,7 @@ - (void)testResolutionPresetWithCanSetSessionPresetMax_mustUpdateCaptureSessionP OCMVerifyAll(videoSessionMock); } -- (void) - testResolutionPresetWithCanSetSessionPresetUltraHigh_mustUpdateCaptureSessionPreset { // TODO +- (void)testResolutionPresetWithCanSetSessionPresetUltraHigh_mustUpdateCaptureSessionPreset { NSString *expectedPreset = AVCaptureSessionPreset3840x2160; id videoSessionMock = OCMClassMock([AVCaptureSession class]); diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index 34093eeb3f2..e0c53b54d45 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -348,7 +348,8 @@ - (NSString *)getTemporaryFilePathWithExtension:(NSString *)extension - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError:(NSError **)error { switch (resolutionPreset) { case FLTResolutionPresetMax: { - AVCaptureDeviceFormat *bestFormat = [self getHighestResolutionFormatFor:_captureDevice]; + AVCaptureDeviceFormat *bestFormat = + [self getHighestResolutionFormatForCaptureDevice:_captureDevice]; if (bestFormat) { _videoCaptureSession.sessionPreset = AVCaptureSessionPresetInputPriority; if ([_captureDevice lockForConfiguration:NULL] == YES) { @@ -425,7 +426,8 @@ - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError: } /// Finds the highest available resolution in terms of pixel count for the given device -- (AVCaptureDeviceFormat *)getHighestResolutionFormatFor:(AVCaptureDevice *)captureDevice { +- (AVCaptureDeviceFormat *)getHighestResolutionFormatForCaptureDevice: + (AVCaptureDevice *)captureDevice { AVCaptureDeviceFormat *bestFormat = nil; NSUInteger maxPixelCount = 0; for (AVCaptureDeviceFormat *format in [_captureDevice formats]) { From 0fe1ac7d82f13df34f0f054dbbe1affbf938b21c Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Fri, 2 Feb 2024 19:11:24 +0300 Subject: [PATCH 12/21] feat: add tests for different resolution presets --- .../RunnerTests/CameraSessionPresetsTest.m | 100 ++++++++++++++++-- 1 file changed, 91 insertions(+), 9 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m index 491af17be75..067741f2a90 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m @@ -9,25 +9,105 @@ #import #import "CameraTestUtils.h" -/// Includes test cases related to photo capture operations for FLTCam class. +/// Includes test cases related to resolution presets setting operations for FLTCam class. @interface FLTCamSessionPresetsTest : XCTestCase +@end + +/// swizzles AVCaptureDevice constructor to return mocked object +@implementation AVCaptureDevice (UniqueIDSwizzling) + ++ (void)swizzleDeviceWithUniqueIDMethod { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class class = object_getClass((id)self); + + SEL originalSelector = @selector(deviceWithUniqueID:); + SEL swizzledSelector = @selector(test_deviceWithUniqueID:); + + Method originalMethod = class_getClassMethod(class, originalSelector); + Method swizzledMethod = class_getClassMethod(class, swizzledSelector); + + method_exchangeImplementations(originalMethod, swizzledMethod); + }); +} + ++ (AVCaptureDevice *)test_deviceWithUniqueID:(NSString *)deviceUniqueID { + id mockDevice = OCMClassMock([AVCaptureDevice class]); + + // always allow locking for configuration to be able to set value for activeFormat + OCMStub([mockDevice lockForConfiguration:NULL]).andReturn(YES); + return mockDevice; +} + +@end + +/// swizzles getHighestResolutionFormatForCaptureDevice private method to always return a value +@implementation FLTCam (TestSwizzling) + ++ (void)swizzleGetHighestResolutionMethod { + Class class = [self class]; + + SEL originalSelector = @selector(getHighestResolutionFormatForCaptureDevice:); + SEL swizzledSelector = @selector(test_getHighestResolutionFormatForCaptureDevice:); + + Method originalMethod = class_getInstanceMethod(class, originalSelector); + Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); + + BOOL didAddMethod = + class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)); + + if (didAddMethod) { + class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + } else { + method_exchangeImplementations(originalMethod, swizzledMethod); + } +} + +- (AVCaptureDeviceFormat *)test_getHighestResolutionFormatForCaptureDevice: + (AVCaptureDevice *)device { + id mockDeviceFormat = OCMClassMock([AVCaptureDeviceFormat class]); + + return mockDeviceFormat; +} @end @implementation FLTCamSessionPresetsTest +- (void)testResolutionPresetWithBestFormat_mustUpdateCaptureSessionPreset { + NSString *expectedPreset = AVCaptureSessionPresetInputPriority; + + // make sure getHighestResolutionFormatForCaptureDevice returns a value + [AVCaptureDevice swizzleDeviceWithUniqueIDMethod]; + // make sure getHighestResolutionFormatForCaptureDevice returns a value + [FLTCam swizzleGetHighestResolutionMethod]; + + id videoSessionMock = OCMClassMock([AVCaptureSession class]); + OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); + + OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); + + FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"max"); + + OCMVerifyAll(videoSessionMock); + + // cleanup + [AVCaptureDevice swizzleDeviceWithUniqueIDMethod]; + [FLTCam swizzleGetHighestResolutionMethod]; +} + - (void)testResolutionPresetWithCanSetSessionPresetMax_mustUpdateCaptureSessionPreset { - NSString *expectedUltraHighPreset = AVCaptureSessionPreset3840x2160; - NSString *expectedMaxPreset = AVCaptureSessionPresetInputPriority; + NSString *expectedPreset = AVCaptureSessionPreset3840x2160; id videoSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); + + // make sure that setting resolution preset for session always succeeds OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); - OCMExpect([videoSessionMock setSessionPreset:[OCMArg checkWithBlock:^BOOL(id value) { - // Return YES if the value is one of the expected presets - return [value isEqualToString:expectedUltraHighPreset] || - [value isEqualToString:expectedMaxPreset]; - }]]); + + OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"max"); @@ -39,6 +119,8 @@ - (void)testResolutionPresetWithCanSetSessionPresetUltraHigh_mustUpdateCaptureSe id videoSessionMock = OCMClassMock([AVCaptureSession class]); OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + + // make sure that setting resolution preset for session always succeeds OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); // expect that setting "ultraHigh" resolutionPreset correctly updates videoCaptureSession From c314755f208f464780c166324b1042cb8810d0ec Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Fri, 2 Feb 2024 19:14:18 +0300 Subject: [PATCH 13/21] refactor: update comment --- .../example/ios/RunnerTests/CameraSessionPresetsTest.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m index 067741f2a90..d9d5f11fab5 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m @@ -79,7 +79,7 @@ @implementation FLTCamSessionPresetsTest - (void)testResolutionPresetWithBestFormat_mustUpdateCaptureSessionPreset { NSString *expectedPreset = AVCaptureSessionPresetInputPriority; - // make sure getHighestResolutionFormatForCaptureDevice returns a value + // make sure initializing AVCaptureDevice always succeeds [AVCaptureDevice swizzleDeviceWithUniqueIDMethod]; // make sure getHighestResolutionFormatForCaptureDevice returns a value [FLTCam swizzleGetHighestResolutionMethod]; From ec1b896fc3968ebc46855dec68cddcb33cf298f0 Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Fri, 2 Feb 2024 22:19:36 +0300 Subject: [PATCH 14/21] refactor: change tests to use DI --- .../RunnerTests/CameraSessionPresetsTest.m | 85 +++---------------- .../example/ios/RunnerTests/CameraTestUtils.h | 10 +++ .../example/ios/RunnerTests/CameraTestUtils.m | 25 ++++++ .../camera_avfoundation/ios/Classes/FLTCam.h | 15 ++++ .../camera_avfoundation/ios/Classes/FLTCam.m | 56 ++++++++++-- 5 files changed, 111 insertions(+), 80 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m index d9d5f11fab5..58fc7149d46 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m @@ -13,89 +13,32 @@ @interface FLTCamSessionPresetsTest : XCTestCase @end -/// swizzles AVCaptureDevice constructor to return mocked object -@implementation AVCaptureDevice (UniqueIDSwizzling) - -+ (void)swizzleDeviceWithUniqueIDMethod { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - Class class = object_getClass((id)self); - - SEL originalSelector = @selector(deviceWithUniqueID:); - SEL swizzledSelector = @selector(test_deviceWithUniqueID:); - - Method originalMethod = class_getClassMethod(class, originalSelector); - Method swizzledMethod = class_getClassMethod(class, swizzledSelector); - - method_exchangeImplementations(originalMethod, swizzledMethod); - }); -} - -+ (AVCaptureDevice *)test_deviceWithUniqueID:(NSString *)deviceUniqueID { - id mockDevice = OCMClassMock([AVCaptureDevice class]); - - // always allow locking for configuration to be able to set value for activeFormat - OCMStub([mockDevice lockForConfiguration:NULL]).andReturn(YES); - return mockDevice; -} - -@end - -/// swizzles getHighestResolutionFormatForCaptureDevice private method to always return a value -@implementation FLTCam (TestSwizzling) - -+ (void)swizzleGetHighestResolutionMethod { - Class class = [self class]; - - SEL originalSelector = @selector(getHighestResolutionFormatForCaptureDevice:); - SEL swizzledSelector = @selector(test_getHighestResolutionFormatForCaptureDevice:); - - Method originalMethod = class_getInstanceMethod(class, originalSelector); - Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); - - BOOL didAddMethod = - class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), - method_getTypeEncoding(swizzledMethod)); - - if (didAddMethod) { - class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), - method_getTypeEncoding(originalMethod)); - } else { - method_exchangeImplementations(originalMethod, swizzledMethod); - } -} - -- (AVCaptureDeviceFormat *)test_getHighestResolutionFormatForCaptureDevice: - (AVCaptureDevice *)device { - id mockDeviceFormat = OCMClassMock([AVCaptureDeviceFormat class]); - - return mockDeviceFormat; -} - -@end - @implementation FLTCamSessionPresetsTest - (void)testResolutionPresetWithBestFormat_mustUpdateCaptureSessionPreset { NSString *expectedPreset = AVCaptureSessionPresetInputPriority; - // make sure initializing AVCaptureDevice always succeeds - [AVCaptureDevice swizzleDeviceWithUniqueIDMethod]; - // make sure getHighestResolutionFormatForCaptureDevice returns a value - [FLTCam swizzleGetHighestResolutionMethod]; - id videoSessionMock = OCMClassMock([AVCaptureSession class]); OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); + id captureFormatMock = OCMClassMock([AVCaptureDeviceFormat class]); + id captureDeviceMock = OCMClassMock([AVCaptureDevice class]); + OCMStub([captureDeviceMock formats]).andReturn(@[ captureFormatMock ]); + + OCMExpect([captureDeviceMock activeFormat]).andReturn(captureFormatMock); + OCMExpect([captureDeviceMock lockForConfiguration:NULL]).andReturn(YES); OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); - FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"max"); + FLTCreateCamWithVideoDimensionsForFormatBlock(videoSessionMock, @"max", captureDeviceMock, + ^CMVideoDimensions(AVCaptureDeviceFormat *format) { + CMVideoDimensions videoDimensions; + videoDimensions.width = 1; + videoDimensions.height = 1; + return videoDimensions; + }); + OCMVerifyAll(captureDeviceMock); OCMVerifyAll(videoSessionMock); - - // cleanup - [AVCaptureDevice swizzleDeviceWithUniqueIDMethod]; - [FLTCam swizzleGetHighestResolutionMethod]; } - (void)testResolutionPresetWithCanSetSessionPresetMax_mustUpdateCaptureSessionPreset { diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h index 18682b0d6c8..bc02e0a9011 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h @@ -18,6 +18,16 @@ extern FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessi extern FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSession, NSString *resolutionPreset); +/// Creates an `FLTCam` with a given captureSession and resolutionPreset +/// @param captureSession AVCaptureSession for video +/// @param resolutionPreset preset for camera's captureSession resolution +/// @param captureDevice AVCaptureDevice to be used +/// @param videoDimensionsForFormatBlock custom code to determine video dimensions +/// @return an FLTCam object. +extern FLTCam *FLTCreateCamWithVideoDimensionsForFormatBlock( + AVCaptureSession *captureSession, NSString *resolutionPreset, AVCaptureDevice *captureDevice, + VideoDimensionsForFormatBlock videoDimensionsForFormatBlock); + /// Creates a test sample buffer. /// @return a test sample buffer. extern CMSampleBufferRef FLTCreateTestSampleBuffer(void); diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index 6acdbe1a331..2df96be96be 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -46,6 +46,31 @@ videoCaptureSession:captureSession audioCaptureSession:audioSessionMock captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) + captureDevice:nil + videoDimensionsForFormatBlock:nil + error:nil]; +} + +FLTCam *FLTCreateCamWithVideoDimensionsForFormatBlock( + AVCaptureSession *captureSession, NSString *resolutionPreset, AVCaptureDevice *captureDevice, + VideoDimensionsForFormatBlock videoDimensionsForFormatBlock) { + id inputMock = OCMClassMock([AVCaptureDeviceInput class]); + OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) + .andReturn(inputMock); + + id audioSessionMock = OCMClassMock([AVCaptureSession class]); + OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); + + return [[FLTCam alloc] initWithCameraName:@"camera" + resolutionPreset:resolutionPreset + enableAudio:true + orientation:UIDeviceOrientationPortrait + videoCaptureSession:captureSession + audioCaptureSession:audioSessionMock + captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) + captureDevice:captureDevice + videoDimensionsForFormatBlock:videoDimensionsForFormatBlock error:nil]; } diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h index f5979a829ca..a340d7af09d 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h @@ -14,6 +14,8 @@ NS_ASSUME_NONNULL_BEGIN +typedef CMVideoDimensions (^VideoDimensionsForFormatBlock)(AVCaptureDeviceFormat *); + /** * A class that manages camera's state and performs camera operations. */ @@ -45,6 +47,19 @@ NS_ASSUME_NONNULL_BEGIN orientation:(UIDeviceOrientation)orientation captureSessionQueue:(dispatch_queue_t)captureSessionQueue error:(NSError **)error; + +- (instancetype)initWithCameraName:(NSString *)cameraName + resolutionPreset:(NSString *)resolutionPreset + enableAudio:(BOOL)enableAudio + orientation:(UIDeviceOrientation)orientation + videoCaptureSession:(AVCaptureSession *)videoCaptureSession + audioCaptureSession:(AVCaptureSession *)audioCaptureSession + captureSessionQueue:(dispatch_queue_t)captureSessionQueue + captureDevice:(nullable AVCaptureDevice *)captureDevice + videoDimensionsForFormatBlock: + (nullable VideoDimensionsForFormatBlock)videoDimensionsForFormatBlock + error:(NSError **)error; + - (void)start; - (void)stop; - (void)setDeviceOrientation:(UIDeviceOrientation)orientation; diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index be7b223bd56..924bbbe939d 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -86,6 +86,7 @@ @interface FLTCam () Date: Fri, 2 Feb 2024 22:21:41 +0300 Subject: [PATCH 15/21] refactor: rename method --- .../camera_avfoundation/ios/Classes/FLTCam.m | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index 924bbbe939d..fce944787bf 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -475,20 +475,13 @@ - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError: return YES; } -- (CMVideoDimensions)preferredFormatForDevice:(AVCaptureDeviceFormat *)format { - if (self.videoDimensionsForFormatBlock) { - return self.videoDimensionsForFormatBlock(format); - } - return CMVideoFormatDescriptionGetDimensions(format.formatDescription); -} - /// Finds the highest available resolution in terms of pixel count for the given device - (AVCaptureDeviceFormat *)getHighestResolutionFormatForCaptureDevice: (AVCaptureDevice *)captureDevice { AVCaptureDeviceFormat *bestFormat = nil; NSUInteger maxPixelCount = 0; for (AVCaptureDeviceFormat *format in [_captureDevice formats]) { - CMVideoDimensions res = [self preferredFormatForDevice:format]; + CMVideoDimensions res = [self videoDimensionsForFormat:format]; NSUInteger height = res.height; NSUInteger width = res.width; NSUInteger pixelCount = height * width; @@ -500,6 +493,14 @@ - (AVCaptureDeviceFormat *)getHighestResolutionFormatForCaptureDevice: return bestFormat; } +/// Wrapper for CMVideoFormatDescriptionGetDimensions to use custom code for testing +- (CMVideoDimensions)videoDimensionsForFormat:(AVCaptureDeviceFormat *)format { + if (self.videoDimensionsForFormatBlock) { + return self.videoDimensionsForFormatBlock(format); + } + return CMVideoFormatDescriptionGetDimensions(format.formatDescription); +} + - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { From decce1ec021a13e6fed990e1a32fdde49bd3d4dd Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Fri, 2 Feb 2024 22:24:38 +0300 Subject: [PATCH 16/21] refactor: rename method --- packages/camera/camera_avfoundation/ios/Classes/FLTCam.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index fce944787bf..57a741724eb 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -399,7 +399,7 @@ - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError: switch (resolutionPreset) { case FLTResolutionPresetMax: { AVCaptureDeviceFormat *bestFormat = - [self getHighestResolutionFormatForCaptureDevice:_captureDevice]; + [self highestResolutionFormatForCaptureDevice:_captureDevice]; if (bestFormat) { _videoCaptureSession.sessionPreset = AVCaptureSessionPresetInputPriority; if ([_captureDevice lockForConfiguration:NULL] == YES) { @@ -476,7 +476,7 @@ - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError: } /// Finds the highest available resolution in terms of pixel count for the given device -- (AVCaptureDeviceFormat *)getHighestResolutionFormatForCaptureDevice: +- (AVCaptureDeviceFormat *)highestResolutionFormatForCaptureDevice: (AVCaptureDevice *)captureDevice { AVCaptureDeviceFormat *bestFormat = nil; NSUInteger maxPixelCount = 0; From 832cc7b79855823633d52c9758a7557f69626e5d Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Tue, 13 Feb 2024 11:30:19 +0300 Subject: [PATCH 17/21] refactor: cleaner DI --- .../ios/Runner.xcodeproj/project.pbxproj | 8 +- ...setsTest.m => CameraSessionPresetsTests.m} | 9 +- .../example/ios/RunnerTests/CameraTestUtils.m | 32 +++---- .../camera_avfoundation/ios/Classes/FLTCam.h | 14 --- .../camera_avfoundation/ios/Classes/FLTCam.m | 94 +++++++++---------- .../ios/Classes/FLTCam_Test.h | 62 ++++++++++++ 6 files changed, 132 insertions(+), 87 deletions(-) rename packages/camera/camera_avfoundation/example/ios/RunnerTests/{CameraSessionPresetsTest.m => CameraSessionPresetsTests.m} (91%) diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index 69e0036fcb7..ac2e7ffb6ec 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -22,7 +22,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - CEF6611A2B5E36A500D33FD4 /* CameraSessionPresetsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTest.m */; }; + CEF6611A2B5E36A500D33FD4 /* CameraSessionPresetsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTests.m */; }; E01EE4A82799F3A5008C1950 /* QueueUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */; }; E032F250279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E032F24F279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m */; }; E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */; }; @@ -90,7 +90,7 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9C5CC6CAD53AD388B2694F3A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; A24F9E418BA48BCC7409B117 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTest.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = CameraSessionPresetsTest.m; sourceTree = ""; }; + CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTests.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = CameraSessionPresetsTests.m; sourceTree = ""; }; E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QueueUtilsTests.m; sourceTree = ""; }; E032F24F279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CameraCaptureSessionQueueRaceConditionTests.m; sourceTree = ""; }; E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTSavePhotoDelegateTests.m; sourceTree = ""; }; @@ -153,7 +153,7 @@ E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */, 788A065927B0E02900533D74 /* StreamingTest.m */, 43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */, - CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTest.m */, + CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTests.m */, ); path = RunnerTests; sourceTree = ""; @@ -454,7 +454,7 @@ F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */, E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */, 334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */, - CEF6611A2B5E36A500D33FD4 /* CameraSessionPresetsTest.m in Sources */, + CEF6611A2B5E36A500D33FD4 /* CameraSessionPresetsTests.m in Sources */, E032F250279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m in Sources */, 788A065A27B0E02900533D74 /* StreamingTest.m in Sources */, E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m similarity index 91% rename from packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m rename to packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m index 58fc7149d46..3328d88a64c 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTest.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m @@ -4,6 +4,7 @@ @import camera_avfoundation; @import camera_avfoundation.Test; + @import AVFoundation; @import XCTest; #import @@ -47,7 +48,7 @@ - (void)testResolutionPresetWithCanSetSessionPresetMax_mustUpdateCaptureSessionP id videoSessionMock = OCMClassMock([AVCaptureSession class]); OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); - // make sure that setting resolution preset for session always succeeds + // Make sure that setting resolution preset for session always succeeds. OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); @@ -61,12 +62,12 @@ - (void)testResolutionPresetWithCanSetSessionPresetUltraHigh_mustUpdateCaptureSe NSString *expectedPreset = AVCaptureSessionPreset3840x2160; id videoSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); - // make sure that setting resolution preset for session always succeeds + // Make sure that setting resolution preset for session always succeeds. OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); - // expect that setting "ultraHigh" resolutionPreset correctly updates videoCaptureSession + // Expect that setting "ultraHigh" resolutionPreset correctly updates videoCaptureSession. OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"ultraHigh"); diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index 2df96be96be..d1bce7dd5ac 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -12,11 +12,11 @@ .andReturn(inputMock); id videoSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); id audioSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); return [[FLTCam alloc] initWithCameraName:@"camera" @@ -36,7 +36,7 @@ .andReturn(inputMock); id audioSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); return [[FLTCam alloc] initWithCameraName:@"camera" @@ -46,8 +46,6 @@ videoCaptureSession:captureSession audioCaptureSession:audioSessionMock captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) - captureDevice:nil - videoDimensionsForFormatBlock:nil error:nil]; } @@ -59,19 +57,21 @@ .andReturn(inputMock); id audioSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); - return [[FLTCam alloc] initWithCameraName:@"camera" - resolutionPreset:resolutionPreset - enableAudio:true - orientation:UIDeviceOrientationPortrait - videoCaptureSession:captureSession - audioCaptureSession:audioSessionMock - captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) - captureDevice:captureDevice - videoDimensionsForFormatBlock:videoDimensionsForFormatBlock - error:nil]; + return + [[FLTCam alloc] initWithResolutionPreset:resolutionPreset + enableAudio:true + orientation:UIDeviceOrientationPortrait + videoCaptureSession:captureSession + audioCaptureSession:audioSessionMock + captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) + captureDeviceBlock:^AVCaptureDevice *(void) { + return captureDevice; + } + videoDimensionsForFormatBlock:videoDimensionsForFormatBlock + error:nil]; } CMSampleBufferRef FLTCreateTestSampleBuffer(void) { diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h index a340d7af09d..daa1281c548 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h @@ -14,8 +14,6 @@ NS_ASSUME_NONNULL_BEGIN -typedef CMVideoDimensions (^VideoDimensionsForFormatBlock)(AVCaptureDeviceFormat *); - /** * A class that manages camera's state and performs camera operations. */ @@ -48,18 +46,6 @@ typedef CMVideoDimensions (^VideoDimensionsForFormatBlock)(AVCaptureDeviceFormat captureSessionQueue:(dispatch_queue_t)captureSessionQueue error:(NSError **)error; -- (instancetype)initWithCameraName:(NSString *)cameraName - resolutionPreset:(NSString *)resolutionPreset - enableAudio:(BOOL)enableAudio - orientation:(UIDeviceOrientation)orientation - videoCaptureSession:(AVCaptureSession *)videoCaptureSession - audioCaptureSession:(AVCaptureSession *)audioCaptureSession - captureSessionQueue:(dispatch_queue_t)captureSessionQueue - captureDevice:(nullable AVCaptureDevice *)captureDevice - videoDimensionsForFormatBlock: - (nullable VideoDimensionsForFormatBlock)videoDimensionsForFormatBlock - error:(NSError **)error; - - (void)start; - (void)stop; - (void)setDeviceOrientation:(UIDeviceOrientation)orientation; diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index 57a741724eb..3419f319edd 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -86,7 +86,14 @@ @interface FLTCam () /// The queue on which `eventSink` property should be accessed. @@ -55,6 +86,37 @@ captureSessionQueue:(dispatch_queue_t)captureSessionQueue error:(NSError **)error; +/** + Initializes an instance for testing with specified resolution, audio preference, orientation, and + direct access to capture sessions and blocks. + + This initializer allows the explicit configuration of media capture settings and direct + manipulation of internal components like capture sessions and device selection blocks, facilitating + thorough testing scenarios. + + @param resolutionPreset A string defining the resolution preset for video capture. + @param enableAudio A boolean indicating whether audio capture is enabled. + @param orientation The device orientation during capture. + @param videoCaptureSession An AVCaptureSession for video capturing. + @param audioCaptureSession An AVCaptureSession for audio capturing. + @param captureSessionQueue The dispatch queue for capture session tasks. + @param captureDeviceBlock A block returning a specific AVCaptureDevice. + @param videoDimensionsForFormatBlock A block to determine video dimensions for a given format. + @param error A pointer to an NSError object to capture any initialization errors. + + @return An instance of the class, configured for testing with provided parameters. +*/ +- (instancetype)initWithResolutionPreset:(NSString *)resolutionPreset + enableAudio:(BOOL)enableAudio + orientation:(UIDeviceOrientation)orientation + videoCaptureSession:(AVCaptureSession *)videoCaptureSession + audioCaptureSession:(AVCaptureSession *)audioCaptureSession + captureSessionQueue:(dispatch_queue_t)captureSessionQueue + captureDeviceBlock:(CaptureDeviceBlock)captureDeviceBlock + videoDimensionsForFormatBlock: + (VideoDimensionsForFormatBlock)videoDimensionsForFormatBlock + error:(NSError **)error; + /// Start streaming images. - (void)startImageStreamWithMessenger:(NSObject *)messenger imageStreamHandler:(FLTImageStreamHandler *)imageStreamHandler; From ddfe77864a236721781b09a47c6dd15f197f2551 Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Wed, 21 Feb 2024 19:56:21 +0300 Subject: [PATCH 18/21] refactor: update comments --- .../RunnerTests/CameraSessionPresetsTests.m | 14 ++--- .../example/ios/RunnerTests/CameraTestUtils.h | 4 +- .../example/ios/RunnerTests/CameraTestUtils.m | 12 ++-- .../camera_avfoundation/ios/Classes/FLTCam.m | 36 +++++------ .../ios/Classes/FLTCam_Test.h | 59 +++---------------- 5 files changed, 40 insertions(+), 85 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m index 3328d88a64c..a5130ad8288 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m @@ -30,13 +30,13 @@ - (void)testResolutionPresetWithBestFormat_mustUpdateCaptureSessionPreset { OCMExpect([captureDeviceMock lockForConfiguration:NULL]).andReturn(YES); OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); - FLTCreateCamWithVideoDimensionsForFormatBlock(videoSessionMock, @"max", captureDeviceMock, - ^CMVideoDimensions(AVCaptureDeviceFormat *format) { - CMVideoDimensions videoDimensions; - videoDimensions.width = 1; - videoDimensions.height = 1; - return videoDimensions; - }); + FLTCreateCamWithVideoDimensionsForFormat(videoSessionMock, @"max", captureDeviceMock, + ^CMVideoDimensions(AVCaptureDeviceFormat *format) { + CMVideoDimensions videoDimensions; + videoDimensions.width = 1; + videoDimensions.height = 1; + return videoDimensions; + }); OCMVerifyAll(captureDeviceMock); OCMVerifyAll(videoSessionMock); diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h index bc02e0a9011..d8bff8543bf 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h @@ -24,9 +24,9 @@ extern FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSess /// @param captureDevice AVCaptureDevice to be used /// @param videoDimensionsForFormatBlock custom code to determine video dimensions /// @return an FLTCam object. -extern FLTCam *FLTCreateCamWithVideoDimensionsForFormatBlock( +extern FLTCam *FLTCreateCamWithVideoDimensionsForFormat( AVCaptureSession *captureSession, NSString *resolutionPreset, AVCaptureDevice *captureDevice, - VideoDimensionsForFormatBlock videoDimensionsForFormatBlock); + VideoDimensionsForFormatBlock videoDimensionsForFormat); /// Creates a test sample buffer. /// @return a test sample buffer. diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index d1bce7dd5ac..8dd1a781571 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -49,9 +49,9 @@ error:nil]; } -FLTCam *FLTCreateCamWithVideoDimensionsForFormatBlock( +FLTCam *FLTCreateCamWithVideoDimensionsForFormat( AVCaptureSession *captureSession, NSString *resolutionPreset, AVCaptureDevice *captureDevice, - VideoDimensionsForFormatBlock videoDimensionsForFormatBlock) { + VideoDimensionsForFormatBlock videoDimensionsForFormat) { id inputMock = OCMClassMock([AVCaptureDeviceInput class]); OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) .andReturn(inputMock); @@ -67,10 +67,10 @@ videoCaptureSession:captureSession audioCaptureSession:audioSessionMock captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) - captureDeviceBlock:^AVCaptureDevice *(void) { - return captureDevice; - } - videoDimensionsForFormatBlock:videoDimensionsForFormatBlock + captureDeviceFactory:^AVCaptureDevice *(void) { + return captureDevice; + } + videoDimensionsForFormat:videoDimensionsForFormat error:nil]; } diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index 3419f319edd..259b8d80b3f 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -86,14 +86,11 @@ @interface FLTCam () @@ -86,35 +63,17 @@ typedef AVCaptureDevice * (^CaptureDeviceBlock)(void); captureSessionQueue:(dispatch_queue_t)captureSessionQueue error:(NSError **)error; -/** - Initializes an instance for testing with specified resolution, audio preference, orientation, and - direct access to capture sessions and blocks. - - This initializer allows the explicit configuration of media capture settings and direct - manipulation of internal components like capture sessions and device selection blocks, facilitating - thorough testing scenarios. - - @param resolutionPreset A string defining the resolution preset for video capture. - @param enableAudio A boolean indicating whether audio capture is enabled. - @param orientation The device orientation during capture. - @param videoCaptureSession An AVCaptureSession for video capturing. - @param audioCaptureSession An AVCaptureSession for audio capturing. - @param captureSessionQueue The dispatch queue for capture session tasks. - @param captureDeviceBlock A block returning a specific AVCaptureDevice. - @param videoDimensionsForFormatBlock A block to determine video dimensions for a given format. - @param error A pointer to an NSError object to capture any initialization errors. - - @return An instance of the class, configured for testing with provided parameters. -*/ +/// Initializes a camera instance. +/// Allows for testing with specified resolution, audio preference, orientation, +/// and direct access to capture sessions and blocks. - (instancetype)initWithResolutionPreset:(NSString *)resolutionPreset enableAudio:(BOOL)enableAudio orientation:(UIDeviceOrientation)orientation videoCaptureSession:(AVCaptureSession *)videoCaptureSession audioCaptureSession:(AVCaptureSession *)audioCaptureSession captureSessionQueue:(dispatch_queue_t)captureSessionQueue - captureDeviceBlock:(CaptureDeviceBlock)captureDeviceBlock - videoDimensionsForFormatBlock: - (VideoDimensionsForFormatBlock)videoDimensionsForFormatBlock + captureDeviceFactory:(CaptureDeviceBlock)captureDeviceFactory + videoDimensionsForFormat:(VideoDimensionsForFormatBlock)videoDimensionsForFormat error:(NSError **)error; /// Start streaming images. From 5c01c8c2f5be3ee8e54379c80404aaeab435f71f Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Wed, 21 Feb 2024 20:07:55 +0300 Subject: [PATCH 19/21] refactor: update comment to match arguments --- .../example/ios/RunnerTests/CameraTestUtils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h index d8bff8543bf..4523460cc64 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h @@ -22,7 +22,7 @@ extern FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSess /// @param captureSession AVCaptureSession for video /// @param resolutionPreset preset for camera's captureSession resolution /// @param captureDevice AVCaptureDevice to be used -/// @param videoDimensionsForFormatBlock custom code to determine video dimensions +/// @param videoDimensionsForFormat custom code to determine video dimensions /// @return an FLTCam object. extern FLTCam *FLTCreateCamWithVideoDimensionsForFormat( AVCaptureSession *captureSession, NSString *resolutionPreset, AVCaptureDevice *captureDevice, From 0c40901934caa6bd179da232ac68dcf1326da731 Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Thu, 22 Feb 2024 08:18:44 +0300 Subject: [PATCH 20/21] refactor: rename types --- .../example/ios/RunnerTests/CameraTestUtils.h | 5 +++-- .../example/ios/RunnerTests/CameraTestUtils.m | 2 +- packages/camera/camera_avfoundation/ios/Classes/FLTCam.m | 8 ++++---- .../camera/camera_avfoundation/ios/Classes/FLTCam_Test.h | 8 ++++---- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h index 4523460cc64..cdc11bff6c8 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h @@ -18,7 +18,8 @@ extern FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessi extern FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSession, NSString *resolutionPreset); -/// Creates an `FLTCam` with a given captureSession and resolutionPreset +/// Creates an `FLTCam` with a given captureSession and resolutionPreset. +/// Allows to inject a capture device and a block to compute the video dimensions. /// @param captureSession AVCaptureSession for video /// @param resolutionPreset preset for camera's captureSession resolution /// @param captureDevice AVCaptureDevice to be used @@ -26,7 +27,7 @@ extern FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSess /// @return an FLTCam object. extern FLTCam *FLTCreateCamWithVideoDimensionsForFormat( AVCaptureSession *captureSession, NSString *resolutionPreset, AVCaptureDevice *captureDevice, - VideoDimensionsForFormatBlock videoDimensionsForFormat); + VideoDimensionsForFormat videoDimensionsForFormat); /// Creates a test sample buffer. /// @return a test sample buffer. diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index 8dd1a781571..d0456f7aa54 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -51,7 +51,7 @@ FLTCam *FLTCreateCamWithVideoDimensionsForFormat( AVCaptureSession *captureSession, NSString *resolutionPreset, AVCaptureDevice *captureDevice, - VideoDimensionsForFormatBlock videoDimensionsForFormat) { + VideoDimensionsForFormat videoDimensionsForFormat) { id inputMock = OCMClassMock([AVCaptureDeviceInput class]); OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) .andReturn(inputMock); diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index 259b8d80b3f..fa8d47b20ba 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -88,9 +88,9 @@ @interface FLTCam () @@ -72,8 +72,8 @@ typedef AVCaptureDevice * (^CaptureDeviceBlock)(void); videoCaptureSession:(AVCaptureSession *)videoCaptureSession audioCaptureSession:(AVCaptureSession *)audioCaptureSession captureSessionQueue:(dispatch_queue_t)captureSessionQueue - captureDeviceFactory:(CaptureDeviceBlock)captureDeviceFactory - videoDimensionsForFormat:(VideoDimensionsForFormatBlock)videoDimensionsForFormat + captureDeviceFactory:(CaptureDeviceFactory)captureDeviceFactory + videoDimensionsForFormat:(VideoDimensionsForFormat)videoDimensionsForFormat error:(NSError **)error; /// Start streaming images. From 8e019b34a0ee83925fa69952b1b4b323b40e657e Mon Sep 17 00:00:00 2001 From: sergeydesenkoBrickit Date: Fri, 1 Mar 2024 16:20:56 +0300 Subject: [PATCH 21/21] refactor: address code review --- packages/camera/camera_avfoundation/ios/Classes/FLTCam.m | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index fa8d47b20ba..b16d65fe40e 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -402,7 +402,7 @@ - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError: [self highestResolutionFormatForCaptureDevice:_captureDevice]; if (bestFormat) { _videoCaptureSession.sessionPreset = AVCaptureSessionPresetInputPriority; - if ([_captureDevice lockForConfiguration:NULL] == YES) { + if ([_captureDevice lockForConfiguration:NULL]) { // Set the best device format found and finish the device configuration. _captureDevice.activeFormat = bestFormat; [_captureDevice unlockForConfiguration]; @@ -415,11 +415,6 @@ - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError: } } } - if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset3840x2160]) { - _videoCaptureSession.sessionPreset = AVCaptureSessionPreset3840x2160; - _previewSize = CGSizeMake(3840, 2160); - break; - } case FLTResolutionPresetUltraHigh: if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset3840x2160]) { _videoCaptureSession.sessionPreset = AVCaptureSessionPreset3840x2160; @@ -480,7 +475,7 @@ - (AVCaptureDeviceFormat *)highestResolutionFormatForCaptureDevice: (AVCaptureDevice *)captureDevice { AVCaptureDeviceFormat *bestFormat = nil; NSUInteger maxPixelCount = 0; - for (AVCaptureDeviceFormat *format in [_captureDevice formats]) { + for (AVCaptureDeviceFormat *format in _captureDevice.formats) { CMVideoDimensions res = self.videoDimensionsForFormat(format); NSUInteger height = res.height; NSUInteger width = res.width;