From 9a774a8215236d36b06817b9c5d2b612516ab215 Mon Sep 17 00:00:00 2001 From: Quentin Date: Tue, 26 Mar 2019 14:29:55 +0000 Subject: [PATCH 1/9] Added camcorderProfile to Android for better quality preset selection. --- .../flutter/plugins/camera/CameraPlugin.java | 181 ++++++++++-------- packages/camera/example/lib/main.dart | 2 +- packages/camera/lib/camera.dart | 6 +- 3 files changed, 105 insertions(+), 84 deletions(-) diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java index 2fafb7d5c3a1..46a2f522ca49 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java @@ -8,7 +8,6 @@ import android.content.Context; import android.content.pm.PackageManager; import android.graphics.ImageFormat; -import android.graphics.Point; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; @@ -19,13 +18,14 @@ import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.StreamConfigurationMap; +import android.media.CamcorderProfile; import android.media.Image; import android.media.ImageReader; import android.media.MediaRecorder; import android.os.Build; import android.os.Bundle; import android.util.Size; -import android.view.Display; +import android.util.SparseArray; import android.view.OrientationEventListener; import android.view.Surface; import androidx.annotation.NonNull; @@ -97,9 +97,7 @@ public void onActivityStarted(Activity activity) {} @Override public void onActivityResumed(Activity activity) { boolean wasRequestingPermission = requestingPermission; - if (requestingPermission) { - requestingPermission = false; - } + requestingPermission = false; if (activity != CameraPlugin.this.activity) { return; } @@ -295,9 +293,9 @@ private class Camera { private Size captureSize; private Size previewSize; private CaptureRequest.Builder captureRequestBuilder; - private Size videoSize; private MediaRecorder mediaRecorder; private boolean recordingVideo; + private CamcorderProfile recordingProfile; Camera(final String cameraName, final String resolutionPreset, @NonNull final Result result) { @@ -307,32 +305,21 @@ private class Camera { registerEventChannel(); try { - int minHeight; - switch (resolutionPreset) { - case "high": - minHeight = 720; - break; - case "medium": - minHeight = 480; - break; - case "low": - minHeight = 240; - break; - default: - throw new IllegalArgumentException("Unknown preset: " + resolutionPreset); - } CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); StreamConfigurationMap streamConfigurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + //noinspection ConstantConditions sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); //noinspection ConstantConditions isFrontFacing = characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT; - computeBestCaptureSize(streamConfigurationMap); - computeBestPreviewAndRecordingSize(streamConfigurationMap, minHeight, captureSize); + + recordingProfile = getBestAvailableCamcorderProfileForResolutionPreset(resolutionPreset); + computeBestPreviewSize(recordingProfile); + computeBestCaptureSize(streamConfigurationMap, recordingProfile, resolutionPreset); if (cameraPermissionContinuation != null) { result.error("cameraPermission", "Camera permission request ongoing", null); @@ -404,62 +391,31 @@ private boolean hasAudioPermission() { == PackageManager.PERMISSION_GRANTED; } - private void computeBestPreviewAndRecordingSize( - StreamConfigurationMap streamConfigurationMap, int minHeight, Size captureSize) { - Size[] sizes = streamConfigurationMap.getOutputSizes(SurfaceTexture.class); - - // Preview size and video size should not be greater than screen resolution or 1080. - Point screenResolution = new Point(); - Display display = activity.getWindowManager().getDefaultDisplay(); - display.getRealSize(screenResolution); - - final boolean swapWH = getMediaOrientation() % 180 == 90; - int screenWidth = swapWH ? screenResolution.y : screenResolution.x; - int screenHeight = swapWH ? screenResolution.x : screenResolution.y; - - List goodEnough = new ArrayList<>(); - for (Size s : sizes) { - if (minHeight <= s.getHeight() - && s.getWidth() <= screenWidth - && s.getHeight() <= screenHeight - && s.getHeight() <= 1080) { - goodEnough.add(s); - } - } - - Collections.sort(goodEnough, new CompareSizesByArea()); - - if (goodEnough.isEmpty()) { - previewSize = sizes[0]; - videoSize = sizes[0]; + // The preview should never be bigger than 720p (1280 x 720) or it will mess up the recording. + private void computeBestPreviewSize(CamcorderProfile profile) { + float ratio = (float) profile.videoFrameWidth / profile.videoFrameHeight; + if (profile.videoFrameWidth > 1280) { + previewSize = new Size(1280, Math.round(1280 / ratio)); + } else if (profile.videoFrameHeight > 1280) { + previewSize = new Size(Math.round(1280 * ratio), 1280); } else { - float captureSizeRatio = (float) captureSize.getWidth() / captureSize.getHeight(); - - previewSize = goodEnough.get(0); - for (Size s : goodEnough) { - if ((float) s.getWidth() / s.getHeight() == captureSizeRatio) { - previewSize = s; - break; - } - } - - Collections.reverse(goodEnough); - videoSize = goodEnough.get(0); - for (Size s : goodEnough) { - if ((float) s.getWidth() / s.getHeight() == captureSizeRatio) { - videoSize = s; - break; - } - } + previewSize = new Size(profile.videoFrameWidth, profile.videoFrameHeight); } } - private void computeBestCaptureSize(StreamConfigurationMap streamConfigurationMap) { - // For still image captures, we use the largest available size. - captureSize = - Collections.max( - Arrays.asList(streamConfigurationMap.getOutputSizes(ImageFormat.JPEG)), - new CompareSizesByArea()); + private void computeBestCaptureSize( + StreamConfigurationMap streamConfigurationMap, + CamcorderProfile profile, + String resolutionPreset) { + // For still image captures, use the largest image size if resolutionPreset is veryHigh + if ("veryHigh".equals(resolutionPreset)) { + captureSize = + Collections.max( + Arrays.asList(streamConfigurationMap.getOutputSizes(ImageFormat.JPEG)), + new CompareSizesByArea()); + } else { + captureSize = new Size(profile.videoFrameWidth, profile.videoFrameHeight); + } } private void prepareMediaRecorder(String outputFilePath) throws IOException { @@ -469,19 +425,77 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { mediaRecorder = new MediaRecorder(); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); - mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); - mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); - mediaRecorder.setVideoEncodingBitRate(1024 * 1000); - mediaRecorder.setAudioSamplingRate(16000); - mediaRecorder.setVideoFrameRate(27); - mediaRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight()); + mediaRecorder.setProfile(recordingProfile); mediaRecorder.setOutputFile(outputFilePath); mediaRecorder.setOrientationHint(getMediaOrientation()); mediaRecorder.prepare(); } + // We need to find the best available profile for the selected resolution. For this we make an ordered list + // of all the profiles (highest number is better quality) and we see if the requested profile is in it. If it's not + // we check the next profile and so on until the lowest quality profile. + private CamcorderProfile getBestAvailableCamcorderProfileForResolutionPreset( + String resolutionPreset) { + int camcorderProfileIndex; + switch (resolutionPreset) { + case "veryHigh": + camcorderProfileIndex = 4; + break; + case "high": + camcorderProfileIndex = 3; + break; + case "medium": + camcorderProfileIndex = 2; + break; + case "low": + camcorderProfileIndex = 1; + break; + case "veryLow": + camcorderProfileIndex = 0; + break; + default: + throw new IllegalArgumentException("Unknown resolution preset: " + resolutionPreset); + } + + // Try to use the closest available profile for requested quality. + SparseArray availableProfileIndexes = getAvailableCamcorderProfiles(); + for (int i = camcorderProfileIndex; i >= 0; i--) { + CamcorderProfile profile = availableProfileIndexes.get(i); + if (profile != null) { + return profile; + } + } + + // Last chance fallback. + if (CamcorderProfile.hasProfile(Integer.parseInt(cameraName), CamcorderProfile.QUALITY_LOW)) { + return CamcorderProfile.get(CamcorderProfile.QUALITY_LOW); + } else { + throw new IllegalArgumentException("No camera profile could be loaded for this camera."); + } + } + + private SparseArray getAvailableCamcorderProfiles() { + int cameraId = Integer.parseInt(cameraName); + SparseArray profiles = new SparseArray<>(); + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_2160P)) { + profiles.append(4, CamcorderProfile.get(CamcorderProfile.QUALITY_2160P)); + } + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) { + profiles.append(3, CamcorderProfile.get(CamcorderProfile.QUALITY_1080P)); + } + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) { + profiles.append(2, CamcorderProfile.get(CamcorderProfile.QUALITY_720P)); + } + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { + profiles.append(1, CamcorderProfile.get(CamcorderProfile.QUALITY_480P)); + } + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_CIF)) { + profiles.append(0, CamcorderProfile.get(CamcorderProfile.QUALITY_CIF)); + } + return profiles; + } + private void open(@Nullable final Result result) { if (!hasCameraPermission()) { if (result != null) result.error("cameraPermission", "Camera permission not granted", null); @@ -494,7 +508,10 @@ private void open(@Nullable final Result result) { // Used to steam image byte data to dart side. imageStreamReader = ImageReader.newInstance( - previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2); + recordingProfile.videoFrameWidth, + recordingProfile.videoFrameHeight, + ImageFormat.YUV_420_888, + 2); cameraManager.openCamera( cameraName, diff --git a/packages/camera/example/lib/main.dart b/packages/camera/example/lib/main.dart index ca01a7ac0f57..2fea4583846b 100644 --- a/packages/camera/example/lib/main.dart +++ b/packages/camera/example/lib/main.dart @@ -203,7 +203,7 @@ class _CameraExampleHomeState extends State { if (controller != null) { await controller.dispose(); } - controller = CameraController(cameraDescription, ResolutionPreset.high); + controller = CameraController(cameraDescription, ResolutionPreset.veryHigh); // If the controller is updated then update the UI. controller.addListener(() { diff --git a/packages/camera/lib/camera.dart b/packages/camera/lib/camera.dart index 8edbb8c59658..c9b51de61987 100644 --- a/packages/camera/lib/camera.dart +++ b/packages/camera/lib/camera.dart @@ -15,19 +15,23 @@ final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); enum CameraLensDirection { front, back, external } -enum ResolutionPreset { low, medium, high } +enum ResolutionPreset { veryLow, low, medium, high, veryHigh } typedef onLatestImageAvailable = Function(CameraImage image); /// Returns the resolution preset as a String. String serializeResolutionPreset(ResolutionPreset resolutionPreset) { switch (resolutionPreset) { + case ResolutionPreset.veryHigh: + return 'veryHigh'; case ResolutionPreset.high: return 'high'; case ResolutionPreset.medium: return 'medium'; case ResolutionPreset.low: return 'low'; + case ResolutionPreset.veryLow: + return 'veryLow'; } throw ArgumentError('Unknown ResolutionPreset value'); } From 1232d2209c68362e65f62f82f960dbf0dda1f84d Mon Sep 17 00:00:00 2001 From: Quentin Date: Tue, 26 Mar 2019 15:53:58 +0000 Subject: [PATCH 2/9] Improved best profile selection algorithm. --- .../flutter/plugins/camera/CameraPlugin.java | 75 ++++++++----------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java index 46a2f522ca49..2778a9edc778 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java @@ -25,7 +25,6 @@ import android.os.Build; import android.os.Bundle; import android.util.Size; -import android.util.SparseArray; import android.view.OrientationEventListener; import android.view.Surface; import androidx.annotation.NonNull; @@ -432,68 +431,60 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { mediaRecorder.prepare(); } - // We need to find the best available profile for the selected resolution. For this we make an ordered list - // of all the profiles (highest number is better quality) and we see if the requested profile is in it. If it's not - // we check the next profile and so on until the lowest quality profile. private CamcorderProfile getBestAvailableCamcorderProfileForResolutionPreset( String resolutionPreset) { int camcorderProfileIndex; switch (resolutionPreset) { case "veryHigh": - camcorderProfileIndex = 4; + camcorderProfileIndex = 0; break; case "high": - camcorderProfileIndex = 3; + camcorderProfileIndex = 1; break; case "medium": camcorderProfileIndex = 2; break; case "low": - camcorderProfileIndex = 1; + camcorderProfileIndex = 3; break; case "veryLow": - camcorderProfileIndex = 0; + camcorderProfileIndex = 4; break; default: throw new IllegalArgumentException("Unknown resolution preset: " + resolutionPreset); } - // Try to use the closest available profile for requested quality. - SparseArray availableProfileIndexes = getAvailableCamcorderProfiles(); - for (int i = camcorderProfileIndex; i >= 0; i--) { - CamcorderProfile profile = availableProfileIndexes.get(i); - if (profile != null) { - return profile; - } - } - - // Last chance fallback. - if (CamcorderProfile.hasProfile(Integer.parseInt(cameraName), CamcorderProfile.QUALITY_LOW)) { - return CamcorderProfile.get(CamcorderProfile.QUALITY_LOW); - } else { - throw new IllegalArgumentException("No camera profile could be loaded for this camera."); - } - } - - private SparseArray getAvailableCamcorderProfiles() { int cameraId = Integer.parseInt(cameraName); - SparseArray profiles = new SparseArray<>(); - if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_2160P)) { - profiles.append(4, CamcorderProfile.get(CamcorderProfile.QUALITY_2160P)); - } - if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) { - profiles.append(3, CamcorderProfile.get(CamcorderProfile.QUALITY_1080P)); - } - if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) { - profiles.append(2, CamcorderProfile.get(CamcorderProfile.QUALITY_720P)); - } - if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { - profiles.append(1, CamcorderProfile.get(CamcorderProfile.QUALITY_480P)); - } - if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_CIF)) { - profiles.append(0, CamcorderProfile.get(CamcorderProfile.QUALITY_CIF)); + switch (camcorderProfileIndex) { + case 0: + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_2160P)) { + return CamcorderProfile.get(CamcorderProfile.QUALITY_2160P); + } + case 1: + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) { + return CamcorderProfile.get(CamcorderProfile.QUALITY_1080P); + } + case 2: + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) { + return CamcorderProfile.get(CamcorderProfile.QUALITY_720P); + } + case 3: + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { + return CamcorderProfile.get(CamcorderProfile.QUALITY_480P); + } + case 4: + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_CIF)) { + return CamcorderProfile.get(CamcorderProfile.QUALITY_CIF); + } + default: + if (CamcorderProfile.hasProfile( + Integer.parseInt(cameraName), CamcorderProfile.QUALITY_LOW)) { + return CamcorderProfile.get(CamcorderProfile.QUALITY_LOW); + } else { + throw new IllegalArgumentException( + "No capture session available for current capture session."); + } } - return profiles; } private void open(@Nullable final Result result) { From 39c2d0efeb7634af6252b06840143ea8fb094a58 Mon Sep 17 00:00:00 2001 From: Quentin Date: Tue, 26 Mar 2019 17:44:37 +0000 Subject: [PATCH 3/9] Camera: Now resolutionPreset also influence image capture quality. Fixed uncaught exception. --- packages/camera/CHANGELOG.md | 7 ++ packages/camera/example/lib/main.dart | 2 +- packages/camera/ios/Classes/CameraPlugin.h | 2 +- packages/camera/ios/Classes/CameraPlugin.m | 86 ++++++++++++++-------- packages/camera/lib/camera.dart | 8 ++ packages/camera/pubspec.yaml | 2 +- 6 files changed, 72 insertions(+), 35 deletions(-) diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md index 933d24cb6100..ac0c8cfd9956 100644 --- a/packages/camera/CHANGELOG.md +++ b/packages/camera/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.4.4 + +* Added 2 new quality presets (veryHigh and veryLow). +* Now quality presets match on Android and iOS +* Now quality presets can be used to control image capture quality. +** NOTE: ** Existing presets have been updated, this will affect the quality of pictures and videos in existing apps. + ## 0.4.3+1 * Catch additional `Exception`s from Android and throw as `CameraException`s. diff --git a/packages/camera/example/lib/main.dart b/packages/camera/example/lib/main.dart index 2fea4583846b..24e4a418e25a 100644 --- a/packages/camera/example/lib/main.dart +++ b/packages/camera/example/lib/main.dart @@ -203,7 +203,7 @@ class _CameraExampleHomeState extends State { if (controller != null) { await controller.dispose(); } - controller = CameraController(cameraDescription, ResolutionPreset.veryHigh); + controller = CameraController(cameraDescription, ResolutionPreset.veryLow); // If the controller is updated then update the UI. controller.addListener(() { diff --git a/packages/camera/ios/Classes/CameraPlugin.h b/packages/camera/ios/Classes/CameraPlugin.h index 84a90f34b98c..a4c4482293ba 100644 --- a/packages/camera/ios/Classes/CameraPlugin.h +++ b/packages/camera/ios/Classes/CameraPlugin.h @@ -1,4 +1,4 @@ #import -@interface CameraPlugin : NSObject +@interface CameraPlugin : NSObject @end diff --git a/packages/camera/ios/Classes/CameraPlugin.m b/packages/camera/ios/Classes/CameraPlugin.m index 7d5d28d3a3a7..e891ceaa16fe 100644 --- a/packages/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/ios/Classes/CameraPlugin.m @@ -6,11 +6,11 @@ static FlutterError *getFlutterError(NSError *error) { return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code] - message:error.domain - details:error.localizedDescription]; + message:error.localizedDescription + details:error.domain]; } -@interface FLTSavePhotoDelegate : NSObject +@interface FLTSavePhotoDelegate : NSObject @property(readonly, nonatomic) NSString *path; @property(readonly, nonatomic) FlutterResult result; @property(readonly, nonatomic) CMMotionManager *motionManager; @@ -22,7 +22,7 @@ @interface FLTSavePhotoDelegate : NSObject cameraPosition:(AVCaptureDevicePosition)cameraPosition; @end -@interface FLTImageStreamHandler : NSObject +@interface FLTImageStreamHandler : NSObject @property FlutterEventSink eventSink; @end @@ -114,10 +114,8 @@ - (UIImageOrientation)getImageRotation { } @end -@interface FLTCam : NSObject +@interface FLTCam : NSObject @property(readonly, nonatomic) int64_t textureId; @property(nonatomic, copy) void (^onFrameAvailable)(); @property(nonatomic) FlutterEventChannel *eventChannel; @@ -140,6 +138,7 @@ @interface FLTCam : NSObject *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera" binaryMessenger:[registrar messenger]]; - CameraPlugin *instance = [[CameraPlugin alloc] initWithRegistry:[registrar textures] - messenger:[registrar messenger]]; + CameraPlugin *instance = + [[CameraPlugin alloc] initWithRegistry:[registrar textures] messenger:[registrar messenger]]; [registrar addMethodCallDelegate:instance channel:channel]; } diff --git a/packages/camera/lib/camera.dart b/packages/camera/lib/camera.dart index c9b51de61987..aaef9fb651c3 100644 --- a/packages/camera/lib/camera.dart +++ b/packages/camera/lib/camera.dart @@ -15,6 +15,14 @@ final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); enum CameraLensDirection { front, back, external } +/// Affect the quality of video recording and image capture: +/// - veryLow = 352x288 (CIF) +/// - low = 640x480 on iOS, 720x480 on Android (480p) +/// - medium = 1280x720 (720p) +/// - high = 1920x1080 (1080p/HD) +/// - veryHigh = 3840x2160 (2160p/4K) +/// +/// If a preset is not available on the camera being used a preset of lower quality will be selected automatically. enum ResolutionPreset { veryLow, low, medium, high, veryHigh } typedef onLatestImageAvailable = Function(CameraImage image); diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index a323aa94ad6d..11f22b3765c1 100644 --- a/packages/camera/pubspec.yaml +++ b/packages/camera/pubspec.yaml @@ -2,7 +2,7 @@ name: camera description: A Flutter plugin for getting information about and controlling the camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.4.3+1 +version: 0.4.4 authors: - Flutter Team - Luigi Agosti From dcac87d22f702c2c052518dcb8a2fc8a912920b5 Mon Sep 17 00:00:00 2001 From: Quentin Date: Wed, 27 Mar 2019 10:37:52 +0000 Subject: [PATCH 4/9] Use medium resolution preset for the example. --- packages/camera/example/lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/example/lib/main.dart b/packages/camera/example/lib/main.dart index 24e4a418e25a..b10113fedb00 100644 --- a/packages/camera/example/lib/main.dart +++ b/packages/camera/example/lib/main.dart @@ -203,7 +203,7 @@ class _CameraExampleHomeState extends State { if (controller != null) { await controller.dispose(); } - controller = CameraController(cameraDescription, ResolutionPreset.veryLow); + controller = CameraController(cameraDescription, ResolutionPreset.medium); // If the controller is updated then update the UI. controller.addListener(() { From 945dc8882726a125f8ce057fd316d3212e03f87a Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 5 Apr 2019 18:30:51 +0100 Subject: [PATCH 5/9] Fixed formating. --- packages/camera/CHANGELOG.md | 4 +-- packages/camera/ios/Classes/CameraPlugin.h | 2 +- packages/camera/ios/Classes/CameraPlugin.m | 29 ++++++++++++---------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md index c065c8270d8d..d919a3c13541 100644 --- a/packages/camera/CHANGELOG.md +++ b/packages/camera/CHANGELOG.md @@ -1,16 +1,14 @@ -<<<<<<< HEAD ## 0.4.4 * Added 2 new quality presets (veryHigh and veryLow). * Now quality presets match on Android and iOS * Now quality presets can be used to control image capture quality. ** NOTE: ** Existing presets have been updated, this will affect the quality of pictures and videos in existing apps. -======= + ## 0.4.3+2 * Bump the minimum Flutter version to 1.2.0. * Add template type parameter to `invokeMethod` calls. ->>>>>>> 6926dc47b948249a078b3f51d6948d83a939e950 ## 0.4.3+1 diff --git a/packages/camera/ios/Classes/CameraPlugin.h b/packages/camera/ios/Classes/CameraPlugin.h index a4c4482293ba..84a90f34b98c 100644 --- a/packages/camera/ios/Classes/CameraPlugin.h +++ b/packages/camera/ios/Classes/CameraPlugin.h @@ -1,4 +1,4 @@ #import -@interface CameraPlugin : NSObject +@interface CameraPlugin : NSObject @end diff --git a/packages/camera/ios/Classes/CameraPlugin.m b/packages/camera/ios/Classes/CameraPlugin.m index e891ceaa16fe..92124fdfaabe 100644 --- a/packages/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/ios/Classes/CameraPlugin.m @@ -10,7 +10,7 @@ details:error.domain]; } -@interface FLTSavePhotoDelegate : NSObject +@interface FLTSavePhotoDelegate : NSObject @property(readonly, nonatomic) NSString *path; @property(readonly, nonatomic) FlutterResult result; @property(readonly, nonatomic) CMMotionManager *motionManager; @@ -22,7 +22,7 @@ @interface FLTSavePhotoDelegate : NSObject cameraPosition:(AVCaptureDevicePosition)cameraPosition; @end -@interface FLTImageStreamHandler : NSObject +@interface FLTImageStreamHandler : NSObject @property FlutterEventSink eventSink; @end @@ -114,8 +114,10 @@ - (UIImageOrientation)getImageRotation { } @end -@interface FLTCam : NSObject +@interface FLTCam : NSObject @property(readonly, nonatomic) int64_t textureId; @property(nonatomic, copy) void (^onFrameAvailable)(); @property(nonatomic) FlutterEventChannel *eventChannel; @@ -172,8 +174,8 @@ - (instancetype)initWithCameraName:(NSString *)cameraName _captureDevice = [AVCaptureDevice deviceWithUniqueID:cameraName]; NSError *localError = nil; - _captureVideoInput = - [AVCaptureDeviceInput deviceInputWithDevice:_captureDevice error:&localError]; + _captureVideoInput = [AVCaptureDeviceInput deviceInputWithDevice:_captureDevice + error:&localError]; if (localError) { *error = localError; return nil; @@ -181,7 +183,7 @@ - (instancetype)initWithCameraName:(NSString *)cameraName _captureVideoOutput = [AVCaptureVideoDataOutput new]; _captureVideoOutput.videoSettings = - @{(NSString *)kCVPixelBufferPixelFormatTypeKey : @(videoFormat) }; + @{(NSString *)kCVPixelBufferPixelFormatTypeKey : @(videoFormat)}; [_captureVideoOutput setAlwaysDiscardsLateVideoFrames:YES]; [_captureVideoOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()]; @@ -558,8 +560,9 @@ - (BOOL)setupWriterForPath:(NSString *)path { if (!_isAudioSetup) { [self setUpCaptureSessionForAudio]; } - _videoWriter = - [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeQuickTimeMovie error:&error]; + _videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL + fileType:AVFileTypeQuickTimeMovie + error:&error]; NSParameterAssert(_videoWriter); if (error) { _eventSink(@{@"event" : @"error", @"errorDescription" : error.description}); @@ -602,8 +605,8 @@ - (void)setUpCaptureSessionForAudio { // Create a device input with the device and add it to the session. // Setup the audio input. AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; - AVCaptureDeviceInput *audioInput = - [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error]; + AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice + error:&error]; if (error) { _eventSink(@{@"event" : @"error", @"errorDescription" : error.description}); } @@ -640,8 +643,8 @@ + (void)registerWithRegistrar:(NSObject *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera" binaryMessenger:[registrar messenger]]; - CameraPlugin *instance = - [[CameraPlugin alloc] initWithRegistry:[registrar textures] messenger:[registrar messenger]]; + CameraPlugin *instance = [[CameraPlugin alloc] initWithRegistry:[registrar textures] + messenger:[registrar messenger]]; [registrar addMethodCallDelegate:instance channel:channel]; } From 5f482cc787ec9e1803a6b4f10fe0276428738159 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Tue, 6 Aug 2019 12:45:20 -0700 Subject: [PATCH 6/9] Add integration test for size settings Only works on Android since it requires automatic accepting of camera permissions to run. --- packages/camera/example/pubspec.yaml | 3 + .../camera/example/test_driver/camera.dart | 130 ++++++++++++++++++ .../example/test_driver/camera_test.dart | 7 + packages/camera/pubspec.yaml | 7 +- 4 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 packages/camera/example/test_driver/camera.dart create mode 100644 packages/camera/example/test_driver/camera_test.dart diff --git a/packages/camera/example/pubspec.yaml b/packages/camera/example/pubspec.yaml index 834fe1b98cee..ec305366df71 100644 --- a/packages/camera/example/pubspec.yaml +++ b/packages/camera/example/pubspec.yaml @@ -13,6 +13,9 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + flutter_driver: + sdk: flutter + test: 1.6.3 flutter: uses-material-design: true diff --git a/packages/camera/example/test_driver/camera.dart b/packages/camera/example/test_driver/camera.dart new file mode 100644 index 000000000000..931d9789f7e3 --- /dev/null +++ b/packages/camera/example/test_driver/camera.dart @@ -0,0 +1,130 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:ui'; + +import 'package:flutter/painting.dart'; +import 'package:flutter_driver/driver_extension.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:camera/camera.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:video_player/video_player.dart'; + +void main() { + final Completer completer = Completer(); + Directory testDir; + enableFlutterDriverExtension(handler: (_) => completer.future); + + setUpAll(() async { + final Directory extDir = await getApplicationDocumentsDirectory(); + testDir = await Directory('${extDir.path}/test').create(recursive: true); + }); + + tearDownAll(() async { + await testDir.delete(recursive: true); + completer.complete(null); + }); + + final Map presetExpectedSizes = { + ResolutionPreset.veryLow: const Size(288, 352), + ResolutionPreset.low: Platform.isAndroid ? const Size(480, 720) : const Size(480, 640), + ResolutionPreset.medium: const Size(720, 1280), + ResolutionPreset.high: const Size(1080, 1920), + // Don't bother checking for veryHigh here since it could be anything. + // ResolutionPreset.veryHigh: const Size(2160, 3840), + }; + + /// Verify that [actual] has dimensions that are at least as large as + /// [expectedSize]. Allows for a mismatch in portrait vs landscape. Returns + /// whether the dimensions exactly match. + bool assertExpectedDimensions(Size expectedSize, Size actual) { + expect(actual.shortestSide, lessThanOrEqualTo(expectedSize.shortestSide)); + expect(actual.longestSide, lessThanOrEqualTo(expectedSize.longestSide)); + return actual.shortestSide == expectedSize.shortestSide && actual.longestSide == expectedSize.longestSide; + } + + // This tests that the capture is no bigger than the preset, since we have + // automatic code to fall back to smaller sizes when we need to. Returns + // whether the image is exactly the desired resolution. + Future testCaptureImageResolution(CameraController controller, ResolutionPreset preset) async { + final Size expectedSize = presetExpectedSizes[preset]; + print('Test capturing photo at $preset (${expectedSize.width}x${expectedSize.height}) using camera ${controller.description.name}'); + + // Take Picture + final String filePath = + '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.jpg'; + await controller.takePicture(filePath); + + // Load picture + final File fileImage = File(filePath); + final Image image = await decodeImageFromList(fileImage.readAsBytesSync()); + + // Verify image dimensions are as expected + expect(image, isNotNull); + return assertExpectedDimensions(expectedSize, Size(image.height.toDouble(), image.width.toDouble())); + } + + test('Capture specific image resolutions', () async { + final List cameras = await availableCameras(); + if (cameras.isEmpty) { + return; + } + for (CameraDescription cameraDescription in cameras) { + bool previousPresetExactlySupported = true; + for (MapEntry preset in presetExpectedSizes.entries) { + final CameraController controller = CameraController(cameraDescription, preset.key); + await controller.initialize(); + final bool presetExactlySupported = await testCaptureImageResolution(controller, preset.key); + assert(!(!previousPresetExactlySupported && previousPresetExactlySupported), + 'The camera took higher resolution pictures at a lower resolution.'); + previousPresetExactlySupported = presetExactlySupported; + await controller.dispose(); + } + } + }); + + + // This tests that the capture is no bigger than the preset, since we have + // automatic code to fall back to smaller sizes when we need to. Returns + // whether the image is exactly the desired resolution. + Future testCaptureVideoResolution(CameraController controller, ResolutionPreset preset) async { + final Size expectedSize = presetExpectedSizes[preset]; + print('Test capturing video at $preset (${expectedSize.width}x${expectedSize.height}) using camera ${controller.description.name}'); + + // Take Video + final String filePath = + '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.mp4'; + await controller.startVideoRecording(filePath); + sleep(Duration(milliseconds: 300)); + await controller.stopVideoRecording(); + + // Load video metadata + final File videoFile = File(filePath); + final VideoPlayerController videoController = VideoPlayerController.file(videoFile); + await videoController.initialize(); + final Size video = videoController.value.size; + + // Verify image dimensions are as expected + expect(video, isNotNull); + return assertExpectedDimensions(expectedSize, Size(video.height, video.width)); + } + + test('Capture specific video resolutions', () async { + final List cameras = await availableCameras(); + if (cameras.isEmpty) { + return; + } + for (CameraDescription cameraDescription in cameras) { + bool previousPresetExactlySupported = true; + for (MapEntry preset in presetExpectedSizes.entries) { + final CameraController controller = CameraController(cameraDescription, preset.key); + await controller.initialize(); + await controller.prepareForVideoRecording(); + final bool presetExactlySupported = await testCaptureVideoResolution(controller, preset.key); + assert(!(!previousPresetExactlySupported && previousPresetExactlySupported), + 'The camera took higher resolution pictures at a lower resolution.'); + previousPresetExactlySupported = presetExactlySupported; + await controller.dispose(); + } + } + }); +} diff --git a/packages/camera/example/test_driver/camera_test.dart b/packages/camera/example/test_driver/camera_test.dart new file mode 100644 index 000000000000..38fe6c447e05 --- /dev/null +++ b/packages/camera/example/test_driver/camera_test.dart @@ -0,0 +1,7 @@ +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + await driver.requestData(null, timeout: const Duration(minutes: 1)); + driver.close(); +} diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index cd608c9987be..850c2d5f03d1 100644 --- a/packages/camera/pubspec.yaml +++ b/packages/camera/pubspec.yaml @@ -18,10 +18,13 @@ dependencies: sdk: flutter dev_dependencies: - flutter_test: - sdk: flutter path_provider: ^0.5.0 video_player: ^0.10.0 + flutter_test: + sdk: flutter + flutter_driver: + sdk: flutter + test: 1.6.3 flutter: plugin: From d376121e7bd54b6bfcb3857a8c46dc14c0a75a07 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Tue, 6 Aug 2019 17:05:10 -0700 Subject: [PATCH 7/9] Review feedback and format --- packages/camera/CHANGELOG.md | 6 +- .../flutter/plugins/camera/CameraPlugin.java | 93 +++++++----------- .../camera/example/test_driver/camera.dart | 72 +++++++++----- packages/camera/ios/Classes/CameraPlugin.m | 96 ++++++++++++------- packages/camera/lib/camera.dart | 31 ++++-- 5 files changed, 165 insertions(+), 133 deletions(-) diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md index e87222a6d1f7..196cd3e2c563 100644 --- a/packages/camera/CHANGELOG.md +++ b/packages/camera/CHANGELOG.md @@ -1,9 +1,7 @@ ## 0.5.3 -* Added 2 new quality presets (veryHigh and veryLow). -* Now quality presets match on Android and iOS -* Now quality presets can be used to control image capture quality. -** NOTE: ** Existing presets have been updated, this will affect the quality of pictures and videos in existing apps. +* Added new quality presets. +* Now all quality presets can be used to control image capture quality. ## 0.5.2+2 diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java index 2117d0dab1fd..47180f599d92 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java @@ -40,8 +40,6 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -61,6 +59,16 @@ public class CameraPlugin implements MethodCallHandler { private final OrientationEventListener orientationEventListener; private int currentOrientation = ORIENTATION_UNKNOWN; + // Mirrors camera.dart + private enum ResolutionPreset { + low, + medium, + high, + veryHigh, + ultraHigh, + max, + } + private CameraPlugin(Registrar registrar, FlutterView view) { this.registrar = registrar; this.view = view; @@ -276,9 +284,10 @@ private class Camera { characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT; - recordingProfile = getBestAvailableCamcorderProfileForResolutionPreset(resolutionPreset); - computeBestPreviewSize(recordingProfile); - computeBestCaptureSize(streamConfigurationMap, recordingProfile, resolutionPreset); + ResolutionPreset preset = ResolutionPreset.valueOf(resolutionPreset); + recordingProfile = getBestAvailableCamcorderProfileForResolutionPreset(preset); + computeBestPreviewSize(preset); + captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight); if (cameraPermissionContinuation != null) { result.error("cameraPermission", "Camera permission request ongoing", null); @@ -364,30 +373,13 @@ private boolean hasAudioPermission() { } // The preview should never be bigger than 720p (1280 x 720) or it will mess up the recording. - private void computeBestPreviewSize(CamcorderProfile profile) { - float ratio = (float) profile.videoFrameWidth / profile.videoFrameHeight; - if (profile.videoFrameWidth > 1280) { - previewSize = new Size(1280, Math.round(1280 / ratio)); - } else if (profile.videoFrameHeight > 1280) { - previewSize = new Size(Math.round(1280 * ratio), 1280); - } else { - previewSize = new Size(profile.videoFrameWidth, profile.videoFrameHeight); + private void computeBestPreviewSize(ResolutionPreset preset) { + if (preset.ordinal() > ResolutionPreset.high.ordinal()) { + preset = ResolutionPreset.high; } - } - private void computeBestCaptureSize( - StreamConfigurationMap streamConfigurationMap, - CamcorderProfile profile, - String resolutionPreset) { - // For still image captures, use the largest image size if resolutionPreset is veryHigh - if ("veryHigh".equals(resolutionPreset)) { - captureSize = - Collections.max( - Arrays.asList(streamConfigurationMap.getOutputSizes(ImageFormat.JPEG)), - new CompareSizesByArea()); - } else { - captureSize = new Size(profile.videoFrameWidth, profile.videoFrameHeight); - } + CamcorderProfile profile = getBestAvailableCamcorderProfileForResolutionPreset(preset); + previewSize = new Size(profile.videoFrameWidth, profile.videoFrameHeight); } private void prepareMediaRecorder(String outputFilePath) throws IOException { @@ -404,7 +396,8 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { mediaRecorder.setVideoEncodingBitRate(recordingProfile.videoBitRate); if (enableAudio) mediaRecorder.setAudioSamplingRate(recordingProfile.audioSampleRate); mediaRecorder.setVideoFrameRate(recordingProfile.videoFrameRate); - mediaRecorder.setVideoSize(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight); + mediaRecorder.setVideoSize( + recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight); mediaRecorder.setOutputFile(outputFilePath); mediaRecorder.setOrientationHint(getMediaOrientation()); @@ -412,49 +405,33 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { } private CamcorderProfile getBestAvailableCamcorderProfileForResolutionPreset( - String resolutionPreset) { - int camcorderProfileIndex; - switch (resolutionPreset) { - case "veryHigh": - camcorderProfileIndex = 0; - break; - case "high": - camcorderProfileIndex = 1; - break; - case "medium": - camcorderProfileIndex = 2; - break; - case "low": - camcorderProfileIndex = 3; - break; - case "veryLow": - camcorderProfileIndex = 4; - break; - default: - throw new IllegalArgumentException("Unknown resolution preset: " + resolutionPreset); - } - + ResolutionPreset preset) { int cameraId = Integer.parseInt(cameraName); - switch (camcorderProfileIndex) { - case 0: + switch (preset) { + // All of these cases deliberately fall through to get the best available profile. + case max: + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH)) { + return CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH); + } + case ultraHigh: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_2160P)) { return CamcorderProfile.get(CamcorderProfile.QUALITY_2160P); } - case 1: + case veryHigh: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) { return CamcorderProfile.get(CamcorderProfile.QUALITY_1080P); } - case 2: + case high: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) { return CamcorderProfile.get(CamcorderProfile.QUALITY_720P); } - case 3: + case medium: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { return CamcorderProfile.get(CamcorderProfile.QUALITY_480P); } - case 4: - if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_CIF)) { - return CamcorderProfile.get(CamcorderProfile.QUALITY_CIF); + case low: + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_QVGA)) { + return CamcorderProfile.get(CamcorderProfile.QUALITY_QVGA); } default: if (CamcorderProfile.hasProfile( diff --git a/packages/camera/example/test_driver/camera.dart b/packages/camera/example/test_driver/camera.dart index 931d9789f7e3..3b268a514eb9 100644 --- a/packages/camera/example/test_driver/camera.dart +++ b/packages/camera/example/test_driver/camera.dart @@ -24,13 +24,16 @@ void main() { completer.complete(null); }); - final Map presetExpectedSizes = { - ResolutionPreset.veryLow: const Size(288, 352), - ResolutionPreset.low: Platform.isAndroid ? const Size(480, 720) : const Size(480, 640), - ResolutionPreset.medium: const Size(720, 1280), - ResolutionPreset.high: const Size(1080, 1920), - // Don't bother checking for veryHigh here since it could be anything. - // ResolutionPreset.veryHigh: const Size(2160, 3840), + final Map presetExpectedSizes = + { + ResolutionPreset.low: + Platform.isAndroid ? const Size(240, 320) : const Size(288, 352), + ResolutionPreset.medium: + Platform.isAndroid ? const Size(480, 720) : const Size(480, 640), + ResolutionPreset.high: const Size(720, 1280), + ResolutionPreset.veryHigh: const Size(1080, 1920), + ResolutionPreset.ultraHigh: const Size(2160, 3840), + // Don't bother checking for max here since it could be anything. }; /// Verify that [actual] has dimensions that are at least as large as @@ -39,15 +42,18 @@ void main() { bool assertExpectedDimensions(Size expectedSize, Size actual) { expect(actual.shortestSide, lessThanOrEqualTo(expectedSize.shortestSide)); expect(actual.longestSide, lessThanOrEqualTo(expectedSize.longestSide)); - return actual.shortestSide == expectedSize.shortestSide && actual.longestSide == expectedSize.longestSide; + return actual.shortestSide == expectedSize.shortestSide && + actual.longestSide == expectedSize.longestSide; } // This tests that the capture is no bigger than the preset, since we have // automatic code to fall back to smaller sizes when we need to. Returns // whether the image is exactly the desired resolution. - Future testCaptureImageResolution(CameraController controller, ResolutionPreset preset) async { + Future testCaptureImageResolution( + CameraController controller, ResolutionPreset preset) async { final Size expectedSize = presetExpectedSizes[preset]; - print('Test capturing photo at $preset (${expectedSize.width}x${expectedSize.height}) using camera ${controller.description.name}'); + print( + 'Capturing photo at $preset (${expectedSize.width}x${expectedSize.height}) using camera ${controller.description.name}'); // Take Picture final String filePath = @@ -60,7 +66,8 @@ void main() { // Verify image dimensions are as expected expect(image, isNotNull); - return assertExpectedDimensions(expectedSize, Size(image.height.toDouble(), image.width.toDouble())); + return assertExpectedDimensions( + expectedSize, Size(image.height.toDouble(), image.width.toDouble())); } test('Capture specific image resolutions', () async { @@ -70,25 +77,31 @@ void main() { } for (CameraDescription cameraDescription in cameras) { bool previousPresetExactlySupported = true; - for (MapEntry preset in presetExpectedSizes.entries) { - final CameraController controller = CameraController(cameraDescription, preset.key); + for (MapEntry preset + in presetExpectedSizes.entries) { + final CameraController controller = + CameraController(cameraDescription, preset.key); await controller.initialize(); - final bool presetExactlySupported = await testCaptureImageResolution(controller, preset.key); - assert(!(!previousPresetExactlySupported && previousPresetExactlySupported), - 'The camera took higher resolution pictures at a lower resolution.'); + final bool presetExactlySupported = + await testCaptureImageResolution(controller, preset.key); + assert( + !(!previousPresetExactlySupported && + previousPresetExactlySupported), + 'The camera took higher resolution pictures at a lower resolution.'); previousPresetExactlySupported = presetExactlySupported; await controller.dispose(); } } }); - // This tests that the capture is no bigger than the preset, since we have // automatic code to fall back to smaller sizes when we need to. Returns // whether the image is exactly the desired resolution. - Future testCaptureVideoResolution(CameraController controller, ResolutionPreset preset) async { + Future testCaptureVideoResolution( + CameraController controller, ResolutionPreset preset) async { final Size expectedSize = presetExpectedSizes[preset]; - print('Test capturing video at $preset (${expectedSize.width}x${expectedSize.height}) using camera ${controller.description.name}'); + print( + 'Capturing video at $preset (${expectedSize.width}x${expectedSize.height}) using camera ${controller.description.name}'); // Take Video final String filePath = @@ -99,13 +112,15 @@ void main() { // Load video metadata final File videoFile = File(filePath); - final VideoPlayerController videoController = VideoPlayerController.file(videoFile); + final VideoPlayerController videoController = + VideoPlayerController.file(videoFile); await videoController.initialize(); final Size video = videoController.value.size; // Verify image dimensions are as expected expect(video, isNotNull); - return assertExpectedDimensions(expectedSize, Size(video.height, video.width)); + return assertExpectedDimensions( + expectedSize, Size(video.height, video.width)); } test('Capture specific video resolutions', () async { @@ -115,13 +130,18 @@ void main() { } for (CameraDescription cameraDescription in cameras) { bool previousPresetExactlySupported = true; - for (MapEntry preset in presetExpectedSizes.entries) { - final CameraController controller = CameraController(cameraDescription, preset.key); + for (MapEntry preset + in presetExpectedSizes.entries) { + final CameraController controller = + CameraController(cameraDescription, preset.key); await controller.initialize(); await controller.prepareForVideoRecording(); - final bool presetExactlySupported = await testCaptureVideoResolution(controller, preset.key); - assert(!(!previousPresetExactlySupported && previousPresetExactlySupported), - 'The camera took higher resolution pictures at a lower resolution.'); + final bool presetExactlySupported = + await testCaptureVideoResolution(controller, preset.key); + assert( + !(!previousPresetExactlySupported && + previousPresetExactlySupported), + 'The camera took higher resolution pictures at a lower resolution.'); previousPresetExactlySupported = presetExactlySupported; await controller.dispose(); } diff --git a/packages/camera/ios/Classes/CameraPlugin.m b/packages/camera/ios/Classes/CameraPlugin.m index 6c77666339cd..9a20f599d003 100644 --- a/packages/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/ios/Classes/CameraPlugin.m @@ -114,6 +114,44 @@ - (UIImageOrientation)getImageRotation { } @end +// Mirrors ResolutionPreset in camera.dart +typedef enum { + veryLow, + low, + medium, + high, + veryHigh, + ultraHigh, + max, +} ResolutionPreset; + +static ResolutionPreset getResolutionPresetForString(NSString * preset) { + if ([preset isEqualToString:@"veryLow"]) { + return veryLow; + } else if ([preset isEqualToString:@"low"]) { + return low; + } else if ([preset isEqualToString:@"medium"]) { + return medium; + } else if ([preset isEqualToString:@"high"]) { + return high; + } else if ([preset isEqualToString:@"veryHigh"]) { + return veryHigh; + } else if ([preset isEqualToString:@"ultraHigh"]) { + return ultraHigh; + } else if ([preset isEqualToString:@"max"]) { + return max; + } else { + NSError *error = [NSError + errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : + [NSString stringWithFormat:@"Unknown resolution preset %@", preset] + }]; + @throw error; + } +} + @interface FLTCam : NSObject Date: Tue, 6 Aug 2019 18:57:55 -0700 Subject: [PATCH 8/9] Fix formatting issues. --- .../camera/example/test_driver/camera.dart | 2 +- packages/camera/ios/Classes/CameraPlugin.m | 53 ++++++++++--------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/packages/camera/example/test_driver/camera.dart b/packages/camera/example/test_driver/camera.dart index 3b268a514eb9..bc3fa3ab436b 100644 --- a/packages/camera/example/test_driver/camera.dart +++ b/packages/camera/example/test_driver/camera.dart @@ -107,7 +107,7 @@ void main() { final String filePath = '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.mp4'; await controller.startVideoRecording(filePath); - sleep(Duration(milliseconds: 300)); + sleep(const Duration(milliseconds: 300)); await controller.stopVideoRecording(); // Load video metadata diff --git a/packages/camera/ios/Classes/CameraPlugin.m b/packages/camera/ios/Classes/CameraPlugin.m index 9a20f599d003..3aeaa4612a3e 100644 --- a/packages/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/ios/Classes/CameraPlugin.m @@ -125,31 +125,30 @@ - (UIImageOrientation)getImageRotation { max, } ResolutionPreset; -static ResolutionPreset getResolutionPresetForString(NSString * preset) { - if ([preset isEqualToString:@"veryLow"]) { - return veryLow; - } else if ([preset isEqualToString:@"low"]) { - return low; - } else if ([preset isEqualToString:@"medium"]) { - return medium; - } else if ([preset isEqualToString:@"high"]) { - return high; - } else if ([preset isEqualToString:@"veryHigh"]) { - return veryHigh; - } else if ([preset isEqualToString:@"ultraHigh"]) { - return ultraHigh; - } else if ([preset isEqualToString:@"max"]) { - return max; - } else { - NSError *error = [NSError - errorWithDomain:NSCocoaErrorDomain - code:NSURLErrorUnknown - userInfo:@{ - NSLocalizedDescriptionKey : - [NSString stringWithFormat:@"Unknown resolution preset %@", preset] - }]; - @throw error; - } +static ResolutionPreset getResolutionPresetForString(NSString *preset) { + if ([preset isEqualToString:@"veryLow"]) { + return veryLow; + } else if ([preset isEqualToString:@"low"]) { + return low; + } else if ([preset isEqualToString:@"medium"]) { + return medium; + } else if ([preset isEqualToString:@"high"]) { + return high; + } else if ([preset isEqualToString:@"veryHigh"]) { + return veryHigh; + } else if ([preset isEqualToString:@"ultraHigh"]) { + return ultraHigh; + } else if ([preset isEqualToString:@"max"]) { + return max; + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"Unknown resolution preset %@", preset] + }]; + @throw error; + } } @interface FLTCam : NSObject Date: Wed, 7 Aug 2019 16:15:22 -0700 Subject: [PATCH 9/9] Review fixup --- packages/camera/example/pubspec.yaml | 1 - packages/camera/example/test_driver/camera.dart | 10 +++------- packages/camera/pubspec.yaml | 1 - 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/camera/example/pubspec.yaml b/packages/camera/example/pubspec.yaml index ec305366df71..59f3821abe21 100644 --- a/packages/camera/example/pubspec.yaml +++ b/packages/camera/example/pubspec.yaml @@ -15,7 +15,6 @@ dev_dependencies: sdk: flutter flutter_driver: sdk: flutter - test: 1.6.3 flutter: uses-material-design: true diff --git a/packages/camera/example/test_driver/camera.dart b/packages/camera/example/test_driver/camera.dart index bc3fa3ab436b..7d59016ff0b1 100644 --- a/packages/camera/example/test_driver/camera.dart +++ b/packages/camera/example/test_driver/camera.dart @@ -15,7 +15,7 @@ void main() { enableFlutterDriverExtension(handler: (_) => completer.future); setUpAll(() async { - final Directory extDir = await getApplicationDocumentsDirectory(); + final Directory extDir = await getTemporaryDirectory(); testDir = await Directory('${extDir.path}/test').create(recursive: true); }); @@ -84,9 +84,7 @@ void main() { await controller.initialize(); final bool presetExactlySupported = await testCaptureImageResolution(controller, preset.key); - assert( - !(!previousPresetExactlySupported && - previousPresetExactlySupported), + assert(!(!previousPresetExactlySupported && presetExactlySupported), 'The camera took higher resolution pictures at a lower resolution.'); previousPresetExactlySupported = presetExactlySupported; await controller.dispose(); @@ -138,9 +136,7 @@ void main() { await controller.prepareForVideoRecording(); final bool presetExactlySupported = await testCaptureVideoResolution(controller, preset.key); - assert( - !(!previousPresetExactlySupported && - previousPresetExactlySupported), + assert(!(!previousPresetExactlySupported && presetExactlySupported), 'The camera took higher resolution pictures at a lower resolution.'); previousPresetExactlySupported = presetExactlySupported; await controller.dispose(); diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index 850c2d5f03d1..3e290f10568d 100644 --- a/packages/camera/pubspec.yaml +++ b/packages/camera/pubspec.yaml @@ -24,7 +24,6 @@ dev_dependencies: sdk: flutter flutter_driver: sdk: flutter - test: 1.6.3 flutter: plugin: