Atlas - SDL_camera_emscripten.c

Home / ext / SDL / src / camera / emscripten Lines: 1 | Size: 11251 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#ifdef SDL_CAMERA_DRIVER_EMSCRIPTEN 24 25#include "../SDL_syscamera.h" 26#include "../SDL_camera_c.h" 27#include "../../video/SDL_pixels_c.h" 28#include "../../video/SDL_surface_c.h" 29 30#include <emscripten/emscripten.h> 31 32// just turn off clang-format for this whole file, this INDENT_OFF stuff on 33// each EM_ASM section is ugly. 34/* *INDENT-OFF* */ // clang-format off 35 36static bool EMSCRIPTENCAMERA_WaitDevice(SDL_Camera *device) 37{ 38 SDL_assert(!"This shouldn't be called"); // we aren't using SDL's internal thread. 39 return false; 40} 41 42static SDL_CameraFrameResult EMSCRIPTENCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) 43{ 44 void *rgba = SDL_malloc(device->actual_spec.width * device->actual_spec.height * 4); 45 if (!rgba) { 46 return SDL_CAMERA_FRAME_ERROR; 47 } 48 49 *timestampNS = SDL_GetTicksNS(); // best we can do here. 50 51 const int rc = MAIN_THREAD_EM_ASM_INT({ 52 const w = $0; 53 const h = $1; 54 const rgba = $2; 55 const SDL3 = Module['SDL3']; 56 if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.ctx2d) === 'undefined')) { 57 return 0; // don't have something we need, oh well. 58 } 59 60 SDL3.camera.ctx2d.drawImage(SDL3.camera.video, 0, 0, w, h); 61 const imgrgba = SDL3.camera.ctx2d.getImageData(0, 0, w, h).data; 62 HEAPU8.set(imgrgba, rgba); 63 64 return 1; 65 }, device->actual_spec.width, device->actual_spec.height, rgba); 66 67 if (!rc) { 68 SDL_free(rgba); 69 return SDL_CAMERA_FRAME_ERROR; // something went wrong, maybe shutting down; just don't return a frame. 70 } 71 72 frame->pixels = rgba; 73 frame->pitch = device->actual_spec.width * 4; 74 75 return SDL_CAMERA_FRAME_READY; 76} 77 78static void EMSCRIPTENCAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame) 79{ 80 SDL_free(frame->pixels); 81} 82 83static void EMSCRIPTENCAMERA_CloseDevice(SDL_Camera *device) 84{ 85 if (device) { 86 MAIN_THREAD_EM_ASM({ 87 const SDL3 = Module['SDL3']; 88 if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.stream) === 'undefined')) { 89 return; // camera was closed and/or subsystem was shut down, we're already done. 90 } 91 SDL3.camera.stream.getTracks().forEach(track => track.stop()); // stop all recording. 92 SDL3.camera = {}; // dump our references to everything. 93 }); 94 SDL_free(device->hidden); 95 device->hidden = NULL; 96 } 97} 98 99EMSCRIPTEN_KEEPALIVE int SDLEmscriptenCameraPermissionOutcome(SDL_Camera *device, int approved, int w, int h, int fps) 100{ 101 if (approved) { 102 device->actual_spec.format = SDL_PIXELFORMAT_RGBA32; 103 device->actual_spec.width = w; 104 device->actual_spec.height = h; 105 device->actual_spec.framerate_numerator = fps; 106 device->actual_spec.framerate_denominator = 1; 107 108 if (!SDL_PrepareCameraSurfaces(device)) { 109 // uhoh, we're in trouble. Probably ran out of memory. 110 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Camera could not prepare surfaces: %s ... revoking approval!", SDL_GetError()); 111 approved = 0; // disconnecting the SDL camera might not be safe here, just mark it as denied by user. 112 } 113 } 114 115 SDL_CameraPermissionOutcome(device, approved ? true : false); 116 return approved; 117} 118 119EMSCRIPTEN_KEEPALIVE bool SDLEmscriptenThreadIterate(SDL_Camera *device) { 120 return SDL_CameraThreadIterate(device); 121} 122 123static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) 124{ 125 MAIN_THREAD_EM_ASM({ 126 // Since we can't get actual specs until we make a move that prompts the user for 127 // permission, we don't list any specs for the device and wrangle it during device open. 128 const device = $0; 129 const w = $1; 130 const h = $2; 131 const framerate_numerator = $3; 132 const framerate_denominator = $4; 133 const outcome = Module._SDLEmscriptenCameraPermissionOutcome; 134 const iterate = Module._SDLEmscriptenThreadIterate; 135 136 const constraints = {}; 137 if ((w <= 0) || (h <= 0)) { 138 constraints.video = true; // didn't ask for anything, let the system choose. 139 } else { 140 constraints.video = {}; // asked for a specific thing: request it as "ideal" but take closest hardware will offer. 141 constraints.video.width = w; 142 constraints.video.height = h; 143 } 144 145 if ((framerate_numerator > 0) && (framerate_denominator > 0)) { 146 var fps = framerate_numerator / framerate_denominator; 147 constraints.video.frameRate = { ideal: fps }; 148 } 149 150 function grabNextCameraFrame() { // !!! FIXME: this (currently) runs as a requestAnimationFrame callback, for lack of a better option. 151 const SDL3 = Module['SDL3']; 152 if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.stream) === 'undefined')) { 153 return; // camera was closed and/or subsystem was shut down, stop iterating here. 154 } 155 156 // time for a new frame from the camera? 157 const nextframems = SDL3.camera.next_frame_time; 158 const now = performance.now(); 159 if (now >= nextframems) { 160 iterate(device); // calls SDL_CameraThreadIterate, which will call our AcquireFrame implementation. 161 162 // bump ahead but try to stay consistent on timing, in case we dropped frames. 163 while (SDL3.camera.next_frame_time < now) { 164 SDL3.camera.next_frame_time += SDL3.camera.fpsincrms; 165 } 166 } 167 168 requestAnimationFrame(grabNextCameraFrame); // run this function again at the display framerate. (!!! FIXME: would this be better as requestIdleCallback?) 169 } 170 171 navigator.mediaDevices.getUserMedia(constraints) 172 .then((stream) => { 173 const settings = stream.getVideoTracks()[0].getSettings(); 174 const actualw = settings.width; 175 const actualh = settings.height; 176 const actualfps = settings.frameRate; 177 console.log("Camera is opened! Actual spec: (" + actualw + "x" + actualh + "), fps=" + actualfps); 178 179 if (outcome(device, 1, actualw, actualh, actualfps)) { 180 const video = document.createElement("video"); 181 video.width = actualw; 182 video.height = actualh; 183 video.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels. 184 video.srcObject = stream; 185 186 const canvas = document.createElement("canvas"); 187 canvas.width = actualw; 188 canvas.height = actualh; 189 canvas.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels. 190 191 const ctx2d = canvas.getContext('2d'); 192 193 const SDL3 = Module['SDL3']; 194 SDL3.camera.width = actualw; 195 SDL3.camera.height = actualh; 196 SDL3.camera.fps = actualfps; 197 SDL3.camera.fpsincrms = 1000.0 / actualfps; 198 SDL3.camera.stream = stream; 199 SDL3.camera.video = video; 200 SDL3.camera.canvas = canvas; 201 SDL3.camera.ctx2d = ctx2d; 202 SDL3.camera.next_frame_time = performance.now(); 203 204 video.play(); 205 video.addEventListener('loadedmetadata', () => { 206 grabNextCameraFrame(); // start this loop going. 207 }); 208 } 209 }) 210 .catch((err) => { 211 console.error("Tried to open camera but it threw an error! " + err.name + ": " + err.message); 212 outcome(device, 0, 0, 0, 0); // we call this a permission error, because it probably is. 213 }); 214 }, device, spec->width, spec->height, spec->framerate_numerator, spec->framerate_denominator); 215 216 return true; // the real work waits until the user approves a camera. 217} 218 219static void EMSCRIPTENCAMERA_FreeDeviceHandle(SDL_Camera *device) 220{ 221 // no-op. 222} 223 224static void EMSCRIPTENCAMERA_Deinitialize(void) 225{ 226 MAIN_THREAD_EM_ASM({ 227 if (typeof(Module['SDL3']) !== 'undefined') { 228 Module['SDL3'].camera = undefined; 229 } 230 }); 231} 232 233static void EMSCRIPTENCAMERA_DetectDevices(void) 234{ 235 // `navigator.mediaDevices` is not defined if unsupported or not in a secure context! 236 const int supported = MAIN_THREAD_EM_ASM_INT({ return (navigator.mediaDevices === undefined) ? 0 : 1; }); 237 238 // if we have support at all, report a single generic camera with no specs. 239 // We'll find out if there really _is_ a camera when we try to open it, but querying it for real here 240 // will pop up a user permission dialog warning them we're trying to access the camera, and we generally 241 // don't want that during SDL_Init(). 242 if (supported) { 243 SDL_AddCamera("Web browser's camera", SDL_CAMERA_POSITION_UNKNOWN, 0, NULL, (void *) (size_t) 0x1); 244 } 245} 246 247static bool EMSCRIPTENCAMERA_Init(SDL_CameraDriverImpl *impl) 248{ 249 MAIN_THREAD_EM_ASM({ 250 if (typeof(Module['SDL3']) === 'undefined') { 251 Module['SDL3'] = {}; 252 } 253 Module['SDL3'].camera = {}; 254 }); 255 256 impl->DetectDevices = EMSCRIPTENCAMERA_DetectDevices; 257 impl->OpenDevice = EMSCRIPTENCAMERA_OpenDevice; 258 impl->CloseDevice = EMSCRIPTENCAMERA_CloseDevice; 259 impl->WaitDevice = EMSCRIPTENCAMERA_WaitDevice; 260 impl->AcquireFrame = EMSCRIPTENCAMERA_AcquireFrame; 261 impl->ReleaseFrame = EMSCRIPTENCAMERA_ReleaseFrame; 262 impl->FreeDeviceHandle = EMSCRIPTENCAMERA_FreeDeviceHandle; 263 impl->Deinitialize = EMSCRIPTENCAMERA_Deinitialize; 264 265 impl->ProvidesOwnCallbackThread = true; 266 267 return true; 268} 269 270CameraBootStrap EMSCRIPTENCAMERA_bootstrap = { 271 "emscripten", "SDL Emscripten MediaStream camera driver", EMSCRIPTENCAMERA_Init, false 272}; 273 274/* *INDENT-ON* */ // clang-format on 275 276#endif // SDL_CAMERA_DRIVER_EMSCRIPTEN 277 278
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.