Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit efe4c1b

Browse files
Add byte streaming capability for the camera (#965)
1 parent bcfbd46 commit efe4c1b

File tree

7 files changed

+550
-19
lines changed

7 files changed

+550
-19
lines changed

packages/camera/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.2.8
2+
3+
* Add access to the image stream from Dart.
4+
* Use `cameraController.startImageStream(listener)` to process the images.
5+
16
## 0.2.7
27

38
* Fix issue with crash when the physical device's orientation is unknown.

packages/camera/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ A Flutter plugin for iOS and Android allowing access to the device cameras.
88

99
* Display live camera preview in a widget.
1010
* Snapshots can be captured and saved to a file.
11+
* Record video.
12+
* Add access to the image stream from Dart.
1113

1214
## Installation
1315

packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java

Lines changed: 140 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,26 @@ public void onMethodCall(MethodCall call, final Result result) {
213213
camera.stopVideoRecording(result);
214214
break;
215215
}
216+
case "startImageStream":
217+
{
218+
try {
219+
camera.startPreviewWithImageStream();
220+
result.success(null);
221+
} catch (CameraAccessException e) {
222+
result.error("CameraAccess", e.getMessage(), null);
223+
}
224+
break;
225+
}
226+
case "stopImageStream":
227+
{
228+
try {
229+
camera.startPreview();
230+
result.success(null);
231+
} catch (CameraAccessException e) {
232+
result.error("CameraAccess", e.getMessage(), null);
233+
}
234+
break;
235+
}
216236
case "dispose":
217237
{
218238
if (camera != null) {
@@ -258,7 +278,8 @@ private class Camera {
258278
private CameraDevice cameraDevice;
259279
private CameraCaptureSession cameraCaptureSession;
260280
private EventChannel.EventSink eventSink;
261-
private ImageReader imageReader;
281+
private ImageReader pictureImageReader;
282+
private ImageReader imageStreamReader;
262283
private int sensorOrientation;
263284
private boolean isFrontFacing;
264285
private String cameraName;
@@ -458,9 +479,15 @@ private void open(@Nullable final Result result) {
458479
if (result != null) result.error("cameraPermission", "Camera permission not granted", null);
459480
} else {
460481
try {
461-
imageReader =
482+
pictureImageReader =
462483
ImageReader.newInstance(
463484
captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2);
485+
486+
// Used to steam image byte data to dart side.
487+
imageStreamReader =
488+
ImageReader.newInstance(
489+
previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2);
490+
464491
cameraManager.openCamera(
465492
cameraName,
466493
new CameraDevice.StateCallback() {
@@ -553,7 +580,7 @@ private void takePicture(String filePath, @NonNull final Result result) {
553580
return;
554581
}
555582

556-
imageReader.setOnImageAvailableListener(
583+
pictureImageReader.setOnImageAvailableListener(
557584
new ImageReader.OnImageAvailableListener() {
558585
@Override
559586
public void onImageAvailable(ImageReader reader) {
@@ -571,7 +598,7 @@ public void onImageAvailable(ImageReader reader) {
571598
try {
572599
final CaptureRequest.Builder captureBuilder =
573600
cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
574-
captureBuilder.addTarget(imageReader.getSurface());
601+
captureBuilder.addTarget(pictureImageReader.getSurface());
575602
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation());
576603

577604
cameraCaptureSession.capture(
@@ -697,7 +724,7 @@ private void startPreview() throws CameraAccessException {
697724
surfaces.add(previewSurface);
698725
captureRequestBuilder.addTarget(previewSurface);
699726

700-
surfaces.add(imageReader.getSurface());
727+
surfaces.add(pictureImageReader.getSurface());
701728

702729
cameraDevice.createCaptureSession(
703730
surfaces,
@@ -727,6 +754,107 @@ public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession
727754
null);
728755
}
729756

757+
private void startPreviewWithImageStream() throws CameraAccessException {
758+
closeCaptureSession();
759+
760+
SurfaceTexture surfaceTexture = textureEntry.surfaceTexture();
761+
surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
762+
763+
captureRequestBuilder =
764+
cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
765+
766+
List<Surface> surfaces = new ArrayList<>();
767+
768+
Surface previewSurface = new Surface(surfaceTexture);
769+
surfaces.add(previewSurface);
770+
captureRequestBuilder.addTarget(previewSurface);
771+
772+
surfaces.add(imageStreamReader.getSurface());
773+
captureRequestBuilder.addTarget(imageStreamReader.getSurface());
774+
775+
cameraDevice.createCaptureSession(
776+
surfaces,
777+
new CameraCaptureSession.StateCallback() {
778+
@Override
779+
public void onConfigured(@NonNull CameraCaptureSession session) {
780+
if (cameraDevice == null) {
781+
sendErrorEvent("The camera was closed during configuration.");
782+
return;
783+
}
784+
try {
785+
cameraCaptureSession = session;
786+
captureRequestBuilder.set(
787+
CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
788+
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
789+
} catch (CameraAccessException e) {
790+
sendErrorEvent(e.getMessage());
791+
}
792+
}
793+
794+
@Override
795+
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
796+
sendErrorEvent("Failed to configure the camera for streaming images.");
797+
}
798+
},
799+
null);
800+
801+
registerImageStreamEventChannel();
802+
}
803+
804+
private void registerImageStreamEventChannel() {
805+
final EventChannel imageStreamChannel =
806+
new EventChannel(registrar.messenger(), "plugins.flutter.io/camera/imageStream");
807+
808+
imageStreamChannel.setStreamHandler(
809+
new EventChannel.StreamHandler() {
810+
@Override
811+
public void onListen(Object o, EventChannel.EventSink eventSink) {
812+
setImageStreamImageAvailableListener(eventSink);
813+
}
814+
815+
@Override
816+
public void onCancel(Object o) {
817+
imageStreamReader.setOnImageAvailableListener(null, null);
818+
}
819+
});
820+
}
821+
822+
private void setImageStreamImageAvailableListener(final EventChannel.EventSink eventSink) {
823+
imageStreamReader.setOnImageAvailableListener(
824+
new ImageReader.OnImageAvailableListener() {
825+
@Override
826+
public void onImageAvailable(final ImageReader reader) {
827+
Image img = reader.acquireLatestImage();
828+
if (img == null) return;
829+
830+
List<Map<String, Object>> planes = new ArrayList<>();
831+
for (Image.Plane plane : img.getPlanes()) {
832+
ByteBuffer buffer = plane.getBuffer();
833+
834+
byte[] bytes = new byte[buffer.remaining()];
835+
buffer.get(bytes, 0, bytes.length);
836+
837+
Map<String, Object> planeBuffer = new HashMap<>();
838+
planeBuffer.put("bytesPerRow", plane.getRowStride());
839+
planeBuffer.put("bytesPerPixel", plane.getPixelStride());
840+
planeBuffer.put("bytes", bytes);
841+
842+
planes.add(planeBuffer);
843+
}
844+
845+
Map<String, Object> imageBuffer = new HashMap<>();
846+
imageBuffer.put("width", img.getWidth());
847+
imageBuffer.put("height", img.getHeight());
848+
imageBuffer.put("format", img.getFormat());
849+
imageBuffer.put("planes", planes);
850+
851+
eventSink.success(imageBuffer);
852+
img.close();
853+
}
854+
},
855+
null);
856+
}
857+
730858
private void sendErrorEvent(String errorDescription) {
731859
if (eventSink != null) {
732860
Map<String, String> event = new HashMap<>();
@@ -750,9 +878,13 @@ private void close() {
750878
cameraDevice.close();
751879
cameraDevice = null;
752880
}
753-
if (imageReader != null) {
754-
imageReader.close();
755-
imageReader = null;
881+
if (pictureImageReader != null) {
882+
pictureImageReader.close();
883+
pictureImageReader = null;
884+
}
885+
if (imageStreamReader != null) {
886+
imageStreamReader.close();
887+
imageStreamReader = null;
756888
}
757889
if (mediaRecorder != null) {
758890
mediaRecorder.reset();

0 commit comments

Comments
 (0)