Skip to content

[camerax] Re-land SurfaceProducer migration with fix for camera preview rotation #6856

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

Merged
merged 27 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
200ac7f
Test 1 changes
camsim99 Jun 3, 2024
8fe3230
Check transformation info
camsim99 Jun 3, 2024
4fb6f74
Pixel fix
camsim99 Jun 3, 2024
b2fbc7d
change sensor orientation impl -- no change
camsim99 Jun 4, 2024
754e0fd
Working state
camsim99 Jun 4, 2024
cf7c4e6
Corrections pt 1
camsim99 Jun 4, 2024
61b9631
Possible landscape tablet fix + working state for pixel, samsung
camsim99 Jun 5, 2024
dfcfe32
Self-review, add tests, bump version
camsim99 Jun 5, 2024
77ebbfa
Clean up plugin code
camsim99 Jun 5, 2024
9e72c49
Fix current tests
camsim99 Jun 5, 2024
ee6dbe9
Add docs
camsim99 Jun 5, 2024
2b1637e
Add most test coverage
camsim99 Jun 6, 2024
0c8b230
Fix tests
camsim99 Jun 6, 2024
c85942f
Final review
camsim99 Jun 6, 2024
673344d
Add landscape fix guess
camsim99 Jun 6, 2024
0de02e8
Update changelog with hopes and dreams
camsim99 Jun 6, 2024
989eea6
Merge remote-tracking branch 'upstream/main' into camx_preview_fix
camsim99 Jun 6, 2024
08fd90d
Rename var and change landscape guess
camsim99 Jun 6, 2024
b70bc89
address review
camsim99 Jun 6, 2024
40793f9
Merge remote-tracking branch 'upstream/main' into camx_preview_fix
camsim99 Jun 25, 2024
876ee43
Remove todos
camsim99 Jun 25, 2024
65554f6
Name change
camsim99 Jul 2, 2024
58c1646
Merge remote-tracking branch 'upstream/main' into camx_preview_fix
camsim99 Jul 2, 2024
5e72187
Merge remote-tracking branch 'upstream/main' into camx_preview_fix
camsim99 Aug 13, 2024
5c510a7
Merge remote-tracking branch 'upstream/main' into camx_preview_fix
camsim99 Aug 13, 2024
c91984e
Add surface producer callback
camsim99 Aug 16, 2024
a05fda8
version bump
camsim99 Aug 19, 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
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());
});
}