camera: Emscripten support!
This also adds code to deal with waiting for the user to approve camera access, reworks testcameraminimal to use main callbacks, etc.main
parent
182f707284
commit
67708f9110
|
@ -1423,6 +1423,12 @@ elseif(EMSCRIPTEN)
|
||||||
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/emscripten/*.c")
|
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/emscripten/*.c")
|
||||||
set(HAVE_SDL_FILESYSTEM TRUE)
|
set(HAVE_SDL_FILESYSTEM TRUE)
|
||||||
|
|
||||||
|
if(SDL_CAMERA)
|
||||||
|
set(SDL_CAMERA_DRIVER_EMSCRIPTEN 1)
|
||||||
|
set(HAVE_CAMERA TRUE)
|
||||||
|
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/emscripten/*.c")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(SDL_JOYSTICK)
|
if(SDL_JOYSTICK)
|
||||||
set(SDL_JOYSTICK_EMSCRIPTEN 1)
|
set(SDL_JOYSTICK_EMSCRIPTEN 1)
|
||||||
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/emscripten/*.c")
|
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/emscripten/*.c")
|
||||||
|
|
|
@ -171,6 +171,13 @@ extern DECLSPEC SDL_CameraDeviceID *SDLCALL SDL_GetCameraDevices(int *count);
|
||||||
* The returned list is owned by the caller, and should be released with
|
* The returned list is owned by the caller, and should be released with
|
||||||
* SDL_free() when no longer needed.
|
* SDL_free() when no longer needed.
|
||||||
*
|
*
|
||||||
|
* Note that it's legal for a camera to supply a list with only the zeroed
|
||||||
|
* final element and `*count` set to zero; this is what will happen on
|
||||||
|
* Emscripten builds, since that platform won't tell _anything_ about
|
||||||
|
* available cameras until you've opened one, and won't even tell if there
|
||||||
|
* _is_ a camera until the user has given you permission to check through
|
||||||
|
* a scary warning popup.
|
||||||
|
*
|
||||||
* \param devid the camera device instance ID to query.
|
* \param devid the camera device instance ID to query.
|
||||||
* \param count a pointer filled in with the number of elements in the list. Can be NULL.
|
* \param count a pointer filled in with the number of elements in the list. Can be NULL.
|
||||||
* \returns a 0 terminated array of SDL_CameraSpecs, which should be
|
* \returns a 0 terminated array of SDL_CameraSpecs, which should be
|
||||||
|
@ -224,6 +231,16 @@ extern DECLSPEC char * SDLCALL SDL_GetCameraDeviceName(SDL_CameraDeviceID instan
|
||||||
* SDL_GetCameraFormat() to see the actual framerate of the opened the device,
|
* SDL_GetCameraFormat() to see the actual framerate of the opened the device,
|
||||||
* and check your timestamps if this is crucial to your app!
|
* and check your timestamps if this is crucial to your app!
|
||||||
*
|
*
|
||||||
|
* Note that the camera is not usable until the user approves its use! On
|
||||||
|
* some platforms, the operating system will prompt the user to permit access
|
||||||
|
* to the camera, and they can choose Yes or No at that point. Until they do,
|
||||||
|
* the camera will not be usable. The app should either wait for an
|
||||||
|
* SDL_EVENT_CAMERA_DEVICE_APPROVED (or SDL_EVENT_CAMERA_DEVICE_DENIED) event,
|
||||||
|
* or poll SDL_IsCameraApproved() occasionally until it returns non-zero. On
|
||||||
|
* platforms that don't require explicit user approval (and perhaps in places
|
||||||
|
* where the user previously permitted access), the approval event might come
|
||||||
|
* immediately, but it might come seconds, minutes, or hours later!
|
||||||
|
*
|
||||||
* \param instance_id the camera device instance ID
|
* \param instance_id the camera device instance ID
|
||||||
* \param spec The desired format for data the device will provide. Can be NULL.
|
* \param spec The desired format for data the device will provide. Can be NULL.
|
||||||
* \returns device, or NULL on failure; call SDL_GetError() for more
|
* \returns device, or NULL on failure; call SDL_GetError() for more
|
||||||
|
@ -238,6 +255,38 @@ extern DECLSPEC char * SDLCALL SDL_GetCameraDeviceName(SDL_CameraDeviceID instan
|
||||||
*/
|
*/
|
||||||
extern DECLSPEC SDL_Camera *SDLCALL SDL_OpenCameraDevice(SDL_CameraDeviceID instance_id, const SDL_CameraSpec *spec);
|
extern DECLSPEC SDL_Camera *SDLCALL SDL_OpenCameraDevice(SDL_CameraDeviceID instance_id, const SDL_CameraSpec *spec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query if camera access has been approved by the user.
|
||||||
|
*
|
||||||
|
* Cameras will not function between when the device is opened by the app
|
||||||
|
* and when the user permits access to the hardware. On some platforms, this
|
||||||
|
* presents as a popup dialog where the user has to explicitly approve access;
|
||||||
|
* on others the approval might be implicit and not alert the user at all.
|
||||||
|
*
|
||||||
|
* This function can be used to check the status of that approval. It will
|
||||||
|
* return 0 if still waiting for user response, 1 if the camera is approved
|
||||||
|
* for use, and -1 if the user denied access.
|
||||||
|
*
|
||||||
|
* Instead of polling with this function, you can wait for a
|
||||||
|
* SDL_EVENT_CAMERA_DEVICE_APPROVED (or SDL_EVENT_CAMERA_DEVICE_DENIED) event
|
||||||
|
* in the standard SDL event loop, which is guaranteed to be sent once when
|
||||||
|
* permission to use the camera is decided.
|
||||||
|
*
|
||||||
|
* If a camera is declined, there's nothing to be done but call
|
||||||
|
* SDL_CloseCamera() to dispose of it.
|
||||||
|
*
|
||||||
|
* \param camera the opened camera device to query
|
||||||
|
* \returns -1 if user denied access to the camera, 1 if user approved access, 0 if no decision has been made yet.
|
||||||
|
*
|
||||||
|
* \threadsafety It is safe to call this function from any thread.
|
||||||
|
*
|
||||||
|
* \since This function is available since SDL 3.0.0.
|
||||||
|
*
|
||||||
|
* \sa SDL_OpenCameraDevice
|
||||||
|
* \sa SDL_CloseCamera
|
||||||
|
*/
|
||||||
|
extern DECLSPEC int SDLCALL SDL_GetCameraPermissionState(SDL_Camera *camera);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the instance ID of an opened camera.
|
* Get the instance ID of an opened camera.
|
||||||
*
|
*
|
||||||
|
@ -275,6 +324,12 @@ extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetCameraProperties(SDL_Camera *cam
|
||||||
* Note that this might not be the native format of the hardware, as SDL
|
* Note that this might not be the native format of the hardware, as SDL
|
||||||
* might be converting to this format behind the scenes.
|
* might be converting to this format behind the scenes.
|
||||||
*
|
*
|
||||||
|
* If the system is waiting for the user to approve access to the camera, as
|
||||||
|
* some platforms require, this will return -1, but this isn't necessarily a
|
||||||
|
* fatal error; you should either wait for an SDL_EVENT_CAMERA_DEVICE_APPROVED
|
||||||
|
* (or SDL_EVENT_CAMERA_DEVICE_DENIED) event, or poll SDL_IsCameraApproved()
|
||||||
|
* occasionally until it returns non-zero.
|
||||||
|
*
|
||||||
* \param camera opened camera device
|
* \param camera opened camera device
|
||||||
* \param spec The SDL_CameraSpec to be initialized by this function.
|
* \param spec The SDL_CameraSpec to be initialized by this function.
|
||||||
* \returns 0 on success or a negative error code on failure; call
|
* \returns 0 on success or a negative error code on failure; call
|
||||||
|
@ -305,13 +360,17 @@ extern DECLSPEC int SDLCALL SDL_GetCameraFormat(SDL_Camera *camera, SDL_CameraSp
|
||||||
* failure here is almost always an out of memory condition.
|
* failure here is almost always an out of memory condition.
|
||||||
*
|
*
|
||||||
* After use, the frame should be released with SDL_ReleaseCameraFrame(). If you
|
* After use, the frame should be released with SDL_ReleaseCameraFrame(). If you
|
||||||
* don't do this, the system may stop providing more video! If the hardware is
|
* don't do this, the system may stop providing more video!
|
||||||
* using DMA to write directly into memory, frames held too long may be overwritten
|
|
||||||
* with new data.
|
|
||||||
*
|
*
|
||||||
* Do not call SDL_FreeSurface() on the returned surface! It must be given back
|
* Do not call SDL_FreeSurface() on the returned surface! It must be given back
|
||||||
* to the camera subsystem with SDL_ReleaseCameraFrame!
|
* to the camera subsystem with SDL_ReleaseCameraFrame!
|
||||||
*
|
*
|
||||||
|
* If the system is waiting for the user to approve access to the camera, as
|
||||||
|
* some platforms require, this will return NULL (no frames available); you should
|
||||||
|
* either wait for an SDL_EVENT_CAMERA_DEVICE_APPROVED (or
|
||||||
|
* SDL_EVENT_CAMERA_DEVICE_DENIED) event, or poll SDL_IsCameraApproved()
|
||||||
|
* occasionally until it returns non-zero.
|
||||||
|
*
|
||||||
* \param camera opened camera device
|
* \param camera opened camera device
|
||||||
* \param timestampNS a pointer filled in with the frame's timestamp, or 0 on error. Can be NULL.
|
* \param timestampNS a pointer filled in with the frame's timestamp, or 0 on error. Can be NULL.
|
||||||
* \returns A new frame of video on success, NULL if none is currently available.
|
* \returns A new frame of video on success, NULL if none is currently available.
|
||||||
|
|
|
@ -208,6 +208,8 @@ typedef enum
|
||||||
/* Camera hotplug events */
|
/* Camera hotplug events */
|
||||||
SDL_EVENT_CAMERA_DEVICE_ADDED = 0x1400, /**< A new camera device is available */
|
SDL_EVENT_CAMERA_DEVICE_ADDED = 0x1400, /**< A new camera device is available */
|
||||||
SDL_EVENT_CAMERA_DEVICE_REMOVED, /**< A camera device has been removed. */
|
SDL_EVENT_CAMERA_DEVICE_REMOVED, /**< A camera device has been removed. */
|
||||||
|
SDL_EVENT_CAMERA_DEVICE_APPROVED, /**< A camera device has been approved for use by the user. */
|
||||||
|
SDL_EVENT_CAMERA_DEVICE_DENIED, /**< A camera device has been denied for use by the user. */
|
||||||
|
|
||||||
/* Render events */
|
/* Render events */
|
||||||
SDL_EVENT_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset and their contents need to be updated */
|
SDL_EVENT_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset and their contents need to be updated */
|
||||||
|
@ -535,7 +537,7 @@ typedef struct SDL_AudioDeviceEvent
|
||||||
*/
|
*/
|
||||||
typedef struct SDL_CameraDeviceEvent
|
typedef struct SDL_CameraDeviceEvent
|
||||||
{
|
{
|
||||||
Uint32 type; /**< ::SDL_EVENT_CAMERA_DEVICE_ADDED, or ::SDL_EVENT_CAMERA_DEVICE_REMOVED */
|
Uint32 type; /**< ::SDL_EVENT_CAMERA_DEVICE_ADDED, ::SDL_EVENT_CAMERA_DEVICE_REMOVED, ::SDL_EVENT_CAMERA_DEVICE_APPROVED, ::SDL_EVENT_CAMERA_DEVICE_DENIED */
|
||||||
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
|
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
|
||||||
SDL_CameraDeviceID which; /**< SDL_CameraDeviceID for the device being added or removed or changing */
|
SDL_CameraDeviceID which; /**< SDL_CameraDeviceID for the device being added or removed or changing */
|
||||||
Uint8 padding1;
|
Uint8 padding1;
|
||||||
|
|
|
@ -472,6 +472,7 @@
|
||||||
#cmakedefine SDL_CAMERA_DRIVER_V4L2 @SDL_CAMERA_DRIVER_V4L2@
|
#cmakedefine SDL_CAMERA_DRIVER_V4L2 @SDL_CAMERA_DRIVER_V4L2@
|
||||||
#cmakedefine SDL_CAMERA_DRIVER_COREMEDIA @SDL_CAMERA_DRIVER_COREMEDIA@
|
#cmakedefine SDL_CAMERA_DRIVER_COREMEDIA @SDL_CAMERA_DRIVER_COREMEDIA@
|
||||||
#cmakedefine SDL_CAMERA_DRIVER_ANDROID @SDL_CAMERA_DRIVER_ANDROID@
|
#cmakedefine SDL_CAMERA_DRIVER_ANDROID @SDL_CAMERA_DRIVER_ANDROID@
|
||||||
|
#cmakedefine SDL_CAMERA_DRIVER_EMSCRIPTEN @SDL_CAMERA_DRIVER_EMSCRIPTEND@
|
||||||
|
|
||||||
/* Enable misc subsystem */
|
/* Enable misc subsystem */
|
||||||
#cmakedefine SDL_MISC_DUMMY @SDL_MISC_DUMMY@
|
#cmakedefine SDL_MISC_DUMMY @SDL_MISC_DUMMY@
|
||||||
|
|
|
@ -209,7 +209,7 @@
|
||||||
/* Enable system filesystem support */
|
/* Enable system filesystem support */
|
||||||
#define SDL_FILESYSTEM_EMSCRIPTEN 1
|
#define SDL_FILESYSTEM_EMSCRIPTEN 1
|
||||||
|
|
||||||
/* Enable the camera driver (src/camera/dummy/\*.c) */ /* !!! FIXME */
|
/* Enable the camera driver */
|
||||||
#define SDL_CAMERA_DRIVER_DUMMY 1
|
#define SDL_CAMERA_DRIVER_EMSCRIPTEN 1
|
||||||
|
|
||||||
#endif /* SDL_build_config_emscripten_h */
|
#endif /* SDL_build_config_emscripten_h */
|
||||||
|
|
|
@ -40,6 +40,9 @@ static const CameraBootStrap *const bootstrap[] = {
|
||||||
#ifdef SDL_CAMERA_DRIVER_ANDROID
|
#ifdef SDL_CAMERA_DRIVER_ANDROID
|
||||||
&ANDROIDCAMERA_bootstrap,
|
&ANDROIDCAMERA_bootstrap,
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef SDL_CAMERA_DRIVER_EMSCRIPTEN
|
||||||
|
&EMSCRIPTENCAMERA_bootstrap,
|
||||||
|
#endif
|
||||||
#ifdef SDL_CAMERA_DRIVER_DUMMY
|
#ifdef SDL_CAMERA_DRIVER_DUMMY
|
||||||
&DUMMYCAMERA_bootstrap,
|
&DUMMYCAMERA_bootstrap,
|
||||||
#endif
|
#endif
|
||||||
|
@ -247,8 +250,8 @@ static int SDLCALL CameraSpecCmp(const void *vpa, const void *vpb)
|
||||||
SDL_CameraDevice *SDL_AddCameraDevice(const char *name, int num_specs, const SDL_CameraSpec *specs, void *handle)
|
SDL_CameraDevice *SDL_AddCameraDevice(const char *name, int num_specs, const SDL_CameraSpec *specs, void *handle)
|
||||||
{
|
{
|
||||||
SDL_assert(name != NULL);
|
SDL_assert(name != NULL);
|
||||||
SDL_assert(num_specs > 0);
|
SDL_assert(num_specs >= 0);
|
||||||
SDL_assert(specs != NULL);
|
SDL_assert((specs != NULL) == (num_specs > 0));
|
||||||
SDL_assert(handle != NULL);
|
SDL_assert(handle != NULL);
|
||||||
|
|
||||||
SDL_LockRWLockForReading(camera_driver.device_hash_lock);
|
SDL_LockRWLockForReading(camera_driver.device_hash_lock);
|
||||||
|
@ -284,22 +287,24 @@ SDL_CameraDevice *SDL_AddCameraDevice(const char *name, int num_specs, const SDL
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_memcpy(device->all_specs, specs, sizeof (*specs) * num_specs);
|
if (num_specs > 0) {
|
||||||
SDL_qsort(device->all_specs, num_specs, sizeof (*specs), CameraSpecCmp);
|
SDL_memcpy(device->all_specs, specs, sizeof (*specs) * num_specs);
|
||||||
|
SDL_qsort(device->all_specs, num_specs, sizeof (*specs), CameraSpecCmp);
|
||||||
|
|
||||||
// weed out duplicates, just in case.
|
// weed out duplicates, just in case.
|
||||||
for (int i = 0; i < num_specs; i++) {
|
for (int i = 0; i < num_specs; i++) {
|
||||||
SDL_CameraSpec *a = &device->all_specs[i];
|
SDL_CameraSpec *a = &device->all_specs[i];
|
||||||
SDL_CameraSpec *b = &device->all_specs[i + 1];
|
SDL_CameraSpec *b = &device->all_specs[i + 1];
|
||||||
if (SDL_memcmp(a, b, sizeof (*a)) == 0) {
|
if (SDL_memcmp(a, b, sizeof (*a)) == 0) {
|
||||||
SDL_memmove(a, b, sizeof (*specs) * (num_specs - i));
|
SDL_memmove(a, b, sizeof (*specs) * (num_specs - i));
|
||||||
i--;
|
i--;
|
||||||
num_specs--;
|
num_specs--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG_CAMERA
|
#if DEBUG_CAMERA
|
||||||
SDL_Log("CAMERA: Adding device ('%s') with %d spec%s:", name, num_specs, (num_specs == 1) ? "" : "s");
|
SDL_Log("CAMERA: Adding device ('%s') with %d spec%s%s", name, num_specs, (num_specs == 1) ? "" : "s", (num_specs == 0) ? "" : ":");
|
||||||
for (int i = 0; i < num_specs; i++) {
|
for (int i = 0; i < num_specs; i++) {
|
||||||
const SDL_CameraSpec *spec = &device->all_specs[i];
|
const SDL_CameraSpec *spec = &device->all_specs[i];
|
||||||
SDL_Log("CAMERA: - fmt=%s, w=%d, h=%d, numerator=%d, denominator=%d", SDL_GetPixelFormatName(spec->format), spec->width, spec->height, spec->interval_numerator, spec->interval_denominator);
|
SDL_Log("CAMERA: - fmt=%s, w=%d, h=%d, numerator=%d, denominator=%d", SDL_GetPixelFormatName(spec->format), spec->width, spec->height, spec->interval_numerator, spec->interval_denominator);
|
||||||
|
@ -398,6 +403,42 @@ sdfsdf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SDL_CameraDevicePermissionOutcome(SDL_CameraDevice *device, SDL_bool approved)
|
||||||
|
{
|
||||||
|
if (!device) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_PendingCameraDeviceEvent pending;
|
||||||
|
pending.next = NULL;
|
||||||
|
SDL_PendingCameraDeviceEvent *pending_tail = &pending;
|
||||||
|
|
||||||
|
const int permission = approved ? 1 : -1;
|
||||||
|
|
||||||
|
ObtainPhysicalCameraDeviceObj(device);
|
||||||
|
if (device->permission != permission) {
|
||||||
|
device->permission = permission;
|
||||||
|
SDL_PendingCameraDeviceEvent *p = (SDL_PendingCameraDeviceEvent *) SDL_malloc(sizeof (SDL_PendingCameraDeviceEvent));
|
||||||
|
if (p) { // if this failed, no event for you, but you have deeper problems anyhow.
|
||||||
|
p->type = approved ? SDL_EVENT_CAMERA_DEVICE_APPROVED : SDL_EVENT_CAMERA_DEVICE_DENIED;
|
||||||
|
p->devid = device->instance_id;
|
||||||
|
p->next = NULL;
|
||||||
|
pending_tail->next = p;
|
||||||
|
pending_tail = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReleaseCameraDevice(device);
|
||||||
|
|
||||||
|
SDL_LockRWLockForWriting(camera_driver.device_hash_lock);
|
||||||
|
SDL_assert(camera_driver.pending_events_tail != NULL);
|
||||||
|
SDL_assert(camera_driver.pending_events_tail->next == NULL);
|
||||||
|
camera_driver.pending_events_tail->next = pending.next;
|
||||||
|
camera_driver.pending_events_tail = pending_tail;
|
||||||
|
SDL_UnlockRWLock(camera_driver.device_hash_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
SDL_CameraDevice *SDL_FindPhysicalCameraDeviceByCallback(SDL_bool (*callback)(SDL_CameraDevice *device, void *userdata), void *userdata)
|
SDL_CameraDevice *SDL_FindPhysicalCameraDeviceByCallback(SDL_bool (*callback)(SDL_CameraDevice *device, void *userdata), void *userdata)
|
||||||
{
|
{
|
||||||
if (!SDL_GetCurrentCameraDriver()) {
|
if (!SDL_GetCurrentCameraDriver()) {
|
||||||
|
@ -439,7 +480,14 @@ int SDL_GetCameraFormat(SDL_Camera *camera, SDL_CameraSpec *spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_CameraDevice *device = (SDL_CameraDevice *) camera; // currently there's no separation between physical and logical device.
|
SDL_CameraDevice *device = (SDL_CameraDevice *) camera; // currently there's no separation between physical and logical device.
|
||||||
SDL_copyp(spec, &device->spec);
|
ObtainPhysicalCameraDeviceObj(device);
|
||||||
|
const int retval = (device->permission > 0) ? 0 : SDL_SetError("Camera permission has not been granted");
|
||||||
|
if (retval == 0) {
|
||||||
|
SDL_copyp(spec, &device->spec);
|
||||||
|
} else {
|
||||||
|
SDL_zerop(spec);
|
||||||
|
}
|
||||||
|
ReleaseCameraDevice(device);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -545,7 +593,11 @@ SDL_bool SDL_CameraThreadIterate(SDL_CameraDevice *device)
|
||||||
return SDL_FALSE; // we're done, shut it down.
|
return SDL_FALSE; // we're done, shut it down.
|
||||||
}
|
}
|
||||||
|
|
||||||
// !!! FIXME: this should block elsewhere without holding the lock until a frame is available, like the audio subsystem does.
|
const int permission = device->permission;
|
||||||
|
if (permission <= 0) {
|
||||||
|
SDL_UnlockMutex(device->lock);
|
||||||
|
return (permission < 0) ? SDL_FALSE : SDL_TRUE; // if permission was denied, shut it down. if undecided, we're done for now.
|
||||||
|
}
|
||||||
|
|
||||||
SDL_bool failed = SDL_FALSE; // set to true if disaster worthy of treating the device as lost has happened.
|
SDL_bool failed = SDL_FALSE; // set to true if disaster worthy of treating the device as lost has happened.
|
||||||
SDL_Surface *acquired = NULL;
|
SDL_Surface *acquired = NULL;
|
||||||
|
@ -661,7 +713,7 @@ static int SDLCALL CameraThread(void *devicep)
|
||||||
SDL_CameraDevice *device = (SDL_CameraDevice *) devicep;
|
SDL_CameraDevice *device = (SDL_CameraDevice *) devicep;
|
||||||
|
|
||||||
#if DEBUG_CAMERA
|
#if DEBUG_CAMERA
|
||||||
SDL_Log("CAMERA: Start thread 'SDL_CameraThread'");
|
SDL_Log("CAMERA: dev[%p] Start thread 'CameraThread'", devicep);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SDL_assert(device != NULL);
|
SDL_assert(device != NULL);
|
||||||
|
@ -676,7 +728,7 @@ static int SDLCALL CameraThread(void *devicep)
|
||||||
SDL_CameraThreadShutdown(device);
|
SDL_CameraThreadShutdown(device);
|
||||||
|
|
||||||
#if DEBUG_CAMERA
|
#if DEBUG_CAMERA
|
||||||
SDL_Log("CAMERA: dev[%p] End thread 'SDL_CameraThread'", (void *)device);
|
SDL_Log("CAMERA: dev[%p] End thread 'CameraThread'", devicep);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -697,9 +749,13 @@ static void ChooseBestCameraSpec(SDL_CameraDevice *device, const SDL_CameraSpec
|
||||||
SDL_zerop(closest);
|
SDL_zerop(closest);
|
||||||
SDL_assert(((Uint32) SDL_PIXELFORMAT_UNKNOWN) == 0); // since we SDL_zerop'd to this value.
|
SDL_assert(((Uint32) SDL_PIXELFORMAT_UNKNOWN) == 0); // since we SDL_zerop'd to this value.
|
||||||
|
|
||||||
if (!spec) { // nothing specifically requested, get the best format we can...
|
if (device->num_specs == 0) { // device listed no specs! You get whatever you want!
|
||||||
|
if (spec) {
|
||||||
|
SDL_copyp(closest, spec);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (!spec) { // nothing specifically requested, get the best format we can...
|
||||||
// we sorted this into the "best" format order when adding the camera.
|
// we sorted this into the "best" format order when adding the camera.
|
||||||
SDL_assert(device->num_specs > 0);
|
|
||||||
SDL_copyp(closest, &device->all_specs[0]);
|
SDL_copyp(closest, &device->all_specs[0]);
|
||||||
} else { // specific thing requested, try to get as close to that as possible...
|
} else { // specific thing requested, try to get as close to that as possible...
|
||||||
const int num_specs = device->num_specs;
|
const int num_specs = device->num_specs;
|
||||||
|
@ -924,6 +980,12 @@ SDL_Surface *SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS)
|
||||||
|
|
||||||
ObtainPhysicalCameraDeviceObj(device);
|
ObtainPhysicalCameraDeviceObj(device);
|
||||||
|
|
||||||
|
if (device->permission <= 0) {
|
||||||
|
ReleaseCameraDevice(device);
|
||||||
|
SDL_SetError("Camera permission has not been granted");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
SDL_Surface *retval = NULL;
|
SDL_Surface *retval = NULL;
|
||||||
|
|
||||||
// frames are in this list from newest to oldest, so find the end of the list...
|
// frames are in this list from newest to oldest, so find the end of the list...
|
||||||
|
@ -996,8 +1058,6 @@ int SDL_ReleaseCameraFrame(SDL_Camera *camera, SDL_Surface *frame)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// !!! FIXME: add a way to "pause" camera output.
|
|
||||||
|
|
||||||
SDL_CameraDeviceID SDL_GetCameraInstanceID(SDL_Camera *camera)
|
SDL_CameraDeviceID SDL_GetCameraInstanceID(SDL_Camera *camera)
|
||||||
{
|
{
|
||||||
SDL_CameraDeviceID retval = 0;
|
SDL_CameraDeviceID retval = 0;
|
||||||
|
@ -1031,6 +1091,22 @@ SDL_PropertiesID SDL_GetCameraProperties(SDL_Camera *camera)
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int SDL_GetCameraPermissionState(SDL_Camera *camera)
|
||||||
|
{
|
||||||
|
int retval;
|
||||||
|
if (!camera) {
|
||||||
|
retval = SDL_InvalidParamError("camera");
|
||||||
|
} else {
|
||||||
|
SDL_CameraDevice *device = (SDL_CameraDevice *) camera; // currently there's no separation between physical and logical device.
|
||||||
|
ObtainPhysicalCameraDeviceObj(device);
|
||||||
|
retval = device->permission;
|
||||||
|
ReleaseCameraDevice(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void CompleteCameraEntryPoints(void)
|
static void CompleteCameraEntryPoints(void)
|
||||||
{
|
{
|
||||||
// this doesn't currently fill in stub implementations, it just asserts the backend filled them all in.
|
// this doesn't currently fill in stub implementations, it just asserts the backend filled them all in.
|
||||||
|
|
|
@ -50,6 +50,9 @@ extern void SDL_CameraDeviceDisconnected(SDL_CameraDevice *device);
|
||||||
// Find an SDL_CameraDevice, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE.
|
// Find an SDL_CameraDevice, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE.
|
||||||
extern SDL_CameraDevice *SDL_FindPhysicalCameraDeviceByCallback(SDL_bool (*callback)(SDL_CameraDevice *device, void *userdata), void *userdata);
|
extern SDL_CameraDevice *SDL_FindPhysicalCameraDeviceByCallback(SDL_bool (*callback)(SDL_CameraDevice *device, void *userdata), void *userdata);
|
||||||
|
|
||||||
|
// Backends should call this when the user has approved/denied access to a camera.
|
||||||
|
extern void SDL_CameraDevicePermissionOutcome(SDL_CameraDevice *device, SDL_bool approved);
|
||||||
|
|
||||||
// These functions are the heart of the camera threads. Backends can call them directly if they aren't using the SDL-provided thread.
|
// These functions are the heart of the camera threads. Backends can call them directly if they aren't using the SDL-provided thread.
|
||||||
extern void SDL_CameraThreadSetup(SDL_CameraDevice *device);
|
extern void SDL_CameraThreadSetup(SDL_CameraDevice *device);
|
||||||
extern SDL_bool SDL_CameraThreadIterate(SDL_CameraDevice *device);
|
extern SDL_bool SDL_CameraThreadIterate(SDL_CameraDevice *device);
|
||||||
|
@ -129,6 +132,9 @@ struct SDL_CameraDevice
|
||||||
// Optional properties.
|
// Optional properties.
|
||||||
SDL_PropertiesID props;
|
SDL_PropertiesID props;
|
||||||
|
|
||||||
|
// -1: user denied permission, 0: waiting for user response, 1: user approved permission.
|
||||||
|
int permission;
|
||||||
|
|
||||||
// Data private to this driver, used when device is opened and running.
|
// Data private to this driver, used when device is opened and running.
|
||||||
struct SDL_PrivateCameraData *hidden;
|
struct SDL_PrivateCameraData *hidden;
|
||||||
};
|
};
|
||||||
|
@ -182,5 +188,6 @@ extern CameraBootStrap DUMMYCAMERA_bootstrap;
|
||||||
extern CameraBootStrap V4L2_bootstrap;
|
extern CameraBootStrap V4L2_bootstrap;
|
||||||
extern CameraBootStrap COREMEDIA_bootstrap;
|
extern CameraBootStrap COREMEDIA_bootstrap;
|
||||||
extern CameraBootStrap ANDROIDCAMERA_bootstrap;
|
extern CameraBootStrap ANDROIDCAMERA_bootstrap;
|
||||||
|
extern CameraBootStrap EMSCRIPTENCAMERA_bootstrap;
|
||||||
|
|
||||||
#endif // SDL_syscamera_h_
|
#endif // SDL_syscamera_h_
|
||||||
|
|
|
@ -0,0 +1,269 @@
|
||||||
|
/*
|
||||||
|
Simple DirectMedia Layer
|
||||||
|
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
*/
|
||||||
|
#include "SDL_internal.h"
|
||||||
|
|
||||||
|
#ifdef SDL_CAMERA_DRIVER_EMSCRIPTEN
|
||||||
|
|
||||||
|
#include "../SDL_syscamera.h"
|
||||||
|
#include "../SDL_camera_c.h"
|
||||||
|
#include "../../video/SDL_pixels_c.h"
|
||||||
|
|
||||||
|
#include <emscripten/emscripten.h>
|
||||||
|
|
||||||
|
// just turn off clang-format for this whole file, this INDENT_OFF stuff on
|
||||||
|
// each EM_ASM section is ugly.
|
||||||
|
/* *INDENT-OFF* */ /* clang-format off */
|
||||||
|
|
||||||
|
EM_JS_DEPS(sdlcamera, "$dynCall");
|
||||||
|
|
||||||
|
static int EMSCRIPTENCAMERA_WaitDevice(SDL_CameraDevice *device)
|
||||||
|
{
|
||||||
|
SDL_assert(!"This shouldn't be called"); // we aren't using SDL's internal thread.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int EMSCRIPTENCAMERA_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
|
||||||
|
{
|
||||||
|
void *rgba = SDL_malloc(device->actual_spec.width * device->actual_spec.height * 4);
|
||||||
|
if (!rgba) {
|
||||||
|
return SDL_OutOfMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
*timestampNS = SDL_GetTicksNS(); // best we can do here.
|
||||||
|
|
||||||
|
const int rc = MAIN_THREAD_EM_ASM_INT({
|
||||||
|
const w = $0;
|
||||||
|
const h = $1;
|
||||||
|
const rgba = $2;
|
||||||
|
const SDL3 = Module['SDL3'];
|
||||||
|
if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.ctx2d) === 'undefined')) {
|
||||||
|
return 0; // don't have something we need, oh well.
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL3.camera.ctx2d.drawImage(SDL3.camera.video, 0, 0, w, h);
|
||||||
|
const imgrgba = SDL3.camera.ctx2d.getImageData(0, 0, w, h).data;
|
||||||
|
Module.HEAPU8.set(imgrgba, rgba);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}, device->actual_spec.width, device->actual_spec.height, rgba);
|
||||||
|
|
||||||
|
if (!rc) {
|
||||||
|
SDL_free(rgba);
|
||||||
|
return 0; // something went wrong, maybe shutting down; just don't return a frame.
|
||||||
|
}
|
||||||
|
|
||||||
|
frame->pixels = rgba;
|
||||||
|
frame->pitch = device->actual_spec.width * 4;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EMSCRIPTENCAMERA_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
|
||||||
|
{
|
||||||
|
SDL_free(frame->pixels);
|
||||||
|
frame->pixels = NULL;
|
||||||
|
frame->pitch = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EMSCRIPTENCAMERA_CloseDevice(SDL_CameraDevice *device)
|
||||||
|
{
|
||||||
|
if (device) {
|
||||||
|
MAIN_THREAD_EM_ASM({
|
||||||
|
const SDL3 = Module['SDL3'];
|
||||||
|
if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.stream) === 'undefined')) {
|
||||||
|
return; // camera was closed and/or subsystem was shut down, we're already done.
|
||||||
|
}
|
||||||
|
SDL3.camera.stream.getTracks().forEach(track => track.stop()); // stop all recording.
|
||||||
|
_SDL_free(SDL3.camera.rgba);
|
||||||
|
SDL3.camera = {}; // dump our references to everything.
|
||||||
|
});
|
||||||
|
SDL_free(device->hidden);
|
||||||
|
device->hidden = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDLEmscriptenCameraDevicePermissionOutcome(SDL_CameraDevice *device, int approved, int w, int h, int fps)
|
||||||
|
{
|
||||||
|
device->spec.width = device->actual_spec.width = w;
|
||||||
|
device->spec.height = device->actual_spec.height = h;
|
||||||
|
device->spec.interval_numerator = device->actual_spec.interval_numerator = 1;
|
||||||
|
device->spec.interval_denominator = device->actual_spec.interval_denominator = fps;
|
||||||
|
SDL_CameraDevicePermissionOutcome(device, approved ? SDL_TRUE : SDL_FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int EMSCRIPTENCAMERA_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec)
|
||||||
|
{
|
||||||
|
MAIN_THREAD_EM_ASM({
|
||||||
|
// Since we can't get actual specs until we make a move that prompts the user for
|
||||||
|
// permission, we don't list any specs for the device and wrangle it during device open.
|
||||||
|
const device = $0;
|
||||||
|
const w = $1;
|
||||||
|
const h = $2;
|
||||||
|
const interval_numerator = $3;
|
||||||
|
const interval_denominator = $4;
|
||||||
|
const outcome = $5;
|
||||||
|
const iterate = $6;
|
||||||
|
|
||||||
|
const constraints = {};
|
||||||
|
if ((w <= 0) || (h <= 0)) {
|
||||||
|
constraints.video = true; // didn't ask for anything, let the system choose.
|
||||||
|
} else {
|
||||||
|
constraints.video = {}; // asked for a specific thing: request it as "ideal" but take closest hardware will offer.
|
||||||
|
constraints.video.width = w;
|
||||||
|
constraints.video.height = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((interval_numerator > 0) && (interval_denominator > 0)) {
|
||||||
|
var fps = interval_denominator / interval_numerator;
|
||||||
|
constraints.video.frameRate = { ideal: fps };
|
||||||
|
}
|
||||||
|
|
||||||
|
function grabNextCameraFrame() { // !!! FIXME: this (currently) runs as a requestAnimationFrame callback, for lack of a better option.
|
||||||
|
const SDL3 = Module['SDL3'];
|
||||||
|
if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.stream) === 'undefined')) {
|
||||||
|
return; // camera was closed and/or subsystem was shut down, stop iterating here.
|
||||||
|
}
|
||||||
|
|
||||||
|
// time for a new frame from the camera?
|
||||||
|
const nextframems = SDL3.camera.next_frame_time;
|
||||||
|
const now = performance.now();
|
||||||
|
if (now >= nextframems) {
|
||||||
|
dynCall('vi', iterate, [device]); // calls SDL_CameraThreadIterate, which will call our AcquireFrame implementation.
|
||||||
|
|
||||||
|
// bump ahead but try to stay consistent on timing, in case we dropped frames.
|
||||||
|
while (SDL3.camera.next_frame_time < now) {
|
||||||
|
SDL3.camera.next_frame_time += SDL3.camera.fpsincrms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(grabNextCameraFrame); // run this function again at the display framerate. (!!! FIXME: would this be better as requestIdleCallback?)
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.mediaDevices.getUserMedia(constraints)
|
||||||
|
.then((stream) => {
|
||||||
|
const settings = stream.getVideoTracks()[0].getSettings();
|
||||||
|
const actualw = settings.width;
|
||||||
|
const actualh = settings.height;
|
||||||
|
const actualfps = settings.frameRate;
|
||||||
|
console.log("Camera is opened! Actual spec: (" + actualw + "x" + actualh + "), fps=" + actualfps);
|
||||||
|
|
||||||
|
dynCall('viiiii', outcome, [device, 1, actualw, actualh, actualfps]);
|
||||||
|
|
||||||
|
const video = document.createElement("video");
|
||||||
|
video.width = actualw;
|
||||||
|
video.height = actualh;
|
||||||
|
video.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels.
|
||||||
|
video.srcObject = stream;
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = actualw;
|
||||||
|
canvas.height = actualh;
|
||||||
|
canvas.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels.
|
||||||
|
|
||||||
|
const ctx2d = canvas.getContext('2d');
|
||||||
|
|
||||||
|
const SDL3 = Module['SDL3'];
|
||||||
|
SDL3.camera.width = actualw;
|
||||||
|
SDL3.camera.height = actualh;
|
||||||
|
SDL3.camera.fps = actualfps;
|
||||||
|
SDL3.camera.fpsincrms = 1000.0 / actualfps;
|
||||||
|
SDL3.camera.stream = stream;
|
||||||
|
SDL3.camera.video = video;
|
||||||
|
SDL3.camera.canvas = canvas;
|
||||||
|
SDL3.camera.ctx2d = ctx2d;
|
||||||
|
SDL3.camera.rgba = 0;
|
||||||
|
SDL3.camera.next_frame_time = performance.now();
|
||||||
|
|
||||||
|
video.play();
|
||||||
|
video.addEventListener('loadedmetadata', () => {
|
||||||
|
grabNextCameraFrame(); // start this loop going.
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Tried to open camera but it threw an error! " + err.name + ": " + err.message);
|
||||||
|
dynCall('viiiii', outcome, [device, 0, 0, 0, 0]); // we call this a permission error, because it probably is.
|
||||||
|
});
|
||||||
|
}, device, spec->width, spec->height, spec->interval_numerator, spec->interval_denominator, SDLEmscriptenCameraDevicePermissionOutcome, SDL_CameraThreadIterate);
|
||||||
|
|
||||||
|
return 0; // the real work waits until the user approves a camera.
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EMSCRIPTENCAMERA_FreeDeviceHandle(SDL_CameraDevice *device)
|
||||||
|
{
|
||||||
|
// no-op.
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EMSCRIPTENCAMERA_Deinitialize(void)
|
||||||
|
{
|
||||||
|
MAIN_THREAD_EM_ASM({
|
||||||
|
if (typeof(Module['SDL3']) !== 'undefined') {
|
||||||
|
Module['SDL3'].camera = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EMSCRIPTENCAMERA_DetectDevices(void)
|
||||||
|
{
|
||||||
|
// `navigator.mediaDevices` is not defined if unsupported or not in a secure context!
|
||||||
|
const int supported = MAIN_THREAD_EM_ASM_INT({ return (navigator.mediaDevices === undefined) ? 0 : 1; });
|
||||||
|
|
||||||
|
// if we have support at all, report a single generic camera with no specs.
|
||||||
|
// We'll find out if there really _is_ a camera when we try to open it, but querying it for real here
|
||||||
|
// will pop up a user permission dialog warning them we're trying to access the camera, and we generally
|
||||||
|
// don't want that during SDL_Init().
|
||||||
|
if (supported) {
|
||||||
|
SDL_AddCameraDevice("Web browser's camera", 0, NULL, (void *) (size_t) 0x1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDL_bool EMSCRIPTENCAMERA_Init(SDL_CameraDriverImpl *impl)
|
||||||
|
{
|
||||||
|
SDL_Log("EMSCRIPTENCAMERA_Init, %s:%d", __FILE__, __LINE__);
|
||||||
|
MAIN_THREAD_EM_ASM({
|
||||||
|
if (typeof(Module['SDL3']) === 'undefined') {
|
||||||
|
Module['SDL3'] = {};
|
||||||
|
}
|
||||||
|
Module['SDL3'].camera = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
SDL_Log("EMSCRIPTENCAMERA_Init, %s:%d", __FILE__, __LINE__);
|
||||||
|
impl->DetectDevices = EMSCRIPTENCAMERA_DetectDevices;
|
||||||
|
impl->OpenDevice = EMSCRIPTENCAMERA_OpenDevice;
|
||||||
|
impl->CloseDevice = EMSCRIPTENCAMERA_CloseDevice;
|
||||||
|
impl->WaitDevice = EMSCRIPTENCAMERA_WaitDevice;
|
||||||
|
impl->AcquireFrame = EMSCRIPTENCAMERA_AcquireFrame;
|
||||||
|
impl->ReleaseFrame = EMSCRIPTENCAMERA_ReleaseFrame;
|
||||||
|
impl->FreeDeviceHandle = EMSCRIPTENCAMERA_FreeDeviceHandle;
|
||||||
|
impl->Deinitialize = EMSCRIPTENCAMERA_Deinitialize;
|
||||||
|
|
||||||
|
impl->ProvidesOwnCallbackThread = SDL_TRUE;
|
||||||
|
|
||||||
|
return SDL_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraBootStrap EMSCRIPTENCAMERA_bootstrap = {
|
||||||
|
"emscripten", "SDL Emscripten MediaStream camera driver", EMSCRIPTENCAMERA_Init, SDL_FALSE
|
||||||
|
};
|
||||||
|
|
||||||
|
/* *INDENT-ON* */ /* clang-format on */
|
||||||
|
|
||||||
|
#endif // SDL_CAMERA_DRIVER_EMSCRIPTEN
|
||||||
|
|
|
@ -619,6 +619,9 @@ static int V4L2_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Currently there is no user permission prompt for camera access, but maybe there will be a D-Bus portal interface at some point.
|
||||||
|
SDL_CameraDevicePermissionOutcome(device, SDL_TRUE);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -970,6 +970,7 @@ SDL3_0.0.0 {
|
||||||
SDL_AcquireCameraFrame;
|
SDL_AcquireCameraFrame;
|
||||||
SDL_ReleaseCameraFrame;
|
SDL_ReleaseCameraFrame;
|
||||||
SDL_CloseCamera;
|
SDL_CloseCamera;
|
||||||
|
SDL_GetCameraPermissionState;
|
||||||
# extra symbols go here (don't modify this line)
|
# extra symbols go here (don't modify this line)
|
||||||
local: *;
|
local: *;
|
||||||
};
|
};
|
||||||
|
|
|
@ -995,3 +995,4 @@
|
||||||
#define SDL_AcquireCameraFrame SDL_AcquireCameraFrame_REAL
|
#define SDL_AcquireCameraFrame SDL_AcquireCameraFrame_REAL
|
||||||
#define SDL_ReleaseCameraFrame SDL_ReleaseCameraFrame_REAL
|
#define SDL_ReleaseCameraFrame SDL_ReleaseCameraFrame_REAL
|
||||||
#define SDL_CloseCamera SDL_CloseCamera_REAL
|
#define SDL_CloseCamera SDL_CloseCamera_REAL
|
||||||
|
#define SDL_GetCameraPermissionState SDL_GetCameraPermissionState_REAL
|
||||||
|
|
|
@ -1020,3 +1020,4 @@ SDL_DYNAPI_PROC(int,SDL_GetCameraFormat,(SDL_Camera *a, SDL_CameraSpec *b),(a,b)
|
||||||
SDL_DYNAPI_PROC(SDL_Surface*,SDL_AcquireCameraFrame,(SDL_Camera *a, Uint64 *b),(a,b),return)
|
SDL_DYNAPI_PROC(SDL_Surface*,SDL_AcquireCameraFrame,(SDL_Camera *a, Uint64 *b),(a,b),return)
|
||||||
SDL_DYNAPI_PROC(int,SDL_ReleaseCameraFrame,(SDL_Camera *a, SDL_Surface *b),(a,b),return)
|
SDL_DYNAPI_PROC(int,SDL_ReleaseCameraFrame,(SDL_Camera *a, SDL_Surface *b),(a,b),return)
|
||||||
SDL_DYNAPI_PROC(void,SDL_CloseCamera,(SDL_Camera *a),(a),)
|
SDL_DYNAPI_PROC(void,SDL_CloseCamera,(SDL_Camera *a),(a),)
|
||||||
|
SDL_DYNAPI_PROC(int,SDL_GetCameraPermissionState,(SDL_Camera *a),(a),return)
|
||||||
|
|
|
@ -562,6 +562,12 @@ static void SDL_LogEvent(const SDL_Event *event)
|
||||||
SDL_EVENT_CASE(SDL_EVENT_CAMERA_DEVICE_REMOVED)
|
SDL_EVENT_CASE(SDL_EVENT_CAMERA_DEVICE_REMOVED)
|
||||||
PRINT_CAMERADEV_EVENT(event);
|
PRINT_CAMERADEV_EVENT(event);
|
||||||
break;
|
break;
|
||||||
|
SDL_EVENT_CASE(SDL_EVENT_CAMERA_DEVICE_APPROVED)
|
||||||
|
PRINT_CAMERADEV_EVENT(event);
|
||||||
|
break;
|
||||||
|
SDL_EVENT_CASE(SDL_EVENT_CAMERA_DEVICE_DENIED)
|
||||||
|
PRINT_CAMERADEV_EVENT(event);
|
||||||
|
break;
|
||||||
#undef PRINT_CAMERADEV_EVENT
|
#undef PRINT_CAMERADEV_EVENT
|
||||||
|
|
||||||
SDL_EVENT_CASE(SDL_EVENT_SENSOR_UPDATE)
|
SDL_EVENT_CASE(SDL_EVENT_SENSOR_UPDATE)
|
||||||
|
|
|
@ -9,38 +9,27 @@
|
||||||
including commercial applications, and to alter it and redistribute it
|
including commercial applications, and to alter it and redistribute it
|
||||||
freely.
|
freely.
|
||||||
*/
|
*/
|
||||||
#include "SDL3/SDL_main.h"
|
|
||||||
#include "SDL3/SDL.h"
|
|
||||||
#include "SDL3/SDL_test.h"
|
|
||||||
#include "SDL3/SDL_camera.h"
|
|
||||||
|
|
||||||
#ifdef SDL_PLATFORM_EMSCRIPTEN
|
#define SDL_MAIN_USE_CALLBACKS 1
|
||||||
#include <emscripten/emscripten.h>
|
#include <SDL3/SDL_test.h>
|
||||||
#endif
|
#include <SDL3/SDL_test_common.h>
|
||||||
|
#include <SDL3/SDL_main.h>
|
||||||
|
|
||||||
#include <stdio.h>
|
static SDL_Window *window = NULL;
|
||||||
|
static SDL_Renderer *renderer = NULL;
|
||||||
|
static SDLTest_CommonState *state = NULL;
|
||||||
|
static SDL_Camera *camera = NULL;
|
||||||
|
static SDL_CameraSpec spec;
|
||||||
|
static SDL_Texture *texture = NULL;
|
||||||
|
static SDL_bool texture_updated = SDL_FALSE;
|
||||||
|
static SDL_Surface *frame_current = NULL;
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int SDL_AppInit(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
SDL_Window *window = NULL;
|
|
||||||
SDL_Renderer *renderer = NULL;
|
|
||||||
SDL_Event evt;
|
|
||||||
int quit = 0;
|
|
||||||
SDLTest_CommonState *state = NULL;
|
|
||||||
|
|
||||||
SDL_Camera *camera = NULL;
|
|
||||||
SDL_CameraSpec spec;
|
|
||||||
SDL_Texture *texture = NULL;
|
|
||||||
int texture_updated = 0;
|
|
||||||
SDL_Surface *frame_current = NULL;
|
|
||||||
|
|
||||||
SDL_zero(evt);
|
|
||||||
SDL_zero(spec);
|
|
||||||
|
|
||||||
/* Initialize test framework */
|
/* Initialize test framework */
|
||||||
state = SDLTest_CommonCreateState(argv, 0);
|
state = SDLTest_CommonCreateState(argv, 0);
|
||||||
if (state == NULL) {
|
if (state == NULL) {
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Enable standard application logging */
|
/* Enable standard application logging */
|
||||||
|
@ -49,13 +38,13 @@ int main(int argc, char **argv)
|
||||||
/* Load the SDL library */
|
/* Load the SDL library */
|
||||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_CAMERA) < 0) {
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_CAMERA) < 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError());
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError());
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
window = SDL_CreateWindow("Local Video", 1000, 800, 0);
|
window = SDL_CreateWindow("Local Video", 1000, 800, 0);
|
||||||
if (window == NULL) {
|
if (window == NULL) {
|
||||||
SDL_Log("Couldn't create window: %s", SDL_GetError());
|
SDL_Log("Couldn't create window: %s", SDL_GetError());
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
|
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
|
||||||
|
@ -63,13 +52,13 @@ int main(int argc, char **argv)
|
||||||
renderer = SDL_CreateRenderer(window, NULL, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
renderer = SDL_CreateRenderer(window, NULL, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||||
if (renderer == NULL) {
|
if (renderer == NULL) {
|
||||||
/* SDL_Log("Couldn't create renderer: %s", SDL_GetError()); */
|
/* SDL_Log("Couldn't create renderer: %s", SDL_GetError()); */
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_CameraDeviceID *devices = SDL_GetCameraDevices(NULL);
|
SDL_CameraDeviceID *devices = SDL_GetCameraDevices(NULL);
|
||||||
if (!devices) {
|
if (!devices) {
|
||||||
SDL_Log("SDL_GetCameraDevices failed: %s", SDL_GetError());
|
SDL_Log("SDL_GetCameraDevices failed: %s", SDL_GetError());
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SDL_CameraDeviceID devid = devices[0]; /* just take the first one. */
|
const SDL_CameraDeviceID devid = devices[0]; /* just take the first one. */
|
||||||
|
@ -77,7 +66,7 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
if (!devid) {
|
if (!devid) {
|
||||||
SDL_Log("No cameras available? %s", SDL_GetError());
|
SDL_Log("No cameras available? %s", SDL_GetError());
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_CameraSpec *pspec = NULL;
|
SDL_CameraSpec *pspec = NULL;
|
||||||
|
@ -91,115 +80,108 @@ int main(int argc, char **argv)
|
||||||
camera = SDL_OpenCameraDevice(devid, pspec);
|
camera = SDL_OpenCameraDevice(devid, pspec);
|
||||||
if (!camera) {
|
if (!camera) {
|
||||||
SDL_Log("Failed to open camera device: %s", SDL_GetError());
|
SDL_Log("Failed to open camera device: %s", SDL_GetError());
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SDL_GetCameraFormat(camera, &spec) < 0) {
|
return 0; /* start the main app loop. */
|
||||||
SDL_Log("Couldn't get camera spec: %s", SDL_GetError());
|
}
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create texture with appropriate format */
|
|
||||||
if (texture == NULL) {
|
|
||||||
texture = SDL_CreateTexture(renderer, spec.format, SDL_TEXTUREACCESS_STATIC, spec.width, spec.height);
|
|
||||||
if (texture == NULL) {
|
|
||||||
SDL_Log("Couldn't create texture: %s", SDL_GetError());
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!quit) {
|
|
||||||
while (SDL_PollEvent(&evt)) {
|
|
||||||
int sym = 0;
|
|
||||||
switch (evt.type)
|
|
||||||
{
|
|
||||||
case SDL_EVENT_KEY_DOWN:
|
|
||||||
{
|
|
||||||
sym = evt.key.keysym.sym;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SDL_EVENT_QUIT:
|
|
||||||
{
|
|
||||||
quit = 1;
|
|
||||||
SDL_Log("Ctlr+C : Quit!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
int SDL_AppEvent(const SDL_Event *event)
|
||||||
|
{
|
||||||
|
switch (event->type) {
|
||||||
|
case SDL_EVENT_KEY_DOWN: {
|
||||||
|
const SDL_Keycode sym = event->key.keysym.sym;
|
||||||
if (sym == SDLK_ESCAPE || sym == SDLK_AC_BACK) {
|
if (sym == SDLK_ESCAPE || sym == SDLK_AC_BACK) {
|
||||||
quit = 1;
|
|
||||||
SDL_Log("Key : Escape!");
|
SDL_Log("Key : Escape!");
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
case SDL_EVENT_QUIT:
|
||||||
Uint64 timestampNS = 0;
|
SDL_Log("Ctlr+C : Quit!");
|
||||||
SDL_Surface *frame_next = SDL_AcquireCameraFrame(camera, ×tampNS);
|
return 1;
|
||||||
|
|
||||||
#if 0
|
case SDL_EVENT_CAMERA_DEVICE_APPROVED:
|
||||||
if (frame_next) {
|
if (SDL_GetCameraFormat(camera, &spec) < 0) {
|
||||||
SDL_Log("frame: %p at %" SDL_PRIu64, (void*)frame_next->pixels, timestampNS);
|
SDL_Log("Couldn't get camera spec: %s", SDL_GetError());
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
if (frame_next) {
|
/* Create texture with appropriate format */
|
||||||
if (frame_current) {
|
texture = SDL_CreateTexture(renderer, spec.format, SDL_TEXTUREACCESS_STATIC, spec.width, spec.height);
|
||||||
if (SDL_ReleaseCameraFrame(camera, frame_current) < 0) {
|
if (texture == NULL) {
|
||||||
SDL_Log("err SDL_ReleaseCameraFrame: %s", SDL_GetError());
|
SDL_Log("Couldn't create texture: %s", SDL_GetError());
|
||||||
}
|
return -1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_EVENT_CAMERA_DEVICE_DENIED:
|
||||||
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Camera permission denied!", "User denied access to the camera!", window);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SDLTest_CommonEventMainCallbacks(state, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
int SDL_AppIterate(void)
|
||||||
|
{
|
||||||
|
SDL_SetRenderDrawColor(renderer, 0x99, 0x99, 0x99, 255);
|
||||||
|
SDL_RenderClear(renderer);
|
||||||
|
|
||||||
|
if (texture != NULL) { /* if not NULL, camera is ready to go. */
|
||||||
|
int win_w, win_h, tw, th;
|
||||||
|
SDL_FRect d;
|
||||||
|
Uint64 timestampNS = 0;
|
||||||
|
SDL_Surface *frame_next = SDL_AcquireCameraFrame(camera, ×tampNS);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
if (frame_next) {
|
||||||
|
SDL_Log("frame: %p at %" SDL_PRIu64, (void*)frame_next->pixels, timestampNS);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (frame_next) {
|
||||||
|
if (frame_current) {
|
||||||
|
if (SDL_ReleaseCameraFrame(camera, frame_current) < 0) {
|
||||||
|
SDL_Log("err SDL_ReleaseCameraFrame: %s", SDL_GetError());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* It's not needed to keep the frame once updated the texture is updated.
|
|
||||||
* But in case of 0-copy, it's needed to have the frame while using the texture.
|
|
||||||
*/
|
|
||||||
frame_current = frame_next;
|
|
||||||
texture_updated = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* It's not needed to keep the frame once updated the texture is updated.
|
||||||
|
* But in case of 0-copy, it's needed to have the frame while using the texture.
|
||||||
|
*/
|
||||||
|
frame_current = frame_next;
|
||||||
|
texture_updated = SDL_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update SDL_Texture with last video frame (only once per new frame) */
|
/* Update SDL_Texture with last video frame (only once per new frame) */
|
||||||
if (frame_current && texture_updated == 0) {
|
if (frame_current && !texture_updated) {
|
||||||
SDL_UpdateTexture(texture, NULL, frame_current->pixels, frame_current->pitch);
|
SDL_UpdateTexture(texture, NULL, frame_current->pixels, frame_current->pitch);
|
||||||
texture_updated = 1;
|
texture_updated = SDL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(renderer, 0x99, 0x99, 0x99, 255);
|
SDL_QueryTexture(texture, NULL, NULL, &tw, &th);
|
||||||
SDL_RenderClear(renderer);
|
SDL_GetRenderOutputSize(renderer, &win_w, &win_h);
|
||||||
{
|
d.x = (float) ((win_w - tw) / 2);
|
||||||
int win_w, win_h, tw, th, w;
|
d.y = (float) ((win_h - th) / 2);
|
||||||
SDL_FRect d;
|
d.w = (float) tw;
|
||||||
SDL_QueryTexture(texture, NULL, NULL, &tw, &th);
|
d.h = (float) th;
|
||||||
SDL_GetRenderOutputSize(renderer, &win_w, &win_h);
|
SDL_RenderTexture(renderer, texture, NULL, &d);
|
||||||
w = win_w;
|
|
||||||
if (tw > w - 20) {
|
|
||||||
float scale = (float) (w - 20) / (float) tw;
|
|
||||||
tw = w - 20;
|
|
||||||
th = (int)((float) th * scale);
|
|
||||||
}
|
|
||||||
d.x = (float)(10 );
|
|
||||||
d.y = ((float)(win_h - th)) / 2.0f;
|
|
||||||
d.w = (float)tw;
|
|
||||||
d.h = (float)(th - 10);
|
|
||||||
SDL_RenderTexture(renderer, texture, NULL, &d);
|
|
||||||
}
|
|
||||||
SDL_Delay(10);
|
|
||||||
SDL_RenderPresent(renderer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frame_current) {
|
SDL_RenderPresent(renderer);
|
||||||
SDL_ReleaseCameraFrame(camera, frame_current);
|
|
||||||
}
|
|
||||||
SDL_CloseCamera(camera);
|
|
||||||
|
|
||||||
if (texture) {
|
return 0; /* keep iterating. */
|
||||||
SDL_DestroyTexture(texture);
|
}
|
||||||
}
|
|
||||||
|
void SDL_AppQuit(void)
|
||||||
SDL_DestroyRenderer(renderer);
|
{
|
||||||
SDL_DestroyWindow(window);
|
SDL_ReleaseCameraFrame(camera, frame_current);
|
||||||
SDL_Quit();
|
SDL_CloseCamera(camera);
|
||||||
SDLTest_CommonDestroyState(state);
|
SDL_DestroyTexture(texture);
|
||||||
|
SDL_DestroyRenderer(renderer);
|
||||||
return 0;
|
SDL_DestroyWindow(window);
|
||||||
|
SDLTest_CommonDestroyState(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue