Add SDL Video Capture, with back-end for linux/macos/ios/android

main
Sylvain 2023-11-09 11:11:07 +01:00 committed by Sam Lantinga
parent 3ab98a3572
commit 59f93e20a7
23 changed files with 4768 additions and 0 deletions

View File

@ -343,6 +343,7 @@ set_option(SDL_METAL "Enable Metal support" ${APPLE})
set_option(SDL_KMSDRM "Use KMS DRM video driver" ${UNIX_SYS})
dep_option(SDL_KMSDRM_SHARED "Dynamically load KMS DRM support" ON "SDL_KMSDRM" OFF)
set_option(SDL_OFFSCREEN "Use offscreen video driver" ON)
dep_option(SDL_VIDEO_CAPTURE "Enable video capturing" ON SDL_VIDEO OFF)
option_string(SDL_BACKGROUNDING_SIGNAL "number to use for magic backgrounding signal or 'OFF'" OFF)
option_string(SDL_FOREGROUNDING_SIGNAL "number to use for magic foregrounding signal or 'OFF'" OFF)
dep_option(SDL_HIDAPI "Enable the HIDAPI subsystem" ON "NOT VISIONOS" OFF)
@ -2047,6 +2048,10 @@ elseif(APPLE)
set(HAVE_SDL_FILE TRUE)
endif()
if(IOS OR TVOS OR MACOSX OR DARWIN)
sdl_sources("${SDL3_SOURCE_DIR}/src/video/SDL_video_capture_apple.m")
endif()
if(SDL_MISC)
if(IOS OR TVOS OR VISIONOS)
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/ios/*.m")
@ -2230,6 +2235,10 @@ elseif(APPLE)
# Actually load the frameworks at the end so we don't duplicate include.
if(SDL_FRAMEWORK_COREVIDEO)
find_library(COREMEDIA CoreMedia)
if(COREMEDIA)
sdl_link_dependency(corevideo LINK_OPTIONS "-Wl,-framework,CoreMedia")
endif()
sdl_link_dependency(corevideo LINK_OPTIONS "-Wl,-framework,CoreVideo")
endif()
if(SDL_FRAMEWORK_COCOA)

View File

@ -89,6 +89,7 @@
<ClInclude Include="..\include\SDL3\SDL_types.h" />
<ClInclude Include="..\include\SDL3\SDL_version.h" />
<ClInclude Include="..\include\SDL3\SDL_video.h" />
<ClInclude Include="..\include\SDL3\SDL_video_capture.h" />
<ClInclude Include="..\src\audio\disk\SDL_diskaudio.h" />
<ClInclude Include="..\src\audio\dummy\SDL_dummyaudio.h" />
<ClInclude Include="..\src\audio\SDL_audiodev_c.h" />
@ -180,6 +181,7 @@
<ClInclude Include="..\src\video\SDL_RLEaccel_c.h" />
<ClInclude Include="..\src\video\SDL_shape_internals.h" />
<ClInclude Include="..\src\video\SDL_sysvideo.h" />
<ClInclude Include="..\src\video\SDL_sysvidocapture.h" />
<ClInclude Include="..\src\video\SDL_yuv_c.h" />
<ClInclude Include="..\src\video\winrt\SDL_winrtevents_c.h" />
<ClInclude Include="..\src\video\winrt\SDL_winrtgamebar_cpp.h" />
@ -520,6 +522,7 @@
<ClCompile Include="..\src\video\SDL_stretch.c" />
<ClCompile Include="..\src\video\SDL_surface.c" />
<ClCompile Include="..\src\video\SDL_video.c" />
<ClCompile Include="..\src\video\SDL_video_capture.c" />
<ClCompile Include="..\src\video\SDL_video_unsupported.c" />
<ClCompile Include="..\src\video\SDL_yuv.c" />
<ClCompile Include="..\src\video\winrt\SDL_winrtevents.cpp">

View File

@ -165,6 +165,9 @@
<ClInclude Include="..\include\SDL3\SDL_video.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\SDL3\SDL_video_capture.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\src\joystick\SDL_gamepad_c.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -405,6 +408,9 @@
<ClInclude Include="..\src\video\SDL_sysvideo.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="..\src\video\SDL_sysvideocapture.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="..\src\video\winrt\SDL_winrtevents_c.h">
<Filter>Source Files</Filter>
</ClInclude>
@ -807,6 +813,9 @@
<ClCompile Include="..\src\video\SDL_video.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\video\SDL_video_capture.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\video\SDL_video_unsupported.c">
<Filter>Source Files</Filter>
</ClCompile>

View File

@ -653,6 +653,7 @@
<ClCompile Include="..\..\src\video\SDL_surface.c" />
<ClCompile Include="..\..\src\video\SDL_video.c" />
<ClCompile Include="..\..\src\video\SDL_video_unsupported.c" />
<ClCompile Include="..\..\src\video\SDL_video_capture.c" />
<ClCompile Include="..\..\src\video\SDL_vulkan_utils.c" />
<ClCompile Include="..\..\src\video\SDL_yuv.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" />

View File

@ -1185,6 +1185,9 @@
<ClCompile Include="..\..\src\video\SDL_video_unsupported.c">
<Filter>video</Filter>
</ClCompile>
<ClCompile Include="..\..\src\video\SDL_video_capture.c">
<Filter>video</Filter>
</ClCompile>
<ClCompile Include="..\..\src\video\SDL_yuv.c">
<Filter>video</Filter>
</ClCompile>

View File

@ -76,6 +76,7 @@
#include <SDL3/SDL_touch.h>
#include <SDL3/SDL_version.h>
#include <SDL3/SDL_video.h>
#include "SDL3/SDL_video_capture.h"
#include <SDL3/SDL_oldnames.h>
#endif /* SDL_h_ */

View File

@ -0,0 +1,369 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_video_capture.h
*
* Video Capture for the SDL library.
*/
#ifndef SDL_video_capture_h_
#define SDL_video_capture_h_
#include "SDL3/SDL_video.h"
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* This is a unique ID for a video capture device for the time it is connected to the system,
* and is never reused for the lifetime of the application. If the device is
* disconnected and reconnected, it will get a new ID.
*
* The ID value starts at 1 and increments from there. The value 0 is an invalid ID.
*
* \sa SDL_GetVideoCaptureDevices
*/
typedef Uint32 SDL_VideoCaptureDeviceID;
/**
* The structure used to identify an SDL video capture device
*/
struct SDL_VideoCaptureDevice;
typedef struct SDL_VideoCaptureDevice SDL_VideoCaptureDevice;
#define SDL_VIDEO_CAPTURE_ALLOW_ANY_CHANGE 1
/**
* SDL_VideoCaptureSpec structure
*
* Only those field can be 'desired' when configuring the device:
* - format
* - width
* - height
*
* \sa SDL_GetVideoCaptureFormat
* \sa SDL_GetVideoCaptureFrameSize
*
*/
typedef struct SDL_VideoCaptureSpec
{
Uint32 format; /**< Frame SDL_PixelFormatEnum format */
int width; /**< Frame width */
int height; /**< Frame height */
} SDL_VideoCaptureSpec;
/**
* SDL Video Capture Status
*
* Change states but calling the function in this order:
*
* SDL_OpenVideoCapture()
* SDL_SetVideoCaptureSpec() -> Init
* SDL_StartVideoCapture() -> Playing
* SDL_StopVideoCapture() -> Stopped
* SDL_CloseVideoCapture()
*
*/
typedef enum
{
SDL_VIDEO_CAPTURE_FAIL = -1, /**< Failed */
SDL_VIDEO_CAPTURE_INIT = 0, /**< Init, spec hasn't been set */
SDL_VIDEO_CAPTURE_STOPPED, /**< Stopped */
SDL_VIDEO_CAPTURE_PLAYING /**< Playing */
} SDL_VideoCaptureStatus;
/**
* SDL Video Capture Status
*/
typedef struct SDL_VideoCaptureFrame
{
Uint64 timestampNS; /**< Frame timestamp in nanoseconds when read from the driver */
int num_planes; /**< Number of planes */
Uint8 *data[3]; /**< Pointer to data of i-th plane */
int pitch[3]; /**< Pitch of i-th plane */
void *internal; /**< Private field */
} SDL_VideoCaptureFrame;
/**
* Get a list of currently connected video capture devices.
*
* \param count a pointer filled in with the number of video capture devices
* \returns a 0 terminated array of video capture instance IDs which should be
* freed with SDL_free(), or NULL on error; call SDL_GetError() for
* more details.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_OpenVideoCapture
*/
extern DECLSPEC SDL_VideoCaptureDeviceID *SDLCALL SDL_GetVideoCaptureDevices(int *count);
/**
* Open a Video Capture device
*
* \param instance_id the video capture device instance ID
* \returns device, or NULL on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_GetVideoCaptureDeviceName
* \sa SDL_GetVideoCaptureDevices
* \sa SDL_OpenVideoCaptureWithSpec
*/
extern DECLSPEC SDL_VideoCaptureDevice *SDLCALL SDL_OpenVideoCapture(SDL_VideoCaptureDeviceID instance_id);
/**
* Set specification
*
* \param device opened video capture device
* \param desired desired video capture spec
* \param obtained obtained video capture spec
* \param allowed_changes allow changes or not
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_OpenVideoCapture
* \sa SDL_OpenVideoCaptureWithSpec
* \sa SDL_GetVideoCaptureSpec
*/
extern DECLSPEC int SDLCALL SDL_SetVideoCaptureSpec(SDL_VideoCaptureDevice *device,
const SDL_VideoCaptureSpec *desired,
SDL_VideoCaptureSpec *obtained,
int allowed_changes);
/**
* Open a Video Capture device and set specification
*
* \param instance_id the video capture device instance ID
* \param desired desired video capture spec
* \param obtained obtained video capture spec
* \param allowed_changes allow changes or not
* \returns device, or NULL on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_OpenVideoCapture
* \sa SDL_SetVideoCaptureSpec
* \sa SDL_GetVideoCaptureSpec
*/
extern DECLSPEC SDL_VideoCaptureDevice *SDLCALL SDL_OpenVideoCaptureWithSpec(SDL_VideoCaptureDeviceID instance_id,
const SDL_VideoCaptureSpec *desired,
SDL_VideoCaptureSpec *obtained,
int allowed_changes);
/**
* Get device name
*
* \param instance_id the video capture device instance ID
* \returns device name, shouldn't be freed
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_GetVideoCaptureDevices
*/
extern DECLSPEC const char * SDLCALL SDL_GetVideoCaptureDeviceName(SDL_VideoCaptureDeviceID instance_id);
/**
* Get the obtained video capture spec
*
* \param device opened video capture device
* \param spec The SDL_VideoCaptureSpec to be initialized by this function.
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_SetVideoCaptureSpec
* \sa SDL_OpenVideoCaptureWithSpec
*/
extern DECLSPEC int SDLCALL SDL_GetVideoCaptureSpec(SDL_VideoCaptureDevice *device, SDL_VideoCaptureSpec *spec);
/**
* Get frame format of video capture device.
* The value can be used to fill SDL_VideoCaptureSpec structure.
*
* \param device opened video capture device
* \param index format between 0 and num -1
* \param format pointer output format (SDL_PixelFormatEnum)
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_GetNumVideoCaptureFormats
*/
extern DECLSPEC int SDLCALL SDL_GetVideoCaptureFormat(SDL_VideoCaptureDevice *device,
int index,
Uint32 *format);
/**
* Number of available formats for the device
*
* \param device opened video capture device
* \returns number of formats or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_GetVideoCaptureFormat
* \sa SDL_SetVideoCaptureSpec
*/
extern DECLSPEC int SDLCALL SDL_GetNumVideoCaptureFormats(SDL_VideoCaptureDevice *device);
/**
* Get frame sizes of the device and the specified input format.
* The value can be used to fill SDL_VideoCaptureSpec structure.
*
* \param device opened video capture device
* \param format a format that can be used by the device (SDL_PixelFormatEnum)
* \param index framesize between 0 and num -1
* \param width output width
* \param height output height
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_GetNumVideoCaptureFrameSizes
*/
extern DECLSPEC int SDLCALL SDL_GetVideoCaptureFrameSize(SDL_VideoCaptureDevice *device, Uint32 format, int index, int *width, int *height);
/**
* Number of different framesizes available for the device and pixel format.
*
* \param device opened video capture device
* \param format frame pixel format (SDL_PixelFormatEnum)
* \returns number of framesizes or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_GetVideoCaptureFrameSize
* \sa SDL_SetVideoCaptureSpec
*/
extern DECLSPEC int SDLCALL SDL_GetNumVideoCaptureFrameSizes(SDL_VideoCaptureDevice *device, Uint32 format);
/**
* Get video capture status
*
* \param device opened video capture device
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_VideoCaptureStatus
*/
extern DECLSPEC SDL_VideoCaptureStatus SDLCALL SDL_GetVideoCaptureStatus(SDL_VideoCaptureDevice *device);
/**
* Start video capture
*
* \param device opened video capture device
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_StopVideoCapture
*/
extern DECLSPEC int SDLCALL SDL_StartVideoCapture(SDL_VideoCaptureDevice *device);
/**
* Acquire a frame.
* The frame is a memory pointer to the image data, whose size and format
* are given by the the obtained spec.
*
* Non blocking API. If there is a frame available, frame->num_planes is non 0.
* If frame->num_planes is 0 and returned code is 0, there is no frame at that time.
*
* After used, the frame should be released with SDL_ReleaseVideoCaptureFrame
*
* \param device opened video capture device
* \param frame pointer to get the frame
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_ReleaseVideoCaptureFrame
*/
extern DECLSPEC int SDLCALL SDL_AcquireVideoCaptureFrame(SDL_VideoCaptureDevice *device, SDL_VideoCaptureFrame *frame);
/**
* Release a frame. Let the back-end re-use the internal buffer for video capture.
*
* All acquired frames should be released before closing the device.
*
* \param device opened video capture device
* \param frame frame pointer.
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_AcquireVideoCaptureFrame
*/
extern DECLSPEC int SDLCALL SDL_ReleaseVideoCaptureFrame(SDL_VideoCaptureDevice *device, SDL_VideoCaptureFrame *frame);
/**
* Stop Video Capture
*
* \param device opened video capture device
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_StartVideoCapture
*/
extern DECLSPEC int SDLCALL SDL_StopVideoCapture(SDL_VideoCaptureDevice *device);
/**
* Use this function to shut down video_capture processing and close the video_capture device.
*
* \param device opened video capture device
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_OpenVideoCaptureWithSpec
* \sa SDL_OpenVideoCapture
*/
extern DECLSPEC void SDLCALL SDL_CloseVideoCapture(SDL_VideoCaptureDevice *device);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include <SDL3/SDL_close_code.h>
#endif /* SDL_video_capture_h_ */

View File

@ -244,11 +244,15 @@
#cmakedefine USE_POSIX_SPAWN @USE_POSIX_SPAWN@
#cmakedefine HAVE_COREMEDIA
/* SDL internal assertion support */
#if @SDL_DEFAULT_ASSERT_LEVEL_CONFIGURED@
#cmakedefine SDL_DEFAULT_ASSERT_LEVEL @SDL_DEFAULT_ASSERT_LEVEL@
#endif
#cmakedefine SDL_VIDEO_CAPTURE
/* Allow disabling of core subsystems */
#cmakedefine SDL_ATOMIC_DISABLED @SDL_ATOMIC_DISABLED@
#cmakedefine SDL_AUDIO_DISABLED @SDL_AUDIO_DISABLED@

View File

@ -197,6 +197,8 @@
#define SDL_VIDEO_METAL 1
#endif
#define HAVE_COREMEDIA 1
/* Enable system power support */
#define SDL_POWER_UIKIT 1

View File

@ -260,6 +260,8 @@
#endif
#endif
#define HAVE_COREMEDIA 1
/* Enable system power support */
#define SDL_POWER_MACOSX 1

View File

@ -923,6 +923,22 @@ SDL3_0.0.0 {
SDL_SetPropertyWithCleanup;
SDL_SetX11EventHook;
SDL_GetGlobalProperties;
SDL_OpenVideoCapture;
SDL_SetVideoCaptureSpec;
SDL_OpenVideoCaptureWithSpec;
SDL_GetVideoCaptureDeviceName;
SDL_GetVideoCaptureSpec;
SDL_GetVideoCaptureFormat;
SDL_GetNumVideoCaptureFormats;
SDL_GetVideoCaptureFrameSize;
SDL_GetNumVideoCaptureFrameSizes;
SDL_GetVideoCaptureStatus;
SDL_StartVideoCapture;
SDL_AcquireVideoCaptureFrame;
SDL_ReleaseVideoCaptureFrame;
SDL_StopVideoCapture;
SDL_CloseVideoCapture;
SDL_GetVideoCaptureDevices;
# extra symbols go here (don't modify this line)
local: *;
};

View File

@ -948,3 +948,19 @@
#define SDL_SetPropertyWithCleanup SDL_SetPropertyWithCleanup_REAL
#define SDL_SetX11EventHook SDL_SetX11EventHook_REAL
#define SDL_GetGlobalProperties SDL_GetGlobalProperties_REAL
#define SDL_OpenVideoCapture SDL_OpenVideoCapture_REAL
#define SDL_SetVideoCaptureSpec SDL_SetVideoCaptureSpec_REAL
#define SDL_OpenVideoCaptureWithSpec SDL_OpenVideoCaptureWithSpec_REAL
#define SDL_GetVideoCaptureDeviceName SDL_GetVideoCaptureDeviceName_REAL
#define SDL_GetVideoCaptureSpec SDL_GetVideoCaptureSpec_REAL
#define SDL_GetVideoCaptureFormat SDL_GetVideoCaptureFormat_REAL
#define SDL_GetNumVideoCaptureFormats SDL_GetNumVideoCaptureFormats_REAL
#define SDL_GetVideoCaptureFrameSize SDL_GetVideoCaptureFrameSize_REAL
#define SDL_GetNumVideoCaptureFrameSizes SDL_GetNumVideoCaptureFrameSizes_REAL
#define SDL_GetVideoCaptureStatus SDL_GetVideoCaptureStatus_REAL
#define SDL_StartVideoCapture SDL_StartVideoCapture_REAL
#define SDL_AcquireVideoCaptureFrame SDL_AcquireVideoCaptureFrame_REAL
#define SDL_ReleaseVideoCaptureFrame SDL_ReleaseVideoCaptureFrame_REAL
#define SDL_StopVideoCapture SDL_StopVideoCapture_REAL
#define SDL_CloseVideoCapture SDL_CloseVideoCapture_REAL
#define SDL_GetVideoCaptureDevices SDL_GetVideoCaptureDevices_REAL

View File

@ -981,3 +981,19 @@ SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetDisplayProperties,(SDL_DisplayID a),(a),
SDL_DYNAPI_PROC(int,SDL_SetPropertyWithCleanup,(SDL_PropertiesID a, const char *b, void *c, void (SDLCALL *d)(void *userdata, void *value), void *e),(a,b,c,d,e),return)
SDL_DYNAPI_PROC(void,SDL_SetX11EventHook,(SDL_X11EventHook a, void *b),(a,b),)
SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGlobalProperties,(void),(),return)
SDL_DYNAPI_PROC(SDL_VideoCaptureDevice*,SDL_OpenVideoCapture,(SDL_VideoCaptureDeviceID a),(a),return)
SDL_DYNAPI_PROC(int,SDL_SetVideoCaptureSpec,(SDL_VideoCaptureDevice *a, const SDL_VideoCaptureSpec *b, SDL_VideoCaptureSpec *c, int d),(a,b,c,d),return)
SDL_DYNAPI_PROC(SDL_VideoCaptureDevice*,SDL_OpenVideoCaptureWithSpec,(SDL_VideoCaptureDeviceID a, const SDL_VideoCaptureSpec *b, SDL_VideoCaptureSpec *c, int d),(a,b,c,d),return)
SDL_DYNAPI_PROC(const char*,SDL_GetVideoCaptureDeviceName,(SDL_VideoCaptureDeviceID a),(a),return)
SDL_DYNAPI_PROC(int,SDL_GetVideoCaptureSpec,(SDL_VideoCaptureDevice *a, SDL_VideoCaptureSpec *b),(a,b),return)
SDL_DYNAPI_PROC(int,SDL_GetVideoCaptureFormat,(SDL_VideoCaptureDevice *a, int b, Uint32 *c),(a,b,c),return)
SDL_DYNAPI_PROC(int,SDL_GetNumVideoCaptureFormats,(SDL_VideoCaptureDevice *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_GetVideoCaptureFrameSize,(SDL_VideoCaptureDevice *a, Uint32 b, int c, int *d, int *e),(a,b,c,d,e),return)
SDL_DYNAPI_PROC(int,SDL_GetNumVideoCaptureFrameSizes,(SDL_VideoCaptureDevice *a, Uint32 b),(a,b),return)
SDL_DYNAPI_PROC(SDL_VideoCaptureStatus,SDL_GetVideoCaptureStatus,(SDL_VideoCaptureDevice *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_StartVideoCapture,(SDL_VideoCaptureDevice *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_AcquireVideoCaptureFrame,(SDL_VideoCaptureDevice *a, SDL_VideoCaptureFrame *b),(a,b),return)
SDL_DYNAPI_PROC(int,SDL_ReleaseVideoCaptureFrame,(SDL_VideoCaptureDevice *a, SDL_VideoCaptureFrame *b),(a,b),return)
SDL_DYNAPI_PROC(int,SDL_StopVideoCapture,(SDL_VideoCaptureDevice *a),(a),return)
SDL_DYNAPI_PROC(void,SDL_CloseVideoCapture,(SDL_VideoCaptureDevice *a),(a),)
SDL_DYNAPI_PROC(SDL_VideoCaptureDeviceID*,SDL_GetVideoCaptureDevices,(int *a),(a),return)

View File

@ -0,0 +1,90 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "../SDL_internal.h"
#ifndef SDL_sysvideocapture_h_
#define SDL_sysvideocapture_h_
#include "../SDL_list.h"
/* The SDL video_capture driver */
typedef struct SDL_VideoCaptureDevice SDL_VideoCaptureDevice;
/* Define the SDL video_capture driver structure */
struct SDL_VideoCaptureDevice
{
/* * * */
/* Data common to all devices */
/* The device's current video_capture specification */
SDL_VideoCaptureSpec spec;
/* Device name */
char *dev_name;
/* Current state flags */
SDL_AtomicInt shutdown;
SDL_AtomicInt enabled;
SDL_bool is_spec_set;
/* A mutex for locking the queue buffers */
SDL_Mutex *device_lock;
SDL_Mutex *acquiring_lock;
/* A thread to feed the video_capture device */
SDL_Thread *thread;
SDL_threadID threadid;
/* Queued buffers (if app not using callback). */
SDL_ListNode *buffer_queue;
/* * * */
/* Data private to this driver */
struct SDL_PrivateVideoCaptureData *hidden;
};
extern int OpenDevice(SDL_VideoCaptureDevice *_this);
extern void CloseDevice(SDL_VideoCaptureDevice *_this);
extern int InitDevice(SDL_VideoCaptureDevice *_this);
extern int GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec);
extern int StartCapture(SDL_VideoCaptureDevice *_this);
extern int StopCapture(SDL_VideoCaptureDevice *_this);
extern int AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame);
extern int ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame);
extern int GetNumFormats(SDL_VideoCaptureDevice *_this);
extern int GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format);
extern int GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format);
extern int GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height);
extern int GetDeviceName(int index, char *buf, int size);
extern int GetNumDevices(void);
extern SDL_bool check_all_device_closed(void);
extern SDL_bool check_device_playing(void);
#endif /* SDL_sysvideocapture_h_ */

View File

@ -31,6 +31,7 @@
#include "SDL_video_c.h"
#include "../events/SDL_events_c.h"
#include "../timer/SDL_timer_c.h"
#include "SDL_video_capture_c.h"
#ifdef SDL_VIDEO_OPENGL
#include <SDL3/SDL_opengl.h>
@ -443,6 +444,7 @@ int SDL_VideoInit(const char *driver_name)
SDL_bool init_keyboard = SDL_FALSE;
SDL_bool init_mouse = SDL_FALSE;
SDL_bool init_touch = SDL_FALSE;
SDL_bool init_video_capture = SDL_FALSE;
int i = 0;
/* Check to make sure we don't overwrite '_this' */
@ -471,6 +473,10 @@ int SDL_VideoInit(const char *driver_name)
goto pre_driver_error;
}
init_touch = SDL_TRUE;
if (SDL_VideoCaptureInit() < 0) {
goto pre_driver_error;
}
init_video_capture = SDL_TRUE;
/* Select the proper video driver */
video = NULL;
@ -565,6 +571,9 @@ int SDL_VideoInit(const char *driver_name)
pre_driver_error:
SDL_assert(_this == NULL);
if (init_video_capture) {
SDL_QuitVideoCapture();
}
if (init_touch) {
SDL_QuitTouch();
}
@ -3684,6 +3693,7 @@ void SDL_VideoQuit(void)
SDL_ClearClipboardData();
/* Halt event processing before doing anything else */
SDL_QuitVideoCapture();
SDL_QuitTouch();
SDL_QuitMouse();
SDL_QuitKeyboard();

View File

@ -0,0 +1,948 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL3/SDL.h"
#include "SDL3/SDL_video_capture.h"
#include "SDL_sysvideocapture.h"
#include "SDL_video_capture_c.h"
#include "SDL_pixels_c.h"
#include "../thread/SDL_systhread.h"
#define DEBUG_VIDEO_CAPTURE_CAPTURE 1
#ifdef SDL_VIDEO_CAPTURE
/* list node entries to share frames between SDL and user app */
typedef struct entry_t
{
SDL_VideoCaptureFrame frame;
} entry_t;
static SDL_VideoCaptureDevice *open_devices[16];
static void
close_device(SDL_VideoCaptureDevice *device)
{
if (!device) {
return;
}
SDL_AtomicSet(&device->shutdown, 1);
SDL_AtomicSet(&device->enabled, 1);
if (device->thread != NULL) {
SDL_WaitThread(device->thread, NULL);
}
if (device->device_lock != NULL) {
SDL_DestroyMutex(device->device_lock);
}
if (device->acquiring_lock != NULL) {
SDL_DestroyMutex(device->acquiring_lock);
}
{
int i, n = SDL_arraysize(open_devices);
for (i = 0; i < n; i++) {
if (open_devices[i] == device) {
open_devices[i] = NULL;
}
}
}
{
entry_t *entry = NULL;
while (device->buffer_queue != NULL) {
SDL_ListPop(&device->buffer_queue, (void**)&entry);
if (entry) {
SDL_VideoCaptureFrame f = entry->frame;
/* Release frames not acquired, if any */
if (f.timestampNS) {
ReleaseFrame(device, &f);
}
SDL_free(entry);
}
}
}
CloseDevice(device);
SDL_free(device->dev_name);
SDL_free(device);
}
/* Tell if all device are closed */
SDL_bool check_all_device_closed(void)
{
int i, n = SDL_arraysize(open_devices);
int all_closed = SDL_TRUE;
for (i = 0; i < n; i++) {
if (open_devices[i]) {
all_closed = SDL_FALSE;
break;
}
}
return all_closed;
}
/* Tell if at least one device is in playing state */
SDL_bool check_device_playing(void)
{
int i, n = SDL_arraysize(open_devices);
for (i = 0; i < n; i++) {
if (open_devices[i]) {
if (SDL_GetVideoCaptureStatus(open_devices[i]) == SDL_VIDEO_CAPTURE_PLAYING) {
return SDL_TRUE;
}
}
}
return SDL_FALSE;
}
#endif /* SDL_VIDEO_CAPTURE */
void
SDL_CloseVideoCapture(SDL_VideoCaptureDevice *device)
{
#ifdef SDL_VIDEO_CAPTURE
if (!device) {
SDL_InvalidParamError("device");
return;
}
close_device(device);
#endif
}
int
SDL_StartVideoCapture(SDL_VideoCaptureDevice *device)
{
#ifdef SDL_VIDEO_CAPTURE
SDL_VideoCaptureStatus status;
int result;
if (!device) {
return SDL_InvalidParamError("device");
}
if (device->is_spec_set == SDL_FALSE) {
return SDL_SetError("no spec set");
}
status = SDL_GetVideoCaptureStatus(device);
if (status != SDL_VIDEO_CAPTURE_INIT) {
return SDL_SetError("invalid state");
}
result = StartCapture(device);
if (result < 0) {
return result;
}
SDL_AtomicSet(&device->enabled, 1);
return 0;
#else
return SDL_Unsupported();
#endif
}
int
SDL_GetVideoCaptureSpec(SDL_VideoCaptureDevice *device, SDL_VideoCaptureSpec *spec)
{
#ifdef SDL_VIDEO_CAPTURE
if (!device) {
return SDL_InvalidParamError("device");
}
if (!spec) {
return SDL_InvalidParamError("spec");
}
SDL_zerop(spec);
return GetDeviceSpec(device, spec);
#else
return SDL_Unsupported();
#endif
}
int
SDL_StopVideoCapture(SDL_VideoCaptureDevice *device)
{
#ifdef SDL_VIDEO_CAPTURE
SDL_VideoCaptureStatus status;
int ret;
if (!device) {
return SDL_InvalidParamError("device");
}
status = SDL_GetVideoCaptureStatus(device);
if (status != SDL_VIDEO_CAPTURE_PLAYING) {
return SDL_SetError("invalid state");
}
SDL_AtomicSet(&device->enabled, 0);
SDL_AtomicSet(&device->shutdown, 1);
SDL_LockMutex(device->acquiring_lock);
ret = StopCapture(device);
SDL_UnlockMutex(device->acquiring_lock);
if (ret < 0) {
return -1;
}
return 0;
#else
return SDL_Unsupported();
#endif
}
#ifdef SDL_VIDEO_CAPTURE
/* Check spec has valid format and frame size */
static int
prepare_video_capturespec(SDL_VideoCaptureDevice *device, const SDL_VideoCaptureSpec *desired, SDL_VideoCaptureSpec *obtained, int allowed_changes)
{
/* Check format */
{
int i, num = SDL_GetNumVideoCaptureFormats(device);
int is_format_valid = 0;
for (i = 0; i < num; i++) {
Uint32 format;
if (SDL_GetVideoCaptureFormat(device, i, &format) == 0) {
if (format == desired->format && format != SDL_PIXELFORMAT_UNKNOWN) {
is_format_valid = 1;
obtained->format = format;
break;
}
}
}
if (!is_format_valid) {
if (allowed_changes) {
for (i = 0; i < num; i++) {
Uint32 format;
if (SDL_GetVideoCaptureFormat(device, i, &format) == 0) {
if (format != SDL_PIXELFORMAT_UNKNOWN) {
obtained->format = format;
is_format_valid = 1;
break;
}
}
}
} else {
SDL_SetError("Not allowed to change the format");
return -1;
}
}
if (!is_format_valid) {
SDL_SetError("Invalid format");
return -1;
}
}
/* Check frame size */
{
int i, num = SDL_GetNumVideoCaptureFrameSizes(device, obtained->format);
int is_framesize_valid = 0;
for (i = 0; i < num; i++) {
int w, h;
if (SDL_GetVideoCaptureFrameSize(device, obtained->format, i, &w, &h) == 0) {
if (desired->width == w && desired->height == h) {
is_framesize_valid = 1;
obtained->width = w;
obtained->height = h;
break;
}
}
}
if (!is_framesize_valid) {
if (allowed_changes) {
int w, h;
if (SDL_GetVideoCaptureFrameSize(device, obtained->format, 0, &w, &h) == 0) {
is_framesize_valid = 1;
obtained->width = w;
obtained->height = h;
}
} else {
SDL_SetError("Not allowed to change the frame size");
return -1;
}
}
if (!is_framesize_valid) {
SDL_SetError("Invalid frame size");
return -1;
}
}
return 0;
}
#endif /* SDL_VIDEO_CAPTURE */
const char *
SDL_GetVideoCaptureDeviceName(SDL_VideoCaptureDeviceID instance_id)
{
#ifdef SDL_VIDEO_CAPTURE
int index = instance_id - 1;
static char buf[256];
buf[0] = 0;
buf[255] = 0;
if (instance_id == 0) {
SDL_InvalidParamError("instance_id");
return NULL;
}
if (GetDeviceName(index, buf, sizeof (buf)) < 0) {
buf[0] = 0;
}
return buf;
#else
SDL_Unsupported();
return NULL;
#endif
}
SDL_VideoCaptureDeviceID *
SDL_GetVideoCaptureDevices(int *count)
{
int i;
#ifdef SDL_VIDEO_CAPTURE
int num = GetNumDevices();
#else
int num = 0;
#endif
SDL_VideoCaptureDeviceID *ret;
ret = (SDL_VideoCaptureDeviceID *)SDL_malloc((num + 1) * sizeof(*ret));
if (ret == NULL) {
SDL_OutOfMemory();
if (count) {
*count = 0;
}
return NULL;
}
for (i = 0; i < num; i++) {
ret[i] = i + 1;
}
ret[num] = 0;
if (count) {
*count = num;
}
return ret;
}
#ifdef SDL_VIDEO_CAPTURE
/* Video capture thread function */
static int SDLCALL
SDL_CaptureVideoThread(void *devicep)
{
const int delay = 20;
SDL_VideoCaptureDevice *device = (SDL_VideoCaptureDevice *) devicep;
#if DEBUG_VIDEO_CAPTURE_CAPTURE
SDL_Log("Start thread 'SDL_CaptureVideo'");
#endif
#ifdef SDL_VIDEO_DRIVER_ANDROID
// TODO
/*
{
// Set thread priority to THREAD_PRIORITY_VIDEO
extern void Android_JNI_VideoCaptureSetThreadPriority(int, int);
Android_JNI_VideoCaptureSetThreadPriority(device->iscapture, device);
}*/
#else
/* The video_capture mixing is always a high priority thread */
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
#endif
/* Perform any thread setup */
device->threadid = SDL_ThreadID();
/* Init state */
while (!SDL_AtomicGet(&device->enabled)) {
SDL_Delay(delay);
}
/* Loop, filling the video_capture buffers */
while (!SDL_AtomicGet(&device->shutdown)) {
SDL_VideoCaptureFrame f;
int ret;
entry_t *entry;
SDL_zero(f);
SDL_LockMutex(device->acquiring_lock);
ret = AcquireFrame(device, &f);
SDL_UnlockMutex(device->acquiring_lock);
if (ret == 0) {
if (f.num_planes == 0) {
continue;
}
}
if (ret < 0) {
/* Flag it as an error */
#if DEBUG_VIDEO_CAPTURE_CAPTURE
SDL_Log("dev[%p] error AcquireFrame: %d %s", (void *)device, ret, SDL_GetError());
#endif
f.num_planes = 0;
}
entry = SDL_malloc(sizeof (entry_t));
if (entry == NULL) {
goto error_mem;
}
entry->frame = f;
SDL_LockMutex(device->device_lock);
ret = SDL_ListAdd(&device->buffer_queue, entry);
SDL_UnlockMutex(device->device_lock);
if (ret < 0) {
SDL_free(entry);
goto error_mem;
}
}
#if DEBUG_VIDEO_CAPTURE_CAPTURE
SDL_Log("dev[%p] End thread 'SDL_CaptureVideo'", (void *)device);
#endif
return 0;
error_mem:
#if DEBUG_VIDEO_CAPTURE_CAPTURE
SDL_Log("dev[%p] End thread 'SDL_CaptureVideo' with error: %s", (void *)device, SDL_GetError());
#endif
SDL_AtomicSet(&device->shutdown, 1);
SDL_OutOfMemory();
return 0;
}
#endif
SDL_VideoCaptureDevice *
SDL_OpenVideoCapture(SDL_VideoCaptureDeviceID instance_id)
{
#ifdef SDL_VIDEO_CAPTURE
int i, n = SDL_arraysize(open_devices);
int id = -1;
SDL_VideoCaptureDevice *device = NULL;
const char *device_name = NULL;
if (!SDL_WasInit(SDL_INIT_VIDEO)) {
SDL_SetError("Video subsystem is not initialized");
goto error;
}
/* !!! FIXME: there is a race condition here if two devices open from two threads at once. */
/* Find an available device ID... */
for (i = 0; i < n; i++) {
if (open_devices[i] == NULL) {
id = i;
break;
}
}
if (id == -1) {
SDL_SetError("Too many open video capture devices");
goto error;
}
if (instance_id != 0) {
device_name = SDL_GetVideoCaptureDeviceName(instance_id);
if (device_name == NULL) {
goto error;
}
} else {
SDL_VideoCaptureDeviceID *devices = SDL_GetVideoCaptureDevices(NULL);
if (devices && devices[0]) {
device_name = SDL_GetVideoCaptureDeviceName(devices[0]);
SDL_free(devices);
}
}
/* Let the user override. */
{
const char *dev = SDL_getenv("SDL_VIDEO_CAPTURE_DEVICE_NAME");
if (dev && dev[0]) {
device_name = dev;
}
}
if (device_name == NULL) {
goto error;
}
device = (SDL_VideoCaptureDevice *) SDL_calloc(1, sizeof (SDL_VideoCaptureDevice));
if (device == NULL) {
SDL_OutOfMemory();
goto error;
}
device->dev_name = SDL_strdup(device_name);
SDL_AtomicSet(&device->shutdown, 0);
SDL_AtomicSet(&device->enabled, 0);
device->device_lock = SDL_CreateMutex();
if (device->device_lock == NULL) {
SDL_SetError("Couldn't create acquiring_lock");
goto error;
}
device->acquiring_lock = SDL_CreateMutex();
if (device->acquiring_lock == NULL) {
SDL_SetError("Couldn't create acquiring_lock");
goto error;
}
if (OpenDevice(device) < 0) {
goto error;
}
/* empty */
device->buffer_queue = NULL;
open_devices[id] = device; /* add it to our list of open devices. */
/* Start the video_capture thread */
{
const size_t stacksize = 64 * 1024;
char threadname[64];
SDL_snprintf(threadname, sizeof (threadname), "SDLVideoC%d", id);
device->thread = SDL_CreateThreadInternal(SDL_CaptureVideoThread, threadname, stacksize, device);
if (device->thread == NULL) {
SDL_SetError("Couldn't create video_capture thread");
goto error;
}
}
return device;
error:
close_device(device);
return NULL;
#else
SDL_Unsupported();
return NULL;
#endif /* SDL_VIDEO_CAPTURE */
}
int
SDL_SetVideoCaptureSpec(SDL_VideoCaptureDevice *device,
const SDL_VideoCaptureSpec *desired,
SDL_VideoCaptureSpec *obtained,
int allowed_changes)
{
#ifdef SDL_VIDEO_CAPTURE
SDL_VideoCaptureSpec _obtained;
SDL_VideoCaptureSpec _desired;
int result;
if (!device) {
return SDL_InvalidParamError("device");
}
if (device->is_spec_set == SDL_TRUE) {
return SDL_SetError("already configured");
}
if (!desired) {
SDL_zero(_desired);
desired = &_desired;
allowed_changes = SDL_VIDEO_CAPTURE_ALLOW_ANY_CHANGE;
} else {
/* in case desired == obtained */
_desired = *desired;
desired = &_desired;
}
if (!obtained) {
obtained = &_obtained;
}
SDL_zerop(obtained);
if (prepare_video_capturespec(device, desired, obtained, allowed_changes) < 0) {
return -1;
}
device->spec = *obtained;
result = InitDevice(device);
if (result < 0) {
return result;
}
*obtained = device->spec;
device->is_spec_set = SDL_TRUE;
return 0;
#else
SDL_zero(*obtained);
return SDL_Unsupported();
#endif /* SDL_VIDEO_CAPTURE */
}
int
SDL_AcquireVideoCaptureFrame(SDL_VideoCaptureDevice *device, SDL_VideoCaptureFrame *frame)
{
#ifdef SDL_VIDEO_CAPTURE
if (!device) {
return SDL_InvalidParamError("device");
}
if (!frame) {
return SDL_InvalidParamError("frame");
}
SDL_zerop(frame);
if (device->thread == NULL) {
int ret;
/* Wait for a frame */
while ((ret = AcquireFrame(device, frame)) == 0) {
if (frame->num_planes) {
return 0;
}
}
return -1;
} else {
entry_t *entry = NULL;
SDL_LockMutex(device->device_lock);
SDL_ListPop(&device->buffer_queue, (void**)&entry);
SDL_UnlockMutex(device->device_lock);
if (entry) {
*frame = entry->frame;
SDL_free(entry);
/* Error from thread */
if (frame->num_planes == 0 && frame->timestampNS == 0) {
return SDL_SetError("error from acquisition thread");
}
} else {
/* Queue is empty. Not an error. */
}
}
return 0;
#else
return SDL_Unsupported();
#endif /* SDL_VIDEO_CAPTURE */
}
int
SDL_ReleaseVideoCaptureFrame(SDL_VideoCaptureDevice *device, SDL_VideoCaptureFrame *frame)
{
#ifdef SDL_VIDEO_CAPTURE
if (!device) {
return SDL_InvalidParamError("device");
}
if (frame == NULL) {
return SDL_InvalidParamError("frame");
}
if (ReleaseFrame(device, frame) < 0) {
return -1;
}
SDL_zerop(frame);
return 0;
#else
return SDL_Unsupported();
#endif /* SDL_VIDEO_CAPTURE */
}
int
SDL_GetNumVideoCaptureFormats(SDL_VideoCaptureDevice *device)
{
#ifdef SDL_VIDEO_CAPTURE
if (!device) {
return SDL_InvalidParamError("device");
}
return GetNumFormats(device);
#else
return 0;
#endif /* SDL_VIDEO_CAPTURE */
}
int
SDL_GetVideoCaptureFormat(SDL_VideoCaptureDevice *device, int index, Uint32 *format)
{
#ifdef SDL_VIDEO_CAPTURE
if (!device) {
return SDL_InvalidParamError("device");
}
if (!format) {
return SDL_InvalidParamError("format");
}
*format = 0;
return GetFormat(device, index, format);
#else
return SDL_Unsupported();
#endif /* SDL_VIDEO_CAPTURE */
}
int
SDL_GetNumVideoCaptureFrameSizes(SDL_VideoCaptureDevice *device, Uint32 format)
{
#ifdef SDL_VIDEO_CAPTURE
if (!device) {
return SDL_InvalidParamError("device");
}
return GetNumFrameSizes(device, format);
#else
return 0;
#endif /* SDL_VIDEO_CAPTURE */
}
int
SDL_GetVideoCaptureFrameSize(SDL_VideoCaptureDevice *device, Uint32 format, int index, int *width, int *height)
{
#ifdef SDL_VIDEO_CAPTURE
if (!device) {
return SDL_InvalidParamError("device");
}
if (!width) {
return SDL_InvalidParamError("width");
}
if (!height) {
return SDL_InvalidParamError("height");
}
*width = 0;
*height = 0;
return GetFrameSize(device, format, index, width, height);
#else
return SDL_Unsupported();
#endif
}
SDL_VideoCaptureDevice *
SDL_OpenVideoCaptureWithSpec(
SDL_VideoCaptureDeviceID instance_id,
const SDL_VideoCaptureSpec *desired,
SDL_VideoCaptureSpec *obtained,
int allowed_changes)
{
#ifdef SDL_VIDEO_CAPTURE
SDL_VideoCaptureDevice *device;
if ((device = SDL_OpenVideoCapture(instance_id)) == NULL) {
return NULL;
}
if (SDL_SetVideoCaptureSpec(device, desired, obtained, allowed_changes) < 0) {
SDL_CloseVideoCapture(device);
return NULL;
}
return device;
#else
SDL_Unsupported();
return NULL;
#endif
}
SDL_VideoCaptureStatus
SDL_GetVideoCaptureStatus(SDL_VideoCaptureDevice *device)
{
#ifdef SDL_VIDEO_CAPTURE
if (device == NULL) {
return SDL_VIDEO_CAPTURE_INIT;
}
if (device->is_spec_set == SDL_FALSE) {
return SDL_VIDEO_CAPTURE_INIT;
}
if (SDL_AtomicGet(&device->shutdown)) {
return SDL_VIDEO_CAPTURE_STOPPED;
}
if (SDL_AtomicGet(&device->enabled)) {
return SDL_VIDEO_CAPTURE_PLAYING;
}
return SDL_VIDEO_CAPTURE_INIT;
#else
SDL_Unsupported();
return SDL_VIDEO_CAPTURE_FAIL;
#endif
}
int
SDL_VideoCaptureInit(void)
{
#ifdef SDL_VIDEO_CAPTURE
SDL_zeroa(open_devices);
return 0;
#else
return 0;
#endif
}
void
SDL_QuitVideoCapture(void)
{
#ifdef SDL_VIDEO_CAPTURE
int i, n = SDL_arraysize(open_devices);
for (i = 0; i < n; i++) {
close_device(open_devices[i]);
}
SDL_zeroa(open_devices);
#endif
}
#ifdef SDL_VIDEO_CAPTURE
#if defined(__linux__) && !defined(__ANDROID__)
/* See SDL_video_capture_v4l2.c */
#elif defined(__ANDROID__) && __ANDROID_API__ >= 24
/* See SDL_android_video_capture.c */
#elif defined(__IPHONEOS__) || defined(__MACOS__)
/* See SDL_video_capture_apple.m */
#else
int
OpenDevice(SDL_VideoCaptureDevice *_this)
{
return SDL_SetError("not implemented");
}
void
CloseDevice(SDL_VideoCaptureDevice *_this)
{
return;
}
int
InitDevice(SDL_VideoCaptureDevice *_this)
{
size_t size, pitch;
SDL_CalculateSize(_this->spec.format, _this->spec.width, _this->spec.height, &size, &pitch, SDL_FALSE);
SDL_Log("Buffer size: %d x %d", _this->spec.width, _this->spec.height);
return -1;
}
int
GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec)
{
return SDL_Unsupported();
}
int
StartCapture(SDL_VideoCaptureDevice *_this)
{
return SDL_Unsupported();
}
int
StopCapture(SDL_VideoCaptureDevice *_this)
{
return -1;
}
int
AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
{
return -1;
}
int
ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
{
return -1;
}
int
GetNumFormats(SDL_VideoCaptureDevice *_this)
{
return -1;
}
int
GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format)
{
return -1;
}
int
GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format)
{
return -1;
}
int
GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height)
{
return -1;
}
int
GetDeviceName(int index, char *buf, int size)
{
return -1;
}
int
GetNumDevices(void)
{
return -1;
}
#endif
#endif /* SDL_VIDEO_CAPTURE */

View File

@ -0,0 +1,615 @@
/*
Simple DirectMedia Layer
Copyright (C) 2021 Valve Corporation
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_VIDEO_CAPTURE
#include "SDL3/SDL.h"
#include "SDL3/SDL_video_capture.h"
#include "SDL_sysvideocapture.h"
#include "SDL_video_capture_c.h"
#include "../thread/SDL_systhread.h"
#if defined(HAVE_COREMEDIA) && defined(__MACOS__) && (__MAC_OS_X_VERSION_MAX_ALLOWED < 101500)
/* AVCaptureDeviceTypeBuiltInWideAngleCamera requires macOS SDK 10.15 */
#undef HAVE_COREMEDIA
#endif
#if TARGET_OS_TV
#undef HAVE_COREMEDIA
#endif
#ifndef HAVE_COREMEDIA
int InitDevice(SDL_VideoCaptureDevice *_this) {
return -1;
}
int OpenDevice(SDL_VideoCaptureDevice *_this) {
return -1;
}
int AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) {
return -1;
}
void CloseDevice(SDL_VideoCaptureDevice *_this) {
}
int GetDeviceName(int index, char *buf, int size) {
return -1;
}
int GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec) {
return -1;
}
int GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format) {
return -1;
}
int GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height) {
return -1;
}
int GetNumDevices(void) {
return 0;
}
int GetNumFormats(SDL_VideoCaptureDevice *_this) {
return 0;
}
int GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format) {
return 0;
}
int ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) {
return 0;
}
int StartCapture(SDL_VideoCaptureDevice *_this) {
return 0;
}
int StopCapture(SDL_VideoCaptureDevice *_this) {
return 0;
}
#else
#import <AVFoundation/AVFoundation.h>
#import <CoreMedia/CoreMedia.h>
/*
* Need to link with:: CoreMedia CoreVideo
*
* Add in pInfo.list:
* <key>NSCameraUsageDescription</key> <string>Access camera</string>
*
*
* MACOSX:
* Add to the Code Sign Entitlement file:
* <key>com.apple.security.device.camera</key> <true/>
*
*
* IOS:
*
* - Need to link with:: CoreMedia CoreVideo
* - Add #define SDL_VIDEO_CAPTURE 1
* to SDL_build_config_ios.h
*/
@class MySampleBufferDelegate;
struct SDL_PrivateVideoCaptureData
{
dispatch_queue_t queue;
MySampleBufferDelegate *delegate;
AVCaptureSession *session;
CMSimpleQueueRef frame_queue;
};
static NSString *
fourcc_to_nstring(Uint32 code)
{
Uint8 buf[4];
*(Uint32 *)buf = code;
return [NSString stringWithFormat:@"%c%c%c%c", buf[3], buf[2], buf[1], buf[0]];
}
static NSArray<AVCaptureDevice *> *
discover_devices()
{
NSArray *deviceType = @[AVCaptureDeviceTypeBuiltInWideAngleCamera];
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
discoverySessionWithDeviceTypes:deviceType
mediaType:AVMediaTypeVideo
position:AVCaptureDevicePositionUnspecified];
NSArray<AVCaptureDevice *> *devices = discoverySession.devices;
if ([devices count] > 0) {
return devices;
} else {
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if (captureDevice == nil) {
return devices;
} else {
NSArray<AVCaptureDevice *> *default_device = @[ captureDevice ];
return default_device;
}
}
return devices;
}
static AVCaptureDevice *
get_device_by_name(const char *dev_name)
{
NSArray<AVCaptureDevice *> *devices = discover_devices();
for (AVCaptureDevice *device in devices) {
char buf[1024];
NSString *cameraID = [device localizedName];
const char *str = [cameraID UTF8String];
SDL_snprintf(buf, sizeof (buf) - 1, "%s", str);
if (SDL_strcmp(buf, dev_name) == 0) {
return device;
}
}
return nil;
}
static Uint32
nsfourcc_to_sdlformat(NSString *nsfourcc)
{
const char *str = [nsfourcc UTF8String];
/* FIXME
* on IOS this mode gives 2 planes, and it's NV12
* on macos, 1 plane/ YVYU
*
*/
#ifdef __MACOS__
if (SDL_strcmp("420v", str) == 0) return SDL_PIXELFORMAT_YVYU;
#else
if (SDL_strcmp("420v", str) == 0) return SDL_PIXELFORMAT_NV12;
#endif
if (SDL_strcmp("yuvs", str) == 0) return SDL_PIXELFORMAT_UYVY;
if (SDL_strcmp("420f", str) == 0) return SDL_PIXELFORMAT_UNKNOWN;
SDL_Log("Unknown format '%s'", str);
return SDL_PIXELFORMAT_UNKNOWN;
}
static NSString *
sdlformat_to_nsfourcc(Uint32 fmt)
{
const char *str = "";
NSString *result;
#ifdef __MACOS__
if (fmt == SDL_PIXELFORMAT_YVYU) str = "420v";
#else
if (fmt == SDL_PIXELFORMAT_NV12) str = "420v";
#endif
if (fmt == SDL_PIXELFORMAT_UYVY) str = "yuvs";
result = [[NSString alloc] initWithUTF8String: str];
return result;
}
@interface MySampleBufferDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
@property struct SDL_PrivateVideoCaptureData *hidden;
- (void) set: (struct SDL_PrivateVideoCaptureData *) val;
@end
@implementation MySampleBufferDelegate
- (void) set: (struct SDL_PrivateVideoCaptureData *) val {
_hidden = val;
}
- (void) captureOutput:(AVCaptureOutput *)output
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *) connection {
CFRetain(sampleBuffer);
CMSimpleQueueEnqueue(_hidden->frame_queue, sampleBuffer);
}
- (void)captureOutput:(AVCaptureOutput *)output
didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
SDL_Log("Drop frame..");
}
@end
int
OpenDevice(SDL_VideoCaptureDevice *_this)
{
_this->hidden = (struct SDL_PrivateVideoCaptureData *) SDL_calloc(1, sizeof (struct SDL_PrivateVideoCaptureData));
if (_this->hidden == NULL) {
SDL_OutOfMemory();
goto error;
}
return 0;
error:
return -1;
}
void
CloseDevice(SDL_VideoCaptureDevice *_this)
{
if (!_this) {
return;
}
if (_this->hidden) {
AVCaptureSession *session = _this->hidden->session;
if (session) {
AVCaptureInput *input;
AVCaptureVideoDataOutput *output;
input = [session.inputs objectAtIndex:0];
[session removeInput:input];
output = (AVCaptureVideoDataOutput*)[session.outputs objectAtIndex:0];
[session removeOutput:output];
// TODO more cleanup ?
}
if (_this->hidden->frame_queue) {
CFRelease(_this->hidden->frame_queue);
}
SDL_free(_this->hidden);
_this->hidden = NULL;
}
}
int
InitDevice(SDL_VideoCaptureDevice *_this)
{
NSString *fmt = sdlformat_to_nsfourcc(_this->spec.format);
int w = _this->spec.width;
int h = _this->spec.height;
NSError *error = nil;
AVCaptureDevice *device = nil;
AVCaptureDeviceInput *input = nil;
AVCaptureVideoDataOutput *output = nil;
AVCaptureDeviceFormat *spec_format = nil;
#ifdef __MACOS__
if (@available(macOS 10.15, *)) {
/* good. */
} else {
return -1;
}
#endif
device = get_device_by_name(_this->dev_name);
if (!device) {
goto error;
}
_this->hidden->session = [[AVCaptureSession alloc] init];
if (_this->hidden->session == nil) {
goto error;
}
[_this->hidden->session setSessionPreset:AVCaptureSessionPresetHigh];
// Pick format that matches the spec
{
NSArray<AVCaptureDeviceFormat *> *formats = [device formats];
for (AVCaptureDeviceFormat *format in formats) {
CMFormatDescriptionRef formatDescription = [format formatDescription];
FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription);
NSString *str = fourcc_to_nstring(mediaSubType);
if (str == fmt) {
CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDescription);
if (dim.width == w && dim.height == h) {
spec_format = format;
break;
}
}
}
}
if (spec_format == nil) {
SDL_SetError("format not found");
goto error;
}
// Set format
if ([device lockForConfiguration:NULL] == YES) {
device.activeFormat = spec_format;
[device unlockForConfiguration];
} else {
SDL_SetError("Cannot lockForConfiguration");
goto error;
}
// Input
input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
SDL_SetError("Cannot create AVCaptureDeviceInput");
goto error;
}
// Output
output = [[AVCaptureVideoDataOutput alloc] init];
#ifdef __MACOS__
// FIXME this now fail on ios ... but not using anything works...
// Specify the pixel format
output.videoSettings =
[NSDictionary dictionaryWithObject:
[NSNumber numberWithInt:kCVPixelFormatType_422YpCbCr8]
forKey:(id)kCVPixelBufferPixelFormatTypeKey];
#endif
_this->hidden->delegate = [[MySampleBufferDelegate alloc] init];
[_this->hidden->delegate set:_this->hidden];
CMSimpleQueueCreate(kCFAllocatorDefault, 30 /* buffers */, &_this->hidden->frame_queue);
if (_this->hidden->frame_queue == nil) {
goto error;
}
_this->hidden->queue = dispatch_queue_create("my_queue", NULL);
[output setSampleBufferDelegate:_this->hidden->delegate queue:_this->hidden->queue];
if ([_this->hidden->session canAddInput:input] ){
[_this->hidden->session addInput:input];
} else {
SDL_SetError("Cannot add AVCaptureDeviceInput");
goto error;
}
if ([_this->hidden->session canAddOutput:output] ){
[_this->hidden->session addOutput:output];
} else {
SDL_SetError("Cannot add AVCaptureVideoDataOutput");
goto error;
}
[_this->hidden->session commitConfiguration];
return 0;
error:
return -1;
}
int
GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec)
{
if (spec) {
*spec = _this->spec;
return 0;
}
return -1;
}
int
StartCapture(SDL_VideoCaptureDevice *_this)
{
[_this->hidden->session startRunning];
return 0;
}
int
StopCapture(SDL_VideoCaptureDevice *_this)
{
[_this->hidden->session stopRunning];
return 0;
}
int
AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
{
if (CMSimpleQueueGetCount(_this->hidden->frame_queue) > 0) {
int i, numPlanes, planar;
CMSampleBufferRef sampleBuffer;
CVImageBufferRef image;
sampleBuffer = (CMSampleBufferRef)CMSimpleQueueDequeue(_this->hidden->frame_queue);
frame->internal = (void *) sampleBuffer;
frame->timestampNS = SDL_GetTicksNS();
i = 0;
image = CMSampleBufferGetImageBuffer(sampleBuffer);
numPlanes = CVPixelBufferGetPlaneCount(image);
planar = CVPixelBufferIsPlanar(image);
#if 0
int w = CVPixelBufferGetWidth(image);
int h = CVPixelBufferGetHeight(image);
int sz = CVPixelBufferGetDataSize(image);
int pitch = CVPixelBufferGetBytesPerRow(image);
SDL_Log("buffer planar=%d count:%d %d x %d sz=%d pitch=%d", planar, numPlanes, w, h, sz, pitch);
#endif
CVPixelBufferLockBaseAddress(image, 0);
if (planar == 0 && numPlanes == 0) {
frame->pitch[0] = CVPixelBufferGetBytesPerRow(image);
frame->data[0] = CVPixelBufferGetBaseAddress(image);
frame->num_planes = 1;
} else {
for (i = 0; i < numPlanes && i < 3; i++) {
int rowStride = 0;
uint8_t *data = NULL;
frame->num_planes += 1;
rowStride = CVPixelBufferGetBytesPerRowOfPlane(image, i);
data = CVPixelBufferGetBaseAddressOfPlane(image, i);
frame->data[i] = data;
frame->pitch[i] = rowStride;
}
}
/* Unlocked when frame is released */
} else {
// no frame
SDL_Delay(20); // TODO fix some delay
}
return 0;
}
int
ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
{
if (frame->internal){
CMSampleBufferRef sampleBuffer = (CMSampleBufferRef) frame->internal;
CVImageBufferRef image = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferUnlockBaseAddress(image, 0);
CFRelease(sampleBuffer);
}
return 0;
}
int
GetNumFormats(SDL_VideoCaptureDevice *_this)
{
AVCaptureDevice *device = get_device_by_name(_this->dev_name);
if (device) {
// LIST FORMATS
NSMutableOrderedSet<NSString *> *array_formats = [NSMutableOrderedSet new];
NSArray<AVCaptureDeviceFormat *> *formats = [device formats];
for (AVCaptureDeviceFormat *format in formats) {
// NSLog(@"%@", formats);
CMFormatDescriptionRef formatDescription = [format formatDescription];
//NSLog(@"%@", formatDescription);
FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription);
NSString *str = fourcc_to_nstring(mediaSubType);
[array_formats addObject:str];
}
return [array_formats count];
}
return 0;
}
int
GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format)
{
AVCaptureDevice *device = get_device_by_name(_this->dev_name);
if (device) {
// LIST FORMATS
NSMutableOrderedSet<NSString *> *array_formats = [NSMutableOrderedSet new];
NSArray<AVCaptureDeviceFormat *> *formats = [device formats];
NSString *str;
for (AVCaptureDeviceFormat *f in formats) {
FourCharCode mediaSubType;
CMFormatDescriptionRef formatDescription;
formatDescription = [f formatDescription];
mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription);
str = fourcc_to_nstring(mediaSubType);
[array_formats addObject:str];
}
str = array_formats[index];
*format = nsfourcc_to_sdlformat(str);
return 0;
}
return -1;
}
int
GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format)
{
AVCaptureDevice *device = get_device_by_name(_this->dev_name);
if (device) {
NSString *fmt = sdlformat_to_nsfourcc(format);
int count = 0;
NSArray<AVCaptureDeviceFormat *> *formats = [device formats];
for (AVCaptureDeviceFormat *f in formats) {
CMFormatDescriptionRef formatDescription = [f formatDescription];
FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription);
NSString *str = fourcc_to_nstring(mediaSubType);
if (str == fmt) {
count += 1;
}
}
return count;
}
return 0;
}
int
GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height)
{
AVCaptureDevice *device = get_device_by_name(_this->dev_name);
if (device) {
NSString *fmt = sdlformat_to_nsfourcc(format);
int count = 0;
NSArray<AVCaptureDeviceFormat *> *formats = [device formats];
for (AVCaptureDeviceFormat *f in formats) {
CMFormatDescriptionRef formatDescription = [f formatDescription];
FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription);
NSString *str = fourcc_to_nstring(mediaSubType);
if (str == fmt) {
if (index == count) {
CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDescription);
*width = dim.width;
*height = dim.height;
return 0;
}
count += 1;
}
}
}
return -1;
}
int
GetDeviceName(int index, char *buf, int size)
{
NSArray<AVCaptureDevice *> *devices = discover_devices();
if (index < [devices count]) {
AVCaptureDevice *device = devices[index];
NSString *cameraID = [device localizedName];
const char *str = [cameraID UTF8String];
SDL_snprintf(buf, size, "%s", str);
return 0;
}
return -1;
}
int
GetNumDevices(void)
{
NSArray<AVCaptureDevice *> *devices = discover_devices();
return [devices count];
}
#endif /* HAVE_COREMEDIA */
#endif /* SDL_VIDEO_CAPTURE */

View File

@ -0,0 +1,33 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "../SDL_internal.h"
#include "../../include/SDL3/SDL_video_capture.h"
#ifndef SDL_video_capture_c_h_
#define SDL_video_capture_c_h_
/* Initialize the video_capture subsystem */
int SDL_VideoCaptureInit(void);
/* Shutdown the video_capture subsystem */
void SDL_QuitVideoCapture(void);
#endif /* SDL_video_capture_c_h_ */

View File

@ -0,0 +1,965 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_VIDEO_CAPTURE
#include "SDL3/SDL.h"
#include "SDL3/SDL_video_capture.h"
#include "SDL_sysvideocapture.h"
#include "SDL_video_capture_c.h"
#include "SDL_pixels_c.h"
#include "../thread/SDL_systhread.h"
#define DEBUG_VIDEO_CAPTURE_CAPTURE 1
#if defined(__linux__) && !defined(__ANDROID__)
enum io_method {
IO_METHOD_READ,
IO_METHOD_MMAP,
IO_METHOD_USERPTR
};
struct buffer {
void *start;
size_t length;
int available; /* Is available in userspace */
};
struct SDL_PrivateVideoCaptureData
{
int fd;
enum io_method io;
int nb_buffers;
struct buffer *buffers;
int first_start;
int driver_pitch;
};
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h> /* low-level i/o */
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <linux/videodev2.h>
static int
xioctl(int fh, int request, void *arg)
{
int r;
do {
r = ioctl(fh, request, arg);
} while (r == -1 && errno == EINTR);
return r;
}
/* -1:error 1:frame 0:no frame*/
static int
acquire_frame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
{
struct v4l2_buffer buf;
int i;
int fd = _this->hidden->fd;
enum io_method io = _this->hidden->io;
size_t size = _this->hidden->buffers[0].length;
switch (io) {
case IO_METHOD_READ:
if (read(fd, _this->hidden->buffers[0].start, size) == -1) {
switch (errno) {
case EAGAIN:
return 0;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
return SDL_SetError("read");
}
}
frame->num_planes = 1;
frame->data[0] = _this->hidden->buffers[0].start;
frame->pitch[0] = _this->hidden->driver_pitch;
break;
case IO_METHOD_MMAP:
SDL_zero(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
switch (errno) {
case EAGAIN:
return 0;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
return SDL_SetError("VIDIOC_DQBUF: %d", errno);
}
}
if ((int)buf.index < 0 || (int)buf.index >= _this->hidden->nb_buffers) {
return SDL_SetError("invalid buffer index");
}
frame->num_planes = 1;
frame->data[0] = _this->hidden->buffers[buf.index].start;
frame->pitch[0] = _this->hidden->driver_pitch;
_this->hidden->buffers[buf.index].available = 1;
#if DEBUG_VIDEO_CAPTURE_CAPTURE
SDL_Log("debug mmap: image %d/%d num_planes:%d data[0]=%p", buf.index, _this->hidden->nb_buffers, frame->num_planes, (void*)frame->data[0]);
#endif
break;
case IO_METHOD_USERPTR:
SDL_zero(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_USERPTR;
if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
switch (errno) {
case EAGAIN:
return 0;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
return SDL_SetError("VIDIOC_DQBUF");
}
}
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
if (buf.m.userptr == (unsigned long)_this->hidden->buffers[i].start && buf.length == size) {
break;
}
}
if (i >= _this->hidden->nb_buffers) {
return SDL_SetError("invalid buffer index");
}
frame->num_planes = 1;
frame->data[0] = (void*)buf.m.userptr;
frame->pitch[0] = _this->hidden->driver_pitch;
_this->hidden->buffers[i].available = 1;
#if DEBUG_VIDEO_CAPTURE_CAPTURE
SDL_Log("debug userptr: image %d/%d num_planes:%d data[0]=%p", buf.index, _this->hidden->nb_buffers, frame->num_planes, (void*)frame->data[0]);
#endif
break;
}
return 1;
}
int
ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
{
struct v4l2_buffer buf;
int i;
int fd = _this->hidden->fd;
enum io_method io = _this->hidden->io;
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
if (frame->num_planes && frame->data[0] == _this->hidden->buffers[i].start) {
break;
}
}
if (i >= _this->hidden->nb_buffers) {
return SDL_SetError("invalid buffer index");
}
switch (io) {
case IO_METHOD_READ:
break;
case IO_METHOD_MMAP:
SDL_zero(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
return SDL_SetError("VIDIOC_QBUF");
}
_this->hidden->buffers[i].available = 0;
break;
case IO_METHOD_USERPTR:
SDL_zero(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_USERPTR;
buf.index = i;
buf.m.userptr = (unsigned long)frame->data[0];
buf.length = (int) _this->hidden->buffers[i].length;
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
return SDL_SetError("VIDIOC_QBUF");
}
_this->hidden->buffers[i].available = 0;
break;
}
return 0;
}
int
AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
{
fd_set fds;
struct timeval tv;
int ret;
int fd = _this->hidden->fd;
FD_ZERO(&fds);
FD_SET(fd, &fds);
/* Timeout. */
tv.tv_sec = 0;
tv.tv_usec = 300 * 1000;
ret = select(fd + 1, &fds, NULL, NULL, &tv);
if (ret == -1) {
if (errno == EINTR) {
#if DEBUG_VIDEO_CAPTURE_CAPTURE
SDL_Log("continue ..");
#endif
return 0;
}
return SDL_SetError("select");
}
if (ret == 0) {
/* Timeout. Not an error */
SDL_SetError("timeout select");
return 0;
}
ret = acquire_frame(_this, frame);
if (ret < 0) {
return -1;
}
if (ret == 1){
frame->timestampNS = SDL_GetTicksNS();
} else if (ret == 0) {
#if DEBUG_VIDEO_CAPTURE_CAPTURE
SDL_Log("No frame continue: %s", SDL_GetError());
#endif
}
/* EAGAIN - continue select loop. */
return 0;
}
int
StopCapture(SDL_VideoCaptureDevice *_this)
{
enum v4l2_buf_type type;
int fd = _this->hidden->fd;
enum io_method io = _this->hidden->io;
switch (io) {
case IO_METHOD_READ:
break;
case IO_METHOD_MMAP:
case IO_METHOD_USERPTR:
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_STREAMOFF, &type) == -1) {
return SDL_SetError("VIDIOC_STREAMOFF");
}
break;
}
return 0;
}
static int
enqueue_buffers(SDL_VideoCaptureDevice *_this)
{
int i;
int fd = _this->hidden->fd;
enum io_method io = _this->hidden->io;
switch (io) {
case IO_METHOD_READ:
break;
case IO_METHOD_MMAP:
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
if (_this->hidden->buffers[i].available == 0) {
struct v4l2_buffer buf;
SDL_zero(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
return SDL_SetError("VIDIOC_QBUF");
}
}
}
break;
case IO_METHOD_USERPTR:
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
if (_this->hidden->buffers[i].available == 0) {
struct v4l2_buffer buf;
SDL_zero(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_USERPTR;
buf.index = i;
buf.m.userptr = (unsigned long)_this->hidden->buffers[i].start;
buf.length = (int) _this->hidden->buffers[i].length;
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
return SDL_SetError("VIDIOC_QBUF");
}
}
}
break;
}
return 0;
}
static int
pre_enqueue_buffers(SDL_VideoCaptureDevice *_this)
{
struct v4l2_requestbuffers req;
int fd = _this->hidden->fd;
enum io_method io = _this->hidden->io;
switch (io) {
case IO_METHOD_READ:
break;
case IO_METHOD_MMAP:
{
SDL_zero(req);
req.count = _this->hidden->nb_buffers;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (xioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
if (errno == EINVAL) {
return SDL_SetError("Does not support memory mapping");
} else {
return SDL_SetError("VIDIOC_REQBUFS");
}
}
if (req.count < 2) {
return SDL_SetError("Insufficient buffer memory");
}
_this->hidden->nb_buffers = req.count;
}
break;
case IO_METHOD_USERPTR:
{
SDL_zero(req);
req.count = _this->hidden->nb_buffers;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_USERPTR;
if (xioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
if (errno == EINVAL) {
return SDL_SetError("Does not support user pointer i/o");
} else {
return SDL_SetError("VIDIOC_REQBUFS");
}
}
}
break;
}
return 0;
}
int
StartCapture(SDL_VideoCaptureDevice *_this)
{
enum v4l2_buf_type type;
int fd = _this->hidden->fd;
enum io_method io = _this->hidden->io;
if (_this->hidden->first_start == 0) {
_this->hidden->first_start = 1;
} else {
int old = _this->hidden->nb_buffers;
// TODO mmap; doesn't work with stop->start
#if 1
/* Can change nb_buffers for mmap */
if (pre_enqueue_buffers(_this) < 0) {
return -1;
}
if (old != _this->hidden->nb_buffers) {
SDL_SetError("different nb of buffers requested");
return -1;
}
#endif
_this->hidden->first_start = 1;
}
if (enqueue_buffers(_this) < 0) {
return -1;
}
switch (io) {
case IO_METHOD_READ:
break;
case IO_METHOD_MMAP:
case IO_METHOD_USERPTR:
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_STREAMON, &type) == -1) {
return SDL_SetError("VIDIOC_STREAMON");
}
break;
}
return 0;
}
static int alloc_buffer_read(SDL_VideoCaptureDevice *_this, size_t buffer_size)
{
_this->hidden->buffers[0].length = buffer_size;
_this->hidden->buffers[0].start = SDL_calloc(1, buffer_size);
if (!_this->hidden->buffers[0].start) {
return SDL_OutOfMemory();
}
return 0;
}
static int
alloc_buffer_mmap(SDL_VideoCaptureDevice *_this)
{
int fd = _this->hidden->fd;
int i;
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
struct v4l2_buffer buf;
SDL_zero(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (xioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
return SDL_SetError("VIDIOC_QUERYBUF");
}
_this->hidden->buffers[i].length = buf.length;
_this->hidden->buffers[i].start =
mmap(NULL /* start anywhere */,
buf.length,
PROT_READ | PROT_WRITE /* required */,
MAP_SHARED /* recommended */,
fd, buf.m.offset);
if (MAP_FAILED == _this->hidden->buffers[i].start) {
return SDL_SetError("mmap");
}
}
return 0;
}
static int
alloc_buffer_userp(SDL_VideoCaptureDevice *_this, size_t buffer_size)
{
int i;
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
_this->hidden->buffers[i].length = buffer_size;
_this->hidden->buffers[i].start = SDL_calloc(1, buffer_size);
if (!_this->hidden->buffers[i].start) {
return SDL_OutOfMemory();
}
}
return 0;
}
static Uint32
format_v4l2_2_sdl(Uint32 fmt)
{
switch (fmt) {
#define CASE(x, y) case x: return y
CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2);
CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_UNKNOWN);
#undef CASE
default:
SDL_Log("Unknown format V4L2_PIX_FORMAT '%d'", fmt);
return SDL_PIXELFORMAT_UNKNOWN;
}
}
static Uint32
format_sdl_2_v4l2(Uint32 fmt)
{
switch (fmt) {
#define CASE(y, x) case x: return y
CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2);
CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_UNKNOWN);
#undef CASE
default:
return 0;
}
}
int
GetNumFormats(SDL_VideoCaptureDevice *_this)
{
int fd = _this->hidden->fd;
int i = 0;
struct v4l2_fmtdesc fmtdesc;
SDL_zero(fmtdesc);
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while (ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc) == 0) {
fmtdesc.index++;
i++;
}
return i;
}
int
GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format)
{
int fd = _this->hidden->fd;
struct v4l2_fmtdesc fmtdesc;
SDL_zero(fmtdesc);
fmtdesc.index = index;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc) == 0) {
*format = format_v4l2_2_sdl(fmtdesc.pixelformat);
#if DEBUG_VIDEO_CAPTURE_CAPTURE
if (fmtdesc.flags & V4L2_FMT_FLAG_EMULATED) {
SDL_Log("%s format emulated", SDL_GetPixelFormatName(*format));
}
if (fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED) {
SDL_Log("%s format compressed", SDL_GetPixelFormatName(*format));
}
#endif
return 0;
}
return -1;
}
int
GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format)
{
int fd = _this->hidden->fd;
int i = 0;
struct v4l2_frmsizeenum frmsizeenum;
SDL_zero(frmsizeenum);
frmsizeenum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
frmsizeenum.pixel_format = format_sdl_2_v4l2(format);
while (ioctl(fd,VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == 0) {
frmsizeenum.index++;
if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
i++;
} else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
i += (1 + (frmsizeenum.stepwise.max_width - frmsizeenum.stepwise.min_width) / frmsizeenum.stepwise.step_width)
* (1 + (frmsizeenum.stepwise.max_height - frmsizeenum.stepwise.min_height) / frmsizeenum.stepwise.step_height);
} else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) {
SDL_SetError("V4L2_FRMSIZE_TYPE_CONTINUOUS not handled");
}
}
return i;
}
int
GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height)
{
int fd = _this->hidden->fd;
struct v4l2_frmsizeenum frmsizeenum;
int i = 0;
SDL_zero(frmsizeenum);
frmsizeenum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
frmsizeenum.pixel_format = format_sdl_2_v4l2(format);
while (ioctl(fd,VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == 0) {
frmsizeenum.index++;
if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
if (i == index) {
*width = frmsizeenum.discrete.width;
*height = frmsizeenum.discrete.height;
return 0;
}
i++;
} else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
unsigned int w;
for (w = frmsizeenum.stepwise.min_width; w <= frmsizeenum.stepwise.max_width; w += frmsizeenum.stepwise.step_width) {
unsigned int h;
for (h = frmsizeenum.stepwise.min_height; h <= frmsizeenum.stepwise.max_height; h += frmsizeenum.stepwise.step_height) {
if (i == index) {
*width = h;
*height = w;
return 0;
}
i++;
}
}
} else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) {
}
}
return -1;
}
static void
dbg_v4l2_pixelformat(const char *str, int f) {
SDL_Log("%s V4L2_format=%d %c%c%c%c", str, f,
(f >> 0) & 0xff,
(f >> 8) & 0xff,
(f >> 16) & 0xff,
(f >> 24) & 0xff);
}
int
GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec)
{
struct v4l2_format fmt;
int fd = _this->hidden->fd;
unsigned int min;
SDL_zero(fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/* Preserve original settings as set by v4l2-ctl for example */
if (xioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
return SDL_SetError("Error VIDIOC_G_FMT");
}
/* Buggy driver paranoia. */
min = fmt.fmt.pix.width * 2;
if (fmt.fmt.pix.bytesperline < min) {
fmt.fmt.pix.bytesperline = min;
}
min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
if (fmt.fmt.pix.sizeimage < min) {
fmt.fmt.pix.sizeimage = min;
}
//spec->width = fmt.fmt.pix.width;
//spec->height = fmt.fmt.pix.height;
_this->hidden->driver_pitch = fmt.fmt.pix.bytesperline;
//spec->format = format_v4l2_2_sdl(fmt.fmt.pix.pixelformat);
return 0;
}
int
InitDevice(SDL_VideoCaptureDevice *_this)
{
struct v4l2_cropcap cropcap;
struct v4l2_crop crop;
int fd = _this->hidden->fd;
enum io_method io = _this->hidden->io;
int ret = -1;
/* Select video input, video standard and tune here. */
SDL_zero(cropcap);
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0) {
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
crop.c = cropcap.defrect; /* reset to default */
if (xioctl(fd, VIDIOC_S_CROP, &crop) == -1) {
switch (errno) {
case EINVAL:
/* Cropping not supported. */
break;
default:
/* Errors ignored. */
break;
}
}
} else {
/* Errors ignored. */
}
{
struct v4l2_format fmt;
SDL_zero(fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = _this->spec.width;
fmt.fmt.pix.height = _this->spec.height;
fmt.fmt.pix.pixelformat = format_sdl_2_v4l2(_this->spec.format);
// fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
#if DEBUG_VIDEO_CAPTURE_CAPTURE
SDL_Log("set SDL format %s", SDL_GetPixelFormatName(_this->spec.format));
dbg_v4l2_pixelformat("set format", fmt.fmt.pix.pixelformat);
#endif
if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
return SDL_SetError("Error VIDIOC_S_FMT");
}
}
GetDeviceSpec(_this, &_this->spec);
if (pre_enqueue_buffers(_this) < 0) {
return -1;
}
{
_this->hidden->buffers = SDL_calloc(_this->hidden->nb_buffers, sizeof(*_this->hidden->buffers));
if (!_this->hidden->buffers) {
return SDL_OutOfMemory();
}
}
{
size_t size, pitch;
SDL_CalculateSize(_this->spec.format, _this->spec.width, _this->spec.height, &size, &pitch, SDL_FALSE);
switch (io) {
case IO_METHOD_READ:
ret = alloc_buffer_read(_this, size);
break;
case IO_METHOD_MMAP:
ret = alloc_buffer_mmap(_this);
break;
case IO_METHOD_USERPTR:
ret = alloc_buffer_userp(_this, size);
break;
}
}
if (ret < 0) {
return -1;
}
return 0;
}
void
CloseDevice(SDL_VideoCaptureDevice *_this)
{
if (!_this) {
return;
}
if (_this->hidden) {
if (_this->hidden->buffers) {
int i;
enum io_method io = _this->hidden->io;
switch (io) {
case IO_METHOD_READ:
SDL_free(_this->hidden->buffers[0].start);
break;
case IO_METHOD_MMAP:
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
if (munmap(_this->hidden->buffers[i].start, _this->hidden->buffers[i].length) == -1) {
SDL_SetError("munmap");
}
}
break;
case IO_METHOD_USERPTR:
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
SDL_free(_this->hidden->buffers[i].start);
}
break;
}
SDL_free(_this->hidden->buffers);
}
if (_this->hidden->fd != -1) {
if (close(_this->hidden->fd)) {
SDL_SetError("close video capture device");
}
}
SDL_free(_this->hidden);
_this->hidden = NULL;
}
}
int
OpenDevice(SDL_VideoCaptureDevice *_this)
{
struct stat st;
struct v4l2_capability cap;
int fd;
enum io_method io;
_this->hidden = (struct SDL_PrivateVideoCaptureData *) SDL_calloc(1, sizeof (struct SDL_PrivateVideoCaptureData));
if (_this->hidden == NULL) {
SDL_OutOfMemory();
return -1;
}
_this->hidden->fd = -1;
if (stat(_this->dev_name, &st) == -1) {
SDL_SetError("Cannot identify '%s': %d, %s", _this->dev_name, errno, strerror(errno));
return -1;
}
if (!S_ISCHR(st.st_mode)) {
SDL_SetError("%s is no device", _this->dev_name);
return -1;
}
fd = open(_this->dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);
if (fd == -1) {
SDL_SetError("Cannot open '%s': %d, %s", _this->dev_name, errno, strerror(errno));
return -1;
}
_this->hidden->fd = fd;
_this->hidden->io = IO_METHOD_MMAP;
// _this->hidden->io = IO_METHOD_USERPTR;
// _this->hidden->io = IO_METHOD_READ;
//
if (_this->hidden->io == IO_METHOD_READ) {
_this->hidden->nb_buffers = 1;
} else {
_this->hidden->nb_buffers = 8; /* Number of image as internal buffer, */
}
io = _this->hidden->io;
if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
if (errno == EINVAL) {
return SDL_SetError("%s is no V4L2 device", _this->dev_name);
} else {
return SDL_SetError("Error VIDIOC_QUERYCAP errno=%d device%s is no V4L2 device", errno, _this->dev_name);
}
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
return SDL_SetError("%s is no video capture device", _this->dev_name);
}
#if 0
if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
SDL_Log("%s is video capture device - single plane", _this->dev_name);
}
if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE)) {
SDL_Log("%s is video capture device - multiple planes", _this->dev_name);
}
#endif
switch (io) {
case IO_METHOD_READ:
if (!(cap.capabilities & V4L2_CAP_READWRITE)) {
return SDL_SetError("%s does not support read i/o", _this->dev_name);
}
break;
case IO_METHOD_MMAP:
case IO_METHOD_USERPTR:
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
return SDL_SetError("%s does not support streaming i/o", _this->dev_name);
}
break;
}
return 0;
}
int
GetDeviceName(int index, char *buf, int size) {
SDL_snprintf(buf, size, "/dev/video%d", index);
return 0;
}
int
GetNumDevices(void) {
int num;
for (num = 0; num < 128; num++) {
static char buf[256];
buf[0] = 0;
buf[255] = 0;
GetDeviceName(num, buf, sizeof (buf));
SDL_RWops *src = SDL_RWFromFile(buf, "rb");
if (src == NULL) {
// When file does not exist, an error is set. Clear it.
SDL_ClearError();
return num;
}
SDL_RWclose(src);
}
return num;
}
#endif
#endif /* SDL_VIDEO_CAPTURE */

View File

@ -0,0 +1,678 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL3/SDL.h"
#include "SDL3/SDL_video_capture.h"
#include "../SDL_sysvideocapture.h"
#include "../SDL_video_capture_c.h"
#include "../SDL_pixels_c.h"
#include "../../thread/SDL_systhread.h"
#define DEBUG_VIDEO_CAPTURE_CAPTURE 1
#if defined(__ANDROID__) && __ANDROID_API__ >= 24
/*
* APP_PLATFORM=android-24
* minSdkVersion=24
*
* link with: -lcamera2ndk -lmediandk
*
* AndroidManifest.xml:
* <uses-permission android:name="android.permission.CAMERA"></uses-permission>
* <uses-feature android:name="android.hardware.camera" />
*
*
* Add: #define SDL_VIDEO_CAPTURE 1
* in: include/build_config/SDL_build_config_android.h
*
*
* Very likely SDL must be build with YUV support (done by default)
*
* https://developer.android.com/reference/android/hardware/camera2/CameraManager
* "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler),
* before configuring sessions on any of the camera devices. * "
*/
#include <camera/NdkCameraDevice.h>
#include <camera/NdkCameraManager.h>
#include <media/NdkImage.h>
#include <media/NdkImageReader.h>
#include "../../core/android/SDL_android.h"
static ACameraManager *cameraMgr = NULL;
static ACameraIdList *cameraIdList = NULL;
static void
create_cameraMgr(void)
{
if (cameraMgr == NULL) {
if (!Android_JNI_RequestPermission("android.permission.CAMERA")) {
SDL_SetError("This app doesn't have CAMERA permission");
return;
}
cameraMgr = ACameraManager_create();
if (cameraMgr == NULL) {
SDL_Log("Error creating ACameraManager");
} else {
SDL_Log("Create ACameraManager");
}
}
}
static void
delete_cameraMgr(void)
{
if (cameraIdList) {
ACameraManager_deleteCameraIdList(cameraIdList);
cameraIdList = NULL;
}
if (cameraMgr) {
ACameraManager_delete(cameraMgr);
cameraMgr = NULL;
}
}
struct SDL_PrivateVideoCaptureData
{
ACameraDevice *device;
ACameraCaptureSession *session;
ACameraDevice_StateCallbacks dev_callbacks;
ACameraCaptureSession_stateCallbacks capture_callbacks;
ACaptureSessionOutputContainer *sessionOutputContainer;
AImageReader *reader;
int num_formats;
int count_formats[6]; // see format_2_id
};
/**/
#define FORMAT_SDL SDL_PIXELFORMAT_NV12
static int
format_2_id(int fmt) {
switch (fmt) {
#define CASE(x, y) case x: return y
CASE(FORMAT_SDL, 0);
CASE(SDL_PIXELFORMAT_RGB565, 1);
CASE(SDL_PIXELFORMAT_RGB888, 2);
CASE(SDL_PIXELFORMAT_RGBA8888, 3);
CASE(SDL_PIXELFORMAT_RGBX8888, 4);
CASE(SDL_PIXELFORMAT_UNKNOWN, 5);
#undef CASE
default:
return 5;
}
}
static int
id_2_format(int fmt) {
switch (fmt) {
#define CASE(x, y) case y: return x
CASE(FORMAT_SDL, 0);
CASE(SDL_PIXELFORMAT_RGB565, 1);
CASE(SDL_PIXELFORMAT_RGB888, 2);
CASE(SDL_PIXELFORMAT_RGBA8888, 3);
CASE(SDL_PIXELFORMAT_RGBX8888, 4);
CASE(SDL_PIXELFORMAT_UNKNOWN, 5);
#undef CASE
default:
return SDL_PIXELFORMAT_UNKNOWN;
}
}
static Uint32
format_android_2_sdl(Uint32 fmt)
{
switch (fmt) {
#define CASE(x, y) case x: return y
CASE(AIMAGE_FORMAT_YUV_420_888, FORMAT_SDL);
CASE(AIMAGE_FORMAT_RGB_565, SDL_PIXELFORMAT_RGB565);
CASE(AIMAGE_FORMAT_RGB_888, SDL_PIXELFORMAT_RGB888);
CASE(AIMAGE_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888);
CASE(AIMAGE_FORMAT_RGBX_8888, SDL_PIXELFORMAT_RGBX8888);
CASE(AIMAGE_FORMAT_RGBA_FP16, SDL_PIXELFORMAT_UNKNOWN); // 64bits
CASE(AIMAGE_FORMAT_RAW_PRIVATE, SDL_PIXELFORMAT_UNKNOWN);
CASE(AIMAGE_FORMAT_JPEG, SDL_PIXELFORMAT_UNKNOWN);
#undef CASE
default:
SDL_Log("Unknown format AIMAGE_FORMAT '%d'", fmt);
return SDL_PIXELFORMAT_UNKNOWN;
}
}
static Uint32
format_sdl_2_android(Uint32 fmt)
{
switch (fmt) {
#define CASE(x, y) case y: return x
CASE(AIMAGE_FORMAT_YUV_420_888, FORMAT_SDL);
CASE(AIMAGE_FORMAT_RGB_565, SDL_PIXELFORMAT_RGB565);
CASE(AIMAGE_FORMAT_RGB_888, SDL_PIXELFORMAT_RGB888);
CASE(AIMAGE_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888);
CASE(AIMAGE_FORMAT_RGBX_8888, SDL_PIXELFORMAT_RGBX8888);
#undef CASE
default:
return 0;
}
}
static void
onDisconnected(void *context, ACameraDevice *device)
{
// SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context;
SDL_Log("CB onDisconnected");
}
static void
onError(void *context, ACameraDevice *device, int error)
{
// SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context;
SDL_Log("CB onError");
}
static void
onClosed(void* context, ACameraCaptureSession *session)
{
// SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context;
SDL_Log("CB onClosed");
}
static void
onReady(void* context, ACameraCaptureSession *session)
{
// SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context;
SDL_Log("CB onReady");
}
static void
onActive(void* context, ACameraCaptureSession *session)
{
// SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context;
SDL_Log("CB onActive");
}
int
OpenDevice(SDL_VideoCaptureDevice *_this)
{
camera_status_t res;
/* Cannot open a second camera, while the first one is opened.
* If you want to play several camera, they must all be opened first, then played.
*
* https://developer.android.com/reference/android/hardware/camera2/CameraManager
* "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler),
* before configuring sessions on any of the camera devices. * "
*
*/
if (check_device_playing()) {
return SDL_SetError("A camera is already playing");
}
_this->hidden = (struct SDL_PrivateVideoCaptureData *) SDL_calloc(1, sizeof (struct SDL_PrivateVideoCaptureData));
if (_this->hidden == NULL) {
return SDL_OutOfMemory();
}
create_cameraMgr();
_this->hidden->dev_callbacks.context = (void *) _this;
_this->hidden->dev_callbacks.onDisconnected = onDisconnected;
_this->hidden->dev_callbacks.onError = onError;
res = ACameraManager_openCamera(cameraMgr, _this->dev_name, &_this->hidden->dev_callbacks, &_this->hidden->device);
if (res != ACAMERA_OK) {
return SDL_SetError("Failed to open camera");
}
return 0;
}
void
CloseDevice(SDL_VideoCaptureDevice *_this)
{
if (_this && _this->hidden) {
if (_this->hidden->session) {
ACameraCaptureSession_close(_this->hidden->session);
}
if (_this->hidden->sessionOutputContainer) {
ACaptureSessionOutputContainer_free(_this->hidden->sessionOutputContainer);
}
if (_this->hidden->reader) {
AImageReader_delete(_this->hidden->reader);
}
if (_this->hidden->device) {
ACameraDevice_close(_this->hidden->device);
}
SDL_free(_this->hidden);
_this->hidden = NULL;
}
if (check_all_device_closed()) {
delete_cameraMgr();
}
}
int
InitDevice(SDL_VideoCaptureDevice *_this)
{
size_t size, pitch;
SDL_CalculateSize(_this->spec.format, _this->spec.width, _this->spec.height, &size, &pitch, SDL_FALSE);
SDL_Log("Buffer size: %d x %d", _this->spec.width, _this->spec.height);
return 0;
}
int
GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec)
{
if (spec) {
*spec = _this->spec;
return 0;
}
return -1;
}
int
StartCapture(SDL_VideoCaptureDevice *_this)
{
camera_status_t res;
media_status_t res2;
ANativeWindow *window = NULL;
ACaptureSessionOutput *sessionOutput;
ACameraOutputTarget *outputTarget;
ACaptureRequest *request;
res2 = AImageReader_new(_this->spec.width, _this->spec.height, format_sdl_2_android(_this->spec.format), 10 /* nb buffers */, &_this->hidden->reader);
if (res2 != AMEDIA_OK) {
SDL_SetError("Error AImageReader_new");
goto error;
}
res2 = AImageReader_getWindow(_this->hidden->reader, &window);
if (res2 != AMEDIA_OK) {
SDL_SetError("Error AImageReader_new");
goto error;
}
res = ACaptureSessionOutput_create(window, &sessionOutput);
if (res != ACAMERA_OK) {
SDL_SetError("Error ACaptureSessionOutput_create");
goto error;
}
res = ACaptureSessionOutputContainer_create(&_this->hidden->sessionOutputContainer);
if (res != ACAMERA_OK) {
SDL_SetError("Error ACaptureSessionOutputContainer_create");
goto error;
}
res = ACaptureSessionOutputContainer_add(_this->hidden->sessionOutputContainer, sessionOutput);
if (res != ACAMERA_OK) {
SDL_SetError("Error ACaptureSessionOutputContainer_add");
goto error;
}
res = ACameraOutputTarget_create(window, &outputTarget);
if (res != ACAMERA_OK) {
SDL_SetError("Error ACameraOutputTarget_create");
goto error;
}
res = ACameraDevice_createCaptureRequest(_this->hidden->device, TEMPLATE_RECORD, &request);
if (res != ACAMERA_OK) {
SDL_SetError("Error ACameraDevice_createCaptureRequest");
goto error;
}
res = ACaptureRequest_addTarget(request, outputTarget);
if (res != ACAMERA_OK) {
SDL_SetError("Error ACaptureRequest_addTarget");
goto error;
}
_this->hidden->capture_callbacks.context = (void *) _this;
_this->hidden->capture_callbacks.onClosed = onClosed;
_this->hidden->capture_callbacks.onReady = onReady;
_this->hidden->capture_callbacks.onActive = onActive;
res = ACameraDevice_createCaptureSession(_this->hidden->device,
_this->hidden->sessionOutputContainer,
&_this->hidden->capture_callbacks,
&_this->hidden->session);
if (res != ACAMERA_OK) {
SDL_SetError("Error ACameraDevice_createCaptureSession");
goto error;
}
res = ACameraCaptureSession_setRepeatingRequest(_this->hidden->session, NULL, 1, &request, NULL);
if (res != ACAMERA_OK) {
SDL_SetError("Error ACameraDevice_createCaptureSession");
goto error;
}
return 0;
error:
return -1;
}
int
StopCapture(SDL_VideoCaptureDevice *_this)
{
ACameraCaptureSession_close(_this->hidden->session);
_this->hidden->session = NULL;
return 0;
}
int
AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
{
media_status_t res;
AImage *image;
res = AImageReader_acquireNextImage(_this->hidden->reader, &image);
/* We could also use this one:
res = AImageReader_acquireLatestImage(_this->hidden->reader, &image);
*/
if (res == AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE ) {
SDL_Delay(20); // TODO fix some delay
#if DEBUG_VIDEO_CAPTURE_CAPTURE
// SDL_Log("AImageReader_acquireNextImage: AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE");
#endif
return 0;
} else if (res == AMEDIA_OK ) {
int i = 0;
int32_t numPlanes = 0;
AImage_getNumberOfPlanes(image, &numPlanes);
frame->timestampNS = SDL_GetTicksNS();
for (i = 0; i < numPlanes && i < 3; i++) {
int dataLength = 0;
int rowStride = 0;
uint8_t *data = NULL;
frame->num_planes += 1;
AImage_getPlaneRowStride(image, i, &rowStride);
res = AImage_getPlaneData(image, i, &data, &dataLength);
if (res == AMEDIA_OK) {
frame->data[i] = data;
frame->pitch[i] = rowStride;
}
}
if (frame->num_planes == 3) {
/* plane 2 and 3 are interleaved NV12. SDL only takes two planes for this format */
int pixelStride = 0;
AImage_getPlanePixelStride(image, 1, &pixelStride);
if (pixelStride == 2) {
frame->num_planes -= 1;
}
}
frame->internal = (void*)image;
return 0;
} else if (res == AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED) {
SDL_SetError("AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED");
} else {
SDL_SetError("AImageReader_acquireNextImage: %d", res);
}
return -1;
}
int
ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
{
if (frame->internal){
AImage_delete((AImage *)frame->internal);
}
return 0;
}
int
GetNumFormats(SDL_VideoCaptureDevice *_this)
{
camera_status_t res;
int i;
int unknown = 0;
ACameraMetadata *metadata;
ACameraMetadata_const_entry entry;
if (_this->hidden->num_formats != 0) {
return _this->hidden->num_formats;
}
res = ACameraManager_getCameraCharacteristics(cameraMgr, _this->dev_name, &metadata);
if (res != ACAMERA_OK) {
return -1;
}
res = ACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry);
if (res != ACAMERA_OK) {
return -1;
}
SDL_Log("got entry ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS");
for (i = 0; i < entry.count; i += 4) {
int32_t format = entry.data.i32[i + 0];
int32_t type = entry.data.i32[i + 3];
Uint32 fmt;
if (type == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) {
continue;
}
fmt = format_android_2_sdl(format);
_this->hidden->count_formats[format_2_id(fmt)] += 1;
#if DEBUG_VIDEO_CAPTURE_CAPTURE
if (fmt != SDL_PIXELFORMAT_UNKNOWN) {
int w = entry.data.i32[i + 1];
int h = entry.data.i32[i + 2];
SDL_Log("Got format android 0x%08x -> %s %d x %d", format, SDL_GetPixelFormatName(fmt), w, h);
} else {
unknown += 1;
}
#endif
}
#if DEBUG_VIDEO_CAPTURE_CAPTURE
if (unknown) {
SDL_Log("Got unknown android");
}
#endif
if ( _this->hidden->count_formats[0]) _this->hidden->num_formats += 1;
if ( _this->hidden->count_formats[1]) _this->hidden->num_formats += 1;
if ( _this->hidden->count_formats[2]) _this->hidden->num_formats += 1;
if ( _this->hidden->count_formats[3]) _this->hidden->num_formats += 1;
if ( _this->hidden->count_formats[4]) _this->hidden->num_formats += 1;
if ( _this->hidden->count_formats[5]) _this->hidden->num_formats += 1;
return _this->hidden->num_formats;
}
int
GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format)
{
int i;
int i2 = 0;
if (_this->hidden->num_formats == 0) {
GetNumFormats(_this);
}
if (index < 0 || index >= _this->hidden->num_formats) {
return -1;
}
for (i = 0; i < SDL_arraysize(_this->hidden->count_formats); i++) {
if (_this->hidden->count_formats[i] == 0) {
continue;
}
if (i2 == index) {
*format = id_2_format(i);
}
i2++;
}
return 0;
}
int
GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format)
{
int i, i2 = 0, index;
if (_this->hidden->num_formats == 0) {
GetNumFormats(_this);
}
index = format_2_id(format);
for (i = 0; i < SDL_arraysize(_this->hidden->count_formats); i++) {
if (_this->hidden->count_formats[i] == 0) {
continue;
}
if (i2 == index) {
/* number of resolution for this format */
return _this->hidden->count_formats[i];
}
i2++;
}
return -1;
}
int
GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height)
{
camera_status_t res;
int i, i2 = 0;
ACameraMetadata *metadata;
ACameraMetadata_const_entry entry;
if (_this->hidden->num_formats == 0) {
GetNumFormats(_this);
}
res = ACameraManager_getCameraCharacteristics(cameraMgr, _this->dev_name, &metadata);
if (res != ACAMERA_OK) {
return -1;
}
res = ACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry);
if (res != ACAMERA_OK) {
return -1;
}
for (i = 0; i < entry.count; i += 4) {
int32_t f = entry.data.i32[i + 0];
int w = entry.data.i32[i + 1];
int h = entry.data.i32[i + 2];
int32_t type = entry.data.i32[i + 3];
Uint32 fmt;
if (type == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) {
continue;
}
fmt = format_android_2_sdl(f);
if (fmt != format) {
continue;
}
if (i2 == index) {
*width = w;
*height = h;
return 0;
}
i2++;
}
return -1;
}
int
GetDeviceName(int index, char *buf, int size)
{
create_cameraMgr();
if (cameraIdList == NULL) {
GetNumDevices();
}
if (cameraIdList) {
if (index >= 0 && index < cameraIdList->numCameras) {
SDL_snprintf(buf, size, "%s", cameraIdList->cameraIds[index]);
return 0;
}
}
return -1;
}
int
GetNumDevices(void)
{
camera_status_t res;
create_cameraMgr();
if (cameraIdList) {
ACameraManager_deleteCameraIdList(cameraIdList);
cameraIdList = NULL;
}
res = ACameraManager_getCameraIdList(cameraMgr, &cameraIdList);
if (res == ACAMERA_OK) {
if (cameraIdList) {
return cameraIdList->numCameras;
}
}
return -1;
}
#endif

View File

@ -358,6 +358,8 @@ add_sdl_test_executable(teststreaming NEEDS_RESOURCES TESTUTILS SOURCES teststre
add_sdl_test_executable(testtimer NONINTERACTIVE NONINTERACTIVE_ARGS --no-interactive NONINTERACTIVE_TIMEOUT 60 SOURCES testtimer.c)
add_sdl_test_executable(testurl SOURCES testurl.c)
add_sdl_test_executable(testver NONINTERACTIVE SOURCES testver.c)
add_sdl_test_executable(testvideocapture SOURCES testvideocapture.c)
add_sdl_test_executable(testvideocaptureminimal SOURCES testvideocaptureminimal.c)
add_sdl_test_executable(testviewport NEEDS_RESOURCES TESTUTILS SOURCES testviewport.c)
add_sdl_test_executable(testwm SOURCES testwm.c)
add_sdl_test_executable(testyuv NONINTERACTIVE NONINTERACTIVE_ARGS "--automated" NEEDS_RESOURCES TESTUTILS SOURCES testyuv.c testyuv_cvt.c)

770
test/testvideocapture.c Normal file
View File

@ -0,0 +1,770 @@
/*
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely.
*/
#include "SDL3/SDL_main.h"
#include "SDL3/SDL.h"
#include "SDL3/SDL_test.h"
#include "SDL3/SDL_video_capture.h"
#include <stdio.h>
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
static const char *usage = "\
\n\
=========================================================================\n\
\n\
Use keyboards:\n\
o: open first video capture device. (close previously opened)\n\
l: switch to, and list video capture devices\n\
i: information about status (Init, Playing, Stopped)\n\
f: formats and resolutions available\n\
s: start / stop capture\n\
h: display help\n\
esc: exit \n\
\n\
=========================================================================\n\
\n\
";
typedef struct {
Uint64 next_check;
int frame_counter;
int check_delay;
double last_fps;
} measure_fps_t;
static void
update_fps(measure_fps_t *m)
{
Uint64 now = SDL_GetTicks();
Uint64 deadline;
m->frame_counter++;
if (m->check_delay == 0) {
m->check_delay = 1500;
}
deadline = m->next_check;
if (now >= deadline) {
/* Print out some timing information */
const Uint64 then = m->next_check - m->check_delay;
m->last_fps = ((double) m->frame_counter * 1000) / (now - then);
m->next_check = now + m->check_delay;
m->frame_counter = 0;
}
}
#if defined(__linux__) && !defined(__ANDROID__)
static void load_average(float *val)
{
FILE *fp = 0;
char line[1024];
fp = fopen("/proc/loadavg", "rt");
if (fp) {
char *s = fgets(line, sizeof(line), fp);
if (s) {
SDL_sscanf(s, "%f", val);
}
fclose(fp);
}
}
#endif
struct data_capture_t {
SDL_VideoCaptureDevice *device;
SDL_VideoCaptureSpec obtained;
int stopped;
SDL_VideoCaptureFrame frame_current;
measure_fps_t fps_capture;
SDL_Texture *texture;
int texture_updated;
};
#define SAVE_CAPTURE_STATE(x) \
data_capture_tab[(x)].device = device; \
data_capture_tab[(x)].obtained = obtained; \
data_capture_tab[(x)].stopped = stopped; \
data_capture_tab[(x)].frame_current = frame_current; \
data_capture_tab[(x)].fps_capture = fps_capture; \
data_capture_tab[(x)].texture = texture; \
data_capture_tab[(x)].texture_updated = texture_updated; \
#define RESTORE_CAPTURE_STATE(x) \
device = data_capture_tab[(x)].device; \
obtained = data_capture_tab[(x)].obtained; \
stopped = data_capture_tab[(x)].stopped; \
frame_current = data_capture_tab[(x)].frame_current; \
fps_capture = data_capture_tab[(x)].fps_capture; \
texture = data_capture_tab[(x)].texture; \
texture_updated = data_capture_tab[(x)].texture_updated; \
static SDL_VideoCaptureDeviceID get_instance_id(int index) {
int ret = 0;
int num = 0;
SDL_VideoCaptureDeviceID *devices;
devices = SDL_GetVideoCaptureDevices(&num);
if (devices) {
if (index >= 0 && index < num) {
ret = devices[index];
}
SDL_free(devices);
}
if (ret == 0) {
SDL_Log("invalid index");
}
return ret;
}
int main(int argc, char **argv)
{
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
SDL_Event evt;
int quit = 0;
SDLTest_CommonState *state;
int current_dev = 0;
measure_fps_t fps_main;
SDL_FRect r_playstop = { 50, 50, 120, 50 };
SDL_FRect r_close = { 50 + (120 + 50) * 1, 50, 120, 50 };
SDL_FRect r_open = { 50 + (120 + 50) * 2, 50, 120, 50 };
SDL_FRect r_format = { 50 + (120 + 50) * 3, 50, 120, 50 };
SDL_FRect r_listdev = { 50 + (120 + 50) * 4, 50, 120, 50 };
SDL_VideoCaptureDevice *device;
SDL_VideoCaptureSpec obtained;
int stopped = 0;
SDL_VideoCaptureFrame frame_current;
measure_fps_t fps_capture;
SDL_Texture *texture = NULL;
int texture_updated = 0;
struct data_capture_t data_capture_tab[16];
const int data_capture_tab_size = SDL_arraysize(data_capture_tab);
SDL_zero(fps_main);
SDL_zero(fps_capture);
SDL_zero(frame_current);
SDL_zeroa(data_capture_tab);
/* Set 0 to disable TouchEvent to be duplicated as MouseEvent with SDL_TOUCH_MOUSEID */
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
/* Set 0 to disable MouseEvent to be duplicated as TouchEvent with SDL_MOUSE_TOUCHID */
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
{
int i;
for (i = 0; i < data_capture_tab_size; i++) {
data_capture_tab[i].device = NULL;
}
}
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, 0);
if (state == NULL) {
return 1;
}
/* Enable standard application logging */
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
/* Parse commandline */
{
int i;
for (i = 1; i < argc;) {
int consumed;
consumed = SDLTest_CommonArg(state, i);
if (consumed <= 0) {
static const char *options[] = {NULL};
SDLTest_CommonLogUsage(state, argv[0], options);
SDLTest_CommonDestroyState(state);
return 1;
}
i += consumed;
}
}
SDL_Log("%s", usage);
/* Load the SDL library */
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError());
return 1;
}
window = SDL_CreateWindow("Local Video", 1000, 800, 0);
if (window == NULL) {
SDL_Log("Couldn't create window: %s", SDL_GetError());
return 1;
}
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
renderer = SDL_CreateRenderer(window, NULL, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (renderer == NULL) {
/* SDL_Log("Couldn't create renderer: %s", SDL_GetError()); */
return 1;
}
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_INFO);
device = SDL_OpenVideoCapture(0);
if (!device) {
SDL_Log("Error SDL_OpenVideoCapture: %s", SDL_GetError());
}
{
/* List formats */
int i, num = SDL_GetNumVideoCaptureFormats(device);
for (i = 0; i < num; i++) {
Uint32 format;
SDL_GetVideoCaptureFormat(device, i, &format);
SDL_Log("format %d/%d: %s", i, num, SDL_GetPixelFormatName(format));
{
int w, h;
int j, num2 = SDL_GetNumVideoCaptureFrameSizes(device, format);
for (j = 0; j < num2; j++) {
SDL_GetVideoCaptureFrameSize(device, format, j, &w, &h);
SDL_Log(" framesizes %d/%d : %d x %d", j, num2, w, h);
}
}
}
}
/* Set Spec */
{
int ret;
/* forced_format */
SDL_VideoCaptureSpec desired;
SDL_zero(desired);
desired.width = 640 * 2;
desired.height = 360 * 2;
desired.format = SDL_PIXELFORMAT_NV12;
ret = SDL_SetVideoCaptureSpec(device, &desired, &obtained, SDL_VIDEO_CAPTURE_ALLOW_ANY_CHANGE);
if (ret < 0) {
SDL_SetVideoCaptureSpec(device, NULL, &obtained, 0);
}
}
SDL_Log("Open capture video device. Obtained spec: size=%d x %d format=%s",
obtained.width, obtained.height, SDL_GetPixelFormatName(obtained.format));
{
SDL_VideoCaptureSpec spec;
if (SDL_GetVideoCaptureSpec(device, &spec) == 0) {
SDL_Log("Read spec: size=%d x %d format=%s",
spec.width, spec.height, SDL_GetPixelFormatName(spec.format));
} else {
SDL_Log("Error read spec: %s", SDL_GetError());
}
}
if (SDL_StartVideoCapture(device) < 0) {
SDL_Log("error SDL_StartVideoCapture(): %s", SDL_GetError());
}
while (!quit) {
SDL_SetRenderDrawColor(renderer, 0x99, 0x99, 0x99, 255);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 0x33, 0x33, 0x33, 255);
SDL_RenderFillRect(renderer, &r_playstop);
SDL_RenderFillRect(renderer, &r_close);
SDL_RenderFillRect(renderer, &r_open);
SDL_RenderFillRect(renderer, &r_format);
SDL_RenderFillRect(renderer, &r_listdev);
SDL_SetRenderDrawColor(renderer, 0xcc, 0xcc, 0xcc, 255);
SDLTest_DrawString(renderer, r_playstop.x + 5, r_playstop.y + 5, "play stop");
SDLTest_DrawString(renderer, r_close.x + 5, r_close.y + 5, "close");
SDLTest_DrawString(renderer, r_open.x + 5, r_open.y + 5, "open dev");
SDLTest_DrawString(renderer, r_format.x + 5, r_format.y + 5, "formats");
{
char buf[256];
SDL_snprintf(buf, 256, "device %d", current_dev);
SDLTest_DrawString(renderer, r_listdev.x + 5, r_listdev.y + 5, buf);
}
while (SDL_PollEvent(&evt)) {
SDL_FRect *r = NULL;
SDL_FPoint pt;
int sym = 0;
pt.x = 0;
pt.y = 0;
SDL_ConvertEventToRenderCoordinates(renderer, &evt);
switch (evt.type)
{
case SDL_EVENT_KEY_DOWN:
{
sym = evt.key.keysym.sym;
break;
}
case SDL_EVENT_QUIT:
{
quit = 1;
SDL_Log("Ctlr+C : Quit!");
}
break;
case SDL_EVENT_FINGER_DOWN:
{
pt.x = evt.tfinger.x;
pt.y = evt.tfinger.y;
}
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
{
pt.x = evt.button.x;
pt.y = evt.button.y;
}
break;
}
if (pt.x != 0 && pt.y != 0) {
if (SDL_PointInRectFloat(&pt, &r_playstop)) {
r = &r_playstop;
sym = SDLK_s;
}
if (SDL_PointInRectFloat(&pt, &r_close)) {
r = &r_close;
sym = SDLK_c;
}
if (SDL_PointInRectFloat(&pt, &r_open)) {
r = &r_open;
sym = SDLK_o;
}
if (SDL_PointInRectFloat(&pt, &r_format)) {
r = &r_format;
sym = SDLK_f;
}
if (SDL_PointInRectFloat(&pt, &r_listdev)) {
r = &r_listdev;
sym = SDLK_l;
}
}
if (r) {
SDL_SetRenderDrawColor(renderer, 0x33, 0, 0, 255);
SDL_RenderFillRect(renderer, r);
}
if (sym == SDLK_c) {
if (frame_current.num_planes) {
SDL_ReleaseVideoCaptureFrame(device, &frame_current);
}
SDL_CloseVideoCapture(device);
device = NULL;
SDL_Log("Close");
}
if (sym == SDLK_o) {
if (device) {
SDL_Log("Close previous ..");
if (frame_current.num_planes) {
SDL_ReleaseVideoCaptureFrame(device, &frame_current);
}
SDL_CloseVideoCapture(device);
}
texture_updated = 0;
SDL_ClearError();
SDL_Log("Try to open:%s", SDL_GetVideoCaptureDeviceName(get_instance_id(current_dev)));
obtained.width = 640 * 2;
obtained.height = 360 * 2;
device = SDL_OpenVideoCaptureWithSpec(get_instance_id(current_dev), &obtained, &obtained, SDL_VIDEO_CAPTURE_ALLOW_ANY_CHANGE);
/* spec may have changed because of re-open */
if (texture) {
SDL_DestroyTexture(texture);
texture = NULL;
}
SDL_Log("Open device:%p %s", (void*)device, SDL_GetError());
stopped = 0;
}
if (sym == SDLK_l) {
int num = 0;
SDL_VideoCaptureDeviceID *devices;
int i;
devices = SDL_GetVideoCaptureDevices(&num);
SDL_Log("Num devices : %d", num);
for (i = 0; i < num; i++) {
SDL_Log("Device %d/%d : %s", i, num, SDL_GetVideoCaptureDeviceName(devices[i]));
}
SDL_free(devices);
SAVE_CAPTURE_STATE(current_dev);
current_dev += 1;
if (current_dev == num || current_dev >= (int) SDL_arraysize(data_capture_tab)) {
current_dev = 0;
}
RESTORE_CAPTURE_STATE(current_dev);
SDL_Log("--> select dev %d / %d", current_dev, num);
}
if (sym == SDLK_i) {
SDL_VideoCaptureStatus status = SDL_GetVideoCaptureStatus(device);
if (status == SDL_VIDEO_CAPTURE_STOPPED) { SDL_Log("STOPPED"); }
if (status == SDL_VIDEO_CAPTURE_PLAYING) { SDL_Log("PLAYING"); }
if (status == SDL_VIDEO_CAPTURE_INIT) { SDL_Log("INIT"); }
}
if (sym == SDLK_s) {
if (stopped) {
SDL_Log("Stop");
SDL_StopVideoCapture(device);
} else {
SDL_Log("Start");
SDL_StartVideoCapture(device);
}
stopped = !stopped;
}
if (sym == SDLK_f) {
SDL_Log("List formats");
if (!device) {
device = SDL_OpenVideoCapture(get_instance_id(current_dev));
}
/* List formats */
{
int i, num = SDL_GetNumVideoCaptureFormats(device);
for (i = 0; i < num; i++) {
Uint32 format;
SDL_GetVideoCaptureFormat(device, i, &format);
SDL_Log("format %d/%d : %s", i, num, SDL_GetPixelFormatName(format));
{
int w, h;
int j, num2 = SDL_GetNumVideoCaptureFrameSizes(device, format);
for (j = 0; j < num2; j++) {
SDL_GetVideoCaptureFrameSize(device, format, j, &w, &h);
SDL_Log(" framesizes %d/%d : %d x %d", j, num2, w, h);
}
}
}
}
}
if (sym == SDLK_ESCAPE || sym == SDLK_AC_BACK) {
quit = 1;
SDL_Log("Key : Escape!");
}
if (sym == SDLK_h || sym == SDLK_F1) {
SDL_Log("%s", usage);
}
}
SAVE_CAPTURE_STATE(current_dev);
{
int i, n = SDL_arraysize(data_capture_tab);
for (i = 0; i < n; i++) {
RESTORE_CAPTURE_STATE(i);
if (!device) {
/* device has been closed */
frame_current.num_planes = 0;
texture_updated = 0;
} else {
int ret;
SDL_VideoCaptureFrame frame_next;
SDL_zero(frame_next);
ret = SDL_AcquireVideoCaptureFrame(device, &frame_next);
if (ret < 0) {
SDL_Log("dev[%d] err SDL_AcquireVideoCaptureFrame: %s", i, SDL_GetError());
}
#if 1
if (frame_next.num_planes) {
SDL_Log("dev[%d] frame: %p at %" SDL_PRIu64, i, (void*)frame_next.data[0], frame_next.timestampNS);
}
#endif
if (frame_next.num_planes) {
update_fps(&fps_capture);
if (frame_current.num_planes) {
ret = SDL_ReleaseVideoCaptureFrame(device, &frame_current);
if (ret < 0) {
SDL_Log("dev[%d] err SDL_ReleaseVideoCaptureFrame: %s", i, SDL_GetError());
}
}
frame_current = frame_next;
texture_updated = 0;
}
}
SAVE_CAPTURE_STATE(i);
}
}
RESTORE_CAPTURE_STATE(current_dev);
/* Moving square */
SDL_SetRenderDrawColor(renderer, 0, 0xff, 0, 255);
{
SDL_FRect r;
static float x = 0;
x += 10;
if (x > 1000) {
x = 0;
}
r.x = x;
r.y = 100;
r.w = r.h = 10;
SDL_RenderFillRect(renderer, &r);
}
SDL_SetRenderDrawColor(renderer, 0x33, 0x33, 0x33, 255);
SAVE_CAPTURE_STATE(current_dev);
{
int i, n = SDL_arraysize(data_capture_tab);
for (i = 0; i < n; i++) {
RESTORE_CAPTURE_STATE(i);
/* Update SDL_Texture with last video frame (only once per new frame) */
if (frame_current.num_planes && texture_updated == 0) {
/* Create texture with appropriate format (for DMABUF or not) */
if (texture == NULL) {
Uint32 format = obtained.format;
texture = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_STATIC, obtained.width, obtained.height);
if (texture == NULL) {
SDL_Log("Couldn't create texture: %s", SDL_GetError());
return 1;
}
}
{
/* Use software data */
if (frame_current.num_planes == 1) {
SDL_UpdateTexture(texture, NULL,
frame_current.data[0], frame_current.pitch[0]);
} else if (frame_current.num_planes == 2) {
SDL_UpdateNVTexture(texture, NULL,
frame_current.data[0], frame_current.pitch[0],
frame_current.data[1], frame_current.pitch[1]);
} else if (frame_current.num_planes == 3) {
SDL_UpdateYUVTexture(texture, NULL, frame_current.data[0], frame_current.pitch[0],
frame_current.data[1], frame_current.pitch[1],
frame_current.data[2], frame_current.pitch[2]);
}
texture_updated = 1;
}
}
SAVE_CAPTURE_STATE(i);
}
}
RESTORE_CAPTURE_STATE(current_dev);
{
int i, n = SDL_arraysize(data_capture_tab);
int win_w, win_h;
int total_texture_updated = 0;
int curr_texture_updated = 0;
for (i = 0; i < n; i++) {
if (data_capture_tab[i].texture_updated) {
total_texture_updated += 1;
}
}
SDL_GetRenderOutputSize(renderer, &win_w, &win_h);
for (i = 0; i < n; i++) {
RESTORE_CAPTURE_STATE(i);
/* RenderCopy the SDL_Texture */
if (texture_updated == 1) {
/* Scale texture to fit the screen */
int tw, th;
int w;
SDL_FRect d;
SDL_QueryTexture(texture, NULL, NULL, &tw, &th);
w = win_w / total_texture_updated;
if (tw > w - 20) {
float scale = (float) (w - 20) / (float) tw;
tw = w - 20;
th = (int)((float) th * scale);
}
d.x = (float)(10 + curr_texture_updated * w);
d.y = (float)(win_h - th);
d.w = (float)tw;
d.h = (float)(th - 10);
SDL_RenderTexture(renderer, texture, NULL, &d);
curr_texture_updated += 1;
}
}
}
RESTORE_CAPTURE_STATE(current_dev);
/* display status and FPS */
if (!device) {
#ifdef __IOS__
const float x_offset = 500;
#else
const float x_offset = 0;
#endif
char buf[256];
SDL_snprintf(buf, 256, "Device %d (%s) is not opened", current_dev, SDL_GetVideoCaptureDeviceName(get_instance_id(current_dev)));
SDLTest_DrawString(renderer, x_offset + 10, 10, buf);
} else {
#ifdef __IOS__
const float x_offset = 500;
#else
const float x_offset = 0;
#endif
const char *status = "no status";
char buf[256];
if (device) {
SDL_VideoCaptureStatus s = SDL_GetVideoCaptureStatus(device);
if (s == SDL_VIDEO_CAPTURE_INIT) {
status = "init";
} else if (s == SDL_VIDEO_CAPTURE_PLAYING) {
status = "playing";
} else if (s == SDL_VIDEO_CAPTURE_STOPPED) {
status = "stopped";
} else if (s == SDL_VIDEO_CAPTURE_FAIL) {
status = "failed";
}
}
/* capture device, capture fps, capture status */
SDL_snprintf(buf, 256, "Device %d - %2.2f fps - %s", current_dev, fps_capture.last_fps, status);
SDLTest_DrawString(renderer, x_offset + 10, 10, buf);
/* capture spec */
SDL_snprintf(buf, sizeof(buf), "%d x %d %s", obtained.width, obtained.height, SDL_GetPixelFormatName(obtained.format));
SDLTest_DrawString(renderer, x_offset + 10, 20, buf);
/* video fps */
SDL_snprintf(buf, sizeof(buf), "%2.2f fps", fps_main.last_fps);
SDLTest_DrawString(renderer, x_offset + 10, 30, buf);
}
/* display last error */
{
SDLTest_DrawString(renderer, 400, 10, SDL_GetError());
}
/* display load average */
#if defined(__linux__) && !defined(__ANDROID__)
{
float val = 0.0f;
char buf[128];
load_average(&val);
if (val != 0.0f) {
SDL_snprintf(buf, sizeof(buf), "load avg %2.2f percent", val);
SDLTest_DrawString(renderer, 800, 10, buf);
}
}
#endif
SDL_Delay(20);
SDL_RenderPresent(renderer);
update_fps(&fps_main);
}
SAVE_CAPTURE_STATE(current_dev);
{
int i, n = SDL_arraysize(data_capture_tab);
for (i = 0; i < n; i++) {
RESTORE_CAPTURE_STATE(i);
if (device) {
if (SDL_StopVideoCapture(device) < 0) {
SDL_Log("error SDL_StopVideoCapture(): %s", SDL_GetError());
}
if (frame_current.num_planes) {
SDL_ReleaseVideoCaptureFrame(device, &frame_current);
}
SDL_CloseVideoCapture(device);
}
if (texture) {
SDL_DestroyTexture(texture);
}
}
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
SDLTest_CommonDestroyState(state);
return 0;
}

View File

@ -0,0 +1,206 @@
/*
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely.
*/
#include "SDL3/SDL_main.h"
#include "SDL3/SDL.h"
#include "SDL3/SDL_test.h"
#include "SDL3/SDL_video_capture.h"
#include <stdio.h>
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
int main(int argc, char **argv)
{
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
SDL_Event evt;
int quit = 0;
SDLTest_CommonState *state = NULL;
SDL_VideoCaptureDevice *device = NULL;
SDL_VideoCaptureSpec obtained;
SDL_VideoCaptureFrame frame_current;
SDL_Texture *texture = NULL;
int texture_updated = 0;
SDL_zero(evt);
SDL_zero(obtained);
SDL_zero(frame_current);
/* Set 0 to disable TouchEvent to be duplicated as MouseEvent with SDL_TOUCH_MOUSEID */
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
/* Set 0 to disable MouseEvent to be duplicated as TouchEvent with SDL_MOUSE_TOUCHID */
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, 0);
if (state == NULL) {
return 1;
}
/* Enable standard application logging */
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
/* Load the SDL library */
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError());
return 1;
}
window = SDL_CreateWindow("Local Video", 1000, 800, 0);
if (window == NULL) {
SDL_Log("Couldn't create window: %s", SDL_GetError());
return 1;
}
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
renderer = SDL_CreateRenderer(window, NULL, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (renderer == NULL) {
/* SDL_Log("Couldn't create renderer: %s", SDL_GetError()); */
return 1;
}
device = SDL_OpenVideoCaptureWithSpec(0, NULL, &obtained, SDL_VIDEO_CAPTURE_ALLOW_ANY_CHANGE);
if (!device) {
SDL_Log("No video capture? %s", SDL_GetError());
return 1;
}
if (SDL_StartVideoCapture(device) < 0) {
SDL_Log("error SDL_StartVideoCapture(): %s", SDL_GetError());
return 1;
}
/* Create texture with appropriate format */
if (texture == NULL) {
texture = SDL_CreateTexture(renderer, obtained.format, SDL_TEXTUREACCESS_STATIC, obtained.width, obtained.height);
if (texture == NULL) {
SDL_Log("Couldn't create texture: %s", SDL_GetError());
return 1;
}
}
while (!quit) {
while (SDL_PollEvent(&evt)) {
int sym = 0;
switch (evt.type)
{
case SDL_EVENT_KEY_DOWN:
{
sym = evt.key.keysym.sym;
break;
}
case SDL_EVENT_QUIT:
{
quit = 1;
SDL_Log("Ctlr+C : Quit!");
}
}
if (sym == SDLK_ESCAPE || sym == SDLK_AC_BACK) {
quit = 1;
SDL_Log("Key : Escape!");
}
}
{
SDL_VideoCaptureFrame frame_next;
SDL_zero(frame_next);
if (SDL_AcquireVideoCaptureFrame(device, &frame_next) < 0) {
SDL_Log("err SDL_AcquireVideoCaptureFrame: %s", SDL_GetError());
}
#if 0
if (frame_next.num_planes) {
SDL_Log("frame: %p at %" SDL_PRIu64, (void*)frame_next.data[0], frame_next.timestampNS);
}
#endif
if (frame_next.num_planes) {
if (frame_current.num_planes) {
if (SDL_ReleaseVideoCaptureFrame(device, &frame_current) < 0) {
SDL_Log("err SDL_ReleaseVideoCaptureFrame: %s", SDL_GetError());
}
}
/* It's not needed to keep the frame once updated the texture is updated.
* But in case of 0-copy, it's needed to have the frame while using the texture.
*/
frame_current = frame_next;
texture_updated = 0;
}
}
/* Update SDL_Texture with last video frame (only once per new frame) */
if (frame_current.num_planes && texture_updated == 0) {
/* Use software data */
if (frame_current.num_planes == 1) {
SDL_UpdateTexture(texture, NULL,
frame_current.data[0], frame_current.pitch[0]);
} else if (frame_current.num_planes == 2) {
SDL_UpdateNVTexture(texture, NULL,
frame_current.data[0], frame_current.pitch[0],
frame_current.data[1], frame_current.pitch[1]);
} else if (frame_current.num_planes == 3) {
SDL_UpdateYUVTexture(texture, NULL, frame_current.data[0], frame_current.pitch[0],
frame_current.data[1], frame_current.pitch[1],
frame_current.data[2], frame_current.pitch[2]);
}
texture_updated = 1;
}
SDL_SetRenderDrawColor(renderer, 0x99, 0x99, 0x99, 255);
SDL_RenderClear(renderer);
{
int win_w, win_h, tw, th, w;
SDL_FRect d;
SDL_QueryTexture(texture, NULL, NULL, &tw, &th);
SDL_GetRenderOutputSize(renderer, &win_w, &win_h);
w = win_w;
if (tw > w - 20) {
float scale = (float) (w - 20) / (float) tw;
tw = w - 20;
th = (int)((float) th * scale);
}
d.x = (float)(10 );
d.y = (float)(win_h - th);
d.w = (float)tw;
d.h = (float)(th - 10);
SDL_RenderTexture(renderer, texture, NULL, &d);
}
SDL_Delay(10);
SDL_RenderPresent(renderer);
}
if (SDL_StopVideoCapture(device) < 0) {
SDL_Log("error SDL_StopVideoCapture(): %s", SDL_GetError());
}
if (frame_current.num_planes) {
SDL_ReleaseVideoCaptureFrame(device, &frame_current);
}
SDL_CloseVideoCapture(device);
if (texture) {
SDL_DestroyTexture(texture);
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
SDLTest_CommonDestroyState(state);
return 0;
}