mirror of
				https://github.com/libsdl-org/SDL.git
				synced 2025-10-26 12:27:44 +00:00 
			
		
		
		
	Add SDL Video Capture, with back-end for linux/macos/ios/android
This commit is contained in:
		| @@ -343,6 +343,7 @@ set_option(SDL_METAL               "Enable Metal support" ${APPLE}) | |||||||
| set_option(SDL_KMSDRM              "Use KMS DRM video driver" ${UNIX_SYS}) | 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) | dep_option(SDL_KMSDRM_SHARED       "Dynamically load KMS DRM support" ON "SDL_KMSDRM" OFF) | ||||||
| set_option(SDL_OFFSCREEN           "Use offscreen video driver" ON) | 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_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) | 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) | dep_option(SDL_HIDAPI              "Enable the HIDAPI subsystem" ON "NOT VISIONOS" OFF) | ||||||
| @@ -2047,6 +2048,10 @@ elseif(APPLE) | |||||||
|     set(HAVE_SDL_FILE TRUE) |     set(HAVE_SDL_FILE TRUE) | ||||||
|   endif() |   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(SDL_MISC) | ||||||
|     if(IOS OR TVOS OR VISIONOS) |     if(IOS OR TVOS OR VISIONOS) | ||||||
|       sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/ios/*.m") |       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. |   # Actually load the frameworks at the end so we don't duplicate include. | ||||||
|   if(SDL_FRAMEWORK_COREVIDEO) |   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") |     sdl_link_dependency(corevideo LINK_OPTIONS "-Wl,-framework,CoreVideo") | ||||||
|   endif() |   endif() | ||||||
|   if(SDL_FRAMEWORK_COCOA) |   if(SDL_FRAMEWORK_COCOA) | ||||||
|   | |||||||
| @@ -89,6 +89,7 @@ | |||||||
|     <ClInclude Include="..\include\SDL3\SDL_types.h" /> |     <ClInclude Include="..\include\SDL3\SDL_types.h" /> | ||||||
|     <ClInclude Include="..\include\SDL3\SDL_version.h" /> |     <ClInclude Include="..\include\SDL3\SDL_version.h" /> | ||||||
|     <ClInclude Include="..\include\SDL3\SDL_video.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\disk\SDL_diskaudio.h" /> | ||||||
|     <ClInclude Include="..\src\audio\dummy\SDL_dummyaudio.h" /> |     <ClInclude Include="..\src\audio\dummy\SDL_dummyaudio.h" /> | ||||||
|     <ClInclude Include="..\src\audio\SDL_audiodev_c.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_RLEaccel_c.h" /> | ||||||
|     <ClInclude Include="..\src\video\SDL_shape_internals.h" /> |     <ClInclude Include="..\src\video\SDL_shape_internals.h" /> | ||||||
|     <ClInclude Include="..\src\video\SDL_sysvideo.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\SDL_yuv_c.h" /> | ||||||
|     <ClInclude Include="..\src\video\winrt\SDL_winrtevents_c.h" /> |     <ClInclude Include="..\src\video\winrt\SDL_winrtevents_c.h" /> | ||||||
|     <ClInclude Include="..\src\video\winrt\SDL_winrtgamebar_cpp.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_stretch.c" /> | ||||||
|     <ClCompile Include="..\src\video\SDL_surface.c" /> |     <ClCompile Include="..\src\video\SDL_surface.c" /> | ||||||
|     <ClCompile Include="..\src\video\SDL_video.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_video_unsupported.c" /> | ||||||
|     <ClCompile Include="..\src\video\SDL_yuv.c" /> |     <ClCompile Include="..\src\video\SDL_yuv.c" /> | ||||||
|     <ClCompile Include="..\src\video\winrt\SDL_winrtevents.cpp"> |     <ClCompile Include="..\src\video\winrt\SDL_winrtevents.cpp"> | ||||||
|   | |||||||
| @@ -165,6 +165,9 @@ | |||||||
|     <ClInclude Include="..\include\SDL3\SDL_video.h"> |     <ClInclude Include="..\include\SDL3\SDL_video.h"> | ||||||
|       <Filter>Header Files</Filter> |       <Filter>Header Files</Filter> | ||||||
|     </ClInclude> |     </ClInclude> | ||||||
|  |     <ClInclude Include="..\include\SDL3\SDL_video_capture.h"> | ||||||
|  |       <Filter>Header Files</Filter> | ||||||
|  |     </ClInclude> | ||||||
|     <ClInclude Include="..\src\joystick\SDL_gamepad_c.h"> |     <ClInclude Include="..\src\joystick\SDL_gamepad_c.h"> | ||||||
|       <Filter>Header Files</Filter> |       <Filter>Header Files</Filter> | ||||||
|     </ClInclude> |     </ClInclude> | ||||||
| @@ -405,6 +408,9 @@ | |||||||
|     <ClInclude Include="..\src\video\SDL_sysvideo.h"> |     <ClInclude Include="..\src\video\SDL_sysvideo.h"> | ||||||
|       <Filter>Source Files</Filter> |       <Filter>Source Files</Filter> | ||||||
|     </ClInclude> |     </ClInclude> | ||||||
|  |     <ClInclude Include="..\src\video\SDL_sysvideocapture.h"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClInclude> | ||||||
|     <ClInclude Include="..\src\video\winrt\SDL_winrtevents_c.h"> |     <ClInclude Include="..\src\video\winrt\SDL_winrtevents_c.h"> | ||||||
|       <Filter>Source Files</Filter> |       <Filter>Source Files</Filter> | ||||||
|     </ClInclude> |     </ClInclude> | ||||||
| @@ -807,6 +813,9 @@ | |||||||
|     <ClCompile Include="..\src\video\SDL_video.c"> |     <ClCompile Include="..\src\video\SDL_video.c"> | ||||||
|       <Filter>Source Files</Filter> |       <Filter>Source Files</Filter> | ||||||
|     </ClCompile> |     </ClCompile> | ||||||
|  |     <ClCompile Include="..\src\video\SDL_video_capture.c"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|     <ClCompile Include="..\src\video\SDL_video_unsupported.c"> |     <ClCompile Include="..\src\video\SDL_video_unsupported.c"> | ||||||
|       <Filter>Source Files</Filter> |       <Filter>Source Files</Filter> | ||||||
|     </ClCompile> |     </ClCompile> | ||||||
|   | |||||||
| @@ -653,6 +653,7 @@ | |||||||
|     <ClCompile Include="..\..\src\video\SDL_surface.c" /> |     <ClCompile Include="..\..\src\video\SDL_surface.c" /> | ||||||
|     <ClCompile Include="..\..\src\video\SDL_video.c" /> |     <ClCompile Include="..\..\src\video\SDL_video.c" /> | ||||||
|     <ClCompile Include="..\..\src\video\SDL_video_unsupported.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_vulkan_utils.c" /> | ||||||
|     <ClCompile Include="..\..\src\video\SDL_yuv.c" /> |     <ClCompile Include="..\..\src\video\SDL_yuv.c" /> | ||||||
|     <ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" /> |     <ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" /> | ||||||
|   | |||||||
| @@ -1185,6 +1185,9 @@ | |||||||
|     <ClCompile Include="..\..\src\video\SDL_video_unsupported.c"> |     <ClCompile Include="..\..\src\video\SDL_video_unsupported.c"> | ||||||
|       <Filter>video</Filter> |       <Filter>video</Filter> | ||||||
|     </ClCompile> |     </ClCompile> | ||||||
|  |     <ClCompile Include="..\..\src\video\SDL_video_capture.c"> | ||||||
|  |       <Filter>video</Filter> | ||||||
|  |     </ClCompile> | ||||||
|     <ClCompile Include="..\..\src\video\SDL_yuv.c"> |     <ClCompile Include="..\..\src\video\SDL_yuv.c"> | ||||||
|       <Filter>video</Filter> |       <Filter>video</Filter> | ||||||
|     </ClCompile> |     </ClCompile> | ||||||
|   | |||||||
| @@ -76,6 +76,7 @@ | |||||||
| #include <SDL3/SDL_touch.h> | #include <SDL3/SDL_touch.h> | ||||||
| #include <SDL3/SDL_version.h> | #include <SDL3/SDL_version.h> | ||||||
| #include <SDL3/SDL_video.h> | #include <SDL3/SDL_video.h> | ||||||
|  | #include "SDL3/SDL_video_capture.h" | ||||||
| #include <SDL3/SDL_oldnames.h> | #include <SDL3/SDL_oldnames.h> | ||||||
|  |  | ||||||
| #endif /* SDL_h_ */ | #endif /* SDL_h_ */ | ||||||
|   | |||||||
							
								
								
									
										369
									
								
								include/SDL3/SDL_video_capture.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										369
									
								
								include/SDL3/SDL_video_capture.h
									
									
									
									
									
										Normal 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_ */ | ||||||
| @@ -244,11 +244,15 @@ | |||||||
|  |  | ||||||
| #cmakedefine USE_POSIX_SPAWN @USE_POSIX_SPAWN@ | #cmakedefine USE_POSIX_SPAWN @USE_POSIX_SPAWN@ | ||||||
|  |  | ||||||
|  | #cmakedefine HAVE_COREMEDIA | ||||||
|  |  | ||||||
| /* SDL internal assertion support */ | /* SDL internal assertion support */ | ||||||
| #if @SDL_DEFAULT_ASSERT_LEVEL_CONFIGURED@ | #if @SDL_DEFAULT_ASSERT_LEVEL_CONFIGURED@ | ||||||
| #cmakedefine SDL_DEFAULT_ASSERT_LEVEL @SDL_DEFAULT_ASSERT_LEVEL@ | #cmakedefine SDL_DEFAULT_ASSERT_LEVEL @SDL_DEFAULT_ASSERT_LEVEL@ | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #cmakedefine SDL_VIDEO_CAPTURE | ||||||
|  |  | ||||||
| /* Allow disabling of core subsystems */ | /* Allow disabling of core subsystems */ | ||||||
| #cmakedefine SDL_ATOMIC_DISABLED @SDL_ATOMIC_DISABLED@ | #cmakedefine SDL_ATOMIC_DISABLED @SDL_ATOMIC_DISABLED@ | ||||||
| #cmakedefine SDL_AUDIO_DISABLED @SDL_AUDIO_DISABLED@ | #cmakedefine SDL_AUDIO_DISABLED @SDL_AUDIO_DISABLED@ | ||||||
|   | |||||||
| @@ -197,6 +197,8 @@ | |||||||
| #define SDL_VIDEO_METAL 1 | #define SDL_VIDEO_METAL 1 | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #define HAVE_COREMEDIA  1 | ||||||
|  |  | ||||||
| /* Enable system power support */ | /* Enable system power support */ | ||||||
| #define SDL_POWER_UIKIT 1 | #define SDL_POWER_UIKIT 1 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -260,6 +260,8 @@ | |||||||
| #endif | #endif | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #define HAVE_COREMEDIA  1 | ||||||
|  |  | ||||||
| /* Enable system power support */ | /* Enable system power support */ | ||||||
| #define SDL_POWER_MACOSX 1 | #define SDL_POWER_MACOSX 1 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -923,6 +923,22 @@ SDL3_0.0.0 { | |||||||
|     SDL_SetPropertyWithCleanup; |     SDL_SetPropertyWithCleanup; | ||||||
|     SDL_SetX11EventHook; |     SDL_SetX11EventHook; | ||||||
|     SDL_GetGlobalProperties; |     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) |     # extra symbols go here (don't modify this line) | ||||||
|   local: *; |   local: *; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -948,3 +948,19 @@ | |||||||
| #define SDL_SetPropertyWithCleanup SDL_SetPropertyWithCleanup_REAL | #define SDL_SetPropertyWithCleanup SDL_SetPropertyWithCleanup_REAL | ||||||
| #define SDL_SetX11EventHook SDL_SetX11EventHook_REAL | #define SDL_SetX11EventHook SDL_SetX11EventHook_REAL | ||||||
| #define SDL_GetGlobalProperties SDL_GetGlobalProperties_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 | ||||||
|   | |||||||
| @@ -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(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(void,SDL_SetX11EventHook,(SDL_X11EventHook a, void *b),(a,b),) | ||||||
| SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGlobalProperties,(void),(),return) | 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) | ||||||
|   | |||||||
							
								
								
									
										90
									
								
								src/video/SDL_sysvideocapture.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/video/SDL_sysvideocapture.h
									
									
									
									
									
										Normal 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_ */ | ||||||
| @@ -31,6 +31,7 @@ | |||||||
| #include "SDL_video_c.h" | #include "SDL_video_c.h" | ||||||
| #include "../events/SDL_events_c.h" | #include "../events/SDL_events_c.h" | ||||||
| #include "../timer/SDL_timer_c.h" | #include "../timer/SDL_timer_c.h" | ||||||
|  | #include "SDL_video_capture_c.h" | ||||||
|  |  | ||||||
| #ifdef SDL_VIDEO_OPENGL | #ifdef SDL_VIDEO_OPENGL | ||||||
| #include <SDL3/SDL_opengl.h> | #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_keyboard = SDL_FALSE; | ||||||
|     SDL_bool init_mouse = SDL_FALSE; |     SDL_bool init_mouse = SDL_FALSE; | ||||||
|     SDL_bool init_touch = SDL_FALSE; |     SDL_bool init_touch = SDL_FALSE; | ||||||
|  |     SDL_bool init_video_capture = SDL_FALSE; | ||||||
|     int i = 0; |     int i = 0; | ||||||
|  |  | ||||||
|     /* Check to make sure we don't overwrite '_this' */ |     /* Check to make sure we don't overwrite '_this' */ | ||||||
| @@ -471,6 +473,10 @@ int SDL_VideoInit(const char *driver_name) | |||||||
|         goto pre_driver_error; |         goto pre_driver_error; | ||||||
|     } |     } | ||||||
|     init_touch = SDL_TRUE; |     init_touch = SDL_TRUE; | ||||||
|  |     if (SDL_VideoCaptureInit() < 0) { | ||||||
|  |         goto pre_driver_error; | ||||||
|  |     } | ||||||
|  |     init_video_capture = SDL_TRUE; | ||||||
|  |  | ||||||
|     /* Select the proper video driver */ |     /* Select the proper video driver */ | ||||||
|     video = NULL; |     video = NULL; | ||||||
| @@ -565,6 +571,9 @@ int SDL_VideoInit(const char *driver_name) | |||||||
|  |  | ||||||
| pre_driver_error: | pre_driver_error: | ||||||
|     SDL_assert(_this == NULL); |     SDL_assert(_this == NULL); | ||||||
|  |     if (init_video_capture) { | ||||||
|  |         SDL_QuitVideoCapture(); | ||||||
|  |     } | ||||||
|     if (init_touch) { |     if (init_touch) { | ||||||
|         SDL_QuitTouch(); |         SDL_QuitTouch(); | ||||||
|     } |     } | ||||||
| @@ -3684,6 +3693,7 @@ void SDL_VideoQuit(void) | |||||||
|     SDL_ClearClipboardData(); |     SDL_ClearClipboardData(); | ||||||
|  |  | ||||||
|     /* Halt event processing before doing anything else */ |     /* Halt event processing before doing anything else */ | ||||||
|  |     SDL_QuitVideoCapture(); | ||||||
|     SDL_QuitTouch(); |     SDL_QuitTouch(); | ||||||
|     SDL_QuitMouse(); |     SDL_QuitMouse(); | ||||||
|     SDL_QuitKeyboard(); |     SDL_QuitKeyboard(); | ||||||
|   | |||||||
							
								
								
									
										948
									
								
								src/video/SDL_video_capture.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										948
									
								
								src/video/SDL_video_capture.c
									
									
									
									
									
										Normal 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 */ | ||||||
							
								
								
									
										615
									
								
								src/video/SDL_video_capture_apple.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										615
									
								
								src/video/SDL_video_capture_apple.m
									
									
									
									
									
										Normal 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 */ | ||||||
|  |  | ||||||
							
								
								
									
										33
									
								
								src/video/SDL_video_capture_c.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/video/SDL_video_capture_c.h
									
									
									
									
									
										Normal 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_ */ | ||||||
							
								
								
									
										965
									
								
								src/video/SDL_video_capture_v4l2.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										965
									
								
								src/video/SDL_video_capture_v4l2.c
									
									
									
									
									
										Normal 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 */ | ||||||
							
								
								
									
										678
									
								
								src/video/android/SDL_android_video_capture.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										678
									
								
								src/video/android/SDL_android_video_capture.c
									
									
									
									
									
										Normal 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 | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -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(testtimer NONINTERACTIVE NONINTERACTIVE_ARGS --no-interactive NONINTERACTIVE_TIMEOUT 60 SOURCES testtimer.c) | ||||||
| add_sdl_test_executable(testurl SOURCES testurl.c) | add_sdl_test_executable(testurl SOURCES testurl.c) | ||||||
| add_sdl_test_executable(testver NONINTERACTIVE SOURCES testver.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(testviewport NEEDS_RESOURCES TESTUTILS SOURCES testviewport.c) | ||||||
| add_sdl_test_executable(testwm SOURCES testwm.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) | add_sdl_test_executable(testyuv NONINTERACTIVE NONINTERACTIVE_ARGS "--automated" NEEDS_RESOURCES TESTUTILS SOURCES testyuv.c testyuv_cvt.c) | ||||||
|   | |||||||
							
								
								
									
										770
									
								
								test/testvideocapture.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										770
									
								
								test/testvideocapture.c
									
									
									
									
									
										Normal 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; | ||||||
|  | } | ||||||
							
								
								
									
										206
									
								
								test/testvideocaptureminimal.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								test/testvideocaptureminimal.c
									
									
									
									
									
										Normal 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; | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Sylvain
					Sylvain