summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/camera/emscripten
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/camera/emscripten')
-rw-r--r--contrib/SDL-3.2.8/src/camera/emscripten/SDL_camera_emscripten.c275
1 files changed, 275 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/camera/emscripten/SDL_camera_emscripten.c b/contrib/SDL-3.2.8/src/camera/emscripten/SDL_camera_emscripten.c
new file mode 100644
index 0000000..fa2a511
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/camera/emscripten/SDL_camera_emscripten.c
@@ -0,0 +1,275 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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
36EM_JS_DEPS(sdlcamera, "$dynCall");
37
38static bool EMSCRIPTENCAMERA_WaitDevice(SDL_Camera *device)
39{
40 SDL_assert(!"This shouldn't be called"); // we aren't using SDL's internal thread.
41 return false;
42}
43
44static SDL_CameraFrameResult EMSCRIPTENCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
45{
46 void *rgba = SDL_malloc(device->actual_spec.width * device->actual_spec.height * 4);
47 if (!rgba) {
48 return SDL_CAMERA_FRAME_ERROR;
49 }
50
51 *timestampNS = SDL_GetTicksNS(); // best we can do here.
52
53 const int rc = MAIN_THREAD_EM_ASM_INT({
54 const w = $0;
55 const h = $1;
56 const rgba = $2;
57 const SDL3 = Module['SDL3'];
58 if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.ctx2d) === 'undefined')) {
59 return 0; // don't have something we need, oh well.
60 }
61
62 SDL3.camera.ctx2d.drawImage(SDL3.camera.video, 0, 0, w, h);
63 const imgrgba = SDL3.camera.ctx2d.getImageData(0, 0, w, h).data;
64 Module.HEAPU8.set(imgrgba, rgba);
65
66 return 1;
67 }, device->actual_spec.width, device->actual_spec.height, rgba);
68
69 if (!rc) {
70 SDL_free(rgba);
71 return SDL_CAMERA_FRAME_ERROR; // something went wrong, maybe shutting down; just don't return a frame.
72 }
73
74 frame->pixels = rgba;
75 frame->pitch = device->actual_spec.width * 4;
76
77 return SDL_CAMERA_FRAME_READY;
78}
79
80static void EMSCRIPTENCAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
81{
82 SDL_free(frame->pixels);
83}
84
85static void EMSCRIPTENCAMERA_CloseDevice(SDL_Camera *device)
86{
87 if (device) {
88 MAIN_THREAD_EM_ASM({
89 const SDL3 = Module['SDL3'];
90 if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.stream) === 'undefined')) {
91 return; // camera was closed and/or subsystem was shut down, we're already done.
92 }
93 SDL3.camera.stream.getTracks().forEach(track => track.stop()); // stop all recording.
94 SDL3.camera = {}; // dump our references to everything.
95 });
96 SDL_free(device->hidden);
97 device->hidden = NULL;
98 }
99}
100
101static int SDLEmscriptenCameraPermissionOutcome(SDL_Camera *device, int approved, int w, int h, int fps)
102{
103 if (approved) {
104 device->actual_spec.format = SDL_PIXELFORMAT_RGBA32;
105 device->actual_spec.width = w;
106 device->actual_spec.height = h;
107 device->actual_spec.framerate_numerator = fps;
108 device->actual_spec.framerate_denominator = 1;
109
110 if (!SDL_PrepareCameraSurfaces(device)) {
111 // uhoh, we're in trouble. Probably ran out of memory.
112 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Camera could not prepare surfaces: %s ... revoking approval!", SDL_GetError());
113 approved = 0; // disconnecting the SDL camera might not be safe here, just mark it as denied by user.
114 }
115 }
116
117 SDL_CameraPermissionOutcome(device, approved ? true : false);
118 return approved;
119}
120
121static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
122{
123 MAIN_THREAD_EM_ASM({
124 // Since we can't get actual specs until we make a move that prompts the user for
125 // permission, we don't list any specs for the device and wrangle it during device open.
126 const device = $0;
127 const w = $1;
128 const h = $2;
129 const framerate_numerator = $3;
130 const framerate_denominator = $4;
131 const outcome = $5;
132 const iterate = $6;
133
134 const constraints = {};
135 if ((w <= 0) || (h <= 0)) {
136 constraints.video = true; // didn't ask for anything, let the system choose.
137 } else {
138 constraints.video = {}; // asked for a specific thing: request it as "ideal" but take closest hardware will offer.
139 constraints.video.width = w;
140 constraints.video.height = h;
141 }
142
143 if ((framerate_numerator > 0) && (framerate_denominator > 0)) {
144 var fps = framerate_numerator / framerate_denominator;
145 constraints.video.frameRate = { ideal: fps };
146 }
147
148 function grabNextCameraFrame() { // !!! FIXME: this (currently) runs as a requestAnimationFrame callback, for lack of a better option.
149 const SDL3 = Module['SDL3'];
150 if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.stream) === 'undefined')) {
151 return; // camera was closed and/or subsystem was shut down, stop iterating here.
152 }
153
154 // time for a new frame from the camera?
155 const nextframems = SDL3.camera.next_frame_time;
156 const now = performance.now();
157 if (now >= nextframems) {
158 dynCall('vi', iterate, [device]); // calls SDL_CameraThreadIterate, which will call our AcquireFrame implementation.
159
160 // bump ahead but try to stay consistent on timing, in case we dropped frames.
161 while (SDL3.camera.next_frame_time < now) {
162 SDL3.camera.next_frame_time += SDL3.camera.fpsincrms;
163 }
164 }
165
166 requestAnimationFrame(grabNextCameraFrame); // run this function again at the display framerate. (!!! FIXME: would this be better as requestIdleCallback?)
167 }
168
169 navigator.mediaDevices.getUserMedia(constraints)
170 .then((stream) => {
171 const settings = stream.getVideoTracks()[0].getSettings();
172 const actualw = settings.width;
173 const actualh = settings.height;
174 const actualfps = settings.frameRate;
175 console.log("Camera is opened! Actual spec: (" + actualw + "x" + actualh + "), fps=" + actualfps);
176
177 if (dynCall('iiiiii', outcome, [device, 1, actualw, actualh, actualfps])) {
178 const video = document.createElement("video");
179 video.width = actualw;
180 video.height = actualh;
181 video.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels.
182 video.srcObject = stream;
183
184 const canvas = document.createElement("canvas");
185 canvas.width = actualw;
186 canvas.height = actualh;
187 canvas.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels.
188
189 const ctx2d = canvas.getContext('2d');
190
191 const SDL3 = Module['SDL3'];
192 SDL3.camera.width = actualw;
193 SDL3.camera.height = actualh;
194 SDL3.camera.fps = actualfps;
195 SDL3.camera.fpsincrms = 1000.0 / actualfps;
196 SDL3.camera.stream = stream;
197 SDL3.camera.video = video;
198 SDL3.camera.canvas = canvas;
199 SDL3.camera.ctx2d = ctx2d;
200 SDL3.camera.next_frame_time = performance.now();
201
202 video.play();
203 video.addEventListener('loadedmetadata', () => {
204 grabNextCameraFrame(); // start this loop going.
205 });
206 }
207 })
208 .catch((err) => {
209 console.error("Tried to open camera but it threw an error! " + err.name + ": " + err.message);
210 dynCall('iiiiii', outcome, [device, 0, 0, 0, 0]); // we call this a permission error, because it probably is.
211 });
212 }, device, spec->width, spec->height, spec->framerate_numerator, spec->framerate_denominator, SDLEmscriptenCameraPermissionOutcome, SDL_CameraThreadIterate);
213
214 return true; // the real work waits until the user approves a camera.
215}
216
217static void EMSCRIPTENCAMERA_FreeDeviceHandle(SDL_Camera *device)
218{
219 // no-op.
220}
221
222static void EMSCRIPTENCAMERA_Deinitialize(void)
223{
224 MAIN_THREAD_EM_ASM({
225 if (typeof(Module['SDL3']) !== 'undefined') {
226 Module['SDL3'].camera = undefined;
227 }
228 });
229}
230
231static void EMSCRIPTENCAMERA_DetectDevices(void)
232{
233 // `navigator.mediaDevices` is not defined if unsupported or not in a secure context!
234 const int supported = MAIN_THREAD_EM_ASM_INT({ return (navigator.mediaDevices === undefined) ? 0 : 1; });
235
236 // if we have support at all, report a single generic camera with no specs.
237 // We'll find out if there really _is_ a camera when we try to open it, but querying it for real here
238 // will pop up a user permission dialog warning them we're trying to access the camera, and we generally
239 // don't want that during SDL_Init().
240 if (supported) {
241 SDL_AddCamera("Web browser's camera", SDL_CAMERA_POSITION_UNKNOWN, 0, NULL, (void *) (size_t) 0x1);
242 }
243}
244
245static bool EMSCRIPTENCAMERA_Init(SDL_CameraDriverImpl *impl)
246{
247 MAIN_THREAD_EM_ASM({
248 if (typeof(Module['SDL3']) === 'undefined') {
249 Module['SDL3'] = {};
250 }
251 Module['SDL3'].camera = {};
252 });
253
254 impl->DetectDevices = EMSCRIPTENCAMERA_DetectDevices;
255 impl->OpenDevice = EMSCRIPTENCAMERA_OpenDevice;
256 impl->CloseDevice = EMSCRIPTENCAMERA_CloseDevice;
257 impl->WaitDevice = EMSCRIPTENCAMERA_WaitDevice;
258 impl->AcquireFrame = EMSCRIPTENCAMERA_AcquireFrame;
259 impl->ReleaseFrame = EMSCRIPTENCAMERA_ReleaseFrame;
260 impl->FreeDeviceHandle = EMSCRIPTENCAMERA_FreeDeviceHandle;
261 impl->Deinitialize = EMSCRIPTENCAMERA_Deinitialize;
262
263 impl->ProvidesOwnCallbackThread = true;
264
265 return true;
266}
267
268CameraBootStrap EMSCRIPTENCAMERA_bootstrap = {
269 "emscripten", "SDL Emscripten MediaStream camera driver", EMSCRIPTENCAMERA_Init, false
270};
271
272/* *INDENT-ON* */ // clang-format on
273
274#endif // SDL_CAMERA_DRIVER_EMSCRIPTEN
275