Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/camera/camera_android_camerax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.6.21

* Implements NV21 support for image streaming.

## 0.6.20+1

* Updates kotlin version to 2.2.0 to enable gradle 8.11 support.
Expand Down
6 changes: 3 additions & 3 deletions packages/camera/camera_android_camerax/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ android {
}

defaultConfig {
// Many of the CameraX APIs require API 21.
minSdkVersion 21
// CameraX APIs require API 23 or later.
minSdkVersion 23
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

Expand Down Expand Up @@ -72,7 +72,7 @@ android {

dependencies {
// CameraX core library using the camera2 implementation must use same version number.
def camerax_version = "1.5.0-beta01"
def camerax_version = "1.5.0-rc01"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// 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.
// Autogenerated from Pigeon (v25.3.2), do not edit directly.
// Autogenerated from Pigeon (v25.5.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")

Expand Down Expand Up @@ -4761,13 +4761,16 @@ abstract class PigeonApiImageAnalysis(
) {
abstract fun pigeon_defaultConstructor(
resolutionSelector: androidx.camera.core.resolutionselector.ResolutionSelector?,
outputImageFormat: Long?,
targetRotation: Long?
): androidx.camera.core.ImageAnalysis

abstract fun resolutionSelector(
pigeon_instance: androidx.camera.core.ImageAnalysis
): androidx.camera.core.resolutionselector.ResolutionSelector?

abstract fun outputImageFormat(pigeon_instance: androidx.camera.core.ImageAnalysis): Long?

/** Sets an analyzer to receive and analyze images. */
abstract fun setAnalyzer(
pigeon_instance: androidx.camera.core.ImageAnalysis,
Expand Down Expand Up @@ -4799,11 +4802,13 @@ abstract class PigeonApiImageAnalysis(
val pigeon_identifierArg = args[0] as Long
val resolutionSelectorArg =
args[1] as androidx.camera.core.resolutionselector.ResolutionSelector?
val targetRotationArg = args[2] as Long?
val outputImageFormatArg = args[2] as Long?
val targetRotationArg = args[3] as Long?
val wrapped: List<Any?> =
try {
api.pigeonRegistrar.instanceManager.addDartCreatedInstance(
api.pigeon_defaultConstructor(resolutionSelectorArg, targetRotationArg),
api.pigeon_defaultConstructor(
resolutionSelectorArg, outputImageFormatArg, targetRotationArg),
pigeon_identifierArg)
listOf(null)
} catch (exception: Throwable) {
Expand Down Expand Up @@ -4905,11 +4910,12 @@ abstract class PigeonApiImageAnalysis(
val pigeon_identifierArg =
pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg)
val resolutionSelectorArg = resolutionSelector(pigeon_instanceArg)
val outputImageFormatArg = outputImageFormat(pigeon_instanceArg)
val binaryMessenger = pigeonRegistrar.binaryMessenger
val codec = pigeonRegistrar.codec
val channelName = "dev.flutter.pigeon.camera_android_camerax.ImageAnalysis.pigeon_newInstance"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(pigeon_identifierArg, resolutionSelectorArg)) {
channel.send(listOf(pigeon_identifierArg, resolutionSelectorArg, outputImageFormatArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,20 @@ class ImageAnalysisProxyApi extends PigeonApiImageAnalysis {
@NonNull
@Override
public ImageAnalysis pigeon_defaultConstructor(
@Nullable ResolutionSelector resolutionSelector, @Nullable Long targetRotation) {
@Nullable ResolutionSelector resolutionSelector,
@Nullable Long targetRotation,
@Nullable Long outputImageFormat) {
final ImageAnalysis.Builder builder = new ImageAnalysis.Builder();
if (resolutionSelector != null) {
builder.setResolutionSelector(resolutionSelector);
}
if (targetRotation != null) {
builder.setTargetRotation(targetRotation.intValue());
}

if (outputImageFormat != null) {
builder.setOutputImageFormat(outputImageFormat.intValue());
}
return builder.build();
}

Expand Down Expand Up @@ -71,4 +77,10 @@ public void setTargetRotation(ImageAnalysis pigeonInstance, long rotation) {
public ResolutionSelector resolutionSelector(@NonNull ImageAnalysis pigeonInstance) {
return pigeonInstance.getResolutionSelector();
}

@Nullable
@Override
public Long outputImageFormat(@NonNull ImageAnalysis pigeonInstance) {
return Long.valueOf(pigeonInstance.getOutputImageFormat());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public void getCameraCharacteristic_returnsCorrespondingValueOfKeyWhenKeyNotReco
assertEquals(value, api.getCameraCharacteristic(instance, key));
}

@Config(minSdk = 21)
@Config(minSdk = 23)
@SuppressWarnings("unchecked")
@Test
public void getCameraCharacteristic_returnsExpectedCameraHardwareLevelWhenRequested() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ public void pigeon_defaultConstructor_createsExpectedImageAnalysisInstance() {

final ResolutionSelector mockResolutionSelector = new ResolutionSelector.Builder().build();
final long targetResolution = Surface.ROTATION_0;
final long outputImageFormat = ImageAnalysis.OUTPUT_IMAGE_FORMAT_NV21;
final ImageAnalysis imageAnalysis =
api.pigeon_defaultConstructor(mockResolutionSelector, targetResolution);
api.pigeon_defaultConstructor(mockResolutionSelector, targetResolution, outputImageFormat);

assertEquals(imageAnalysis.getResolutionSelector(), mockResolutionSelector);
assertEquals(imageAnalysis.getTargetRotation(), Surface.ROTATION_0);
assertEquals(imageAnalysis.getOutputImageFormat(), ImageAnalysis.OUTPUT_IMAGE_FORMAT_NV21);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public void start_callsStartOnInstance() {
.when(() -> ContextCompat.getMainExecutor(any()))
.thenAnswer((Answer<Executor>) invocation -> mock(Executor.class));

when(instance.start(any(), any())).thenReturn(value);
when(instance.start(any(Executor.class), any())).thenReturn(value);

assertEquals(value, api.start(instance, listener));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,15 +174,25 @@ class AndroidCameraCameraX extends CameraPlatform {
@visibleForTesting
StreamController<CameraImageData>? cameraImageDataStreamController;

/// Constant representing the multi-plane Android YUV 420 image format.
/// Constant representing the multi-plane Android YUV 420 image format used by ImageProxy.
///
/// See https://developer.android.com/reference/android/graphics/ImageFormat#YUV_420_888.
static const int imageFormatYuv420_888 = 35;
static const int imageProxyFormatYuv420_888 = 35;

/// Constant representing the compressed JPEG image format.
/// Constant representing the compressed JPEG image format used by ImageProxy.
///
/// See https://developer.android.com/reference/android/graphics/ImageFormat#JPEG.
static const int imageFormatJpeg = 256;
static const int imageProxyFormatJpeg = 256;

/// Constant representing the YUV 420 image format used for configuring ImageAnalysis.
///
/// See https://developer.android.com/reference/androidx/camera/core/ImageAnalysis#OUTPUT_IMAGE_FORMAT_YUV_420_888()
static const int imageAnalysisOutputImageFormatYuv420_888 = 1;

/// Constant representing the NV21 image format used for configuring ImageAnalysis.
///
/// See https://developer.android.com/reference/androidx/camera/core/ImageAnalysis#OUTPUT_IMAGE_FORMAT_NV21().
static const int imageAnalysisOutputImageFormatNv21 = 3;

/// Error code indicating a [ZoomState] was requested, but one has not been
/// set for the camera in use.
Expand Down Expand Up @@ -269,6 +279,12 @@ class AndroidCameraCameraX extends CameraPlatform {
/// A map to associate a [CameraInfo] with its camera name.
final Map<String, CameraInfo> _savedCameras = <String, CameraInfo>{};

/// The preset resolution selector for the camera.
ResolutionSelector? _presetResolutionSelector;

/// The ID of the surface texture that the camera preview is drawn to.
late int _flutterSurfaceTextureId;

/// Returns list of all available cameras and their descriptions.
@override
Future<List<CameraDescription>> availableCameras() async {
Expand Down Expand Up @@ -380,8 +396,9 @@ class AndroidCameraCameraX extends CameraPlatform {
);
// Determine ResolutionSelector and QualitySelector based on
// resolutionPreset for camera UseCases.
final ResolutionSelector? presetResolutionSelector =
_getResolutionSelectorFromPreset(mediaSettings?.resolutionPreset);
_presetResolutionSelector = _getResolutionSelectorFromPreset(
mediaSettings?.resolutionPreset,
);
final QualitySelector? presetQualitySelector =
_getQualitySelectorFromPreset(mediaSettings?.resolutionPreset);

Expand All @@ -391,55 +408,26 @@ class AndroidCameraCameraX extends CameraPlatform {

// Configure Preview instance.
preview = proxy.newPreview(
resolutionSelector: presetResolutionSelector,
resolutionSelector: _presetResolutionSelector,
/* use CameraX default target rotation */ targetRotation: null,
);
final int flutterSurfaceTextureId = await preview!.setSurfaceProvider(
_flutterSurfaceTextureId = await preview!.setSurfaceProvider(
systemServicesManager,
);

// Configure ImageCapture instance.
imageCapture = proxy.newImageCapture(
resolutionSelector: presetResolutionSelector,
resolutionSelector: _presetResolutionSelector,
/* use CameraX default target rotation */ targetRotation:
await deviceOrientationManager.getDefaultDisplayRotation(),
);

// Configure ImageAnalysis instance.
// Defaults to YUV_420_888 image format.
imageAnalysis = proxy.newImageAnalysis(
resolutionSelector: presetResolutionSelector,
/* use CameraX default target rotation */ targetRotation: null,
);

// Configure VideoCapture and Recorder instances.
recorder = proxy.newRecorder(qualitySelector: presetQualitySelector);
videoCapture = proxy.withOutputVideoCapture(videoOutput: recorder!);

// Bind configured UseCases to ProcessCameraProvider instance & mark Preview
// instance as bound but not paused. Video capture is bound at first use
// instead of here.
camera = await processCameraProvider!.bindToLifecycle(
cameraSelector!,
<UseCase>[preview!, imageCapture!, imageAnalysis!],
);
await _updateCameraInfoAndLiveCameraState(flutterSurfaceTextureId);
previewInitiallyBound = true;
_previewIsPaused = false;

// Retrieve info required for correcting the rotation of the camera preview
// if necessary.

final Camera2CameraInfo camera2CameraInfo = proxy.fromCamera2CameraInfo(
cameraInfo: cameraInfo!,
);
sensorOrientationDegrees =
((await camera2CameraInfo.getCameraCharacteristic(
proxy.sensorOrientationCameraCharacteristics(),
))!
as int)
.toDouble();

sensorOrientationDegrees = cameraDescription.sensorOrientation.toDouble();
_handlesCropAndRotation = await preview!
.surfaceProducerHandlesCropAndRotation();
Expand All @@ -449,35 +437,59 @@ class AndroidCameraCameraX extends CameraPlatform {
_initialDefaultDisplayRotation = await deviceOrientationManager
.getDefaultDisplayRotation();

return flutterSurfaceTextureId;
return _flutterSurfaceTextureId;
}

/// Initializes the camera on the device.
///
/// Since initialization of a camera does not directly map as an operation to
/// the CameraX library, this method just retrieves information about the
/// camera and sends a [CameraInitializedEvent].
/// Specifically, this method:
/// * Configures the [ImageAnalysis] instance according to the specified
/// [imageFormatGroup]
/// * Binds the configured [Preview], [ImageCapture], and [ImageAnalysis]
/// instances to the [ProcessCameraProvider] instance.
/// * Retrieves information about the camera and sends a [CameraInitializedEvent].
///
/// [imageFormatGroup] is used to specify the image format used for image
/// streaming, but CameraX currently only supports YUV_420_888 (supported by
/// Flutter) and RGBA (not supported by Flutter). CameraX uses YUV_420_888
/// by default, so [imageFormatGroup] is not used.
/// streaming, but CameraX currently only supports YUV_420_888 (the CameraX default),
/// NV21, and RGBA (not supported by Flutter).
@override
Future<void> initializeCamera(
int cameraId, {
ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown,
}) async {
// Configure CameraInitializedEvent to send as representation of a
// configured camera:
// Retrieve preview resolution.
// If preview has not been created, then no camera has been created, which signals that
// createCamera was not called before initializeCamera.
if (preview == null) {
// No camera has been created; createCamera must be called before initializeCamera.
throw CameraException(
'cameraNotFound',
"Camera not found. Please call the 'create' method before calling 'initialize'",
);
}
// Configure ImageAnalysis instance.
// Defaults to YUV_420_888 image format.
imageAnalysis = proxy.newImageAnalysis(
resolutionSelector: _presetResolutionSelector,
/* use CameraX default target rotation */ targetRotation: null,
outputImageFormat: _imageAnalysisOutputFormatFromImageFormatGroup(
imageFormatGroup,
),
);

// Bind configured UseCases to ProcessCameraProvider instance & mark Preview
// instance as bound but not paused. Video capture is bound at first use
// instead of here.
camera = await processCameraProvider!.bindToLifecycle(
cameraSelector!,
<UseCase>[preview!, imageCapture!, imageAnalysis!],
);
await _updateCameraInfoAndLiveCameraState(_flutterSurfaceTextureId);
previewInitiallyBound = true;
_previewIsPaused = false;

// Configure CameraInitializedEvent to send as representation of a
// configured camera:

// Retrieve preview resolution.
final ResolutionInfo previewResolutionInfo = (await preview!
.getResolutionInfo())!;

Expand Down Expand Up @@ -1326,14 +1338,28 @@ class AndroidCameraCameraX extends CameraPlatform {
await imageAnalysis!.clearAnalyzer();
}

/// Converts between Android ImageFormat constants and [ImageFormatGroup]s.
/// Converts [ImageFormatGroup]s to Android ImageAnalysis output format constants.
///
/// See https://developer.android.com/reference/androidx/camera/core/ImageAnalysis.
int? _imageAnalysisOutputFormatFromImageFormatGroup(dynamic format) {
switch (format) {
case ImageFormatGroup.yuv420:
return imageAnalysisOutputImageFormatYuv420_888;
case ImageFormatGroup.nv21:
return imageAnalysisOutputImageFormatNv21;
}

return null;
}

/// Converts from Android ImageFormat constants to [ImageFormatGroup]s.
///
/// See https://developer.android.com/reference/android/graphics/ImageFormat.
ImageFormatGroup _imageFormatGroupFromPlatformData(dynamic data) {
switch (data) {
case imageFormatYuv420_888: // android.graphics.ImageFormat.YUV_420_888
case imageProxyFormatYuv420_888: // android.graphics.ImageFormat.YUV_420_888
return ImageFormatGroup.yuv420;
case imageFormatJpeg: // android.graphics.ImageFormat.JPEG
case imageProxyFormatJpeg: // android.graphics.ImageFormat.JPEG
return ImageFormatGroup.jpeg;
}

Expand Down
Loading