Skip to content

Commit 058851b

Browse files
authored
Migrate camera/android from SurfaceTexture->SurfaceProducer. (flutter#6461)
_**WIP**: We do not plan to land this PR until the next stable release (>= April 3rd 2024)_. Work towards flutter#145930. ## Details Migrates uses of `createSurfaceTexture` to `createSurfaceProducer`, which is intended to have no change in behavior, but _does_ change the backend rendering path, so it will require more testing (and we're also open to minor API renames or changes before it becomes stable). ## Background Android plugins previously requested a `SurfaceTexture` from the Android embedder, and used that to produce a `Surface` to render external textures on (i.e. `video_player`). This worked because 100% of Flutter applications on Android used OpenGLES (via our Skia backend), and `SurfaceTexture` is actually an (opaque) OpenGLES-texture. Starting soon (roughly ~Q3, this is not a guarantee and just an estimate), Flutter on Android will start to use our new Impeller graphics backend, which on newer devices (`>= API_VERSION_28`), will default to the Vulkan, _not_ OpenGLES. In other words, `SurfaceTexture` will cease to work (it is possible, but non-trivial, to map an OpenGLES texture over to Vulkan). After consultation with the Android team, they helped us understand that vending `SurfaceTexture` (the _consumer-side_ API) was never the right abstraction, and we should have been vending the _producer-side_ API, or `Surface` directly. The new `SurfaceProducer` API is exactly that - it generates a `Surface`, and similar to our platform view strategy, picks the "right" _consumer-side_ implementation details _for_ the user/plugin packages. The new `SurfaceProducer` API has 2 possible rendering types (as an implementation detail): - `SurfaceTexture`, for older OpenGLES devices, which works exactly as it does today. - `ImageReader`, for newer OpenGLES _or_ Vulkan devices. These are some subtle nuances in how these two APIs work differently (one example: flutter#144407), but our theory at this point is we don't expect these changes to be observed by any users, and we have other ideas if necessary. > [!NOTE] > These invariants are [tested on CI in `flutter/engine`](https://github.com/flutter/engine/tree/main/testing/scenario_app/android#ci-configuration). Points of contact: - @matanlurey or @jonahwilliams (Flutter Engine) - @johnmccutchan or @reidbaker (Flutter on Android)
1 parent e35f291 commit 058851b

File tree

6 files changed

+47
-79
lines changed

6 files changed

+47
-79
lines changed

packages/camera/camera_android/CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.10.9+4
2+
3+
* [Supports Impeller](https://docs.flutter.dev/release/breaking-changes/android-surface-plugins).
4+
15
## 0.10.9+3
26

37
* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4.
@@ -36,7 +40,7 @@
3640

3741
## 0.10.8+14
3842

39-
* Fixes `pausePreview` null pointer error. `pausePreview` should not be called
43+
* Fixes `pausePreview` null pointer error. `pausePreview` should not be called
4044
when camera is closed or not configured.
4145
* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
4246

packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import android.app.Activity;
1010
import android.content.Context;
1111
import android.graphics.ImageFormat;
12-
import android.graphics.SurfaceTexture;
1312
import android.hardware.camera2.CameraAccessException;
1413
import android.hardware.camera2.CameraCaptureSession;
1514
import android.hardware.camera2.CameraDevice;
@@ -63,7 +62,7 @@
6362
import io.flutter.plugins.camera.media.MediaRecorderBuilder;
6463
import io.flutter.plugins.camera.types.CameraCaptureProperties;
6564
import io.flutter.plugins.camera.types.CaptureTimeoutsWrapper;
66-
import io.flutter.view.TextureRegistry.SurfaceTextureEntry;
65+
import io.flutter.view.TextureRegistry;
6766
import java.io.File;
6867
import java.io.IOException;
6968
import java.util.ArrayList;
@@ -113,7 +112,7 @@ class Camera
113112
*/
114113
@VisibleForTesting int initialCameraFacing;
115114

116-
@VisibleForTesting final SurfaceTextureEntry flutterTexture;
115+
@VisibleForTesting final TextureRegistry.SurfaceProducer surfaceProducer;
117116
private final VideoCaptureSettings videoCaptureSettings;
118117
private final Context applicationContext;
119118
final DartMessenger dartMessenger;
@@ -214,17 +213,16 @@ public VideoCaptureSettings(@NonNull ResolutionPreset resolutionPreset, boolean
214213

215214
public Camera(
216215
final Activity activity,
217-
final SurfaceTextureEntry flutterTexture,
216+
final TextureRegistry.SurfaceProducer surfaceProducer,
218217
final CameraFeatureFactory cameraFeatureFactory,
219218
final DartMessenger dartMessenger,
220219
final CameraProperties cameraProperties,
221220
final VideoCaptureSettings videoCaptureSettings) {
222-
223221
if (activity == null) {
224222
throw new IllegalStateException("No activity available!");
225223
}
226224
this.activity = activity;
227-
this.flutterTexture = flutterTexture;
225+
this.surfaceProducer = surfaceProducer;
228226
this.dartMessenger = dartMessenger;
229227
this.applicationContext = activity.getApplicationContext();
230228
this.cameraProperties = cameraProperties;
@@ -243,7 +241,6 @@ public Camera(
243241
if (videoCaptureSettings.fps != null && videoCaptureSettings.fps.intValue() > 0) {
244242
recordingFps = videoCaptureSettings.fps;
245243
} else {
246-
247244
if (SdkCapabilityChecker.supportsEncoderProfiles()) {
248245
EncoderProfiles encoderProfiles = getRecordingProfile();
249246
if (encoderProfiles != null && encoderProfiles.getVideoProfiles().size() > 0) {
@@ -256,7 +253,6 @@ public Camera(
256253
}
257254

258255
if (recordingFps != null && recordingFps.intValue() > 0) {
259-
260256
final FpsRangeFeature fpsRange = new FpsRangeFeature(cameraProperties);
261257
fpsRange.setValue(new Range<Integer>(recordingFps, recordingFps));
262258
this.cameraFeatures.setFpsRange(fpsRange);
@@ -307,8 +303,9 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException {
307303

308304
MediaRecorderBuilder mediaRecorderBuilder;
309305

310-
// TODO(camsim99): Revert changes that allow legacy code to be used when recordingProfile is null
311-
// once this has largely been fixed on the Android side. https://github.com/flutter/flutter/issues/119668
306+
// TODO(camsim99): Revert changes that allow legacy code to be used when recordingProfile is
307+
// null once this has largely been fixed on the Android side.
308+
// https://github.com/flutter/flutter/issues/119668
312309
if (SdkCapabilityChecker.supportsEncoderProfiles() && getRecordingProfile() != null) {
313310
mediaRecorderBuilder =
314311
new MediaRecorderBuilder(
@@ -386,7 +383,8 @@ public void onOpened(@NonNull CameraDevice device) {
386383
cameraDevice = new DefaultCameraDeviceWrapper(device);
387384
try {
388385
startPreview();
389-
if (!recordingVideo) { // only send initialization if we werent already recording and switching cameras
386+
if (!recordingVideo) { // only send initialization if we werent already recording and
387+
// switching cameras
390388
dartMessenger.sendCameraInitializedEvent(
391389
resolutionFeature.getPreviewSize().getWidth(),
392390
resolutionFeature.getPreviewSize().getHeight(),
@@ -470,11 +468,10 @@ private void createCaptureSession(
470468

471469
// Build Flutter surface to render to.
472470
ResolutionFeature resolutionFeature = cameraFeatures.getResolution();
473-
SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture();
474-
surfaceTexture.setDefaultBufferSize(
471+
surfaceProducer.setSize(
475472
resolutionFeature.getPreviewSize().getWidth(),
476473
resolutionFeature.getPreviewSize().getHeight());
477-
Surface flutterSurface = new Surface(surfaceTexture);
474+
Surface flutterSurface = surfaceProducer.getSurface();
478475
previewRequestBuilder.addTarget(flutterSurface);
479476

480477
List<Surface> remainingSurfaces = Arrays.asList(surfaces);
@@ -1160,7 +1157,8 @@ public void resumePreview() {
11601157
}
11611158

11621159
public void startPreview() throws CameraAccessException, InterruptedException {
1163-
// If recording is already in progress, the camera is being flipped, so send it through the VideoRenderer to keep the correct orientation.
1160+
// If recording is already in progress, the camera is being flipped, so send it through the
1161+
// VideoRenderer to keep the correct orientation.
11641162
if (recordingVideo) {
11651163
startPreviewWithVideoRendererStream();
11661164
} else {
@@ -1193,7 +1191,6 @@ private void startPreviewWithVideoRendererStream()
11931191
}
11941192

11951193
if (cameraProperties.getLensFacing() != initialCameraFacing) {
1196-
11971194
// If the new camera is facing the opposite way than the initial recording,
11981195
// the rotation should be flipped 180 degrees.
11991196
rotation = (rotation + 180) % 360;
@@ -1361,13 +1358,13 @@ public void uncaughtException(Thread thread, Throwable ex) {
13611358

13621359
public void setDescriptionWhileRecording(
13631360
@NonNull final Result result, CameraProperties properties) {
1364-
13651361
if (!recordingVideo) {
13661362
result.error("setDescriptionWhileRecordingFailed", "Device was not recording", null);
13671363
return;
13681364
}
13691365

1370-
// See VideoRenderer.java; support for this EGL extension is required to switch camera while recording.
1366+
// See VideoRenderer.java; support for this EGL extension is required to switch camera while
1367+
// recording.
13711368
if (!SdkCapabilityChecker.supportsEglRecordableAndroid()) {
13721369
result.error(
13731370
"setDescriptionWhileRecordingFailed",
@@ -1400,7 +1397,7 @@ public void dispose() {
14001397
Log.i(TAG, "dispose");
14011398

14021399
close();
1403-
flutterTexture.release();
1400+
surfaceProducer.release();
14041401
getDeviceOrientationManager().stop();
14051402
}
14061403

packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -393,27 +393,25 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce
393393
Integer videoBitrate = call.argument("videoBitrate");
394394
Integer audioBitrate = call.argument("audioBitrate");
395395

396-
TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture =
397-
textureRegistry.createSurfaceTexture();
396+
TextureRegistry.SurfaceProducer surfaceProducer = textureRegistry.createSurfaceProducer();
398397
DartMessenger dartMessenger =
399-
new DartMessenger(
400-
messenger, flutterSurfaceTexture.id(), new Handler(Looper.getMainLooper()));
398+
new DartMessenger(messenger, surfaceProducer.id(), new Handler(Looper.getMainLooper()));
401399
CameraProperties cameraProperties =
402400
new CameraPropertiesImpl(cameraName, CameraUtils.getCameraManager(activity));
403401
ResolutionPreset resolutionPreset = ResolutionPreset.valueOf(preset);
404402

405403
camera =
406404
new Camera(
407405
activity,
408-
flutterSurfaceTexture,
406+
surfaceProducer,
409407
new CameraFeatureFactoryImpl(),
410408
dartMessenger,
411409
cameraProperties,
412410
new Camera.VideoCaptureSettings(
413411
resolutionPreset, enableAudio, fps, videoBitrate, audioBitrate));
414412

415413
Map<String, Object> reply = new HashMap<>();
416-
reply.put("cameraId", flutterSurfaceTexture.id());
414+
reply.put("cameraId", surfaceProducer.id());
417415
result.success(reply);
418416
}
419417

0 commit comments

Comments
 (0)