@@ -120,6 +120,8 @@ public class AndroidCameraImpl extends CameraImpl {
120120 private final AtomicBoolean listenerBusy = new AtomicBoolean ();
121121 private volatile FrameListener frameListener ;
122122 private volatile FrameFormat frameFormat = FrameFormat .JPEG ;
123+ private volatile long frameIntervalNanos ;
124+ private long lastFrameNanos ;
123125 private CameraInfo info ;
124126 private CameraSessionOptions options ;
125127 private ExecutorService cameraExecutor ;
@@ -422,8 +424,9 @@ public void stopVideoRecording(AsyncResource<String> result) {
422424
423425 @ Override
424426 public void setFrameListener (FrameListener listener , FrameFormat format , int maxFps ) {
425- this .frameListener = listener ;
426427 this .frameFormat = format == null ? FrameFormat .JPEG : format ;
428+ this .frameIntervalNanos = maxFps > 0 ? 1000000000L / maxFps : 0 ;
429+ this .frameListener = listener ;
427430 }
428431
429432 private void installFrameAnalyzer () {
@@ -459,25 +462,23 @@ private void onImageProxy(Object imageProxy) {
459462 return ;
460463 }
461464 try {
462- // Convert ImageProxy -> Image -> JPEG bytes
463- byte [] jpeg = null ;
464- int w = 0 , h = 0 ;
465- int rotation = 0 ;
465+ long now = System .nanoTime ();
466+ if (lastFrameNanos != 0 && now - lastFrameNanos < frameIntervalNanos ) {
467+ return ;
468+ }
469+ CameraFrame frame ;
466470 try {
467471 Image img = (Image ) imageProxy .getClass ().getMethod ("getImage" ).invoke (imageProxy );
468472 Object imageInfo = imageProxy .getClass ().getMethod ("getImageInfo" ).invoke (imageProxy );
469- rotation = (Integer ) imageInfo .getClass ().getMethod ("getRotationDegrees" ).invoke (imageInfo );
470- if (img != null ) {
471- w = img .getWidth ();
472- h = img .getHeight ();
473- jpeg = yuvImageToJpeg (img );
474- }
473+ int rotation = (Integer ) imageInfo .getClass ().getMethod ("getRotationDegrees" ).invoke (imageInfo );
474+ frame = img == null ? null : createFrame (img , rotation , now , frameFormat );
475475 } catch (Throwable t ) {
476476 Log .w (TAG , "Frame conversion failed: " + t .getMessage ());
477+ return ;
477478 }
478- if (jpeg != null ) {
479- l . onFrame ( new CameraFrame ( jpeg , null , w , h , rotation ,
480- System . nanoTime (), frameFormat ) );
479+ if (frame != null ) {
480+ lastFrameNanos = now ;
481+ l . onFrame ( frame );
481482 }
482483 } finally {
483484 listenerBusy .set (false );
@@ -491,31 +492,39 @@ private static void closeImageProxy(Object imageProxy) {
491492 } catch (Throwable ignored ) { }
492493 }
493494
494- private static byte [] yuvImageToJpeg (Image image ) {
495+ private static CameraFrame createFrame (Image image , int rotation , long timestamp ,
496+ FrameFormat requested ) {
497+ final int w = image .getWidth ();
498+ final int h = image .getHeight ();
495499 if (image .getFormat () == ImageFormat .JPEG ) {
496- ByteBuffer buf = image .getPlanes ()[0 ].getBuffer ();
497- byte [] out = new byte [buf .remaining ()];
498- buf .get (out );
499- return out ;
500+ ByteBuffer buf = image .getPlanes ()[0 ].getBuffer ().duplicate ();
501+ byte [] jpeg = new byte [buf .remaining ()];
502+ buf .get (jpeg );
503+ return new CameraFrame (jpeg , null , w , h , rotation , timestamp , requested );
504+ }
505+ if (image .getFormat () != ImageFormat .YUV_420_888 ) {
506+ throw new IllegalArgumentException ("Unsupported camera image format" );
500507 }
501- // YUV_420_888 -> NV21 -> JPEG via YuvImage
502- int w = image .getWidth ();
503- int h = image .getHeight ();
504508 Image .Plane [] planes = image .getPlanes ();
505- ByteBuffer y = planes [0 ].getBuffer ();
506- ByteBuffer u = planes [1 ].getBuffer ();
507- ByteBuffer v = planes [2 ].getBuffer ();
508- int ySize = y .remaining ();
509- int uSize = u .remaining ();
510- int vSize = v .remaining ();
511- byte [] nv21 = new byte [ySize + uSize + vSize ];
512- y .get (nv21 , 0 , ySize );
513- v .get (nv21 , ySize , vSize );
514- u .get (nv21 , ySize + vSize , uSize );
515- YuvImage yuv = new YuvImage (nv21 , ImageFormat .NV21 , w , h , null );
516- ByteArrayOutputStream baos = new ByteArrayOutputStream ();
517- yuv .compressToJpeg (new Rect (0 , 0 , w , h ), 80 , baos );
518- return baos .toByteArray ();
509+ final byte [] nv21 = AndroidCameraFrameConverter .toNV21 (w , h ,
510+ planes [0 ].getBuffer (), planes [0 ].getRowStride (), planes [0 ].getPixelStride (),
511+ planes [1 ].getBuffer (), planes [1 ].getRowStride (), planes [1 ].getPixelStride (),
512+ planes [2 ].getBuffer (), planes [2 ].getRowStride (), planes [2 ].getPixelStride ());
513+ CameraFrame .JpegEncoder encoder = new CameraFrame .JpegEncoder () {
514+ public byte [] encode () {
515+ YuvImage yuv = new YuvImage (nv21 , ImageFormat .NV21 , w , h , null );
516+ ByteArrayOutputStream baos = new ByteArrayOutputStream ();
517+ if (!yuv .compressToJpeg (new Rect (0 , 0 , w , h ), 80 , baos )) {
518+ throw new IllegalStateException ("Could not encode camera frame" );
519+ }
520+ return baos .toByteArray ();
521+ }
522+ };
523+ if (requested == FrameFormat .NV21 ) {
524+ return new CameraFrame (null , nv21 , w , h , rotation , timestamp , requested , encoder );
525+ }
526+ // JPEG remains the fallback for raw formats this port cannot expose.
527+ return new CameraFrame (encoder .encode (), null , w , h , rotation , timestamp , requested );
519528 }
520529
521530 @ Override
0 commit comments