SDL_OpenCameraDevice doesn't handle some width and height
I've noticed that some width and height values causes the camera thread to crash a second or so after calling SDL_OpenCameraDevice. For me this happens for example when height=100 or when width=30.
SDL_GetCameraDeviceSupportedFormats says the supported format is 640x480 but according to the docs it should work with other specs.
The following program can be used to reproduce the problem:
#include <SDL3/SDL.h>
int main()
{
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_CAMERA);
int camera_count;
SDL_CameraDeviceID* camera_ids = SDL_GetCameraDevices(&camera_count);
if (camera_ids != NULL && camera_count > 0)
{
SDL_CameraSpec spec = {0};
spec.format = SDL_PIXELFORMAT_RGBA8888;
spec.width = 100;
spec.height = 100;
spec.interval_numerator = 1;
spec.interval_denominator = 30;
SDL_Camera* camera = SDL_OpenCameraDevice(*camera_ids, &spec);
if (camera != NULL)
{
SDL_Delay(3000); // wait for the bug to happen
SDL_CloseCamera(camera);
}
}
SDL_Quit();
}
The error message is:
double free or corruption (!prev)
Aborted
Running it through Valgrind gives the following message:
==12201== Thread 2 SDLCamera2:
==12201== Invalid write of size 4
==12201== at 0x49BCE2F: scale_mat_nearest_4 (in /usr/local/lib/libSDL3.so.0.1.1)
==12201== by 0x49BCF4D: SDL_LowerSoftStretchNearest (in /usr/local/lib/libSDL3.so.0.1.1)
==12201== by 0x49BAD8C: SDL_SoftStretch_REAL (in /usr/local/lib/libSDL3.so.0.1.1)
==12201== by 0x4891ADF: SDL_CameraThreadIterate (in /usr/local/lib/libSDL3.so.0.1.1)
==12201== by 0x4891CB5: CameraThread (in /usr/local/lib/libSDL3.so.0.1.1)
==12201== by 0x4941AE9: SDL_RunThread (in /usr/local/lib/libSDL3.so.0.1.1)
==12201== by 0x4AB9D64: RunThread (in /usr/local/lib/libSDL3.so.0.1.1)
==12201== by 0x4EB5EA6: start_thread (pthread_create.c:477)
==12201== by 0x4C8BA2E: clone (clone.S:95)
==12201== Address 0x6678c68 is 0 bytes after a block of size 20,040 alloc'd
==12201== at 0x483877F: malloc (vg_replace_malloc.c:307)
==12201== by 0x493C193: real_malloc (in /usr/local/lib/libSDL3.so.0.1.1)
==12201== by 0x493C3DF: SDL_malloc_REAL (in /usr/local/lib/libSDL3.so.0.1.1)
==12201== by 0x493EC18: SDL_aligned_alloc_REAL (in /usr/local/lib/libSDL3.so.0.1.1)
==12201== by 0x49BD67F: SDL_CreateSurface_REAL (in /usr/local/lib/libSDL3.so.0.1.1)
==12201== by 0x489268E: SDL_OpenCameraDevice_REAL (in /usr/local/lib/libSDL3.so.0.1.1)
==12201== by 0x48A3B17: SDL_OpenCameraDevice (in /usr/local/lib/libSDL3.so.0.1.1)
==12201== by 0x109202: main (test.c:17)
Sam made some fixes to image size choice recently; if you get a moment, can you try the latest and see if it fixes this issue?
It's not fixed.
I looked a bit deeper and I think I know what's going wrong now.
- SDL will use
SDL_SoftStretchwhen the specified size is not among the sizes returned fromSDL_GetCameraDeviceSupportedFormats. - My camera uses YUY2 format.
SDL_SoftStretchdoes not handle the YUY2 format properly which leads to memory corruption.- Specifying a different format does not help because stretching still happens in YUY2 format (presumably format conversion happens after stretching).
Two other functions that also have issues with the YUY2 format are SDL_FillSurfaceRect and SDL_FillSurfaceRects.
Can you try this patch?
diff --git a/src/video/SDL_stretch.c b/src/video/SDL_stretch.c
index fb97cf6f6..1778ccc04 100644
--- a/src/video/SDL_stretch.c
+++ b/src/video/SDL_stretch.c
@@ -35,9 +35,53 @@ int SDL_SoftStretch(SDL_Surface *src, const SDL_Rect *srcrect,
SDL_Rect full_src;
SDL_Rect full_dst;
+ if (!src) {
+ return SDL_InvalidParamError("src");
+ }
+ if (!dst) {
+ return SDL_InvalidParamError("dst");
+ }
if (src->format->format != dst->format->format) {
- return SDL_SetError("Only works with same format surfaces");
+ // Slow!
+ SDL_Surface *src_tmp = SDL_ConvertSurfaceFormat(src, dst->format->format);
+ if (!src_tmp) {
+ return -1;
+ }
+ ret = SDL_SoftStretch(src_tmp, srcrect, dst, dstrect, scaleMode);
+ SDL_DestroySurface(src_tmp);
+ return ret;
+ }
+
+ if (SDL_ISPIXELFORMAT_FOURCC(src->format->format)) {
+ // Slow!
+ if (!dstrect) {
+ full_dst.x = 0;
+ full_dst.y = 0;
+ full_dst.w = dst->w;
+ full_dst.h = dst->h;
+ dstrect = &full_dst;
+ }
+
+ SDL_Surface *src_tmp = SDL_ConvertSurfaceFormat(src, SDL_PIXELFORMAT_XRGB8888);
+ SDL_Surface *dst_tmp = SDL_CreateSurface(dstrect->w, dstrect->h, SDL_PIXELFORMAT_XRGB8888);
+ if (src_tmp && dst_tmp) {
+ ret = SDL_SoftStretch(src_tmp, srcrect, dst_tmp, NULL, scaleMode);
+ if (ret == 0) {
+ SDL_Colorspace dst_colorspace = SDL_COLORSPACE_UNKNOWN;
+ SDL_GetSurfaceColorspace(dst, &dst_colorspace);
+ SDL_ConvertPixelsAndColorspace(dstrect->w, dstrect->h,
+ dst_tmp->format->format, SDL_COLORSPACE_SRGB, 0,
+ dst_tmp->pixels, dst_tmp->pitch,
+ dst->format->format, dst_colorspace, SDL_GetSurfaceProperties(dst),
+ (Uint8 *)dst->pixels + dstrect->y * dst->pitch + dstrect->x * dst->format->bytes_per_pixel, dst->pitch);
+ }
+ } else {
+ ret = -1;
+ }
+ SDL_DestroySurface(src_tmp);
+ SDL_DestroySurface(dst_tmp);
+ return ret;
}
if (scaleMode != SDL_SCALEMODE_NEAREST && scaleMode != SDL_SCALEMODE_LINEAR && scaleMode != SDL_SCALEMODE_BEST) {
That patch fixes the issue. I no longer experience any crashes or memory corruption.
Great, thanks!