Skip to content

Commit da1b463

Browse files
danielroekanniek-valkmvanbeusekom
authored
[camera] Implemented ImageStream ImageFormat setting for Dart and Android (flutter#3359)
* Implemented ImageStream ImageFormat setting for Dart and Android * Fixed formatting and toString test * Apply suggestions from code review * Removed imageStreamImageFormat from CameraValue * Removed imageStreamImageFormat from CameraValue * Removed imageStreamImageFormat from CameraValue * fixed formatting * fixed formatting * fixed formatting * WIP: iOS implementation * Imaplemented suggested changes, added tests. * iOS switch case videoFormat * Added imageFormatGroup to initialize * Apply suggestions from code review Co-authored-by: Maurits van Beusekom <[email protected]> * Added period to sentence * Moved ImageFormatGroup to platform_interface; Added extension to convert ImageFormatGroup to name; Changed int to ImageFormatGroup for initializeCamera * Fixed test * Separated Android and iOS in name extension * Clarified returns on name extension * updated Android implementation to support String output * removed getOrDefault * Updated camera implementation to use ImageFormatGroupName; Updated to Dart 2.7.0 to support extensions; * removed unused import * Export image_format_group.dart in types.dart * Changed enum values to lowercase * Added ImageFormatGroup test * Fixed formatting * made enum strings lowercase * Removed target platform switch. * Fixed formatting * Updated Android implementation * Updated iOS implementation * updated log message for unsupported ImageFormatGroup * Updated Android implementation * fixed period in docs * Switch change to if-statement * Moved switching videoFormat to method in iOS * Implemented feedback * fixed formatting * fixed mistakingly removed bracket * fixed formatting * Updated version * Updated version * fixed formatting * Define TAG correctly Co-authored-by: anniek <[email protected]> Co-authored-by: Maurits van Beusekom <[email protected]> Co-authored-by: Maurits van Beusekom <[email protected]>
1 parent 34faaaf commit da1b463

File tree

12 files changed

+85
-41
lines changed

12 files changed

+85
-41
lines changed

packages/camera/camera/CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 0.6.5
2+
3+
* Adds ImageFormat selection for ImageStream and Video(iOS only).
14
## 0.6.4+5
25

36
* Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets.
@@ -16,7 +19,7 @@
1619

1720
## 0.6.4+1
1821

19-
* Added closeCaptureSession() to stopVideoRecording in Camera.java to fix an Android 6 crash
22+
* Added closeCaptureSession() to stopVideoRecording in Camera.java to fix an Android 6 crash.
2023

2124
## 0.6.4
2225

packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ interface ErrorCallback {
6767
}
6868

6969
public class Camera {
70+
private static final String TAG = "Camera";
71+
7072
private final SurfaceTextureEntry flutterTexture;
7173
private final CameraManager cameraManager;
7274
private final OrientationEventListener orientationEventListener;
@@ -99,6 +101,14 @@ public class Camera {
99101
private boolean useAutoFocus = true;
100102
private Range<Integer> fpsRange;
101103

104+
private static final HashMap<String, Integer> supportedImageFormats;
105+
// Current supported outputs
106+
static {
107+
supportedImageFormats = new HashMap<>();
108+
supportedImageFormats.put("yuv420", 35);
109+
supportedImageFormats.put("jpeg", 256);
110+
}
111+
102112
public Camera(
103113
final Activity activity,
104114
final SurfaceTextureEntry flutterTexture,
@@ -183,15 +193,20 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException {
183193
}
184194

185195
@SuppressLint("MissingPermission")
186-
public void open() throws CameraAccessException {
196+
public void open(String imageFormatGroup) throws CameraAccessException {
187197
pictureImageReader =
188198
ImageReader.newInstance(
189199
captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2);
190200

201+
Integer imageFormat = supportedImageFormats.get(imageFormatGroup);
202+
if (imageFormat == null) {
203+
Log.w(TAG, "The selected imageFormatGroup is not supported by Android. Defaulting to yuv420");
204+
imageFormat = ImageFormat.YUV_420_888;
205+
}
206+
191207
// Used to steam image byte data to dart side.
192208
imageStreamReader =
193-
ImageReader.newInstance(
194-
previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2);
209+
ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), imageFormat, 2);
195210

196211
cameraManager.openCamera(
197212
cameraName,

packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
8080
{
8181
if (camera != null) {
8282
try {
83-
camera.open();
83+
camera.open(call.argument("imageFormatGroup"));
8484
result.success(null);
8585
} catch (Exception e) {
8686
handleException(e, result);

packages/camera/camera/example/lib/main.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
486486
cameraDescription,
487487
ResolutionPreset.medium,
488488
enableAudio: enableAudio,
489+
imageFormatGroup: ImageFormatGroup.jpeg,
489490
);
490491

491492
// If the controller is updated then update the UI.

packages/camera/camera/example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ flutter:
2222
uses-material-design: true
2323

2424
environment:
25-
sdk: ">=2.0.0-dev.28.0 <3.0.0"
25+
sdk: ">=2.7.0 <3.0.0"
2626
flutter: ">=1.9.1+hotfix.4 <2.0.0"

packages/camera/camera/ios/Classes/CameraPlugin.m

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,17 @@ static FlashMode getFlashModeForString(NSString *mode) {
162162
}
163163
}
164164

165+
static OSType getVideoFormatFromString(NSString *videoFormatString) {
166+
if ([videoFormatString isEqualToString:@"bgra8888"]) {
167+
return kCVPixelFormatType_32BGRA;
168+
} else if ([videoFormatString isEqualToString:@"yuv420"]) {
169+
return kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
170+
} else {
171+
NSLog(@"The selected imageFormatGroup is not supported by iOS. Defaulting to brga8888");
172+
return kCVPixelFormatType_32BGRA;
173+
}
174+
}
175+
165176
static AVCaptureFlashMode getAVCaptureFlashModeForFlashMode(FlashMode mode) {
166177
switch (mode) {
167178
case FlashModeOff:
@@ -296,7 +307,7 @@ @implementation FLTCam {
296307
dispatch_queue_t _dispatchQueue;
297308
}
298309
// Format used for video and image streaming.
299-
FourCharCode const videoFormat = kCVPixelFormatType_32BGRA;
310+
FourCharCode videoFormat = kCVPixelFormatType_32BGRA;
300311
NSString *const errorMethod = @"error";
301312

302313
- (instancetype)initWithCameraName:(NSString *)cameraName
@@ -1147,6 +1158,9 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re
11471158
NSDictionary *argsMap = call.arguments;
11481159
NSUInteger cameraId = ((NSNumber *)argsMap[@"cameraId"]).unsignedIntegerValue;
11491160
if ([@"initialize" isEqualToString:call.method]) {
1161+
NSString *videoFormatValue = ((NSString *)argsMap[@"imageFormatGroup"]);
1162+
videoFormat = getVideoFormatFromString(videoFormatValue);
1163+
11501164
__weak CameraPlugin *weakSelf = self;
11511165
_camera.onFrameAvailable = ^{
11521166
[weakSelf.registry textureFrameAvailable:cameraId];

packages/camera/camera/lib/camera.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export 'package:camera_platform_interface/camera_platform_interface.dart'
1414
FlashMode,
1515
ExposureMode,
1616
ResolutionPreset,
17-
XFile;
17+
XFile,
18+
ImageFormatGroup;

packages/camera/camera/lib/src/camera_controller.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ class CameraController extends ValueNotifier<CameraValue> {
160160
this.description,
161161
this.resolutionPreset, {
162162
this.enableAudio = true,
163+
this.imageFormatGroup,
163164
}) : super(const CameraValue.uninitialized());
164165

165166
/// The properties of the camera device controlled by this controller.
@@ -176,6 +177,11 @@ class CameraController extends ValueNotifier<CameraValue> {
176177
/// Whether to include audio when recording a video.
177178
final bool enableAudio;
178179

180+
/// The [ImageFormatGroup] describes the output of the raw image format.
181+
///
182+
/// When null the imageFormat will fallback to the platforms default.
183+
final ImageFormatGroup imageFormatGroup;
184+
179185
int _cameraId;
180186
bool _isDisposed = false;
181187
StreamSubscription<dynamic> _imageStreamSubscription;
@@ -217,7 +223,10 @@ class CameraController extends ValueNotifier<CameraValue> {
217223
_initializeCompleter.complete(event);
218224
}));
219225

220-
await CameraPlatform.instance.initializeCamera(_cameraId);
226+
await CameraPlatform.instance.initializeCamera(
227+
_cameraId,
228+
imageFormatGroup: imageFormatGroup,
229+
);
221230

222231
value = value.copyWith(
223232
isInitialized: true,

packages/camera/camera/lib/src/camera_image.dart

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:typed_data';
66

77
import 'package:flutter/foundation.dart';
88
import 'package:flutter/material.dart';
9+
import 'package:camera_platform_interface/camera_platform_interface.dart';
910

1011
/// A single color plane of image data.
1112
///
@@ -41,32 +42,6 @@ class Plane {
4142
final int width;
4243
}
4344

44-
// TODO:(bmparr) Turn [ImageFormatGroup] to a class with int values.
45-
/// Group of image formats that are comparable across Android and iOS platforms.
46-
enum ImageFormatGroup {
47-
/// The image format does not fit into any specific group.
48-
unknown,
49-
50-
/// Multi-plane YUV 420 format.
51-
///
52-
/// This format is a generic YCbCr format, capable of describing any 4:2:0
53-
/// chroma-subsampled planar or semiplanar buffer (but not fully interleaved),
54-
/// with 8 bits per color sample.
55-
///
56-
/// On Android, this is `android.graphics.ImageFormat.YUV_420_888`. See
57-
/// https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV_420_888
58-
///
59-
/// On iOS, this is `kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange`. See
60-
/// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_420ypcbcr8biplanarvideorange?language=objc
61-
yuv420,
62-
63-
/// 32-bit BGRA.
64-
///
65-
/// On iOS, this is `kCVPixelFormatType_32BGRA`. See
66-
/// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_32bgra?language=objc
67-
bgra8888,
68-
}
69-
7045
/// Describes how pixels are represented in an image.
7146
class ImageFormat {
7247
ImageFormat._fromPlatformData(this.raw) : group = _asImageFormatGroup(raw);
@@ -86,9 +61,13 @@ class ImageFormat {
8661

8762
ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) {
8863
if (defaultTargetPlatform == TargetPlatform.android) {
89-
// android.graphics.ImageFormat.YUV_420_888
90-
if (rawFormat == 35) {
91-
return ImageFormatGroup.yuv420;
64+
switch (rawFormat) {
65+
// android.graphics.ImageFormat.YUV_420_888
66+
case 35:
67+
return ImageFormatGroup.yuv420;
68+
// android.graphics.ImageFormat.JPEG
69+
case 256:
70+
return ImageFormatGroup.jpeg;
9271
}
9372
}
9473

packages/camera/camera/pubspec.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ name: camera
22
description: A Flutter plugin for getting information about and controlling the
33
camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video,
44
and streaming image buffers to dart.
5-
version: 0.6.4+5
5+
version: 0.6.5
66
homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera
77

88
dependencies:
99
flutter:
1010
sdk: flutter
11-
camera_platform_interface: ^1.2.0
11+
camera_platform_interface: ^1.3.0
1212
pedantic: ^1.8.0
1313

1414
dev_dependencies:
@@ -31,5 +31,5 @@ flutter:
3131
pluginClass: CameraPlugin
3232

3333
environment:
34-
sdk: ">=2.1.0 <3.0.0"
34+
sdk: ">=2.7.0 <3.0.0"
3535
flutter: ">=1.12.13+hotfix.5"

packages/camera/camera/test/camera_image_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:typed_data';
66

77
import 'package:camera/camera.dart';
8+
import 'package:camera_platform_interface/camera_platform_interface.dart';
89
import 'package:flutter/cupertino.dart';
910
import 'package:flutter/foundation.dart';
1011
import 'package:flutter_test/flutter_test.dart';

packages/camera/camera/test/camera_test.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'dart:ui';
88

99
import 'package:camera/camera.dart';
1010
import 'package:camera_platform_interface/camera_platform_interface.dart';
11+
import 'package:flutter/foundation.dart';
1112
import 'package:flutter/services.dart';
1213
import 'package:flutter/widgets.dart';
1314
import 'package:flutter_test/flutter_test.dart';
@@ -164,6 +165,22 @@ void main() {
164165
mockPlatformException = false;
165166
});
166167

168+
test('initialize() sets imageFormat', () async {
169+
debugDefaultTargetPlatformOverride = TargetPlatform.android;
170+
CameraController cameraController = CameraController(
171+
CameraDescription(
172+
name: 'cam',
173+
lensDirection: CameraLensDirection.back,
174+
sensorOrientation: 90),
175+
ResolutionPreset.max,
176+
imageFormatGroup: ImageFormatGroup.yuv420,
177+
);
178+
await cameraController.initialize();
179+
verify(CameraPlatform.instance
180+
.initializeCamera(13, imageFormatGroup: ImageFormatGroup.yuv420))
181+
.called(1);
182+
});
183+
167184
test('prepareForVideoRecording() calls $CameraPlatform ', () async {
168185
CameraController cameraController = CameraController(
169186
CameraDescription(
@@ -1004,6 +1021,10 @@ class MockCameraPlatform extends Mock
10041021
with MockPlatformInterfaceMixin
10051022
implements CameraPlatform {
10061023
@override
1024+
Future<void> initializeCamera(int cameraId,
1025+
{ImageFormatGroup imageFormatGroup});
1026+
1027+
@override
10071028
Future<List<CameraDescription>> availableCameras() =>
10081029
Future.value(mockAvailableCameras);
10091030

0 commit comments

Comments
 (0)