Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b6729ac
Start implementation of setting points + offset
camsim99 Feb 2, 2024
14d1024
Finish adding intial impls
camsim99 Feb 5, 2024
975806b
Fix crashes
camsim99 Feb 5, 2024
a7181b2
Cleanup pt1
camsim99 Feb 6, 2024
ea5f15f
Cleanup pt2
camsim99 Feb 6, 2024
5fe2976
Self review
camsim99 Feb 6, 2024
e41a825
Add tests
camsim99 Feb 7, 2024
9736287
Merge remote-tracking branch 'upstream/main' into camx_fe
camsim99 Feb 7, 2024
38e88a8
Format
camsim99 Feb 7, 2024
2843626
Update readme + bump version
camsim99 Feb 7, 2024
35ebf13
Improve docs
camsim99 Feb 7, 2024
c2f75ec
Change metering point factory
camsim99 Feb 8, 2024
b8526e1
Self review
camsim99 Feb 8, 2024
928b4cd
Nits + lints
camsim99 Feb 8, 2024
3527d9e
Merge remote-tracking branch 'upstream/main' into camx_fe
camsim99 Feb 9, 2024
1d4d37e
Update video cap test
camsim99 Feb 9, 2024
35ab369
Fix nit
camsim99 Feb 12, 2024
0de865a
Update packages/camera/camera_android_camerax/lib/src/android_camera_…
camsim99 Feb 14, 2024
bb13a35
Address review
camsim99 Feb 14, 2024
1a601a4
Merge remote-tracking branch 'refs/remotes/origin/camx_fe' into camx_fe
camsim99 Feb 14, 2024
2c3c9ab
Update packages/camera/camera_android_camerax/test/android_camera_cam…
camsim99 Feb 20, 2024
6652171
Use absolute paths
camsim99 Feb 20, 2024
a08eed6
Merge remote-tracking branch 'refs/remotes/origin/camx_fe' into camx_fe
camsim99 Feb 20, 2024
c35fa15
Address nit + change getExposureOffsetStepSize to return 0
camsim99 Feb 21, 2024
fac3ff6
Merge remote-tracking branch 'upstream/main' into camx_fe
camsim99 Feb 21, 2024
077ee37
Change to -1
camsim99 Feb 21, 2024
ff3fc39
Update wording
camsim99 Feb 21, 2024
fc60e77
Undo platform interface changes
camsim99 Feb 21, 2024
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
Prev Previous commit
Next Next commit
Cleanup pt2
  • Loading branch information
camsim99 committed Feb 6, 2024
commit ea5f15ff8b01c40b787b3624d1b3a2f01f21d537
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,12 @@ public void setUp(
cameraControlHostApiImpl =
new CameraControlHostApiImpl(binaryMessenger, instanceManager, context);
GeneratedCameraXLibrary.CameraControlHostApi.setup(binaryMessenger, cameraControlHostApiImpl);
GeneratedCameraXLibrary.FocusMeteringActionHostApi.setup(binaryMessenger, new FocusMeteringActionHostApiImpl(instanceManager));
GeneratedCameraXLibrary.FocusMeteringResultHostApi.setup(binaryMessenger, new FocusMeteringResultHostApiImpl(instanceManager));
GeneratedCameraXLibrary.MeteringPointHostApi.setup(binaryMessenger, new MeteringPointHostApiImpl(instanceManager));
GeneratedCameraXLibrary.FocusMeteringActionHostApi.setup(
binaryMessenger, new FocusMeteringActionHostApiImpl(instanceManager));
GeneratedCameraXLibrary.FocusMeteringResultHostApi.setup(
binaryMessenger, new FocusMeteringResultHostApiImpl(instanceManager));
GeneratedCameraXLibrary.MeteringPointHostApi.setup(
binaryMessenger, new MeteringPointHostApiImpl(instanceManager));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ public void onSuccess(Void voidResult) {
}

public void onFailure(Throwable t) {
if (t instanceof CameraControl.OperationCanceledException) {
// Operation was canceled due to camera being closed or a new request was submitted, which
// is not actionable and should not block a new value from potentially being submitted.
result.success(null);
return;
}
result.error(t);
}
},
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import 'dart:math' show Point;

import 'package:async/async.dart';
import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:flutter/services.dart' show DeviceOrientation;
import 'package:flutter/services.dart'
show DeviceOrientation, PlatformException;
import 'package:flutter/widgets.dart';
import 'package:stream_transform/stream_transform.dart';

Expand Down Expand Up @@ -72,6 +73,9 @@ class AndroidCameraCameraX extends CameraPlatform {
@visibleForTesting
CameraInfo? cameraInfo;

/// The [CameraControl] instance that corresponds to the [camera] instance.
CameraControl? cameraControl;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decided to save this whenever the related camera is updated versus making async calls to retrieve it every time that it's needed.

/// The [LiveData] of the [CameraState] that represents the state of the
/// [camera] instance.
LiveData<CameraState>? liveCameraState;
Expand Down Expand Up @@ -498,27 +502,25 @@ class AndroidCameraCameraX extends CameraPlatform {
/// Returns the (rounded) offset value that was set.
@override
Future<double> setExposureOffset(int cameraId, double offset) async {
// TODO(camsim99): cache camera + related values if possible.
final double minOffset = await getMinExposureOffset(cameraId);
final double maxOffset = await getMaxExposureOffset(cameraId);

if (offset < minOffset || offset > maxOffset) {
throw CameraException('TODO(camsim99)', 'TODO(camsim99)');
}

final double exposureOffsetStepSize =
(await cameraInfo!.getExposureState()).exposureCompensationStep;
if (exposureOffsetStepSize == 0) {
throw CameraException(
'TODO(camsim99)', 'Exposure compensation not supported');
throw CameraException(exposureCompensationNotSupported,
'Exposure compensation not supported');
}

final int roundedExposureCompensationIndex =
(offset / exposureOffsetStepSize).round();
final CameraControl cameraControl = await camera!.getCameraControl();

await cameraControl
.setExposureCompensationIndex(roundedExposureCompensationIndex);
try {
await cameraControl!
.setExposureCompensationIndex(roundedExposureCompensationIndex);
} on PlatformException catch (e) {
throw CameraException(
'setExposureOffsetFailed',
e.message ??
'Setting the camera exposure compensation index failed.');
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking for feedback on throwing an exception here and forwarding the exception from CameraControl in order to conform with the platform interface, in the sense that the method

Throws a CameraException when an illegal offset is supplied.

as per its documentation. Another option is explicitly checking in this method, but was worried that such an asynchronous call could impact performance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bparrishMines no pressure on a review, but if you have time to take a look, would appreciate your feedback on this decision!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with throwing an exception here since a user is explicitly warned about it in the app-facing package.

Although this does highlight the need for a way to check if a feature is supported without using a try/catch for every method. Something we will consider when the camera platform interface is updated.

return roundedExposureCompensationIndex * exposureOffsetStepSize;
}

Expand Down Expand Up @@ -575,8 +577,7 @@ class AndroidCameraCameraX extends CameraPlatform {
/// Throws a `CameraException` when an illegal zoom level is supplied.
@override
Future<void> setZoomLevel(int cameraId, double zoom) async {
final CameraControl cameraControl = await camera!.getCameraControl();
await cameraControl.setZoomRatio(zoom);
await cameraControl!.setZoomRatio(zoom);
}

/// The ui orientation changed.
Expand Down Expand Up @@ -663,8 +664,7 @@ class AndroidCameraCameraX extends CameraPlatform {
CameraControl? cameraControl;
// Turn off torch mode if it is enabled and not being redundantly set.
if (mode != FlashMode.torch && torchEnabled) {
cameraControl = await camera!.getCameraControl();
await cameraControl.enableTorch(false);
await cameraControl!.enableTorch(false);
torchEnabled = false;
}

Expand All @@ -681,8 +681,7 @@ class AndroidCameraCameraX extends CameraPlatform {
// Torch mode enabled already.
return;
}
cameraControl = await camera!.getCameraControl();
await cameraControl.enableTorch(true);
await cameraControl!.enableTorch(true);
torchEnabled = true;
}
}
Expand Down Expand Up @@ -754,7 +753,7 @@ class AndroidCameraCameraX extends CameraPlatform {
if (videoOutputPath == null) {
// Stop the current active recording as we will be unable to complete it
// in this error case.
unawaited(recording!.close());
await recording!.close();
recording = null;
pendingRecording = null;
throw CameraException(
Expand All @@ -763,7 +762,7 @@ class AndroidCameraCameraX extends CameraPlatform {
'while reporting success. The platform should always '
'return a valid path or report an error.');
}
unawaited(recording!.close());
await recording!.close();
recording = null;
pendingRecording = null;
return XFile(videoOutputPath!);
Expand Down Expand Up @@ -905,14 +904,15 @@ class AndroidCameraCameraX extends CameraPlatform {

// Methods concerning camera state:

/// Updates [cameraInfo] to the information corresponding to [camera] and
/// adds observers to the [LiveData] of the [CameraState] of the current
/// [camera], saved as [liveCameraState].
/// Updates [cameraInfo] and [cameraControl] to the information corresponding
/// to [camera] and adds observers to the [LiveData] of the [CameraState] of
/// the current [camera], saved as [liveCameraState].
///
/// If a previous [liveCameraState] was stored, existing observers are
/// removed, as well.
Future<void> _updateCameraInfoAndLiveCameraState(int cameraId) async {
cameraInfo = await camera!.getCameraInfo();
cameraControl = await camera!.getCameraControl();
await liveCameraState?.removeObservers();
liveCameraState = await cameraInfo!.getCameraState();
await liveCameraState!.observe(_createCameraClosingObserver(cameraId));
Expand Down Expand Up @@ -1054,50 +1054,83 @@ class AndroidCameraCameraX extends CameraPlatform {
videoQuality: videoQuality, fallbackStrategy: fallbackStrategy);
}

/// TODO(camsim99)
// Methods for configuring auto-focus and auto-exposure:

/// Starts a foucs and metering action.
///
/// If [meteringPoint] is non-null, this action includes
/// * metering points and their modes previously added to
/// [currentFocusMeteringAction] that do not share a metering mode with
/// [meteringPoint] and
/// * [meteringPoint] with the specified [meteringMode].
/// If [meteringPoint] is null, this action includes only metering points and
/// their modes previously added to [currentFocusMeteringAction] that do not
/// share a metering mode with [meteringPoint]. If there are no such metering
/// points, then any previously enabled focus and metering actions will be
/// canceled.
Future<void> _startFocusAndMeteringFor(
{required Point<double>? meteringPoint,
required int? meteringMode}) async {
// TODO(camsim99): Consider caching cameraControl when camera created.
final CameraControl cameraControl = await camera!.getCameraControl();
required int meteringMode}) async {
if (meteringPoint == null) {
// Try to clear any metering point from previous action with the specified
// meteringMode.
if (currentFocusMeteringAction == null) {
// Attempting to clear a metering point from a previous action, but no
// such action exists.
return;
}

// Remove metering point with specified meteringMode from current focus
// and metering action, as only one focus or exposure point may be set
// at once in this plugin.
final List<(MeteringPoint, int?)> newMeteringPointInfos =
currentFocusMeteringAction!.meteringPointInfos
.where(((MeteringPoint, int?) meteringPointInfo) =>
// meteringPointInfo may technically include points without a
// mode specified, but this logic is safe because this plugin
// only explicitly uses FocusMeteringAction.flagAe or
// FocusMeteringAction.flagAf.
meteringPointInfo.$2 != meteringMode)
.toList();

if (newMeteringPointInfos.isEmpty) {
await cameraControl.cancelFocusAndMetering();
// If no other metering points were specified, cancel any previously
// started focus and metering actions.
await cameraControl!.cancelFocusAndMetering();
return;
}

currentFocusMeteringAction =
FocusMeteringAction(meteringPointInfos: newMeteringPointInfos);

await cameraControl.startFocusAndMetering(currentFocusMeteringAction!);
return;
} else if (meteringPoint.x < 0 ||
meteringPoint.x > 1 ||
meteringPoint.y < 0 && meteringPoint.y > 1) {
throw CameraException('TODO(camsim99)', 'TODO(camsim99)');
}

List<(MeteringPoint, int?)> newMeteringPointInfos =
<(MeteringPoint, int?)>[];
if (currentFocusMeteringAction != null) {
newMeteringPointInfos = currentFocusMeteringAction!.meteringPointInfos
.where(((MeteringPoint, int?) meteringPointInfo) =>
meteringPointInfo.$2 != meteringMode)
.toList();
throw CameraException('meteringPointInvalid',
'The coordinates of a metering point for an auto-focus or auto-exposure action must be within (0,0) and (1,1).');
} else {
// Add new metering point with specified meteringMode, which might
// involve replacing a metering point with the same specified meteringMode
// from the current focus and metering action.
List<(MeteringPoint, int?)> newMeteringPointInfos =
<(MeteringPoint, int?)>[];

if (currentFocusMeteringAction != null) {
newMeteringPointInfos = currentFocusMeteringAction!.meteringPointInfos
.where(((MeteringPoint, int?) meteringPointInfo) =>
// meteringPointInfo may technically include points without a
// mode specified, but this logic is safe because this plugin
// only explicitly uses FocusMeteringAction.flagAe or
// FocusMeteringAction.flagAf.
meteringPointInfo.$2 != meteringMode)
.toList();
}
final MeteringPoint newMeteringPoint =
MeteringPoint(x: meteringPoint.x, y: meteringPoint.y);
newMeteringPointInfos.add((newMeteringPoint, meteringMode));
currentFocusMeteringAction =
FocusMeteringAction(meteringPointInfos: newMeteringPointInfos);
}
final MeteringPoint newMeteringPoint =
MeteringPoint(x: meteringPoint!.x, y: meteringPoint.y);
newMeteringPointInfos.add((newMeteringPoint, meteringMode));
currentFocusMeteringAction =
FocusMeteringAction(meteringPointInfos: newMeteringPointInfos);

await cameraControl.startFocusAndMetering(currentFocusMeteringAction!);
await cameraControl!.startFocusAndMetering(currentFocusMeteringAction!);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,9 @@ class _CameraControlHostApiImpl extends CameraControlHostApi {
}
return exposureCompensationIndex;
} on PlatformException catch (e) {
// TODO(camsim99): perhaps throw cameraexception here.
SystemServices.cameraErrorStreamController.add(e.message ??
'Setting the camera exposure compensation index failed.');
return Future<int?>.value();
rethrow;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}
}
Expand Down
Loading