Skip to content

Commit 530300e

Browse files
committed
Add SDL3 GB camera backend
1 parent 7a688e1 commit 530300e

File tree

6 files changed

+299
-4
lines changed

6 files changed

+299
-4
lines changed

projects/unix/Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,9 +355,13 @@ ifeq ($(origin SDL_CFLAGS) $(origin SDL_LDLIBS), undefined undefined)
355355
ifeq ($(shell $(PKG_CONFIG) --modversion sdl2 2>/dev/null),)
356356
$(error No SDL3 or SDL2 development libraries found!)
357357
endif
358+
ifeq ($(SDL3_CAMERA), 1)
359+
$(error SDL3_CAMERA requires SDL3!)
360+
endif
358361
SDL_CFLAGS += $(shell $(PKG_CONFIG) --cflags sdl2)
359362
SDL_LDLIBS += $(shell $(PKG_CONFIG) --libs sdl2)
360363
else
364+
SDL3_CAMERA := 1
361365
SDL_CFLAGS += -DUSE_SDL3
362366
SDL_CFLAGS += $(shell $(PKG_CONFIG) --cflags sdl3)
363367
SDL_LDLIBS += $(shell $(PKG_CONFIG) --libs sdl3)
@@ -762,6 +766,10 @@ ifeq ($(OPENCV), 1)
762766
CFLAGS += -DM64P_OPENCV
763767
endif
764768

769+
ifeq ($(SDL3_CAMERA), 1)
770+
SOURCE += $(SRCDIR)/backends/sdl3_video_capture.cpp
771+
CFLAGS += -DSDL3_CAMERA
772+
endif
765773

766774
# generate a list of object files to build, make a temporary directory for them
767775
OBJECTS := $(patsubst $(SRCDIR)/%.c, $(OBJDIR)/%.o, $(filter $(SRCDIR)/%.c, $(SOURCE)))

src/backends/api/video_capture_backend.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,18 @@ extern const struct video_capture_backend_interface g_idummy_video_capture_backe
2828
#if defined(M64P_OPENCV)
2929
extern const struct video_capture_backend_interface g_iopencv_video_capture_backend;
3030
#endif
31+
#if defined(SDL3_CAMERA)
32+
extern const struct video_capture_backend_interface g_isdl3_video_capture_backend;
33+
#endif
3134

3235

3336
const struct video_capture_backend_interface* g_video_capture_backend_interfaces[] =
3437
{
3538
#if defined(M64P_OPENCV)
3639
&g_iopencv_video_capture_backend,
40+
#endif
41+
#if defined(SDL3_CAMERA)
42+
&g_isdl3_video_capture_backend,
3743
#endif
3844
&g_idummy_video_capture_backend,
3945
NULL /* sentinel - must be last element */

src/backends/api/video_capture_backend.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
#if !defined(DEFAULT_VIDEO_CAPTURE_BACKEND)
3131
#if defined(M64P_OPENCV)
3232
#define DEFAULT_VIDEO_CAPTURE_BACKEND "opencv"
33+
#elif defined(SDL3_CAMERA)
34+
#define DEFAULT_VIDEO_CAPTURE_BACKEND "sdl3"
3335
#else
3436
#define DEFAULT_VIDEO_CAPTURE_BACKEND ""
3537
#endif

src/backends/sdl3_video_capture.c

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2+
* Mupen64plus - sdl3_video_capture.cpp *
3+
* Mupen64Plus homepage: https://mupen64plus.org/ *
4+
* Copyright (C) 2025 Rosalie Wanders *
5+
* *
6+
* This program is free software; you can redistribute it and/or modify *
7+
* it under the terms of the GNU General Public License as published by *
8+
* the Free Software Foundation; either version 2 of the License, or *
9+
* (at your option) any later version. *
10+
* *
11+
* This program is distributed in the hope that it will be useful, *
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14+
* GNU General Public License for more details. *
15+
* *
16+
* You should have received a copy of the GNU General Public License *
17+
* along with this program; if not, write to the *
18+
* Free Software Foundation, Inc., *
19+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
20+
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
21+
22+
#include <SDL3/SDL.h>
23+
24+
struct sdl3_video_capture
25+
{
26+
unsigned int width;
27+
unsigned int height;
28+
29+
SDL_Camera* camera;
30+
SDL_CameraID camera_id;
31+
32+
char* target_camera_name;
33+
};
34+
35+
#include "backends/api/video_capture_backend.h"
36+
37+
#define M64P_CORE_PROTOTYPES 1
38+
#include "api/callbacks.h"
39+
#include "api/m64p_types.h"
40+
#include "api/m64p_config.h"
41+
42+
#include <stdlib.h>
43+
44+
const struct video_capture_backend_interface g_isdl3_video_capture_backend;
45+
46+
static m64p_error sdl3_init(void** vcap, const char* section)
47+
{
48+
/* initialize data */
49+
*vcap = malloc(sizeof(struct sdl3_video_capture));
50+
if (*vcap == NULL)
51+
{
52+
return M64ERR_NO_MEMORY;
53+
}
54+
55+
memset(*vcap, 0, sizeof(struct sdl3_video_capture));
56+
struct sdl3_video_capture* sdl = (struct sdl3_video_capture*)(*vcap);
57+
58+
if (!SDL_Init(SDL_INIT_CAMERA))
59+
{
60+
DebugMessage(M64MSG_ERROR, "Failed to initialize SDL camera subsystem: %s", SDL_GetError());
61+
return M64ERR_SYSTEM_FAIL;
62+
}
63+
64+
/* default parameters */
65+
const char* device = NULL;
66+
67+
if (section && strlen(section) > 0)
68+
{
69+
m64p_handle config = NULL;
70+
71+
if (ConfigOpenSection(section, &config) != M64ERR_SUCCESS)
72+
{
73+
DebugMessage(M64MSG_WARNING, "Failed to open video configuration section: %s, falling back to default video device", section);
74+
return M64ERR_SUCCESS;
75+
}
76+
77+
/* set default parameters */
78+
ConfigSetDefaultString(config, "device", "", "Device name to use for capture or empty for default.");
79+
80+
/* get parameters */
81+
device = ConfigGetParamString(config, "device");
82+
83+
/* fallback to NULL when default value has been used */
84+
if (strlen(device) == 0)
85+
{
86+
device = NULL;
87+
}
88+
}
89+
90+
/* store device name for later */
91+
if (device != NULL)
92+
{
93+
sdl->target_camera_name = strdup(device);
94+
}
95+
96+
return M64ERR_SUCCESS;
97+
}
98+
99+
static void sdl3_release(void* vcap)
100+
{
101+
struct sdl3_video_capture* sdl = (struct sdl3_video_capture*)(vcap);
102+
if (sdl != NULL)
103+
{
104+
if (sdl->target_camera_name != NULL)
105+
{
106+
free(sdl->target_camera_name);
107+
}
108+
109+
free(sdl);
110+
}
111+
112+
if (SDL_WasInit(SDL_INIT_CAMERA))
113+
{
114+
SDL_QuitSubSystem(SDL_INIT_CAMERA);
115+
}
116+
}
117+
118+
static m64p_error sdl3_open(void* vcap, unsigned int width, unsigned int height)
119+
{
120+
struct sdl3_video_capture* sdl = (struct sdl3_video_capture*)(vcap);
121+
if (sdl == NULL)
122+
{
123+
return M64ERR_NOT_INIT;
124+
}
125+
126+
sdl->width = width;
127+
sdl->height = height;
128+
129+
int cameras_count = 0;
130+
SDL_CameraID* cameras = SDL_GetCameras(&cameras_count);
131+
if (cameras == NULL)
132+
{
133+
DebugMessage(M64MSG_ERROR, "Failed to retrieve list of video devices: %s", SDL_GetError());
134+
return M64ERR_SYSTEM_FAIL;
135+
}
136+
if (cameras_count == 0)
137+
{
138+
DebugMessage(M64MSG_WARNING, "Failed to find video devices");
139+
return M64ERR_INPUT_NOT_FOUND;
140+
}
141+
142+
/* fallback to default camera */
143+
sdl->camera_id = cameras[0];
144+
145+
/* print name of every camera to user, and
146+
* attempt to find the camera that the user
147+
* specified in the config */
148+
int found_camera = 0;
149+
for (int i = 0; i < cameras_count; i++)
150+
{
151+
const char* name = SDL_GetCameraName(cameras[i]);
152+
if (name == NULL)
153+
{
154+
DebugMessage(M64MSG_ERROR, "Failed to retrieve video device name: %s", SDL_GetError());
155+
continue;
156+
}
157+
158+
DebugMessage(M64MSG_INFO, "Found video device: \"%s\"", name);
159+
160+
if (sdl->target_camera_name != NULL &&
161+
strcmp(sdl->target_camera_name, name) == 0)
162+
{
163+
sdl->camera_id = cameras[i];
164+
found_camera = 1;
165+
break;
166+
}
167+
}
168+
169+
/* show warning when device was not found */
170+
if (sdl->target_camera_name != NULL && found_camera == 0)
171+
{
172+
DebugMessage(M64MSG_WARNING, "Failed to find video device with name \"%s\", falling back to default", sdl->target_camera_name);
173+
}
174+
175+
SDL_free(cameras);
176+
177+
/* attempt to open camera */
178+
sdl->camera = SDL_OpenCamera(sdl->camera_id, NULL);
179+
if (sdl->camera == NULL)
180+
{
181+
DebugMessage(M64MSG_ERROR, "Failed to open video device: %s", SDL_GetError());
182+
return M64ERR_SYSTEM_FAIL;
183+
}
184+
185+
/* attempt to get permission for the camera */
186+
int permission_state = SDL_GetCameraPermissionState(sdl->camera);
187+
if (permission_state == 0)
188+
{
189+
DebugMessage(M64MSG_INFO, "Waiting until user has approved video access");
190+
do
191+
{
192+
SDL_Delay(250);
193+
permission_state = SDL_GetCameraPermissionState(sdl->camera);
194+
} while (permission_state == 0);
195+
}
196+
197+
if (permission_state == -1)
198+
{
199+
DebugMessage(M64MSG_ERROR, "Failed to open video device: permission denied");
200+
return M64ERR_SYSTEM_FAIL;
201+
}
202+
203+
DebugMessage(M64MSG_INFO, "Video successfully opened: %s", SDL_GetCameraName(sdl->camera_id));
204+
return M64ERR_SUCCESS;
205+
}
206+
207+
static void sdl3_close(void* vcap)
208+
{
209+
struct sdl3_video_capture* sdl = (struct sdl3_video_capture*)(vcap);
210+
if (sdl == NULL)
211+
{
212+
return;
213+
}
214+
215+
if (sdl->camera != NULL)
216+
{
217+
SDL_CloseCamera(sdl->camera);
218+
}
219+
220+
DebugMessage(M64MSG_INFO, "Video closed");
221+
}
222+
223+
static m64p_error sdl3_grab_image(void* vcap, void* data)
224+
{
225+
struct sdl3_video_capture* sdl = (struct sdl3_video_capture*)(vcap);
226+
if (sdl == NULL || sdl->camera == NULL)
227+
{
228+
return M64ERR_NOT_INIT;
229+
}
230+
231+
SDL_Surface* frame_surface = SDL_AcquireCameraFrame(sdl->camera, NULL);
232+
if (frame_surface == NULL)
233+
{
234+
DebugMessage(M64MSG_ERROR, "Failed to grab video frame: %s", SDL_GetError());
235+
return M64ERR_SYSTEM_FAIL;
236+
}
237+
238+
SDL_Surface* target_surface = SDL_CreateSurface(sdl->width, sdl->height, SDL_PIXELFORMAT_BGR24);
239+
if (target_surface == NULL)
240+
{
241+
DebugMessage(M64MSG_ERROR, "Failed to create target surface: %s\n", SDL_GetError());
242+
SDL_ReleaseCameraFrame(sdl->camera, frame_surface);
243+
return M64ERR_SYSTEM_FAIL;
244+
}
245+
246+
int frame_size = SDL_min(frame_surface->w, frame_surface->h);
247+
SDL_Rect frame_rect;
248+
frame_rect.x = (frame_surface->w / 2) - (frame_size / 2);
249+
frame_rect.y = 0;
250+
frame_rect.w = frame_size;
251+
frame_rect.h = frame_size;
252+
253+
if (!SDL_BlitSurfaceScaled(frame_surface, &frame_rect, target_surface, NULL, SDL_SCALEMODE_NEAREST))
254+
{
255+
DebugMessage(M64MSG_ERROR, "Failed to blit surface: %s", SDL_GetError());
256+
SDL_ReleaseCameraFrame(sdl->camera, frame_surface);
257+
SDL_DestroySurface(target_surface);
258+
return M64ERR_SYSTEM_FAIL;
259+
}
260+
261+
memcpy(data, target_surface->pixels, target_surface->w * target_surface->h * 3);
262+
263+
SDL_ReleaseCameraFrame(sdl->camera, frame_surface);
264+
SDL_DestroySurface(target_surface);
265+
return M64ERR_SUCCESS;
266+
}
267+
268+
269+
270+
const struct video_capture_backend_interface g_isdl3_video_capture_backend =
271+
{
272+
"sdl3",
273+
sdl3_init,
274+
sdl3_release,
275+
sdl3_open,
276+
sdl3_close,
277+
sdl3_grab_image
278+
};
279+

src/device/gb/gb_cart.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,9 @@ static void init_pocket_cam(struct pocket_cam* cam, uint8_t* ram, void* vcap, co
715715

716716
cam->vcap = vcap;
717717
cam->ivcap = ivcap;
718+
719+
/* open video device */
720+
cam->ivcap->open(cam->vcap, M64282FP_SENSOR_W, M64282FP_SENSOR_H);
718721
}
719722

720723
static void poweron_pocket_cam(struct pocket_cam* cam)

src/main/main.c

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1704,10 +1704,7 @@ m64p_error main_run(void)
17041704

17051705
/* init GbCamera backend specified in the configuration file */
17061706
init_video_capture_backend(&igbcam_backend, &gbcam_backend,
1707-
g_CoreConfig, "GbCameraVideoCaptureBackend1");
1708-
1709-
/* open GB cam video device */
1710-
igbcam_backend->open(gbcam_backend, M64282FP_SENSOR_W, M64282FP_SENSOR_H);
1707+
g_CoreConfig, "GbCameraVideoCaptureBackend1");
17111708

17121709
/* open storage files, provide default content if not present */
17131710
open_mpk_file(&mpk);

0 commit comments

Comments
 (0)