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}) | ||||
| dep_option(SDL_KMSDRM_SHARED       "Dynamically load KMS DRM support" ON "SDL_KMSDRM" OFF) | ||||
| set_option(SDL_OFFSCREEN           "Use offscreen video driver" ON) | ||||
| dep_option(SDL_VIDEO_CAPTURE       "Enable video capturing" ON SDL_VIDEO OFF) | ||||
| option_string(SDL_BACKGROUNDING_SIGNAL "number to use for magic backgrounding signal or 'OFF'" OFF) | ||||
| option_string(SDL_FOREGROUNDING_SIGNAL "number to use for magic foregrounding signal or 'OFF'" OFF) | ||||
| dep_option(SDL_HIDAPI              "Enable the HIDAPI subsystem" ON "NOT VISIONOS" OFF) | ||||
| @@ -2047,6 +2048,10 @@ elseif(APPLE) | ||||
|     set(HAVE_SDL_FILE TRUE) | ||||
|   endif() | ||||
|  | ||||
|   if(IOS OR TVOS OR MACOSX OR DARWIN) | ||||
|     sdl_sources("${SDL3_SOURCE_DIR}/src/video/SDL_video_capture_apple.m") | ||||
|   endif() | ||||
|  | ||||
|   if(SDL_MISC) | ||||
|     if(IOS OR TVOS OR VISIONOS) | ||||
|       sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/ios/*.m") | ||||
| @@ -2230,6 +2235,10 @@ elseif(APPLE) | ||||
|  | ||||
|   # Actually load the frameworks at the end so we don't duplicate include. | ||||
|   if(SDL_FRAMEWORK_COREVIDEO) | ||||
|     find_library(COREMEDIA CoreMedia) | ||||
|     if(COREMEDIA) | ||||
|       sdl_link_dependency(corevideo LINK_OPTIONS "-Wl,-framework,CoreMedia") | ||||
|     endif() | ||||
|     sdl_link_dependency(corevideo LINK_OPTIONS "-Wl,-framework,CoreVideo") | ||||
|   endif() | ||||
|   if(SDL_FRAMEWORK_COCOA) | ||||
|   | ||||
| @@ -89,6 +89,7 @@ | ||||
|     <ClInclude Include="..\include\SDL3\SDL_types.h" /> | ||||
|     <ClInclude Include="..\include\SDL3\SDL_version.h" /> | ||||
|     <ClInclude Include="..\include\SDL3\SDL_video.h" /> | ||||
|     <ClInclude Include="..\include\SDL3\SDL_video_capture.h" /> | ||||
|     <ClInclude Include="..\src\audio\disk\SDL_diskaudio.h" /> | ||||
|     <ClInclude Include="..\src\audio\dummy\SDL_dummyaudio.h" /> | ||||
|     <ClInclude Include="..\src\audio\SDL_audiodev_c.h" /> | ||||
| @@ -180,6 +181,7 @@ | ||||
|     <ClInclude Include="..\src\video\SDL_RLEaccel_c.h" /> | ||||
|     <ClInclude Include="..\src\video\SDL_shape_internals.h" /> | ||||
|     <ClInclude Include="..\src\video\SDL_sysvideo.h" /> | ||||
|     <ClInclude Include="..\src\video\SDL_sysvidocapture.h" /> | ||||
|     <ClInclude Include="..\src\video\SDL_yuv_c.h" /> | ||||
|     <ClInclude Include="..\src\video\winrt\SDL_winrtevents_c.h" /> | ||||
|     <ClInclude Include="..\src\video\winrt\SDL_winrtgamebar_cpp.h" /> | ||||
| @@ -520,6 +522,7 @@ | ||||
|     <ClCompile Include="..\src\video\SDL_stretch.c" /> | ||||
|     <ClCompile Include="..\src\video\SDL_surface.c" /> | ||||
|     <ClCompile Include="..\src\video\SDL_video.c" /> | ||||
|     <ClCompile Include="..\src\video\SDL_video_capture.c" /> | ||||
|     <ClCompile Include="..\src\video\SDL_video_unsupported.c" /> | ||||
|     <ClCompile Include="..\src\video\SDL_yuv.c" /> | ||||
|     <ClCompile Include="..\src\video\winrt\SDL_winrtevents.cpp"> | ||||
|   | ||||
| @@ -165,6 +165,9 @@ | ||||
|     <ClInclude Include="..\include\SDL3\SDL_video.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="..\include\SDL3\SDL_video_capture.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="..\src\joystick\SDL_gamepad_c.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
| @@ -405,6 +408,9 @@ | ||||
|     <ClInclude Include="..\src\video\SDL_sysvideo.h"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="..\src\video\SDL_sysvideocapture.h"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="..\src\video\winrt\SDL_winrtevents_c.h"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClInclude> | ||||
| @@ -807,6 +813,9 @@ | ||||
|     <ClCompile Include="..\src\video\SDL_video.c"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="..\src\video\SDL_video_capture.c"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="..\src\video\SDL_video_unsupported.c"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|   | ||||
| @@ -653,6 +653,7 @@ | ||||
|     <ClCompile Include="..\..\src\video\SDL_surface.c" /> | ||||
|     <ClCompile Include="..\..\src\video\SDL_video.c" /> | ||||
|     <ClCompile Include="..\..\src\video\SDL_video_unsupported.c" /> | ||||
|     <ClCompile Include="..\..\src\video\SDL_video_capture.c" /> | ||||
|     <ClCompile Include="..\..\src\video\SDL_vulkan_utils.c" /> | ||||
|     <ClCompile Include="..\..\src\video\SDL_yuv.c" /> | ||||
|     <ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" /> | ||||
|   | ||||
| @@ -1185,6 +1185,9 @@ | ||||
|     <ClCompile Include="..\..\src\video\SDL_video_unsupported.c"> | ||||
|       <Filter>video</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="..\..\src\video\SDL_video_capture.c"> | ||||
|       <Filter>video</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="..\..\src\video\SDL_yuv.c"> | ||||
|       <Filter>video</Filter> | ||||
|     </ClCompile> | ||||
|   | ||||
| @@ -76,6 +76,7 @@ | ||||
| #include <SDL3/SDL_touch.h> | ||||
| #include <SDL3/SDL_version.h> | ||||
| #include <SDL3/SDL_video.h> | ||||
| #include "SDL3/SDL_video_capture.h" | ||||
| #include <SDL3/SDL_oldnames.h> | ||||
|  | ||||
| #endif /* SDL_h_ */ | ||||
|   | ||||
							
								
								
									
										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 HAVE_COREMEDIA | ||||
|  | ||||
| /* SDL internal assertion support */ | ||||
| #if @SDL_DEFAULT_ASSERT_LEVEL_CONFIGURED@ | ||||
| #cmakedefine SDL_DEFAULT_ASSERT_LEVEL @SDL_DEFAULT_ASSERT_LEVEL@ | ||||
| #endif | ||||
|  | ||||
| #cmakedefine SDL_VIDEO_CAPTURE | ||||
|  | ||||
| /* Allow disabling of core subsystems */ | ||||
| #cmakedefine SDL_ATOMIC_DISABLED @SDL_ATOMIC_DISABLED@ | ||||
| #cmakedefine SDL_AUDIO_DISABLED @SDL_AUDIO_DISABLED@ | ||||
|   | ||||
| @@ -197,6 +197,8 @@ | ||||
| #define SDL_VIDEO_METAL 1 | ||||
| #endif | ||||
|  | ||||
| #define HAVE_COREMEDIA  1 | ||||
|  | ||||
| /* Enable system power support */ | ||||
| #define SDL_POWER_UIKIT 1 | ||||
|  | ||||
|   | ||||
| @@ -260,6 +260,8 @@ | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| #define HAVE_COREMEDIA  1 | ||||
|  | ||||
| /* Enable system power support */ | ||||
| #define SDL_POWER_MACOSX 1 | ||||
|  | ||||
|   | ||||
| @@ -923,6 +923,22 @@ SDL3_0.0.0 { | ||||
|     SDL_SetPropertyWithCleanup; | ||||
|     SDL_SetX11EventHook; | ||||
|     SDL_GetGlobalProperties; | ||||
|     SDL_OpenVideoCapture; | ||||
|     SDL_SetVideoCaptureSpec; | ||||
|     SDL_OpenVideoCaptureWithSpec; | ||||
|     SDL_GetVideoCaptureDeviceName; | ||||
|     SDL_GetVideoCaptureSpec; | ||||
|     SDL_GetVideoCaptureFormat; | ||||
|     SDL_GetNumVideoCaptureFormats; | ||||
|     SDL_GetVideoCaptureFrameSize; | ||||
|     SDL_GetNumVideoCaptureFrameSizes; | ||||
|     SDL_GetVideoCaptureStatus; | ||||
|     SDL_StartVideoCapture; | ||||
|     SDL_AcquireVideoCaptureFrame; | ||||
|     SDL_ReleaseVideoCaptureFrame; | ||||
|     SDL_StopVideoCapture; | ||||
|     SDL_CloseVideoCapture; | ||||
|     SDL_GetVideoCaptureDevices; | ||||
|     # extra symbols go here (don't modify this line) | ||||
|   local: *; | ||||
| }; | ||||
|   | ||||
| @@ -948,3 +948,19 @@ | ||||
| #define SDL_SetPropertyWithCleanup SDL_SetPropertyWithCleanup_REAL | ||||
| #define SDL_SetX11EventHook SDL_SetX11EventHook_REAL | ||||
| #define SDL_GetGlobalProperties SDL_GetGlobalProperties_REAL | ||||
| #define SDL_OpenVideoCapture SDL_OpenVideoCapture_REAL | ||||
| #define SDL_SetVideoCaptureSpec SDL_SetVideoCaptureSpec_REAL | ||||
| #define SDL_OpenVideoCaptureWithSpec SDL_OpenVideoCaptureWithSpec_REAL | ||||
| #define SDL_GetVideoCaptureDeviceName SDL_GetVideoCaptureDeviceName_REAL | ||||
| #define SDL_GetVideoCaptureSpec SDL_GetVideoCaptureSpec_REAL | ||||
| #define SDL_GetVideoCaptureFormat SDL_GetVideoCaptureFormat_REAL | ||||
| #define SDL_GetNumVideoCaptureFormats SDL_GetNumVideoCaptureFormats_REAL | ||||
| #define SDL_GetVideoCaptureFrameSize SDL_GetVideoCaptureFrameSize_REAL | ||||
| #define SDL_GetNumVideoCaptureFrameSizes SDL_GetNumVideoCaptureFrameSizes_REAL | ||||
| #define SDL_GetVideoCaptureStatus SDL_GetVideoCaptureStatus_REAL | ||||
| #define SDL_StartVideoCapture SDL_StartVideoCapture_REAL | ||||
| #define SDL_AcquireVideoCaptureFrame SDL_AcquireVideoCaptureFrame_REAL | ||||
| #define SDL_ReleaseVideoCaptureFrame SDL_ReleaseVideoCaptureFrame_REAL | ||||
| #define SDL_StopVideoCapture SDL_StopVideoCapture_REAL | ||||
| #define SDL_CloseVideoCapture SDL_CloseVideoCapture_REAL | ||||
| #define SDL_GetVideoCaptureDevices SDL_GetVideoCaptureDevices_REAL | ||||
|   | ||||
| @@ -981,3 +981,19 @@ SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetDisplayProperties,(SDL_DisplayID a),(a), | ||||
| SDL_DYNAPI_PROC(int,SDL_SetPropertyWithCleanup,(SDL_PropertiesID a, const char *b, void *c, void (SDLCALL *d)(void *userdata, void *value), void *e),(a,b,c,d,e),return) | ||||
| SDL_DYNAPI_PROC(void,SDL_SetX11EventHook,(SDL_X11EventHook a, void *b),(a,b),) | ||||
| SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGlobalProperties,(void),(),return) | ||||
| SDL_DYNAPI_PROC(SDL_VideoCaptureDevice*,SDL_OpenVideoCapture,(SDL_VideoCaptureDeviceID a),(a),return) | ||||
| SDL_DYNAPI_PROC(int,SDL_SetVideoCaptureSpec,(SDL_VideoCaptureDevice *a, const SDL_VideoCaptureSpec *b, SDL_VideoCaptureSpec *c, int d),(a,b,c,d),return) | ||||
| SDL_DYNAPI_PROC(SDL_VideoCaptureDevice*,SDL_OpenVideoCaptureWithSpec,(SDL_VideoCaptureDeviceID a, const SDL_VideoCaptureSpec *b, SDL_VideoCaptureSpec *c, int d),(a,b,c,d),return) | ||||
| SDL_DYNAPI_PROC(const char*,SDL_GetVideoCaptureDeviceName,(SDL_VideoCaptureDeviceID a),(a),return) | ||||
| SDL_DYNAPI_PROC(int,SDL_GetVideoCaptureSpec,(SDL_VideoCaptureDevice *a, SDL_VideoCaptureSpec *b),(a,b),return) | ||||
| SDL_DYNAPI_PROC(int,SDL_GetVideoCaptureFormat,(SDL_VideoCaptureDevice *a, int b, Uint32 *c),(a,b,c),return) | ||||
| SDL_DYNAPI_PROC(int,SDL_GetNumVideoCaptureFormats,(SDL_VideoCaptureDevice *a),(a),return) | ||||
| SDL_DYNAPI_PROC(int,SDL_GetVideoCaptureFrameSize,(SDL_VideoCaptureDevice *a, Uint32 b, int c, int *d, int *e),(a,b,c,d,e),return) | ||||
| SDL_DYNAPI_PROC(int,SDL_GetNumVideoCaptureFrameSizes,(SDL_VideoCaptureDevice *a, Uint32 b),(a,b),return) | ||||
| SDL_DYNAPI_PROC(SDL_VideoCaptureStatus,SDL_GetVideoCaptureStatus,(SDL_VideoCaptureDevice *a),(a),return) | ||||
| SDL_DYNAPI_PROC(int,SDL_StartVideoCapture,(SDL_VideoCaptureDevice *a),(a),return) | ||||
| SDL_DYNAPI_PROC(int,SDL_AcquireVideoCaptureFrame,(SDL_VideoCaptureDevice *a, SDL_VideoCaptureFrame *b),(a,b),return) | ||||
| SDL_DYNAPI_PROC(int,SDL_ReleaseVideoCaptureFrame,(SDL_VideoCaptureDevice *a, SDL_VideoCaptureFrame *b),(a,b),return) | ||||
| SDL_DYNAPI_PROC(int,SDL_StopVideoCapture,(SDL_VideoCaptureDevice *a),(a),return) | ||||
| SDL_DYNAPI_PROC(void,SDL_CloseVideoCapture,(SDL_VideoCaptureDevice *a),(a),) | ||||
| SDL_DYNAPI_PROC(SDL_VideoCaptureDeviceID*,SDL_GetVideoCaptureDevices,(int *a),(a),return) | ||||
|   | ||||
							
								
								
									
										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 "../events/SDL_events_c.h" | ||||
| #include "../timer/SDL_timer_c.h" | ||||
| #include "SDL_video_capture_c.h" | ||||
|  | ||||
| #ifdef SDL_VIDEO_OPENGL | ||||
| #include <SDL3/SDL_opengl.h> | ||||
| @@ -443,6 +444,7 @@ int SDL_VideoInit(const char *driver_name) | ||||
|     SDL_bool init_keyboard = SDL_FALSE; | ||||
|     SDL_bool init_mouse = SDL_FALSE; | ||||
|     SDL_bool init_touch = SDL_FALSE; | ||||
|     SDL_bool init_video_capture = SDL_FALSE; | ||||
|     int i = 0; | ||||
|  | ||||
|     /* Check to make sure we don't overwrite '_this' */ | ||||
| @@ -471,6 +473,10 @@ int SDL_VideoInit(const char *driver_name) | ||||
|         goto pre_driver_error; | ||||
|     } | ||||
|     init_touch = SDL_TRUE; | ||||
|     if (SDL_VideoCaptureInit() < 0) { | ||||
|         goto pre_driver_error; | ||||
|     } | ||||
|     init_video_capture = SDL_TRUE; | ||||
|  | ||||
|     /* Select the proper video driver */ | ||||
|     video = NULL; | ||||
| @@ -565,6 +571,9 @@ int SDL_VideoInit(const char *driver_name) | ||||
|  | ||||
| pre_driver_error: | ||||
|     SDL_assert(_this == NULL); | ||||
|     if (init_video_capture) { | ||||
|         SDL_QuitVideoCapture(); | ||||
|     } | ||||
|     if (init_touch) { | ||||
|         SDL_QuitTouch(); | ||||
|     } | ||||
| @@ -3684,6 +3693,7 @@ void SDL_VideoQuit(void) | ||||
|     SDL_ClearClipboardData(); | ||||
|  | ||||
|     /* Halt event processing before doing anything else */ | ||||
|     SDL_QuitVideoCapture(); | ||||
|     SDL_QuitTouch(); | ||||
|     SDL_QuitMouse(); | ||||
|     SDL_QuitKeyboard(); | ||||
|   | ||||
							
								
								
									
										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(testurl SOURCES testurl.c) | ||||
| add_sdl_test_executable(testver NONINTERACTIVE SOURCES testver.c) | ||||
| add_sdl_test_executable(testvideocapture SOURCES testvideocapture.c) | ||||
| add_sdl_test_executable(testvideocaptureminimal SOURCES testvideocaptureminimal.c) | ||||
| add_sdl_test_executable(testviewport NEEDS_RESOURCES TESTUTILS SOURCES testviewport.c) | ||||
| add_sdl_test_executable(testwm SOURCES testwm.c) | ||||
| add_sdl_test_executable(testyuv NONINTERACTIVE NONINTERACTIVE_ARGS "--automated" NEEDS_RESOURCES TESTUTILS SOURCES testyuv.c testyuv_cvt.c) | ||||
|   | ||||
							
								
								
									
										770
									
								
								test/testvideocapture.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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