Skip to content

[camera_android_camerax] Fix the rotation of captured videos and streamed images #9637

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation;
import java.util.Objects;
import androidx.camera.core.UseCase;

/**
* Support class to help to determine the media orientation based on the orientation of the device.
Expand Down Expand Up @@ -71,6 +72,8 @@ protected OrientationEventListener createOrientationEventListener() {
return new OrientationEventListener(getContext()) {
@Override
public void onOrientationChanged(int orientation) {
System.out.println("CAMILLE:::::::::::::::::::::::::::::::: NEW ORIENTAITON ---------------" + Integer.toString(orientation));
// System.out.println("CAMILLE:::::::::::::::::::::::::::::::: CAMERAX SNAP TO SUGGESTION --------------" + Integer.toString(UseCase.snapToSurfaceRotation(orientation)));
handleUiOrientationChange();
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,17 +195,6 @@ class AndroidCameraCameraX extends CameraPlatform {
@visibleForTesting
bool captureOrientationLocked = false;

/// Whether or not the default rotation for [UseCase]s needs to be set
/// manually because the capture orientation was previously locked.
///
/// Currently, CameraX provides no way to unset target rotations for
/// [UseCase]s, so once they are set and unset, this plugin must start setting
/// the default orientation manually.
///
/// See https://developer.android.com/reference/androidx/camera/core/ImageCapture#setTargetRotation(int)
/// for an example on how setting target rotations for [UseCase]s works.
bool shouldSetDefaultRotation = false;

/// Error code indicating that an exposure offset value failed to be set.
static const String setExposureOffsetFailedErrorCode =
'setExposureOffsetFailed';
Expand Down Expand Up @@ -289,12 +278,14 @@ class AndroidCameraCameraX extends CameraPlatform {
// Determine the lens direction by filtering the CameraInfo
// TODO(gmackall): replace this with call to CameraInfo.getLensFacing when changes containing that method are available
if ((await proxy
.newCameraSelector(requireLensFacing: LensFacing.back)
.filter(<CameraInfo>[cameraInfo])).isNotEmpty) {
.newCameraSelector(requireLensFacing: LensFacing.back)
.filter(<CameraInfo>[cameraInfo]))
.isNotEmpty) {
cameraLensDirection = CameraLensDirection.back;
} else if ((await proxy
.newCameraSelector(requireLensFacing: LensFacing.front)
.filter(<CameraInfo>[cameraInfo])).isNotEmpty) {
.newCameraSelector(requireLensFacing: LensFacing.front)
.filter(<CameraInfo>[cameraInfo]))
.isNotEmpty) {
cameraLensDirection = CameraLensDirection.front;
} else {
//Skip this CameraInfo as its lens direction is unknown
Expand Down Expand Up @@ -397,17 +388,21 @@ class AndroidCameraCameraX extends CameraPlatform {
);

// Configure ImageCapture instance.
_initialDefaultDisplayRotation = await deviceOrientationManager
.getDefaultDisplayRotation();
print(
'CAMILLE::::::::::::::::::::::::::::::::::::::::initial default display rotation: $_initialDefaultDisplayRotation',
);
imageCapture = proxy.newImageCapture(
resolutionSelector: presetResolutionSelector,
/* use CameraX default target rotation */ targetRotation:
await deviceOrientationManager.getDefaultDisplayRotation(),
targetRotation: _initialDefaultDisplayRotation,
);

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

// Configure VideoCapture and Recorder instances.
Expand Down Expand Up @@ -439,13 +434,11 @@ class AndroidCameraCameraX extends CameraPlatform {
.toDouble();

sensorOrientationDegrees = cameraDescription.sensorOrientation.toDouble();
_handlesCropAndRotation =
await preview!.surfaceProducerHandlesCropAndRotation();
_handlesCropAndRotation = await preview!
.surfaceProducerHandlesCropAndRotation();
_initialDeviceOrientation = _deserializeDeviceOrientation(
await deviceOrientationManager.getUiOrientation(),
);
_initialDefaultDisplayRotation =
await deviceOrientationManager.getDefaultDisplayRotation();

return flutterSurfaceTextureId;
}
Expand Down Expand Up @@ -476,8 +469,8 @@ class AndroidCameraCameraX extends CameraPlatform {
);
}

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

// Mark auto-focus, auto-exposure and setting points for focus & exposure
// as available operations as CameraX does its best across devices to
Expand Down Expand Up @@ -559,10 +552,7 @@ class AndroidCameraCameraX extends CameraPlatform {
int cameraId,
DeviceOrientation orientation,
) async {
// Flag that (1) default rotation for UseCases will need to be set manually
// if orientation is ever unlocked and (2) the capture orientation is locked
// and should not be changed until unlocked.
shouldSetDefaultRotation = true;
// Flag that the capture orientation is locked and should not be changed until unlocked.
captureOrientationLocked = true;

// Get target rotation based on locked orientation.
Expand Down Expand Up @@ -647,10 +637,9 @@ class AndroidCameraCameraX extends CameraPlatform {
case FocusMode.auto:
// Determine auto-focus point to restore, if any. We do not restore
// default auto-focus point if set previously to lock focus.
final MeteringPoint? unLockedFocusPoint =
_defaultFocusPointLocked
? null
: currentFocusMeteringAction!.meteringPointsAf.first;
final MeteringPoint? unLockedFocusPoint = _defaultFocusPointLocked
? null
: currentFocusMeteringAction!.meteringPointsAf.first;
_defaultFocusPointLocked = false;
autoFocusPoint = unLockedFocusPoint;
disableAutoCancel = false;
Expand All @@ -661,10 +650,9 @@ class AndroidCameraCameraX extends CameraPlatform {
if (currentFocusMeteringAction != null) {
final List<MeteringPoint> possibleCurrentAfPoints =
currentFocusMeteringAction!.meteringPointsAf;
lockedFocusPoint =
possibleCurrentAfPoints.isEmpty
? null
: possibleCurrentAfPoints.first;
lockedFocusPoint = possibleCurrentAfPoints.isEmpty
? null
: possibleCurrentAfPoints.first;
}

// If there isn't, lock center of entire sensor area by default.
Expand Down Expand Up @@ -1111,10 +1099,13 @@ class AndroidCameraCameraX extends CameraPlatform {

// Set target rotation to default CameraX rotation only if capture
// orientation not locked.
if (!captureOrientationLocked && shouldSetDefaultRotation) {
await videoCapture!.setTargetRotation(
await deviceOrientationManager.getDefaultDisplayRotation(),
if (!captureOrientationLocked) {
int targetRotation = await deviceOrientationManager
.getDefaultDisplayRotation();
print(
'CAMILLE::::::::::::::::::::::::::::::::::::::::::::::::::: target rotation: $targetRotation',
);
// await videoCapture!.setTargetRotation(targetRotation);
}

videoOutputPath = await systemServicesManager.getTempFilePath(
Expand Down Expand Up @@ -1260,7 +1251,7 @@ class AndroidCameraCameraX extends CameraPlatform {

// Set target rotation to default CameraX rotation only if capture
// orientation not locked.
if (!captureOrientationLocked && shouldSetDefaultRotation) {
if (!captureOrientationLocked) {
await imageAnalysis!.setTargetRotation(
await deviceOrientationManager.getDefaultDisplayRotation(),
Comment on lines +1254 to 1256

Choose a reason for hiding this comment

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

medium

Instead of calling deviceOrientationManager.getDefaultDisplayRotation() directly within setTargetRotation, consider storing the value in a variable and reusing it. This avoids redundant calls to the platform, improving efficiency.

final rotation = await deviceOrientationManager.getDefaultDisplayRotation();
      await imageAnalysis!.setTargetRotation(
        rotation,
      );

);
Expand Down Expand Up @@ -1494,13 +1485,12 @@ class AndroidCameraCameraX extends CameraPlatform {
);
final ResolutionFilter resolutionFilter = proxy
.createWithOnePreferredSizeResolutionFilter(preferredSize: boundSize);
final AspectRatioStrategy? aspectRatioStrategy =
aspectRatio == null
? null
: proxy.newAspectRatioStrategy(
preferredAspectRatio: aspectRatio,
fallbackRule: AspectRatioStrategyFallbackRule.auto,
);
final AspectRatioStrategy? aspectRatioStrategy = aspectRatio == null
? null
: proxy.newAspectRatioStrategy(
preferredAspectRatio: aspectRatio,
fallbackRule: AspectRatioStrategyFallbackRule.auto,
);
return proxy.newResolutionSelector(
resolutionStrategy: resolutionStrategy,
resolutionFilter: resolutionFilter,
Expand Down Expand Up @@ -1617,17 +1607,17 @@ class AndroidCameraCameraX extends CameraPlatform {
// 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, MeteringMode)> newMeteringPointInfos =
originalMeteringPoints
.where(
((MeteringPoint, MeteringMode) meteringPointInfo) =>
// meteringPointInfo may technically include points without a
// mode specified, but this logic is safe because this plugin
// only uses points that explicitly have mode
// FocusMeteringAction.flagAe or FocusMeteringAction.flagAf.
meteringPointInfo.$2 != meteringMode,
)
.toList();
final List<(MeteringPoint, MeteringMode)>
newMeteringPointInfos = originalMeteringPoints
.where(
((MeteringPoint, MeteringMode) meteringPointInfo) =>
// meteringPointInfo may technically include points without a
// mode specified, but this logic is safe because this plugin
// only uses points that explicitly have mode
// FocusMeteringAction.flagAe or FocusMeteringAction.flagAf.
meteringPointInfo.$2 != meteringMode,
)
.toList();

if (newMeteringPointInfos.isEmpty) {
// If no other metering points were specified, cancel any previously
Expand Down Expand Up @@ -1664,17 +1654,16 @@ class AndroidCameraCameraX extends CameraPlatform {
final Iterable<(MeteringPoint, MeteringMode)> originalMeteringPoints =
_combineMeteringPoints(currentFocusMeteringAction!);

newMeteringPointInfos =
originalMeteringPoints
.where(
((MeteringPoint, MeteringMode) meteringPointInfo) =>
// meteringPointInfo may technically include points without a
// mode specified, but this logic is safe because this plugin
// only uses points that explicitly have mode
// FocusMeteringAction.flagAe or FocusMeteringAction.flagAf.
meteringPointInfo.$2 != meteringMode,
)
.toList();
newMeteringPointInfos = originalMeteringPoints
.where(
((MeteringPoint, MeteringMode) meteringPointInfo) =>
// meteringPointInfo may technically include points without a
// mode specified, but this logic is safe because this plugin
// only uses points that explicitly have mode
// FocusMeteringAction.flagAe or FocusMeteringAction.flagAf.
meteringPointInfo.$2 != meteringMode,
)
.toList();
}

newMeteringPointInfos.add((meteringPoint, meteringMode));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ final class _ImageReaderRotatedPreviewState
late StreamSubscription<DeviceOrientation> deviceOrientationSubscription;

Future<int> _getCurrentDefaultDisplayRotationDegrees() async {
final int currentDefaultDisplayRotationQuarterTurns =
await widget.deviceOrientationManager.getDefaultDisplayRotation();
final int currentDefaultDisplayRotationQuarterTurns = await widget
.deviceOrientationManager
.getDefaultDisplayRotation();
return getQuarterTurnsFromSurfaceRotationConstant(
currentDefaultDisplayRotationQuarterTurns,
) *
Expand Down Expand Up @@ -150,6 +151,12 @@ final class _ImageReaderRotatedPreviewState
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
final int currentDefaultDisplayRotation = snapshot.data!;
print(
'CAMILLE:::::::::::::::::::::::: currentDefaultDisplayRotation detected for preview: $currentDefaultDisplayRotation',
);
print(
'CAMILLE:::::::::::::::::::::::: deviceOrientation detected for preview: $deviceOrientation',
);
final double rotationDegrees = _computeRotationDegrees(
deviceOrientation,
currentDefaultDisplayRotation,
Expand Down