FreeRDP
Loading...
Searching...
No Matches
camera_android.c
1
20#include <stdlib.h>
21#include <string.h>
22
23#include <winpr/wtypes.h>
24
25#include <camera/NdkCameraDevice.h>
26#include <camera/NdkCameraManager.h>
27#include <camera/NdkCameraMetadata.h>
28#include <camera/NdkCameraCaptureSession.h>
29#include <media/NdkImage.h>
30#include <media/NdkImageReader.h>
31
32#include "../camera.h"
33
34#define TAG CHANNELS_TAG("rdpecam-android.client")
35
36#define ANDROID_CAMERA_PREFIX "android-camera-"
37#define ANDROID_CAMERA_PREFIX_LEN (sizeof(ANDROID_CAMERA_PREFIX) - 1)
38
39#define CAM_ANDROID_FPS 30
40
41typedef struct
42{
43 ICamHal iHal;
44 ACameraManager* manager;
45 wHashTable* streams; /* key: deviceId, value: CamAndroidStream */
46} CamAndroidHal;
47
48typedef struct
49{
50 size_t streamIndex;
51
52 ACameraDevice* device;
53 ACameraCaptureSession* session;
54 ACaptureSessionOutputContainer* outputContainer;
55 ACaptureSessionOutput* sessionOutput;
56 ACameraOutputTarget* outputTarget;
57 ACaptureRequest* captureRequest;
58 AImageReader* imageReader;
59 ANativeWindow* window;
60
61 ICamHalSampleCapturedCallback sampleCallback;
62 CameraDevice* dev;
63 BOOL streaming;
64 BOOL deviceError;
66
67 BYTE* nv12Buffer;
68 size_t nv12BufferSize;
69} CamAndroidStream;
70
71static const char* cam_android_extract_camera_id(const char* deviceId)
72{
73 WINPR_ASSERT(deviceId);
74
75 if (strncmp(deviceId, ANDROID_CAMERA_PREFIX, ANDROID_CAMERA_PREFIX_LEN) != 0)
76 return nullptr;
77 return deviceId + ANDROID_CAMERA_PREFIX_LEN;
78}
79
80/* Tear down the capture session and its request objects. The ImageReader and device are kept. */
81static void cam_android_close_session(CamAndroidStream* stream)
82{
83 WINPR_ASSERT(stream);
84
85 if (stream->session)
86 {
87 ACameraCaptureSession_stopRepeating(stream->session);
88 ACameraCaptureSession_close(stream->session);
89 stream->session = nullptr;
90 }
91 if (stream->captureRequest)
92 {
93 if (stream->outputTarget)
94 ACaptureRequest_removeTarget(stream->captureRequest, stream->outputTarget);
95 ACaptureRequest_free(stream->captureRequest);
96 stream->captureRequest = nullptr;
97 }
98 if (stream->outputTarget)
99 {
100 ACameraOutputTarget_free(stream->outputTarget);
101 stream->outputTarget = nullptr;
102 }
103 if (stream->outputContainer)
104 {
105 ACaptureSessionOutputContainer_free(stream->outputContainer);
106 stream->outputContainer = nullptr;
107 }
108 if (stream->sessionOutput)
109 {
110 ACaptureSessionOutput_free(stream->sessionOutput);
111 stream->sessionOutput = nullptr;
112 }
113}
114
115/* Pack a YUV_420_888 frame into NV12 (handles NV12, NV21 and planar I420 layouts). */
116static void cam_android_yuv420_888_to_nv12(BYTE* dst, int width, int height, const uint8_t* yData,
117 int yRowStride, const uint8_t* uData, int uRowStride,
118 int uPixelStride, const uint8_t* vData, int vRowStride,
119 int vPixelStride)
120{
121 WINPR_ASSERT(dst);
122 WINPR_ASSERT(yData);
123 WINPR_ASSERT(uData);
124 WINPR_ASSERT(vData);
125
126 const size_t ySize = (size_t)width * height;
127
128 /* Y plane */
129 if (yRowStride == width)
130 memcpy(dst, yData, ySize);
131 else
132 {
133 for (int row = 0; row < height; row++)
134 memcpy(dst + (size_t)row * width, yData + (size_t)row * yRowStride, (size_t)width);
135 }
136
137 /* UV planes -> interleaved NV12 */
138 BYTE* uv = dst + ySize;
139 const int uvHeight = height / 2;
140 const int uvWidth = width / 2;
141 const size_t uvRowBytes = (size_t)(uvWidth * 2);
142
143 if (uPixelStride == 2 && vPixelStride == 2)
144 {
145 /* Semi-planar: NV12 (U first) or NV21 (V first). */
146 if (uData < vData)
147 {
148 /* NV12: copy the UV block directly. */
149 if (uRowStride == (int)uvRowBytes)
150 memcpy(uv, uData, (size_t)uvHeight * uvRowBytes);
151 else
152 {
153 for (int row = 0; row < uvHeight; row++)
154 memcpy(uv + (size_t)row * uvRowBytes, uData + (size_t)row * uRowStride,
155 uvRowBytes);
156 }
157 }
158 else
159 {
160 /* NV21: swap U and V bytes. */
161 for (int row = 0; row < uvHeight; row++)
162 {
163 const uint8_t* u = uData + (size_t)row * uRowStride;
164 const uint8_t* v = vData + (size_t)row * vRowStride;
165 BYTE* d = uv + (size_t)row * uvRowBytes;
166 for (int col = 0; col < uvWidth; col++, u += 2, v += 2, d += 2)
167 {
168 d[0] = u[0];
169 d[1] = v[0];
170 }
171 }
172 }
173 }
174 else
175 {
176 /* Planar I420: interleave U and V. */
177 for (int row = 0; row < uvHeight; row++)
178 {
179 const uint8_t* uRow = uData + (size_t)row * uRowStride;
180 const uint8_t* vRow = vData + (size_t)row * vRowStride;
181 BYTE* dstRow = uv + (size_t)row * uvRowBytes;
182 for (int col = 0; col < uvWidth; col++)
183 {
184 dstRow[col * 2] = uRow[col * uPixelStride];
185 dstRow[col * 2 + 1] = vRow[col * vPixelStride];
186 }
187 }
188 }
189}
190
191static void cam_android_deliver_frame(CamAndroidStream* stream, AImage* image)
192{
193 WINPR_ASSERT(stream);
194 WINPR_ASSERT(image);
195
196 int32_t width = 0;
197 int32_t height = 0;
198 if (AImage_getWidth(image, &width) != AMEDIA_OK ||
199 AImage_getHeight(image, &height) != AMEDIA_OK)
200 return;
201
202 const size_t ySize = (size_t)(width * height);
203 const size_t nv12Size = ySize + ySize / 2;
204
205 if (stream->nv12BufferSize < nv12Size)
206 {
207 BYTE* tmp = (BYTE*)realloc(stream->nv12Buffer, nv12Size);
208 if (!tmp)
209 return;
210 stream->nv12Buffer = tmp;
211 stream->nv12BufferSize = nv12Size;
212 }
213
214 /* Fetch the planes; the length out-param is required but unused. */
215 int dataLength = 0;
216 uint8_t* yData = nullptr;
217 uint8_t* uData = nullptr;
218 uint8_t* vData = nullptr;
219 if (AImage_getPlaneData(image, 0, &yData, &dataLength) != AMEDIA_OK ||
220 AImage_getPlaneData(image, 1, &uData, &dataLength) != AMEDIA_OK ||
221 AImage_getPlaneData(image, 2, &vData, &dataLength) != AMEDIA_OK)
222 return;
223
224 int yRowStride = 0;
225 int uRowStride = 0;
226 int uPixelStride = 0;
227 int vRowStride = 0;
228 int vPixelStride = 0;
229 AImage_getPlaneRowStride(image, 0, &yRowStride);
230 AImage_getPlaneRowStride(image, 1, &uRowStride);
231 AImage_getPlanePixelStride(image, 1, &uPixelStride);
232 AImage_getPlaneRowStride(image, 2, &vRowStride);
233 AImage_getPlanePixelStride(image, 2, &vPixelStride);
234
235 cam_android_yuv420_888_to_nv12(stream->nv12Buffer, width, height, yData, yRowStride, uData,
236 uRowStride, uPixelStride, vData, vRowStride, vPixelStride);
237
238 stream->sampleCallback(stream->dev, stream->streamIndex, stream->nv12Buffer, nv12Size);
239}
240
241static void cam_android_on_image_available(void* context, AImageReader* reader)
242{
243 CamAndroidStream* stream = (CamAndroidStream*)context;
244 WINPR_ASSERT(stream);
245 WINPR_ASSERT(reader);
246
247 EnterCriticalSection(&stream->lock);
248 const BOOL streaming = stream->streaming;
249 LeaveCriticalSection(&stream->lock);
250
251 if (!streaming)
252 return;
253
254 AImage* image = nullptr;
255 if (AImageReader_acquireLatestImage(reader, &image) == AMEDIA_OK && image)
256 {
257 cam_android_deliver_frame(stream, image);
258 AImage_delete(image);
259 }
260}
261
262/* Mark the device broken so it is reopened on next use. */
263static void cam_android_mark_device_error(CamAndroidStream* stream)
264{
265 if (!stream)
266 return;
267 EnterCriticalSection(&stream->lock);
268 stream->deviceError = TRUE;
269 stream->streaming = FALSE;
270 LeaveCriticalSection(&stream->lock);
271}
272
273static void cam_android_device_disconnected(void* context, ACameraDevice* device)
274{
275 WINPR_UNUSED(device);
276 WLog_WARN(TAG, "Camera device disconnected");
277 cam_android_mark_device_error((CamAndroidStream*)context);
278}
279
280static void cam_android_device_error(void* context, ACameraDevice* device, int error)
281{
282 WINPR_UNUSED(device);
283 WLog_ERR(TAG, "Camera device error %d", error);
284 cam_android_mark_device_error((CamAndroidStream*)context);
285}
286
287static void cam_android_session_active(void* context, ACameraCaptureSession* session)
288{
289 WINPR_UNUSED(context);
290 WINPR_UNUSED(session);
291 WLog_DBG(TAG, "Capture session active");
292}
293
294static void cam_android_session_closed(void* context, ACameraCaptureSession* session)
295{
296 WINPR_UNUSED(context);
297 WINPR_UNUSED(session);
298 WLog_DBG(TAG, "Capture session closed");
299}
300
301static void cam_android_session_ready(void* context, ACameraCaptureSession* session)
302{
303 WINPR_UNUSED(context);
304 WINPR_UNUSED(session);
305 WLog_DBG(TAG, "Capture session ready");
306}
307
308/* TRUE if the camera is BACKWARD_COMPATIBLE. Depth/IR/sub-sensor cameras lack this and cannot
309 * configure a normal YUV session, so they must not be offered to the server. */
310static BOOL cam_android_is_backward_compatible(ACameraMetadata* characteristics)
311{
312 WINPR_ASSERT(characteristics);
313
314 ACameraMetadata_const_entry caps;
315 if (ACameraMetadata_getConstEntry(characteristics, ACAMERA_REQUEST_AVAILABLE_CAPABILITIES,
316 &caps) != ACAMERA_OK)
317 return FALSE;
318
319 for (uint32_t i = 0; i < caps.count; i++)
320 {
321 if (caps.data.u8[i] == ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE)
322 return TRUE;
323 }
324 return FALSE;
325}
326
327static UINT cam_android_enumerate(ICamHal* ihal, ICamHalEnumCallback callback, CameraPlugin* ecam,
328 GENERIC_CHANNEL_CALLBACK* hchannel)
329{
330 CamAndroidHal* hal = (CamAndroidHal*)ihal;
331 WINPR_ASSERT(hal);
332
333 UINT count = 0;
334
335 /* NV12 capture needs FFmpeg/swscale to be encoded; without it, offer no cameras. */
336 if (!freerdp_video_conversion_supported(FREERDP_VIDEO_FORMAT_NV12,
337 FREERDP_VIDEO_FORMAT_YUV420P))
338 {
339 WLog_WARN(TAG, "Camera redirection unavailable: video conversion (FFmpeg/swscale) missing");
340 return 0;
341 }
342
343 ACameraIdList* idList = nullptr;
344 if (ACameraManager_getCameraIdList(hal->manager, &idList) != ACAMERA_OK || !idList)
345 return 0;
346
347 /* Offer only the primary camera per facing direction: auxiliary back cameras (ultrawide/tele/
348 * macro/depth) advertise resolutions but fail to configure a YUV session and freeze the
349 * channel. Indexed by ACAMERA_LENS_FACING_* (FRONT=0, BACK=1, EXTERNAL=2). */
350 BOOL seenFacing[ACAMERA_LENS_FACING_EXTERNAL + 1] = WINPR_C_ARRAY_INIT;
351
352 for (int i = 0; i < idList->numCameras; i++)
353 {
354 const char* cameraId = idList->cameraIds[i];
355
356 ACameraMetadata* characteristics = nullptr;
357 if (ACameraManager_getCameraCharacteristics(hal->manager, cameraId, &characteristics) !=
358 ACAMERA_OK)
359 continue;
360
361 /* Skip depth/IR/sub-sensor cameras that can't configure a normal capture session. */
362 if (!cam_android_is_backward_compatible(characteristics))
363 {
364 WLog_DBG(TAG, "Skipping camera %s: not BACKWARD_COMPATIBLE", cameraId);
365 ACameraMetadata_free(characteristics);
366 continue;
367 }
368
369 ACameraMetadata_const_entry facingEntry;
370 const char* facingName = "Camera";
371 uint8_t facing = ACAMERA_LENS_FACING_EXTERNAL;
372 if (ACameraMetadata_getConstEntry(characteristics, ACAMERA_LENS_FACING, &facingEntry) ==
373 ACAMERA_OK)
374 {
375 facing = facingEntry.data.u8[0];
376 switch (facing)
377 {
378 case ACAMERA_LENS_FACING_FRONT:
379 facingName = "Front Camera";
380 break;
381 case ACAMERA_LENS_FACING_BACK:
382 facingName = "Back Camera";
383 break;
384 default:
385 facingName = "External Camera";
386 break;
387 }
388 }
389
390 /* Bounds-guard seenFacing[] against an out-of-range facing value. */
391 if (facing <= ACAMERA_LENS_FACING_EXTERNAL)
392 {
393 if (seenFacing[facing])
394 {
395 WLog_DBG(TAG, "Skipping camera %s: already have a %s", cameraId, facingName);
396 ACameraMetadata_free(characteristics);
397 continue;
398 }
399 seenFacing[facing] = TRUE;
400 }
401
402 char deviceId[64];
403 (void)_snprintf(deviceId, sizeof(deviceId), ANDROID_CAMERA_PREFIX "%s", cameraId);
404
405 IFCALL(callback, ecam, hchannel, deviceId, facingName);
406 count++;
407
408 ACameraMetadata_free(characteristics);
409 }
410
411 ACameraManager_deleteCameraIdList(idList);
412 return count;
413}
414
415/* Stop streaming and fully release the camera. Order matters: close the session and device before
416 * the ImageReader, or the camera service fails to unconfigure and leaves the device running. */
417static void cam_android_close_device(CamAndroidStream* stream)
418{
419 WINPR_ASSERT(stream);
420
421 EnterCriticalSection(&stream->lock);
422 stream->streaming = FALSE;
423 LeaveCriticalSection(&stream->lock);
424
425 if (stream->imageReader)
426 AImageReader_setImageListener(stream->imageReader, nullptr);
427
428 cam_android_close_session(stream);
429
430 if (stream->device)
431 {
432 ACameraDevice_close(stream->device);
433 stream->device = nullptr;
434 }
435
436 if (stream->imageReader)
437 {
438 AImageReader_delete(stream->imageReader);
439 stream->imageReader = nullptr;
440 stream->window = nullptr;
441 }
442 free(stream->nv12Buffer);
443 stream->nv12Buffer = nullptr;
444 stream->nv12BufferSize = 0;
445 stream->deviceError = FALSE;
446}
447
448/* Close every open camera except keepDeviceId. Phones share camera hardware, so holding more than
449 * one open makes the next openCamera fail with CAMERA_DISCONNECTED and freeze the channel. */
450static void cam_android_close_other_devices(CamAndroidHal* hal, const char* keepDeviceId)
451{
452 WINPR_ASSERT(hal);
453 WINPR_ASSERT(keepDeviceId);
454
455 ULONG_PTR* keys = nullptr;
456 const size_t count = HashTable_GetKeys(hal->streams, &keys);
457 for (size_t i = 0; i < count; i++)
458 {
459 const char* id = (const char*)keys[i];
460 if (strcmp(id, keepDeviceId) == 0)
461 continue;
462
463 CamAndroidStream* other = (CamAndroidStream*)HashTable_GetItemValue(hal->streams, id);
464 if (!other || !other->device)
465 continue;
466
467 WLog_DBG(TAG, "Closing camera %s to free resources for %s", id, keepDeviceId);
468 cam_android_close_device(other);
469 }
470 free(keys);
471}
472
473static CamAndroidStream* cam_android_get_or_create_stream(CamAndroidHal* hal, const char* deviceId,
474 CAM_ERROR_CODE* errorCode)
475{
476 WINPR_ASSERT(hal);
477 WINPR_ASSERT(deviceId);
478 WINPR_ASSERT(errorCode);
479
480 CamAndroidStream* stream = (CamAndroidStream*)HashTable_GetItemValue(hal->streams, deviceId);
481 if (stream)
482 return stream;
483
484 stream = (CamAndroidStream*)calloc(1, sizeof(CamAndroidStream));
485 if (!stream)
486 {
487 *errorCode = CAM_ERROR_CODE_OutOfMemory;
488 return nullptr;
489 }
490 if (!InitializeCriticalSectionEx(&stream->lock, 0, 0))
491 {
492 free(stream);
493 *errorCode = CAM_ERROR_CODE_UnexpectedError;
494 return nullptr;
495 }
496 if (!HashTable_Insert(hal->streams, deviceId, stream))
497 {
498 DeleteCriticalSection(&stream->lock);
499 free(stream);
500 *errorCode = CAM_ERROR_CODE_UnexpectedError;
501 return nullptr;
502 }
503 return stream;
504}
505
506/* Open the camera, closing any other open one first (phones keep only one open). Deferred from
507 * Activate to StartStream so media-type probing doesn't thrash the hardware. */
508static BOOL cam_android_open_device(CamAndroidHal* hal, CamAndroidStream* stream,
509 const char* deviceId, const char* cameraId,
510 CAM_ERROR_CODE* errorCode)
511{
512 WINPR_ASSERT(hal);
513 WINPR_ASSERT(stream);
514 WINPR_ASSERT(deviceId);
515 WINPR_ASSERT(cameraId);
516 WINPR_ASSERT(errorCode);
517
518 /* Reopen a device left broken by a previous error. */
519 EnterCriticalSection(&stream->lock);
520 const BOOL hadError = stream->deviceError;
521 LeaveCriticalSection(&stream->lock);
522 if (stream->device && hadError)
523 cam_android_close_device(stream);
524
525 if (stream->device)
526 return TRUE; /* already open */
527
528 cam_android_close_other_devices(hal, deviceId);
529
530 ACameraDevice_StateCallbacks deviceCallbacks = {
531 .context = stream,
532 .onDisconnected = cam_android_device_disconnected,
533 .onError = cam_android_device_error,
534 };
535
536 camera_status_t status =
537 ACameraManager_openCamera(hal->manager, cameraId, &deviceCallbacks, &stream->device);
538 if (status != ACAMERA_OK)
539 {
540 WLog_ERR(TAG, "ACameraManager_openCamera failed: %d", status);
541 *errorCode = CAM_ERROR_CODE_InvalidRequest;
542 return FALSE;
543 }
544
545 WLog_DBG(TAG, "Opened camera %s", cameraId);
546 return TRUE;
547}
548
549static BOOL cam_android_activate(ICamHal* ihal, const char* deviceId, CAM_ERROR_CODE* errorCode)
550{
551 CamAndroidHal* hal = (CamAndroidHal*)ihal;
552 WINPR_ASSERT(hal);
553 WINPR_ASSERT(deviceId);
554 WINPR_ASSERT(errorCode);
555
556 *errorCode = CAM_ERROR_CODE_None;
557
558 const char* cameraId = cam_android_extract_camera_id(deviceId);
559 if (!cameraId)
560 {
561 *errorCode = CAM_ERROR_CODE_InvalidRequest;
562 return FALSE;
563 }
564
565 /* Hardware is opened lazily in StartStream, not here. */
566 return cam_android_get_or_create_stream(hal, deviceId, errorCode) != nullptr;
567}
568
569static BOOL cam_android_deactivate(ICamHal* ihal, const char* deviceId, CAM_ERROR_CODE* errorCode)
570{
571 CamAndroidHal* hal = (CamAndroidHal*)ihal;
572 WINPR_ASSERT(hal);
573 WINPR_ASSERT(deviceId);
574 WINPR_ASSERT(errorCode);
575
576 *errorCode = CAM_ERROR_CODE_None;
577
578 CamAndroidStream* stream = (CamAndroidStream*)HashTable_GetItemValue(hal->streams, deviceId);
579 if (!stream || !stream->device)
580 return TRUE;
581
582 cam_android_close_device(stream);
583 return TRUE;
584}
585
586static INT16 cam_android_get_media_type_descriptions(ICamHal* ihal, const char* deviceId,
587 size_t streamIndex,
588 const CAM_MEDIA_FORMAT_INFO* supportedFormats,
589 size_t nSupportedFormats,
590 CAM_MEDIA_TYPE_DESCRIPTION* mediaTypes,
591 size_t* nMediaTypes)
592{
593 WINPR_UNUSED(streamIndex);
594 CamAndroidHal* hal = (CamAndroidHal*)ihal;
595 WINPR_ASSERT(hal);
596 WINPR_ASSERT(deviceId);
597 WINPR_ASSERT(supportedFormats || (nSupportedFormats == 0));
598 WINPR_ASSERT(mediaTypes);
599 WINPR_ASSERT(nMediaTypes);
600
601 size_t maxMediaTypes = *nMediaTypes;
602 *nMediaTypes = 0;
603
604 const char* cameraId = cam_android_extract_camera_id(deviceId);
605 if (!cameraId)
606 return -1;
607
608 /* Find the NV12 format index. */
609 INT16 formatIndex = -1;
610 for (size_t i = 0; i < nSupportedFormats; i++)
611 {
612 if (supportedFormats[i].inputFormat == CAM_MEDIA_FORMAT_NV12)
613 {
614 formatIndex = (INT16)i;
615 break;
616 }
617 }
618 if (formatIndex < 0)
619 {
620 WLog_WARN(TAG, "Server does not offer NV12 format");
621 return -1;
622 }
623
624 ACameraMetadata* characteristics = nullptr;
625 if (ACameraManager_getCameraCharacteristics(hal->manager, cameraId, &characteristics) !=
626 ACAMERA_OK)
627 return -1;
628
629 /* Stream configs: [format, width, height, isInput] tuples. */
630 ACameraMetadata_const_entry entry;
631 if (ACameraMetadata_getConstEntry(
632 characteristics, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry) != ACAMERA_OK)
633 {
634 ACameraMetadata_free(characteristics);
635 return -1;
636 }
637
638 size_t nTypes = 0;
639 for (uint32_t i = 0; i + 3 < entry.count; i += 4)
640 {
641 const int32_t fmt = entry.data.i32[i];
642 const int32_t w = entry.data.i32[i + 1];
643 const int32_t h = entry.data.i32[i + 2];
644 const int32_t isInput = entry.data.i32[i + 3];
645
646 if (isInput != 0)
647 continue;
648 if (fmt != AIMAGE_FORMAT_YUV_420_888)
649 continue;
650 /* Skip resolutions larger than 1080p to avoid saturating the RDP link */
651 if (w > 1920 || h > 1080)
652 continue;
653
654 mediaTypes[nTypes].Format = CAM_MEDIA_FORMAT_NV12;
655 mediaTypes[nTypes].Width = (UINT32)w;
656 mediaTypes[nTypes].Height = (UINT32)h;
657 mediaTypes[nTypes].FrameRateNumerator = CAM_ANDROID_FPS;
658 mediaTypes[nTypes].FrameRateDenominator = 1;
659 mediaTypes[nTypes].PixelAspectRatioNumerator = 1;
660 mediaTypes[nTypes].PixelAspectRatioDenominator = 1;
661
662 WLog_DBG(TAG, "Camera format NV12 %dx%d @ %dfps", w, h, CAM_ANDROID_FPS);
663
664 if (++nTypes >= maxMediaTypes)
665 break;
666 }
667
668 *nMediaTypes = nTypes;
669 ACameraMetadata_free(characteristics);
670
671 if (nTypes == 0)
672 {
673 WLog_ERR(TAG, "No supported YUV resolutions found for camera %s", cameraId);
674 return -1;
675 }
676
677 return formatIndex;
678}
679
680static CAM_ERROR_CODE cam_android_start_stream(ICamHal* ihal, CameraDevice* dev, size_t streamIndex,
681 const CAM_MEDIA_TYPE_DESCRIPTION* mediaType,
682 ICamHalSampleCapturedCallback callback)
683{
684 CamAndroidHal* hal = (CamAndroidHal*)ihal;
685 WINPR_ASSERT(hal);
686 WINPR_ASSERT(dev);
687 WINPR_ASSERT(mediaType);
688 WINPR_ASSERT(callback);
689
690 const char* deviceId = dev->deviceId;
691
692 const char* cameraId = cam_android_extract_camera_id(deviceId);
693 if (!cameraId)
694 return CAM_ERROR_CODE_InvalidRequest;
695
696 CAM_ERROR_CODE errorCode = CAM_ERROR_CODE_None;
697 CamAndroidStream* stream = cam_android_get_or_create_stream(hal, deviceId, &errorCode);
698 if (!stream)
699 return errorCode;
700
701 if (!cam_android_open_device(hal, stream, deviceId, cameraId, &errorCode))
702 return errorCode;
703
704 stream->dev = dev;
705 stream->streamIndex = streamIndex;
706 stream->sampleCallback = callback;
707
708 const int32_t width = (int32_t)mediaType->Width;
709 const int32_t height = (int32_t)mediaType->Height;
710
711 if (AImageReader_new(width, height, AIMAGE_FORMAT_YUV_420_888, 4, &stream->imageReader) !=
712 AMEDIA_OK)
713 {
714 WLog_ERR(TAG, "AImageReader_new failed");
715 return CAM_ERROR_CODE_UnexpectedError;
716 }
717
718 AImageReader_ImageListener listener = {
719 .context = stream,
720 .onImageAvailable = cam_android_on_image_available,
721 };
722 AImageReader_setImageListener(stream->imageReader, &listener);
723
724 if (AImageReader_getWindow(stream->imageReader, &stream->window) != AMEDIA_OK)
725 {
726 WLog_ERR(TAG, "AImageReader_getWindow failed");
727 goto error;
728 }
729
730 ACaptureSessionOutput_create(stream->window, &stream->sessionOutput);
731 ACaptureSessionOutputContainer_create(&stream->outputContainer);
732 ACaptureSessionOutputContainer_add(stream->outputContainer, stream->sessionOutput);
733 ACameraOutputTarget_create(stream->window, &stream->outputTarget);
734 ACameraDevice_createCaptureRequest(stream->device, TEMPLATE_RECORD, &stream->captureRequest);
735 ACaptureRequest_addTarget(stream->captureRequest, stream->outputTarget);
736
737 ACameraCaptureSession_stateCallbacks sessionCallbacks = {
738 .context = stream,
739 .onActive = cam_android_session_active,
740 .onClosed = cam_android_session_closed,
741 .onReady = cam_android_session_ready,
742 };
743
744 if (ACameraDevice_createCaptureSession(stream->device, stream->outputContainer,
745 &sessionCallbacks, &stream->session) != ACAMERA_OK)
746 {
747 WLog_ERR(TAG, "ACameraDevice_createCaptureSession failed");
748 goto error;
749 }
750
751 if (ACameraCaptureSession_setRepeatingRequest(stream->session, nullptr, 1,
752 &stream->captureRequest, nullptr) != ACAMERA_OK)
753 {
754 WLog_ERR(TAG, "ACameraCaptureSession_setRepeatingRequest failed");
755 goto error;
756 }
757
758 EnterCriticalSection(&stream->lock);
759 stream->streaming = TRUE;
760 LeaveCriticalSection(&stream->lock);
761
762 WLog_DBG(TAG, "Camera stream started: %dx%d", width, height);
763 return CAM_ERROR_CODE_None;
764
765error:
766 cam_android_close_device(stream);
767 return CAM_ERROR_CODE_UnexpectedError;
768}
769
770static CAM_ERROR_CODE cam_android_stop_stream(ICamHal* ihal, const char* deviceId,
771 size_t streamIndex)
772{
773 WINPR_UNUSED(streamIndex);
774 CamAndroidHal* hal = (CamAndroidHal*)ihal;
775 WINPR_ASSERT(hal);
776 WINPR_ASSERT(deviceId);
777
778 CamAndroidStream* stream = (CamAndroidStream*)HashTable_GetItemValue(hal->streams, deviceId);
779 if (!stream)
780 return CAM_ERROR_CODE_None;
781
782 cam_android_close_device(stream);
783
784 WLog_DBG(TAG, "Camera stream stopped for %s", deviceId);
785 return CAM_ERROR_CODE_None;
786}
787
788static void cam_android_stream_free(void* obj)
789{
790 CamAndroidStream* stream = (CamAndroidStream*)obj;
791 if (!stream)
792 return;
793
794 cam_android_close_device(stream);
795 DeleteCriticalSection(&stream->lock);
796 free(stream);
797}
798
799static CAM_ERROR_CODE cam_android_free(ICamHal* ihal)
800{
801 CamAndroidHal* hal = (CamAndroidHal*)ihal;
802 if (!hal)
803 return CAM_ERROR_CODE_NotInitialized;
804
805 HashTable_Free(hal->streams);
806 if (hal->manager)
807 ACameraManager_delete(hal->manager);
808 free(hal);
809 return CAM_ERROR_CODE_None;
810}
811
812FREERDP_ENTRY_POINT(UINT VCAPITYPE android_freerdp_rdpecam_client_subsystem_entry(
814{
815 WINPR_ASSERT(pEntryPoints);
816
817 CamAndroidHal* hal = (CamAndroidHal*)calloc(1, sizeof(CamAndroidHal));
818 if (!hal)
819 return CHANNEL_RC_NO_MEMORY;
820
821 hal->manager = ACameraManager_create();
822 if (!hal->manager)
823 {
824 free(hal);
825 return ERROR_INTERNAL_ERROR;
826 }
827
828 hal->streams = HashTable_New(FALSE);
829 if (!hal->streams)
830 goto error;
831
832 if (!HashTable_SetupForStringData(hal->streams, FALSE))
833 goto error;
834
835 wObject* obj = HashTable_ValueObject(hal->streams);
836 WINPR_ASSERT(obj);
837 obj->fnObjectFree = cam_android_stream_free;
838
839 hal->iHal.Enumerate = cam_android_enumerate;
840 hal->iHal.Activate = cam_android_activate;
841 hal->iHal.Deactivate = cam_android_deactivate;
842 hal->iHal.GetMediaTypeDescriptions = cam_android_get_media_type_descriptions;
843 hal->iHal.StartStream = cam_android_start_stream;
844 hal->iHal.StopStream = cam_android_stop_stream;
845 hal->iHal.Free = cam_android_free;
846
847 UINT ret = pEntryPoints->pRegisterCameraHal(pEntryPoints->plugin, &hal->iHal);
848 if (ret != CHANNEL_RC_OK)
849 {
850 WLog_ERR(TAG, "RegisterCameraHal failed: %" PRIu32, ret);
851 goto error;
852 }
853
854 return ret;
855
856error:
857 cam_android_free(&hal->iHal);
858 return ERROR_INTERNAL_ERROR;
859}
Definition camera.h:221
This struct contains function pointer to initialize/free objects.
Definition collections.h:52
OBJECT_FREE_FN fnObjectFree
Definition collections.h:59