From 66b54b6fed290fb65bb18bf27b6e1c5b0b3c53bc Mon Sep 17 00:00:00 2001 From: Milan Nikolic Date: Tue, 30 Jun 2026 15:58:25 +0200 Subject: [PATCH] Update Android backend --- camera/camera_android.go | 415 +++++++++++++++++++++++---------------- 1 file changed, 242 insertions(+), 173 deletions(-) diff --git a/camera/camera_android.go b/camera/camera_android.go index 2bfea1c..5341335 100644 --- a/camera/camera_android.go +++ b/camera/camera_android.go @@ -4,218 +4,223 @@ package camera /* +#include + #include #include #include #include +#include -#define TAG "camera" +#define TAG "cam2ip" #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) -#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) AImage *image; -AImageReader *imageReader; +static AImageReader *imageReader; +static ANativeWindow *nativeWindow; -ANativeWindow *nativeWindow; +static ACameraDevice *cameraDevice; +static ACameraManager *cameraManager; +static ACameraOutputTarget *cameraOutputTarget; +static ACameraCaptureSession *cameraCaptureSession; -ACameraDevice *cameraDevice; -ACameraManager *cameraManager; -ACameraOutputTarget *cameraOutputTarget; -ACameraCaptureSession *cameraCaptureSession; +static ACaptureRequest *captureRequest; +static ACaptureSessionOutput *captureSessionOutput; +static ACaptureSessionOutputContainer *captureSessionOutputContainer; -ACaptureRequest *captureRequest; -ACaptureSessionOutput *captureSessionOutput; -ACaptureSessionOutputContainer *captureSessionOutputContainer; - -void device_on_disconnected(void *context, ACameraDevice *device) { - LOGI("camera %s is disconnected.\n", ACameraDevice_getId(device)); +static void device_on_disconnected(void *context, ACameraDevice *device) { + LOGI("camera %s disconnected", ACameraDevice_getId(device)); } -void device_on_error(void *context, ACameraDevice *device, int error) { - LOGE("error %d on camera %s.\n", error, ACameraDevice_getId(device)); +static void device_on_error(void *context, ACameraDevice *device, int error) { + LOGE("camera %s error %d", ACameraDevice_getId(device), error); } -ACameraDevice_stateCallbacks deviceStateCallbacks = { - .context = NULL, - .onDisconnected = device_on_disconnected, - .onError = device_on_error, +static ACameraDevice_stateCallbacks deviceStateCallbacks = { + .onDisconnected = device_on_disconnected, + .onError = device_on_error, }; -void session_on_ready(void *context, ACameraCaptureSession *session) { - LOGI("session is ready. %p\n", session); -} - -void session_on_active(void *context, ACameraCaptureSession *session) { - LOGI("session is activated. %p\n", session); -} - -void session_on_closed(void *context, ACameraCaptureSession *session) { - LOGI("session is closed. %p\n", session); -} - -ACameraCaptureSession_stateCallbacks captureSessionStateCallbacks = { - .context = NULL, - .onActive = session_on_active, - .onReady = session_on_ready, - .onClosed = session_on_closed, -}; - -void image_callback(void *context, AImageReader *reader) { - LOGD("image_callback"); - - media_status_t status = AImageReader_acquireLatestImage(reader, &image); - if(status != AMEDIA_OK) { - LOGE("failed to acquire next image (reason: %d).\n", status); - } -} - -AImageReader_ImageListener imageListener = { - .context = NULL, - .onImageAvailable = image_callback, -}; +static ACameraCaptureSession_stateCallbacks captureSessionStateCallbacks = {0}; int openCamera(int index, int width, int height) { - ACameraIdList *cameraIdList; - const char *selectedCameraId; - - camera_status_t status = ACAMERA_OK; + ACameraIdList *cameraIdList = NULL; cameraManager = ACameraManager_create(); - status = ACameraManager_getCameraIdList(cameraManager, &cameraIdList); - if(status != ACAMERA_OK) { - LOGE("failed to get camera id list (reason: %d).\n", status); - return status; + camera_status_t status = ACameraManager_getCameraIdList(cameraManager, &cameraIdList); + if (status != ACAMERA_OK) { + return status; } - if(cameraIdList->numCameras < 1) { - LOGE("no camera device detected.\n"); + if (index < 0 || index >= cameraIdList->numCameras) { + ACameraManager_deleteCameraIdList(cameraIdList); + return -1; } - if(cameraIdList->numCameras < index+1) { - LOGE("no camera at index %d.\n", index); + const char *cameraId = cameraIdList->cameraIds[index]; + LOGI("open camera %s (%d available)", cameraId, cameraIdList->numCameras); + + status = ACameraManager_openCamera(cameraManager, cameraId, &deviceStateCallbacks, &cameraDevice); + ACameraManager_deleteCameraIdList(cameraIdList); + if (status != ACAMERA_OK) { + return status; } - selectedCameraId = cameraIdList->cameraIds[index]; - LOGI("open camera (id: %s, num of cameras: %d).\n", selectedCameraId, cameraIdList->numCameras); - - status = ACameraManager_openCamera(cameraManager, selectedCameraId, &deviceStateCallbacks, &cameraDevice); - if(status != ACAMERA_OK) { - LOGE("failed to open camera device (id: %s)\n", selectedCameraId); - return status; + media_status_t mstatus = AImageReader_new(width, height, AIMAGE_FORMAT_YUV_420_888, 4, &imageReader); + if (mstatus != AMEDIA_OK) { + return mstatus; } - status = ACameraDevice_createCaptureRequest(cameraDevice, TEMPLATE_STILL_CAPTURE, &captureRequest); - if(status != ACAMERA_OK) { - LOGE("failed to create snapshot capture request (id: %s)\n", selectedCameraId); - return status; - } - - status = ACaptureSessionOutputContainer_create(&captureSessionOutputContainer); - if(status != ACAMERA_OK) { - LOGE("failed to create session output container (id: %s)\n", selectedCameraId); - return status; - } - - media_status_t mstatus = AImageReader_new(width, height, AIMAGE_FORMAT_YUV_420_888, 2, &imageReader); - if(mstatus != AMEDIA_OK) { - LOGE("failed to create image reader (reason: %d).\n", mstatus); - return mstatus; - } - - mstatus = AImageReader_setImageListener(imageReader, &imageListener); - if(mstatus != AMEDIA_OK) { - LOGE("failed to set image listener (reason: %d).\n", mstatus); - return mstatus; - } - - AImageReader_getWindow(imageReader, &nativeWindow); + AImageReader_getWindow(imageReader, &nativeWindow); ANativeWindow_acquire(nativeWindow); + status = ACameraDevice_createCaptureRequest(cameraDevice, TEMPLATE_PREVIEW, &captureRequest); + if (status != ACAMERA_OK) { + return status; + } + + // Auto exposure and a frame rate range so the sensor streams continuously. + uint8_t controlMode = ACAMERA_CONTROL_MODE_AUTO; + ACaptureRequest_setEntry_u8(captureRequest, ACAMERA_CONTROL_MODE, 1, &controlMode); + uint8_t aeMode = ACAMERA_CONTROL_AE_MODE_ON; + ACaptureRequest_setEntry_u8(captureRequest, ACAMERA_CONTROL_AE_MODE, 1, &aeMode); + int32_t fpsRange[2] = {15, 30}; + ACaptureRequest_setEntry_i32(captureRequest, ACAMERA_CONTROL_AE_TARGET_FPS_RANGE, 2, fpsRange); + ACameraOutputTarget_create(nativeWindow, &cameraOutputTarget); ACaptureRequest_addTarget(captureRequest, cameraOutputTarget); + ACaptureSessionOutputContainer_create(&captureSessionOutputContainer); ACaptureSessionOutput_create(nativeWindow, &captureSessionOutput); - ACaptureSessionOutputContainer_add(captureSessionOutputContainer, captureSessionOutput); + ACaptureSessionOutputContainer_add(captureSessionOutputContainer, captureSessionOutput); status = ACameraDevice_createCaptureSession(cameraDevice, captureSessionOutputContainer, &captureSessionStateCallbacks, &cameraCaptureSession); - if(status != ACAMERA_OK) { - LOGE("failed to create capture session (reason: %d).\n", status); - return status; + if (status != ACAMERA_OK) { + return status; } - ACameraManager_deleteCameraIdList(cameraIdList); - ACameraManager_delete(cameraManager); + status = ACameraCaptureSession_setRepeatingRequest(cameraCaptureSession, NULL, 1, &captureRequest, NULL); + if (status != ACAMERA_OK) { + return status; + } return ACAMERA_OK; } -int captureCamera() { - camera_status_t status = ACameraCaptureSession_capture(cameraCaptureSession, NULL, 1, &captureRequest, NULL); - if(status != ACAMERA_OK) { - LOGE("failed to capture image (reason: %d).\n", status); +// acquireImage releases the previous frame and acquires the most recent one. +int acquireImage() { + if (image != NULL) { + AImage_delete(image); + image = NULL; } - return status; + return AImageReader_acquireLatestImage(imageReader, &image); } int closeCamera() { - camera_status_t status = ACAMERA_OK; + if (cameraCaptureSession != NULL) { + ACameraCaptureSession_stopRepeating(cameraCaptureSession); + ACameraCaptureSession_close(cameraCaptureSession); + cameraCaptureSession = NULL; + } - if(captureRequest != NULL) { + if (captureRequest != NULL) { ACaptureRequest_free(captureRequest); captureRequest = NULL; } - if(cameraOutputTarget != NULL) { + if (cameraOutputTarget != NULL) { ACameraOutputTarget_free(cameraOutputTarget); cameraOutputTarget = NULL; } - if(cameraDevice != NULL) { - status = ACameraDevice_close(cameraDevice); - - if(status != ACAMERA_OK) { - LOGE("failed to close camera device.\n"); - return status; - } - - cameraDevice = NULL; + if (cameraDevice != NULL) { + ACameraDevice_close(cameraDevice); + cameraDevice = NULL; } - if(captureSessionOutput != NULL) { + if (captureSessionOutput != NULL) { ACaptureSessionOutput_free(captureSessionOutput); captureSessionOutput = NULL; } - if(captureSessionOutputContainer != NULL) { + if (captureSessionOutputContainer != NULL) { ACaptureSessionOutputContainer_free(captureSessionOutputContainer); captureSessionOutputContainer = NULL; } - if(imageReader != NULL) { - AImageReader_delete(imageReader); - imageReader = NULL; + if (nativeWindow != NULL) { + ANativeWindow_release(nativeWindow); + nativeWindow = NULL; } - if(image != NULL) { - AImage_delete(image); - image = NULL; - } + if (imageReader != NULL) { + AImageReader_delete(imageReader); + imageReader = NULL; + } + + if (image != NULL) { + AImage_delete(image); + image = NULL; + } + + if (cameraManager != NULL) { + ACameraManager_delete(cameraManager); + cameraManager = NULL; + } - LOGI("camera closed.\n"); return ACAMERA_OK; } -int openCamera(int index, int width, int height); -int captureCamera(); -int closeCamera(); +static ACameraManager *enumManager; +static ACameraIdList *enumList; + +int camerasOpen() { + enumManager = ACameraManager_create(); + if (ACameraManager_getCameraIdList(enumManager, &enumList) != ACAMERA_OK) { + return -1; + } + + return enumList->numCameras; +} + +const char *cameraIdAt(int i) { + return enumList->cameraIds[i]; +} + +int cameraFacingAt(int i) { + ACameraMetadata *meta; + if (ACameraManager_getCameraCharacteristics(enumManager, enumList->cameraIds[i], &meta) != ACAMERA_OK) { + return -1; + } + + int facing = -1; + ACameraMetadata_const_entry entry; + if (ACameraMetadata_getConstEntry(meta, ACAMERA_LENS_FACING, &entry) == ACAMERA_OK) { + facing = entry.data.u8[0]; + } + + ACameraMetadata_free(meta); + + return facing; +} + +void camerasClose() { + if (enumList != NULL) { + ACameraManager_deleteCameraIdList(enumList); + enumList = NULL; + } + + if (enumManager != NULL) { + ACameraManager_delete(enumManager); + enumManager = NULL; + } +} -#cgo android CFLAGS: -D__ANDROID_API__=24 #cgo android LDFLAGS: -lcamera2ndk -lmediandk -llog -landroid */ import "C" @@ -223,81 +228,145 @@ import "C" import ( "fmt" "image" + "time" "unsafe" + + im "github.com/gen2brain/cam2ip/image" ) // Camera represents camera. type Camera struct { - opts Options - img *image.YCbCr + opts Options + width int + height int + ycbcr *image.YCbCr } // New returns new Camera for given camera index. -func New(opts Options) (camera *Camera, err error) { - camera = &Camera{} - camera.opts = opts +func New(opts Options) (*Camera, error) { + c := &Camera{opts: opts} + c.width = int(opts.Width) + c.height = int(opts.Height) + c.ycbcr = image.NewYCbCr(image.Rect(0, 0, c.width, c.height), image.YCbCrSubsampleRatio420) - camera.img = image.NewYCbCr(image.Rect(0, 0, int(opts.Width), int(opts.Height)), image.YCbCrSubsampleRatio420) - - ret := C.openCamera(C.int(opts.Index), C.int(opts.Width), C.int(opts.Height)) + ret := C.openCamera(C.int(opts.Index), C.int(c.width), C.int(c.height)) if int(ret) != 0 { - err = fmt.Errorf("camera: can not open camera %d: error %d", opts.Index, int(ret)) - - return + return nil, fmt.Errorf("camera: can not open camera %d: error %d", opts.Index, int(ret)) } - return + return c, nil } // Read reads next frame from camera and returns image. func (c *Camera) Read() (img image.Image, err error) { - ret := C.captureCamera() - if int(ret) != 0 { - err = fmt.Errorf("camera: can not grab frame: error %d", int(ret)) + for i := 0; ; i++ { + if int(C.acquireImage()) == 0 && C.image != nil { + break + } - return + if i == 99 { + return nil, fmt.Errorf("camera: can not retrieve frame") + } + + time.Sleep(10 * time.Millisecond) } - if C.image == nil { - err = fmt.Errorf("camera: can not retrieve frame") + c.convert() - return + img = c.ycbcr + + if c.opts.Rotate != 0 { + img = im.Rotate(img, c.opts.Rotate) } - var yStride C.int - var yLen, cbLen, crLen C.int - var yPtr, cbPtr, crPtr *C.uint8_t + if c.opts.Flip != "" { + img = im.Flip(img, c.opts.Flip) + } + + if c.opts.Timestamp { + img = im.Timestamp(img, c.opts.TimeFormat) + } + + return img, nil +} + +// convert copies the YUV_420_888 planes into the YCbCr image, handling row and pixel strides (planar or semi-planar). +func (c *Camera) convert() { + var yStride, uStride, vStride C.int32_t + var uPixel, vPixel C.int32_t + var yPtr, uPtr, vPtr *C.uint8_t + var yLen, uLen, vLen C.int C.AImage_getPlaneRowStride(C.image, 0, &yStride) + C.AImage_getPlaneRowStride(C.image, 1, &uStride) + C.AImage_getPlaneRowStride(C.image, 2, &vStride) + C.AImage_getPlanePixelStride(C.image, 1, &uPixel) + C.AImage_getPlanePixelStride(C.image, 2, &vPixel) C.AImage_getPlaneData(C.image, 0, &yPtr, &yLen) - C.AImage_getPlaneData(C.image, 1, &cbPtr, &cbLen) - C.AImage_getPlaneData(C.image, 2, &crPtr, &crLen) + C.AImage_getPlaneData(C.image, 1, &uPtr, &uLen) + C.AImage_getPlaneData(C.image, 2, &vPtr, &vLen) - c.img.YStride = int(yStride) - c.img.CStride = int(yStride) / 2 + ySrc := unsafe.Slice((*byte)(unsafe.Pointer(yPtr)), int(yLen)) + uSrc := unsafe.Slice((*byte)(unsafe.Pointer(uPtr)), int(uLen)) + vSrc := unsafe.Slice((*byte)(unsafe.Pointer(vPtr)), int(vLen)) - c.img.Y = C.GoBytes(unsafe.Pointer(yPtr), yLen) - c.img.Cb = C.GoBytes(unsafe.Pointer(cbPtr), cbLen) - c.img.Cr = C.GoBytes(unsafe.Pointer(crPtr), crLen) + ys := int(yStride) + for r := 0; r < c.height; r++ { + copy(c.ycbcr.Y[r*c.ycbcr.YStride:r*c.ycbcr.YStride+c.width], ySrc[r*ys:r*ys+c.width]) + } - img = c.img + cw, ch := c.width/2, c.height/2 + us, up := int(uStride), int(uPixel) + vs, vp := int(vStride), int(vPixel) - return + for r := 0; r < ch; r++ { + for col := 0; col < cw; col++ { + c.ycbcr.Cb[r*c.ycbcr.CStride+col] = uSrc[r*us+col*up] + c.ycbcr.Cr[r*c.ycbcr.CStride+col] = vSrc[r*vs+col*vp] + } + } } // Close closes camera. -func (c *Camera) Close() (err error) { - ret := C.closeCamera() - if int(ret) != 0 { - err = fmt.Errorf("camera: can not close camera %d: error %d", c.opts.Index, int(ret)) - - return +func (c *Camera) Close() error { + if int(C.closeCamera()) != 0 { + return fmt.Errorf("camera: can not close camera %d", c.opts.Index) } - return + return nil +} + +// Info returns the negotiated capture format. +func (c *Camera) Info() Info { + return Info{Format: "YUV420", Width: c.width, Height: c.height} } // Devices returns the available capture devices. func Devices() ([]DeviceInfo, error) { - return nil, fmt.Errorf("camera: device enumeration not implemented on android") + count := int(C.camerasOpen()) + if count < 0 { + C.camerasClose() + + return nil, fmt.Errorf("camera: can not list cameras") + } + defer C.camerasClose() + + devices := make([]DeviceInfo, 0, count) + for i := 0; i < count; i++ { + id := C.GoString(C.cameraIdAt(C.int(i))) + + name := id + switch int(C.cameraFacingAt(C.int(i))) { + case 0: + name = id + " (front)" + case 1: + name = id + " (back)" + case 2: + name = id + " (external)" + } + + devices = append(devices, DeviceInfo{Index: i, Name: name}) + } + + return devices, nil }