|
| 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 | + |
0 commit comments