Skip to content

Commit

Permalink
[camerax] Re-land SurfaceProducer migration with fix for camera previ…
Browse files Browse the repository at this point in the history
…ew rotation (#6856)

Re-lands #6462 with the following change to `buildPreview`:

#### Switches on a fix for correctly rotating the camera preview
This fix is required for `Surface`s not backed by a `SurfaceTexture` because they don't contain the transformation information needed to correctly rotate the camera preview. In that case, we use the logic described in https://developer.android.com/media/camera/camera2/camera-preview#orientation_calculation. This logic was added in a previous PR (#7044); this simply enables it for devices where `Surface`s are not backed by a `SurfaceTexture` (see [how this is determined](https://github.com/flutter/packages/blob/f118119cf830fe369b34a04ed63c9ed66c647985/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java#L107-L120)).

Fixes flutter/flutter#149294.
  • Loading branch information
camsim99 authored Aug 19, 2024
1 parent b5d9074 commit 7740990
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 43 deletions.
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.8+1

* Re-lands support for Impeller.

## 0.6.8

* Updates Guava version to 33.3.0.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
package io.flutter.plugins.camerax;

import android.app.Activity;
import android.graphics.SurfaceTexture;
import android.util.Size;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
Expand Down Expand Up @@ -51,11 +49,6 @@ public class CameraXProxy {
return new Preview.Builder();
}

/** Creates a {@link Surface} instance from the specified {@link SurfaceTexture}. */
public @NonNull Surface createSurface(@NonNull SurfaceTexture surfaceTexture) {
return new Surface(surfaceTexture);
}

/**
* Creates an instance of the {@link SystemServicesFlutterApiImpl}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

package io.flutter.plugins.camerax;

import android.graphics.SurfaceTexture;
import android.util.Size;
import android.view.Surface;
import androidx.annotation.NonNull;
Expand All @@ -25,7 +24,7 @@ public class PreviewHostApiImpl implements PreviewHostApi {
private final TextureRegistry textureRegistry;

@VisibleForTesting public @NonNull CameraXProxy cameraXProxy = new CameraXProxy();
@VisibleForTesting public @Nullable TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture;
@VisibleForTesting public @Nullable TextureRegistry.SurfaceProducer flutterSurfaceProducer;

public PreviewHostApiImpl(
@NonNull BinaryMessenger binaryMessenger,
Expand Down Expand Up @@ -62,12 +61,11 @@ public void create(
@Override
public @NonNull Long setSurfaceProvider(@NonNull Long identifier) {
Preview preview = getPreviewInstance(identifier);
flutterSurfaceTexture = textureRegistry.createSurfaceTexture();
SurfaceTexture surfaceTexture = flutterSurfaceTexture.surfaceTexture();
Preview.SurfaceProvider surfaceProvider = createSurfaceProvider(surfaceTexture);
flutterSurfaceProducer = textureRegistry.createSurfaceProducer();
Preview.SurfaceProvider surfaceProvider = createSurfaceProvider(flutterSurfaceProducer);
preview.setSurfaceProvider(surfaceProvider);

return flutterSurfaceTexture.id();
return flutterSurfaceProducer.id();
}

/**
Expand All @@ -76,13 +74,32 @@ public void create(
*/
@VisibleForTesting
public @NonNull Preview.SurfaceProvider createSurfaceProvider(
@NonNull SurfaceTexture surfaceTexture) {
@NonNull TextureRegistry.SurfaceProducer surfaceProducer) {
return new Preview.SurfaceProvider() {
@Override
public void onSurfaceRequested(@NonNull SurfaceRequest request) {
surfaceTexture.setDefaultBufferSize(
// Set callback for surfaceProducer to invalidate Surfaces that it produces when they
// get destroyed.
surfaceProducer.setCallback(
new TextureRegistry.SurfaceProducer.Callback() {
@Override
public void onSurfaceCreated() {
// Do nothing. The Preview.SurfaceProvider will handle this whenever a new
// Surface is needed.
}

@Override
public void onSurfaceDestroyed() {
// Invalidate the SurfaceRequest so that CameraX knows to to make a new request
// for a surface.
request.invalidate();
}
});

// Provide surface.
surfaceProducer.setSize(
request.getResolution().getWidth(), request.getResolution().getHeight());
Surface flutterSurface = cameraXProxy.createSurface(surfaceTexture);
Surface flutterSurface = surfaceProducer.getSurface();
request.provideSurface(
flutterSurface,
Executors.newSingleThreadExecutor(),
Expand Down Expand Up @@ -133,8 +150,8 @@ String getProvideSurfaceErrorDescription(int resultCode) {
*/
@Override
public void releaseFlutterSurfaceTexture() {
if (flutterSurfaceTexture != null) {
flutterSurfaceTexture.release();
if (flutterSurfaceProducer != null) {
flutterSurfaceProducer.release();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import android.graphics.SurfaceTexture;
import android.util.Size;
import android.view.Surface;
import androidx.camera.core.Preview;
Expand Down Expand Up @@ -83,62 +83,93 @@ public void create_createsPreviewWithCorrectConfiguration() {
}

@Test
public void setSurfaceProviderTest_createsSurfaceProviderAndReturnsTextureEntryId() {
public void setSurfaceProvider_createsSurfaceProviderAndReturnsTextureEntryId() {
final PreviewHostApiImpl previewHostApi =
spy(new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry));
final TextureRegistry.SurfaceTextureEntry mockSurfaceTextureEntry =
mock(TextureRegistry.SurfaceTextureEntry.class);
final SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class);
final TextureRegistry.SurfaceProducer mockSurfaceProducer =
mock(TextureRegistry.SurfaceProducer.class);
final Long previewIdentifier = 5L;
final Long surfaceTextureEntryId = 120L;
final Long surfaceProducerEntryId = 120L;

previewHostApi.cameraXProxy = mockCameraXProxy;
testInstanceManager.addDartCreatedInstance(mockPreview, previewIdentifier);

when(mockTextureRegistry.createSurfaceTexture()).thenReturn(mockSurfaceTextureEntry);
when(mockSurfaceTextureEntry.surfaceTexture()).thenReturn(mockSurfaceTexture);
when(mockSurfaceTextureEntry.id()).thenReturn(surfaceTextureEntryId);
when(mockTextureRegistry.createSurfaceProducer()).thenReturn(mockSurfaceProducer);
when(mockSurfaceProducer.id()).thenReturn(surfaceProducerEntryId);

final ArgumentCaptor<Preview.SurfaceProvider> surfaceProviderCaptor =
ArgumentCaptor.forClass(Preview.SurfaceProvider.class);
final ArgumentCaptor<Surface> surfaceCaptor = ArgumentCaptor.forClass(Surface.class);

// Test that surface provider was set and the surface texture ID was returned.
assertEquals(previewHostApi.setSurfaceProvider(previewIdentifier), surfaceTextureEntryId);
assertEquals(previewHostApi.setSurfaceProvider(previewIdentifier), surfaceProducerEntryId);
verify(mockPreview).setSurfaceProvider(surfaceProviderCaptor.capture());
verify(previewHostApi).createSurfaceProvider(mockSurfaceTexture);
verify(previewHostApi).createSurfaceProvider(mockSurfaceProducer);
}

@Test
public void createSurfaceProducer_setsExpectedSurfaceProducerCallback() {
final PreviewHostApiImpl previewHostApi =
new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry);
final TextureRegistry.SurfaceProducer mockSurfaceProducer =
mock(TextureRegistry.SurfaceProducer.class);
final SurfaceRequest mockSurfaceRequest = mock(SurfaceRequest.class);
final ArgumentCaptor<TextureRegistry.SurfaceProducer.Callback> callbackCaptor =
ArgumentCaptor.forClass(TextureRegistry.SurfaceProducer.Callback.class);

when(mockSurfaceRequest.getResolution()).thenReturn(new Size(5, 6));
when(mockSurfaceProducer.getSurface()).thenReturn(mock(Surface.class));

Preview.SurfaceProvider previewSurfaceProvider =
previewHostApi.createSurfaceProvider(mockSurfaceProducer);
previewSurfaceProvider.onSurfaceRequested(mockSurfaceRequest);

verify(mockSurfaceProducer).setCallback(callbackCaptor.capture());

TextureRegistry.SurfaceProducer.Callback callback = callbackCaptor.getValue();

// Verify callback's onSurfaceDestroyed invalidates SurfaceRequest.
callback.onSurfaceDestroyed();
verify(mockSurfaceRequest).invalidate();

reset(mockSurfaceRequest);

// Verify callback's onSurfaceCreated does not interact with the SurfaceRequest.
callback.onSurfaceCreated();
verifyNoMoreInteractions(mockSurfaceRequest);
}

@Test
public void createSurfaceProvider_createsExpectedPreviewSurfaceProvider() {
final PreviewHostApiImpl previewHostApi =
new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry);
final SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class);
final TextureRegistry.SurfaceProducer mockSurfaceProducer =
mock(TextureRegistry.SurfaceProducer.class);
final Surface mockSurface = mock(Surface.class);
final SurfaceRequest mockSurfaceRequest = mock(SurfaceRequest.class);
final SurfaceRequest.Result mockSurfaceRequestResult = mock(SurfaceRequest.Result.class);
final SystemServicesFlutterApiImpl mockSystemServicesFlutterApi =
mock(SystemServicesFlutterApiImpl.class);
final int resolutionWidth = 200;
final int resolutionHeight = 500;
final Long surfaceProducerEntryId = 120L;

previewHostApi.cameraXProxy = mockCameraXProxy;
when(mockCameraXProxy.createSurface(mockSurfaceTexture)).thenReturn(mockSurface);
when(mockSurfaceRequest.getResolution())
.thenReturn(new Size(resolutionWidth, resolutionHeight));
when(mockCameraXProxy.createSystemServicesFlutterApiImpl(mockBinaryMessenger))
.thenReturn(mockSystemServicesFlutterApi);
when(mockSurfaceProducer.getSurface()).thenReturn(mockSurface);

final ArgumentCaptor<Surface> surfaceCaptor = ArgumentCaptor.forClass(Surface.class);
@SuppressWarnings("unchecked")
final ArgumentCaptor<Consumer<SurfaceRequest.Result>> consumerCaptor =
ArgumentCaptor.forClass(Consumer.class);

Preview.SurfaceProvider previewSurfaceProvider =
previewHostApi.createSurfaceProvider(mockSurfaceTexture);
previewHostApi.createSurfaceProvider(mockSurfaceProducer);
previewSurfaceProvider.onSurfaceRequested(mockSurfaceRequest);

verify(mockSurfaceTexture).setDefaultBufferSize(resolutionWidth, resolutionHeight);
verify(mockSurfaceProducer).setSize(resolutionWidth, resolutionHeight);
verify(mockSurfaceRequest)
.provideSurface(surfaceCaptor.capture(), any(Executor.class), consumerCaptor.capture());

Expand Down Expand Up @@ -189,13 +220,13 @@ public void createSurfaceProvider_createsExpectedPreviewSurfaceProvider() {
public void releaseFlutterSurfaceTexture_makesCallToReleaseFlutterSurfaceTexture() {
final PreviewHostApiImpl previewHostApi =
new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry);
final TextureRegistry.SurfaceTextureEntry mockSurfaceTextureEntry =
mock(TextureRegistry.SurfaceTextureEntry.class);
final TextureRegistry.SurfaceProducer mockSurfaceProducer =
mock(TextureRegistry.SurfaceProducer.class);

previewHostApi.flutterSurfaceTexture = mockSurfaceTextureEntry;
previewHostApi.flutterSurfaceProducer = mockSurfaceProducer;

previewHostApi.releaseFlutterSurfaceTexture();
verify(mockSurfaceTextureEntry).release();
verify(mockSurfaceProducer).release();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,9 @@ class SystemServices {
/// be corrected by the plugin.
static Future<bool> isPreviewPreTransformed(
{BinaryMessenger? binaryMessenger}) {
// TODO(camsim99): Make call to Java host to determine true value when
// Impeller support is re-landed.
// https://github.com/flutter/flutter/issues/149294
return Future<bool>.value(true);
final SystemServicesHostApi api =
SystemServicesHostApi(binaryMessenger: binaryMessenger);
return api.isPreviewPreTransformed();
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera_android_camerax/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: camera_android_camerax
description: Android implementation of the camera plugin using the CameraX library.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.6.8
version: 0.6.8+1

environment:
sdk: ^3.4.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ void main() {
});

test('isPreviewPreTransformed returns expected answer', () async {
final MockTestSystemServicesHostApi mockApi =
MockTestSystemServicesHostApi();
TestSystemServicesHostApi.setup(mockApi);
const bool isPreviewPreTransformed = true;

when(mockApi.isPreviewPreTransformed()).thenReturn(isPreviewPreTransformed);

expect(await SystemServices.isPreviewPreTransformed(), isTrue);
verify(mockApi.isPreviewPreTransformed());
});
}

0 comments on commit 7740990

Please sign in to comment.