@@ -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