diff --git a/CMakeLists.txt b/CMakeLists.txt index b1c226b67..7c8b119e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2009,6 +2009,11 @@ elseif(APPLE) set(HAVE_SDL_FILE TRUE) endif() + if(IOS OR TVOS OR MACOSX OR DARWIN) + file(GLOB SDL_VIDEO_CAPTURE_SOURCES ${SDL3_SOURCE_DIR}/src/video/SDL_video_capture_apple.m) + list(APPEND SOURCE_FILES ${SDL_VIDEO_CAPTURE_SOURCES}) + endif() + if(SDL_MISC) if(IOS OR TVOS) file(GLOB MISC_SOURCES ${SDL3_SOURCE_DIR}/src/misc/ios/*.m) @@ -2201,6 +2206,11 @@ 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) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_COREMEDIA=1") + list(APPEND SDL_EXTRA_LDFLAGS "-Wl,-weak_framework,CoreMedia") + endif() list(APPEND SDL_EXTRA_LDFLAGS "-Wl,-framework,CoreVideo") endif() if(SDL_FRAMEWORK_COCOA) diff --git a/VisualC-WinRT/SDL-UWP.vcxproj b/VisualC-WinRT/SDL-UWP.vcxproj index 0ca94fdc2..9c719709a 100644 --- a/VisualC-WinRT/SDL-UWP.vcxproj +++ b/VisualC-WinRT/SDL-UWP.vcxproj @@ -487,6 +487,7 @@ + true diff --git a/VisualC-WinRT/SDL-UWP.vcxproj.filters b/VisualC-WinRT/SDL-UWP.vcxproj.filters index c6941033a..d5fbb7c92 100644 --- a/VisualC-WinRT/SDL-UWP.vcxproj.filters +++ b/VisualC-WinRT/SDL-UWP.vcxproj.filters @@ -756,6 +756,9 @@ Source Files + + Source Files + Source Files diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index 2ea1875c2..583504a5f 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -630,6 +630,7 @@ + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index 27420accc..3f8f16f79 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -1144,6 +1144,9 @@ video + + video + video diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index ae548100f..ad095b35a 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -130,6 +130,54 @@ A1626A582617008D003F1973 /* SDL_triangle.h in Headers */ = {isa = PBXBuildFile; fileRef = A1626A512617008C003F1973 /* SDL_triangle.h */; }; A1626A592617008D003F1973 /* SDL_triangle.h in Headers */ = {isa = PBXBuildFile; fileRef = A1626A512617008C003F1973 /* SDL_triangle.h */; }; A1626A5A2617008D003F1973 /* SDL_triangle.h in Headers */ = {isa = PBXBuildFile; fileRef = A1626A512617008C003F1973 /* SDL_triangle.h */; }; + A16680C229967B6900318FE2 /* SDL_video_capture.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C129967B6900318FE2 /* SDL_video_capture.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A16680C329967B6900318FE2 /* SDL_video_capture.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C129967B6900318FE2 /* SDL_video_capture.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A16680C429967B6900318FE2 /* SDL_video_capture.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C129967B6900318FE2 /* SDL_video_capture.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A16680CA29967BD100318FE2 /* SDL_video_capture.c in Sources */ = {isa = PBXBuildFile; fileRef = A16680C529967BD000318FE2 /* SDL_video_capture.c */; }; + A16680CB29967BD100318FE2 /* SDL_video_capture.c in Sources */ = {isa = PBXBuildFile; fileRef = A16680C529967BD000318FE2 /* SDL_video_capture.c */; }; + A16680CC29967BD100318FE2 /* SDL_video_capture.c in Sources */ = {isa = PBXBuildFile; fileRef = A16680C529967BD000318FE2 /* SDL_video_capture.c */; }; + A16680CD29967BD100318FE2 /* SDL_sysvideocapture.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C629967BD000318FE2 /* SDL_sysvideocapture.h */; }; + A16680CE29967BD100318FE2 /* SDL_sysvideocapture.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C629967BD000318FE2 /* SDL_sysvideocapture.h */; }; + A16680CF29967BD100318FE2 /* SDL_sysvideocapture.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C629967BD000318FE2 /* SDL_sysvideocapture.h */; }; + A16680D029967BD100318FE2 /* SDL_video_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C729967BD100318FE2 /* SDL_video_c.h */; }; + A16680D129967BD100318FE2 /* SDL_video_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C729967BD100318FE2 /* SDL_video_c.h */; }; + A16680D229967BD100318FE2 /* SDL_video_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C729967BD100318FE2 /* SDL_video_c.h */; }; + A16680D329967BD100318FE2 /* SDL_video_capture_apple.m in Sources */ = {isa = PBXBuildFile; fileRef = A16680C829967BD100318FE2 /* SDL_video_capture_apple.m */; }; + A16680D429967BD100318FE2 /* SDL_video_capture_apple.m in Sources */ = {isa = PBXBuildFile; fileRef = A16680C829967BD100318FE2 /* SDL_video_capture_apple.m */; }; + A16680D529967BD100318FE2 /* SDL_video_capture_apple.m in Sources */ = {isa = PBXBuildFile; fileRef = A16680C829967BD100318FE2 /* SDL_video_capture_apple.m */; }; + A16680D629967BD100318FE2 /* SDL_video_capture_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C929967BD100318FE2 /* SDL_video_capture_c.h */; }; + A16680D729967BD100318FE2 /* SDL_video_capture_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C929967BD100318FE2 /* SDL_video_capture_c.h */; }; + A16680D829967BD100318FE2 /* SDL_video_capture_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C929967BD100318FE2 /* SDL_video_capture_c.h */; }; + A16680D929967BEE00318FE2 /* SDL_sysvideocapture.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C629967BD000318FE2 /* SDL_sysvideocapture.h */; }; + A16680DA29967BEE00318FE2 /* SDL_sysvideocapture.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C629967BD000318FE2 /* SDL_sysvideocapture.h */; }; + A16680DB29967BEF00318FE2 /* SDL_sysvideocapture.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C629967BD000318FE2 /* SDL_sysvideocapture.h */; }; + A16680DC29967BEF00318FE2 /* SDL_sysvideocapture.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C629967BD000318FE2 /* SDL_sysvideocapture.h */; }; + A16680DD29967BF000318FE2 /* SDL_sysvideocapture.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C629967BD000318FE2 /* SDL_sysvideocapture.h */; }; + A16680DE29967BF000318FE2 /* SDL_sysvideocapture.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C629967BD000318FE2 /* SDL_sysvideocapture.h */; }; + A16680DF29967C0A00318FE2 /* SDL_video_capture.c in Sources */ = {isa = PBXBuildFile; fileRef = A16680C529967BD000318FE2 /* SDL_video_capture.c */; }; + A16680E029967C0A00318FE2 /* SDL_video_capture_apple.m in Sources */ = {isa = PBXBuildFile; fileRef = A16680C829967BD100318FE2 /* SDL_video_capture_apple.m */; }; + A16680E129967C0A00318FE2 /* SDL_video_capture_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C929967BD100318FE2 /* SDL_video_capture_c.h */; }; + A16680E229967C0A00318FE2 /* SDL_video_capture.c in Sources */ = {isa = PBXBuildFile; fileRef = A16680C529967BD000318FE2 /* SDL_video_capture.c */; }; + A16680E329967C0A00318FE2 /* SDL_video_capture_apple.m in Sources */ = {isa = PBXBuildFile; fileRef = A16680C829967BD100318FE2 /* SDL_video_capture_apple.m */; }; + A16680E429967C0A00318FE2 /* SDL_video_capture_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C929967BD100318FE2 /* SDL_video_capture_c.h */; }; + A16680E529967C0B00318FE2 /* SDL_video_capture.c in Sources */ = {isa = PBXBuildFile; fileRef = A16680C529967BD000318FE2 /* SDL_video_capture.c */; }; + A16680E629967C0B00318FE2 /* SDL_video_capture_apple.m in Sources */ = {isa = PBXBuildFile; fileRef = A16680C829967BD100318FE2 /* SDL_video_capture_apple.m */; }; + A16680E729967C0B00318FE2 /* SDL_video_capture_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C929967BD100318FE2 /* SDL_video_capture_c.h */; }; + A16680E829967C0B00318FE2 /* SDL_video_capture.c in Sources */ = {isa = PBXBuildFile; fileRef = A16680C529967BD000318FE2 /* SDL_video_capture.c */; }; + A16680E929967C0B00318FE2 /* SDL_video_capture_apple.m in Sources */ = {isa = PBXBuildFile; fileRef = A16680C829967BD100318FE2 /* SDL_video_capture_apple.m */; }; + A16680EA29967C0B00318FE2 /* SDL_video_capture_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C929967BD100318FE2 /* SDL_video_capture_c.h */; }; + A16680EB29967C0C00318FE2 /* SDL_video_capture.c in Sources */ = {isa = PBXBuildFile; fileRef = A16680C529967BD000318FE2 /* SDL_video_capture.c */; }; + A16680EC29967C0C00318FE2 /* SDL_video_capture_apple.m in Sources */ = {isa = PBXBuildFile; fileRef = A16680C829967BD100318FE2 /* SDL_video_capture_apple.m */; }; + A16680ED29967C0C00318FE2 /* SDL_video_capture_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C929967BD100318FE2 /* SDL_video_capture_c.h */; }; + A16680EE29967C0C00318FE2 /* SDL_video_capture.c in Sources */ = {isa = PBXBuildFile; fileRef = A16680C529967BD000318FE2 /* SDL_video_capture.c */; }; + A16680EF29967C0C00318FE2 /* SDL_video_capture_apple.m in Sources */ = {isa = PBXBuildFile; fileRef = A16680C829967BD100318FE2 /* SDL_video_capture_apple.m */; }; + A16680F029967C0C00318FE2 /* SDL_video_capture_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C929967BD100318FE2 /* SDL_video_capture_c.h */; }; + A16680F129967C1B00318FE2 /* SDL_video_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C729967BD100318FE2 /* SDL_video_c.h */; }; + A16680F229967C1C00318FE2 /* SDL_video_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C729967BD100318FE2 /* SDL_video_c.h */; }; + A16680F329967C1C00318FE2 /* SDL_video_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C729967BD100318FE2 /* SDL_video_c.h */; }; + A16680F429967C1C00318FE2 /* SDL_video_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C729967BD100318FE2 /* SDL_video_c.h */; }; + A16680F529967C1D00318FE2 /* SDL_video_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C729967BD100318FE2 /* SDL_video_c.h */; }; + A16680F629967C1D00318FE2 /* SDL_video_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A16680C729967BD100318FE2 /* SDL_video_c.h */; }; A1BB8B6327F6CF330057CFA8 /* SDL_list.c in Sources */ = {isa = PBXBuildFile; fileRef = A1BB8B6127F6CF320057CFA8 /* SDL_list.c */; }; A1BB8B6427F6CF330057CFA8 /* SDL_list.c in Sources */ = {isa = PBXBuildFile; fileRef = A1BB8B6127F6CF320057CFA8 /* SDL_list.c */; }; A1BB8B6527F6CF330057CFA8 /* SDL_list.c in Sources */ = {isa = PBXBuildFile; fileRef = A1BB8B6127F6CF320057CFA8 /* SDL_list.c */; }; @@ -3490,6 +3538,12 @@ 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_shield.c; sourceTree = ""; }; A1626A3D2617006A003F1973 /* SDL_triangle.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_triangle.c; sourceTree = ""; }; A1626A512617008C003F1973 /* SDL_triangle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_triangle.h; sourceTree = ""; }; + A16680C129967B6900318FE2 /* SDL_video_capture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_video_capture.h; path = SDL3/SDL_video_capture.h; sourceTree = ""; }; + A16680C529967BD000318FE2 /* SDL_video_capture.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_video_capture.c; sourceTree = ""; }; + A16680C629967BD000318FE2 /* SDL_sysvideocapture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_sysvideocapture.h; sourceTree = ""; }; + A16680C729967BD100318FE2 /* SDL_video_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_video_c.h; sourceTree = ""; }; + A16680C829967BD100318FE2 /* SDL_video_capture_apple.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_video_capture_apple.m; sourceTree = ""; }; + A16680C929967BD100318FE2 /* SDL_video_capture_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_video_capture_c.h; sourceTree = ""; }; A1BB8B6127F6CF320057CFA8 /* SDL_list.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_list.c; sourceTree = ""; }; A1BB8B6227F6CF330057CFA8 /* SDL_list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_list.h; sourceTree = ""; }; A7381E931D8B69C300B177DD /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; @@ -4160,6 +4214,7 @@ F3F7D8AF2933074900816151 /* SDL_touch.h */, F3F7D8E42933074D00816151 /* SDL_version.h */, F3F7D8C52933074B00816151 /* SDL_video.h */, + A16680C129967B6900318FE2 /* SDL_video_capture.h */, F3F7D8D42933074C00816151 /* SDL_vulkan.h */, ); name = "Public Headers"; @@ -4493,7 +4548,12 @@ A7D8A60323E2513D00DCD162 /* SDL_stretch.c */, A7D8A61423E2513D00DCD162 /* SDL_surface.c */, A7D8A61723E2513D00DCD162 /* SDL_sysvideo.h */, + A16680C629967BD000318FE2 /* SDL_sysvideocapture.h */, A7D8A60E23E2513D00DCD162 /* SDL_video.c */, + A16680C729967BD100318FE2 /* SDL_video_c.h */, + A16680C829967BD100318FE2 /* SDL_video_capture_apple.m */, + A16680C929967BD100318FE2 /* SDL_video_capture_c.h */, + A16680C529967BD000318FE2 /* SDL_video_capture.c */, A7D8A63E23E2513D00DCD162 /* SDL_vulkan_internal.h */, A7D8A64023E2513D00DCD162 /* SDL_vulkan_utils.c */, A7D8A76A23E2513E00DCD162 /* SDL_yuv_c.h */, @@ -5190,6 +5250,7 @@ A75FCDD623E25AB700529352 /* SDL_dynapi_procs.h in Headers */, A75FCD1F23E25AB700529352 /* SDL_egl_c.h in Headers */, A75FCD1923E25AB700529352 /* SDL_error_c.h in Headers */, + A16680DD29967BF000318FE2 /* SDL_sysvideocapture.h in Headers */, A75FCD7923E25AB700529352 /* SDL_events_c.h in Headers */, A75FCD8B23E25AB700529352 /* SDL_gamepad_db.h in Headers */, A75FCDDB23E25AB700529352 /* SDL_gles2funcs.h in Headers */, @@ -5204,6 +5265,7 @@ A75FCDC823E25AB700529352 /* SDL_keyboard_c.h in Headers */, A1BB8B7327F6CF330057CFA8 /* SDL_list.h in Headers */, F386F6EE2884663E001840AA /* SDL_log_c.h in Headers */, + A16680ED29967C0C00318FE2 /* SDL_video_capture_c.h in Headers */, F395C1C12569C6A000942BFF /* SDL_mfijoystick_c.h in Headers */, A75FCDCC23E25AB700529352 /* SDL_mouse_c.h in Headers */, A75FCD8523E25AB700529352 /* SDL_nullevents_c.h in Headers */, @@ -5221,6 +5283,7 @@ A75FCDBE23E25AB700529352 /* SDL_rwopsbundlesupport.h in Headers */, A75FCD9623E25AB700529352 /* SDL_sensor_c.h in Headers */, A75FCD4623E25AB700529352 /* SDL_shaders_gl.h in Headers */, + A16680F529967C1D00318FE2 /* SDL_video_c.h in Headers */, A75FCD8023E25AB700529352 /* SDL_shaders_gles2.h in Headers */, A75FCD0C23E25AB700529352 /* SDL_shaders_metal_ios.h in Headers */, A75FCD0B23E25AB700529352 /* SDL_shaders_metal_macos.h in Headers */, @@ -5355,6 +5418,7 @@ A75FCF8F23E25AC700529352 /* SDL_dynapi_procs.h in Headers */, A75FCED823E25AC700529352 /* SDL_egl_c.h in Headers */, A75FCED223E25AC700529352 /* SDL_error_c.h in Headers */, + A16680DE29967BF000318FE2 /* SDL_sysvideocapture.h in Headers */, A75FCF3223E25AC700529352 /* SDL_events_c.h in Headers */, A75FCF4423E25AC700529352 /* SDL_gamepad_db.h in Headers */, A75FCF9423E25AC700529352 /* SDL_gles2funcs.h in Headers */, @@ -5369,6 +5433,7 @@ A75FCF8123E25AC700529352 /* SDL_keyboard_c.h in Headers */, A1BB8B7427F6CF330057CFA8 /* SDL_list.h in Headers */, F386F6EF2884663E001840AA /* SDL_log_c.h in Headers */, + A16680F029967C0C00318FE2 /* SDL_video_capture_c.h in Headers */, F395C1C22569C6A000942BFF /* SDL_mfijoystick_c.h in Headers */, A75FCF8523E25AC700529352 /* SDL_mouse_c.h in Headers */, A75FCF3E23E25AC700529352 /* SDL_nullevents_c.h in Headers */, @@ -5386,6 +5451,7 @@ A75FCF7723E25AC700529352 /* SDL_rwopsbundlesupport.h in Headers */, A75FCF4F23E25AC700529352 /* SDL_sensor_c.h in Headers */, A75FCEFF23E25AC700529352 /* SDL_shaders_gl.h in Headers */, + A16680F629967C1D00318FE2 /* SDL_video_c.h in Headers */, A75FCF3923E25AC700529352 /* SDL_shaders_gles2.h in Headers */, A75FCEC523E25AC700529352 /* SDL_shaders_metal_ios.h in Headers */, A75FCEC423E25AC700529352 /* SDL_shaders_metal_macos.h in Headers */, @@ -5520,6 +5586,7 @@ A769B15D23E259AE00872273 /* SDL_dynapi_procs.h in Headers */, A769B0A623E259AE00872273 /* SDL_egl_c.h in Headers */, A769B0A023E259AE00872273 /* SDL_error_c.h in Headers */, + A16680DB29967BEF00318FE2 /* SDL_sysvideocapture.h in Headers */, A769B10123E259AE00872273 /* SDL_events_c.h in Headers */, A769B11123E259AE00872273 /* SDL_gamepad_db.h in Headers */, A769B16323E259AE00872273 /* SDL_gles2funcs.h in Headers */, @@ -5534,6 +5601,7 @@ A769B14F23E259AE00872273 /* SDL_keyboard_c.h in Headers */, A1BB8B7127F6CF330057CFA8 /* SDL_list.h in Headers */, F386F6EC2884663E001840AA /* SDL_log_c.h in Headers */, + A16680E729967C0B00318FE2 /* SDL_video_capture_c.h in Headers */, F395C1BF2569C6A000942BFF /* SDL_mfijoystick_c.h in Headers */, A769B15323E259AE00872273 /* SDL_mouse_c.h in Headers */, A769B10C23E259AE00872273 /* SDL_nullevents_c.h in Headers */, @@ -5551,6 +5619,7 @@ A769B14523E259AE00872273 /* SDL_rwopsbundlesupport.h in Headers */, A769B11E23E259AE00872273 /* SDL_sensor_c.h in Headers */, A769B0CC23E259AE00872273 /* SDL_shaders_gl.h in Headers */, + A16680F329967C1C00318FE2 /* SDL_video_c.h in Headers */, A769B10723E259AE00872273 /* SDL_shaders_gles2.h in Headers */, A769B09323E259AE00872273 /* SDL_shaders_metal_ios.h in Headers */, A769B09223E259AE00872273 /* SDL_shaders_metal_macos.h in Headers */, @@ -5678,6 +5747,7 @@ A7D8AEE923E2514100DCD162 /* SDL_cocoavulkan.h in Headers */, A7D8AEFB23E2514100DCD162 /* SDL_cocoawindow.h in Headers */, F3F7D9D22933074E00816151 /* SDL_copying.h in Headers */, + A16680D129967BD100318FE2 /* SDL_video_c.h in Headers */, A7D8B8CD23E2514400DCD162 /* SDL_coreaudio.h in Headers */, A7D8A97023E2514000DCD162 /* SDL_coremotionsensor.h in Headers */, F3F7D9BA2933074E00816151 /* SDL_cpuinfo.h in Headers */, @@ -5693,6 +5763,7 @@ A7D8A96423E2514000DCD162 /* SDL_dummysensor.h in Headers */, A7D8AB0B23E2514100DCD162 /* SDL_dynapi.h in Headers */, A7D8AB1123E2514100DCD162 /* SDL_dynapi_overrides.h in Headers */, + A16680C329967B6900318FE2 /* SDL_video_capture.h in Headers */, A7D8AB1D23E2514100DCD162 /* SDL_dynapi_procs.h in Headers */, F3F7D9262933074E00816151 /* SDL_egl.h in Headers */, A7D8ABDA23E2514100DCD162 /* SDL_egl_c.h in Headers */, @@ -5765,6 +5836,7 @@ A7D8AC0423E2514100DCD162 /* SDL_rect_c.h in Headers */, F3F7D9BE2933074E00816151 /* SDL_render.h in Headers */, A7D8B9FC23E2514400DCD162 /* SDL_render_sw_c.h in Headers */, + A16680CE29967BD100318FE2 /* SDL_sysvideocapture.h in Headers */, F3F7D9162933074E00816151 /* SDL_revision.h in Headers */, A7D8BA3223E2514400DCD162 /* SDL_rotate.h in Headers */, F3F7D9662933074E00816151 /* SDL_rwops.h in Headers */, @@ -5777,6 +5849,7 @@ A7D8B98D23E2514400DCD162 /* SDL_shaders_metal_ios.h in Headers */, A7D8B99C23E2514400DCD162 /* SDL_shaders_metal_macos.h in Headers */, A7D8B9A223E2514400DCD162 /* SDL_shaders_metal_tvos.h in Headers */, + A16680D729967BD100318FE2 /* SDL_video_capture_c.h in Headers */, F3F7D92E2933074E00816151 /* SDL_shape.h in Headers */, A7D8AC0A23E2514100DCD162 /* SDL_shape_internals.h in Headers */, F3F7D8F62933074E00816151 /* SDL_stdinc.h in Headers */, @@ -5911,6 +5984,7 @@ A7D8AEEA23E2514100DCD162 /* SDL_cocoavulkan.h in Headers */, A7D8AEFC23E2514100DCD162 /* SDL_cocoawindow.h in Headers */, F3F7D9D32933074E00816151 /* SDL_copying.h in Headers */, + A16680D229967BD100318FE2 /* SDL_video_c.h in Headers */, A7D8B8CE23E2514400DCD162 /* SDL_coreaudio.h in Headers */, A7D8A97123E2514000DCD162 /* SDL_coremotionsensor.h in Headers */, F3F7D9BB2933074E00816151 /* SDL_cpuinfo.h in Headers */, @@ -5926,6 +6000,7 @@ A7D8A96523E2514000DCD162 /* SDL_dummysensor.h in Headers */, A7D8AB0C23E2514100DCD162 /* SDL_dynapi.h in Headers */, A7D8AB1223E2514100DCD162 /* SDL_dynapi_overrides.h in Headers */, + A16680C429967B6900318FE2 /* SDL_video_capture.h in Headers */, A7D8AB1E23E2514100DCD162 /* SDL_dynapi_procs.h in Headers */, F3F7D9272933074E00816151 /* SDL_egl.h in Headers */, A7D8ABDB23E2514100DCD162 /* SDL_egl_c.h in Headers */, @@ -5998,6 +6073,7 @@ A7D8AC0523E2514100DCD162 /* SDL_rect_c.h in Headers */, F3F7D9BF2933074E00816151 /* SDL_render.h in Headers */, A7D8B9FD23E2514400DCD162 /* SDL_render_sw_c.h in Headers */, + A16680CF29967BD100318FE2 /* SDL_sysvideocapture.h in Headers */, F3F7D9172933074E00816151 /* SDL_revision.h in Headers */, A7D8BA3323E2514400DCD162 /* SDL_rotate.h in Headers */, F3F7D9672933074E00816151 /* SDL_rwops.h in Headers */, @@ -6010,6 +6086,7 @@ A7D8B98E23E2514400DCD162 /* SDL_shaders_metal_ios.h in Headers */, A7D8B99D23E2514400DCD162 /* SDL_shaders_metal_macos.h in Headers */, A7D8B9A323E2514400DCD162 /* SDL_shaders_metal_tvos.h in Headers */, + A16680D829967BD100318FE2 /* SDL_video_capture_c.h in Headers */, F3F7D92F2933074E00816151 /* SDL_shape.h in Headers */, A7D8AC0B23E2514100DCD162 /* SDL_shape_internals.h in Headers */, F3F7D8F72933074E00816151 /* SDL_stdinc.h in Headers */, @@ -6151,6 +6228,7 @@ A7D8AB2023E2514100DCD162 /* SDL_dynapi_procs.h in Headers */, A7D8ABDD23E2514100DCD162 /* SDL_egl_c.h in Headers */, A7D8A96123E2514000DCD162 /* SDL_error_c.h in Headers */, + A16680DA29967BEE00318FE2 /* SDL_sysvideocapture.h in Headers */, A7D8BBA923E2514500DCD162 /* SDL_events_c.h in Headers */, A7D8B4B023E2514300DCD162 /* SDL_gamepad_db.h in Headers */, A7D8BA5923E2514400DCD162 /* SDL_gles2funcs.h in Headers */, @@ -6165,6 +6243,7 @@ A7D8BB8B23E2514500DCD162 /* SDL_keyboard_c.h in Headers */, A1BB8B7027F6CF330057CFA8 /* SDL_list.h in Headers */, F386F6EB2884663E001840AA /* SDL_log_c.h in Headers */, + A16680E429967C0A00318FE2 /* SDL_video_capture_c.h in Headers */, F395C1BE2569C6A000942BFF /* SDL_mfijoystick_c.h in Headers */, A7D8BB1F23E2514500DCD162 /* SDL_mouse_c.h in Headers */, A7D8AC0123E2514100DCD162 /* SDL_nullevents_c.h in Headers */, @@ -6182,6 +6261,7 @@ A7D8B5C723E2514300DCD162 /* SDL_rwopsbundlesupport.h in Headers */, A7D8A99123E2514000DCD162 /* SDL_sensor_c.h in Headers */, A7D8BA7723E2514400DCD162 /* SDL_shaders_gl.h in Headers */, + A16680F229967C1C00318FE2 /* SDL_video_c.h in Headers */, A7D8BA5323E2514400DCD162 /* SDL_shaders_gles2.h in Headers */, A7D8B99023E2514400DCD162 /* SDL_shaders_metal_ios.h in Headers */, A7D8B99F23E2514400DCD162 /* SDL_shaders_metal_macos.h in Headers */, @@ -6309,6 +6389,7 @@ A7D8AEE823E2514100DCD162 /* SDL_cocoavulkan.h in Headers */, A7D8AEFA23E2514100DCD162 /* SDL_cocoawindow.h in Headers */, F3F7D9D12933074E00816151 /* SDL_copying.h in Headers */, + A16680D029967BD100318FE2 /* SDL_video_c.h in Headers */, A7D8B8CC23E2514400DCD162 /* SDL_coreaudio.h in Headers */, A7D8A96F23E2514000DCD162 /* SDL_coremotionsensor.h in Headers */, F3F7D9B92933074E00816151 /* SDL_cpuinfo.h in Headers */, @@ -6324,6 +6405,7 @@ A7D8A96323E2514000DCD162 /* SDL_dummysensor.h in Headers */, A7D8AB0A23E2514100DCD162 /* SDL_dynapi.h in Headers */, A7D8AB1023E2514100DCD162 /* SDL_dynapi_overrides.h in Headers */, + A16680C229967B6900318FE2 /* SDL_video_capture.h in Headers */, A7D8AB1C23E2514100DCD162 /* SDL_dynapi_procs.h in Headers */, F3F7D9252933074E00816151 /* SDL_egl.h in Headers */, A7D8ABD923E2514100DCD162 /* SDL_egl_c.h in Headers */, @@ -6396,6 +6478,7 @@ A7D8AC0323E2514100DCD162 /* SDL_rect_c.h in Headers */, F3F7D9BD2933074E00816151 /* SDL_render.h in Headers */, A7D8B9FB23E2514400DCD162 /* SDL_render_sw_c.h in Headers */, + A16680CD29967BD100318FE2 /* SDL_sysvideocapture.h in Headers */, F3F7D9152933074E00816151 /* SDL_revision.h in Headers */, A7D8BA3123E2514400DCD162 /* SDL_rotate.h in Headers */, F3F7D9652933074E00816151 /* SDL_rwops.h in Headers */, @@ -6408,6 +6491,7 @@ A7D8B98C23E2514400DCD162 /* SDL_shaders_metal_ios.h in Headers */, A7D8B99B23E2514400DCD162 /* SDL_shaders_metal_macos.h in Headers */, A7D8B9A123E2514400DCD162 /* SDL_shaders_metal_tvos.h in Headers */, + A16680D629967BD100318FE2 /* SDL_video_capture_c.h in Headers */, F3F7D92D2933074E00816151 /* SDL_shape.h in Headers */, A7D8AC0923E2514100DCD162 /* SDL_shape_internals.h in Headers */, F3F7D8F52933074E00816151 /* SDL_stdinc.h in Headers */, @@ -6549,6 +6633,7 @@ A7D8AB1F23E2514100DCD162 /* SDL_dynapi_procs.h in Headers */, A7D8ABDC23E2514100DCD162 /* SDL_egl_c.h in Headers */, A7D8A96023E2514000DCD162 /* SDL_error_c.h in Headers */, + A16680D929967BEE00318FE2 /* SDL_sysvideocapture.h in Headers */, A7D8BBA823E2514500DCD162 /* SDL_events_c.h in Headers */, A7D8B4AF23E2514300DCD162 /* SDL_gamepad_db.h in Headers */, A7D8BA5823E2514400DCD162 /* SDL_gles2funcs.h in Headers */, @@ -6563,6 +6648,7 @@ A7D8BB8A23E2514500DCD162 /* SDL_keyboard_c.h in Headers */, A1BB8B6F27F6CF330057CFA8 /* SDL_list.h in Headers */, F386F6EA2884663E001840AA /* SDL_log_c.h in Headers */, + A16680E129967C0A00318FE2 /* SDL_video_capture_c.h in Headers */, F395C1BD2569C6A000942BFF /* SDL_mfijoystick_c.h in Headers */, A7D8BB1E23E2514500DCD162 /* SDL_mouse_c.h in Headers */, A7D8AC0023E2514100DCD162 /* SDL_nullevents_c.h in Headers */, @@ -6580,6 +6666,7 @@ A7D8B5C623E2514300DCD162 /* SDL_rwopsbundlesupport.h in Headers */, A7D8A99023E2514000DCD162 /* SDL_sensor_c.h in Headers */, A7D8BA7623E2514400DCD162 /* SDL_shaders_gl.h in Headers */, + A16680F129967C1B00318FE2 /* SDL_video_c.h in Headers */, A7D8BA5223E2514400DCD162 /* SDL_shaders_gles2.h in Headers */, A7D8B98F23E2514400DCD162 /* SDL_shaders_metal_ios.h in Headers */, A7D8B99E23E2514400DCD162 /* SDL_shaders_metal_macos.h in Headers */, @@ -6714,6 +6801,7 @@ A7D8AB2123E2514100DCD162 /* SDL_dynapi_procs.h in Headers */, A7D8ABDE23E2514100DCD162 /* SDL_egl_c.h in Headers */, A7D8A96223E2514000DCD162 /* SDL_error_c.h in Headers */, + A16680DC29967BEF00318FE2 /* SDL_sysvideocapture.h in Headers */, A7D8BBAA23E2514500DCD162 /* SDL_events_c.h in Headers */, A7D8B4B123E2514300DCD162 /* SDL_gamepad_db.h in Headers */, A7D8BA5A23E2514400DCD162 /* SDL_gles2funcs.h in Headers */, @@ -6728,6 +6816,7 @@ A7D8BB8C23E2514500DCD162 /* SDL_keyboard_c.h in Headers */, A1BB8B7227F6CF330057CFA8 /* SDL_list.h in Headers */, F386F6ED2884663E001840AA /* SDL_log_c.h in Headers */, + A16680EA29967C0B00318FE2 /* SDL_video_capture_c.h in Headers */, F395C1C02569C6A000942BFF /* SDL_mfijoystick_c.h in Headers */, A7D8BB2023E2514500DCD162 /* SDL_mouse_c.h in Headers */, A7D8AC0223E2514100DCD162 /* SDL_nullevents_c.h in Headers */, @@ -6745,6 +6834,7 @@ A7D8B5C823E2514300DCD162 /* SDL_rwopsbundlesupport.h in Headers */, A7D8A99223E2514000DCD162 /* SDL_sensor_c.h in Headers */, A7D8BA7823E2514400DCD162 /* SDL_shaders_gl.h in Headers */, + A16680F429967C1C00318FE2 /* SDL_video_c.h in Headers */, A7D8BA5423E2514400DCD162 /* SDL_shaders_gles2.h in Headers */, A7D8B99123E2514400DCD162 /* SDL_shaders_metal_ios.h in Headers */, A7D8B9A023E2514400DCD162 /* SDL_shaders_metal_macos.h in Headers */, @@ -7301,6 +7391,7 @@ A75FCE4B23E25AB700529352 /* SDL_render.c in Sources */, A75FCE4C23E25AB700529352 /* SDL_stretch.c in Sources */, A75FCE4D23E25AB700529352 /* s_floor.c in Sources */, + A16680EC29967C0C00318FE2 /* SDL_video_capture_apple.m in Sources */, A75FCE4E23E25AB700529352 /* SDL_blit_copy.c in Sources */, A75FCE4F23E25AB700529352 /* e_fmod.c in Sources */, A75FCE5023E25AB700529352 /* SDL_syspower.m in Sources */, @@ -7335,6 +7426,7 @@ A75FCE6F23E25AB700529352 /* SDL_surface.c in Sources */, A75FCE7023E25AB700529352 /* SDL_hidapi_xboxone.c in Sources */, A75FCE7123E25AB700529352 /* SDL_blit_auto.c in Sources */, + A16680EB29967C0C00318FE2 /* SDL_video_capture.c in Sources */, A75FCE7323E25AB700529352 /* SDL_keyboard.c in Sources */, A75FCE7523E25AB700529352 /* SDL_rect.c in Sources */, A75FCE7623E25AB700529352 /* SDL_cocoaopengles.m in Sources */, @@ -7496,6 +7588,7 @@ A75FD00423E25AC700529352 /* SDL_render.c in Sources */, A75FD00523E25AC700529352 /* SDL_stretch.c in Sources */, A75FD00623E25AC700529352 /* s_floor.c in Sources */, + A16680EF29967C0C00318FE2 /* SDL_video_capture_apple.m in Sources */, A75FD00723E25AC700529352 /* SDL_blit_copy.c in Sources */, A75FD00823E25AC700529352 /* e_fmod.c in Sources */, A75FD00923E25AC700529352 /* SDL_syspower.m in Sources */, @@ -7530,6 +7623,7 @@ A75FD02823E25AC700529352 /* SDL_surface.c in Sources */, A75FD02923E25AC700529352 /* SDL_hidapi_xboxone.c in Sources */, A75FD02A23E25AC700529352 /* SDL_blit_auto.c in Sources */, + A16680EE29967C0C00318FE2 /* SDL_video_capture.c in Sources */, A75FD02C23E25AC700529352 /* SDL_keyboard.c in Sources */, A75FD02E23E25AC700529352 /* SDL_rect.c in Sources */, A75FD02F23E25AC700529352 /* SDL_cocoaopengles.m in Sources */, @@ -7691,6 +7785,7 @@ A769B1DF23E259AE00872273 /* SDL_blit_0.c in Sources */, F36C7AD7294BA009004D61C3 /* SDL_runapp.c in Sources */, A769B1E023E259AE00872273 /* k_tan.c in Sources */, + A16680E629967C0B00318FE2 /* SDL_video_capture_apple.m in Sources */, A769B1E223E259AE00872273 /* SDL_diskaudio.c in Sources */, A769B1E423E259AE00872273 /* SDL_egl.c in Sources */, A769B1E523E259AE00872273 /* SDL_RLEaccel.c in Sources */, @@ -7725,6 +7820,7 @@ A769B20323E259AE00872273 /* SDL_qsort.c in Sources */, A769B20423E259AE00872273 /* SDL_hidapi_switch.c in Sources */, F3984CD525BCC92900374F43 /* SDL_hidapi_stadia.c in Sources */, + A16680E529967C0B00318FE2 /* SDL_video_capture.c in Sources */, A769B20523E259AE00872273 /* SDL_strtokr.c in Sources */, 5605720B2473687A00B46B66 /* SDL_syslocale.m in Sources */, F3820718284F3609004DD584 /* controller_type.c in Sources */, @@ -7886,6 +7982,7 @@ A7D8BAFE23E2514500DCD162 /* s_floor.c in Sources */, A7D8AC3A23E2514100DCD162 /* SDL_blit_copy.c in Sources */, A7D8BAE023E2514500DCD162 /* e_fmod.c in Sources */, + A16680D429967BD100318FE2 /* SDL_video_capture_apple.m in Sources */, A7D8B5D023E2514300DCD162 /* SDL_syspower.m in Sources */, A7D8BAEC23E2514500DCD162 /* e_log10.c in Sources */, A7D8AC7023E2514100DCD162 /* SDL_uikitopenglview.m in Sources */, @@ -7920,6 +8017,7 @@ A7D8AD2423E2514100DCD162 /* SDL_blit_auto.c in Sources */, A7D8BB6A23E2514500DCD162 /* SDL_keyboard.c in Sources */, A7D8ACE823E2514100DCD162 /* SDL_rect.c in Sources */, + A16680CB29967BD100318FE2 /* SDL_video_capture.c in Sources */, A7D8AE9B23E2514100DCD162 /* SDL_cocoaopengles.m in Sources */, A7D8B96923E2514400DCD162 /* SDL_qsort.c in Sources */, A7D8B55223E2514300DCD162 /* SDL_hidapi_switch.c in Sources */, @@ -8081,6 +8179,7 @@ A7D8BAFF23E2514500DCD162 /* s_floor.c in Sources */, A7D8AC3B23E2514100DCD162 /* SDL_blit_copy.c in Sources */, A7D8BAE123E2514500DCD162 /* e_fmod.c in Sources */, + A16680D529967BD100318FE2 /* SDL_video_capture_apple.m in Sources */, A7D8B5D123E2514300DCD162 /* SDL_syspower.m in Sources */, A7D8BAED23E2514500DCD162 /* e_log10.c in Sources */, A7D8AC7123E2514100DCD162 /* SDL_uikitopenglview.m in Sources */, @@ -8115,6 +8214,7 @@ A7D8AD2523E2514100DCD162 /* SDL_blit_auto.c in Sources */, A7D8BB6B23E2514500DCD162 /* SDL_keyboard.c in Sources */, A7D8ACE923E2514100DCD162 /* SDL_rect.c in Sources */, + A16680CC29967BD100318FE2 /* SDL_video_capture.c in Sources */, A7D8AE9C23E2514100DCD162 /* SDL_cocoaopengles.m in Sources */, A7D8B96A23E2514400DCD162 /* SDL_qsort.c in Sources */, A7D8B55323E2514300DCD162 /* SDL_hidapi_switch.c in Sources */, @@ -8276,6 +8376,7 @@ A7D8ADEA23E2514100DCD162 /* SDL_blit_0.c in Sources */, F36C7AD6294BA009004D61C3 /* SDL_runapp.c in Sources */, A7D8BB0D23E2514500DCD162 /* k_tan.c in Sources */, + A16680E329967C0A00318FE2 /* SDL_video_capture_apple.m in Sources */, A7D8B8AC23E2514400DCD162 /* SDL_diskaudio.c in Sources */, A7D8AFC423E2514200DCD162 /* SDL_egl.c in Sources */, A7D8AC3723E2514100DCD162 /* SDL_RLEaccel.c in Sources */, @@ -8310,6 +8411,7 @@ A7D8B96C23E2514400DCD162 /* SDL_qsort.c in Sources */, A7D8B55523E2514300DCD162 /* SDL_hidapi_switch.c in Sources */, F3984CD425BCC92900374F43 /* SDL_hidapi_stadia.c in Sources */, + A16680E229967C0A00318FE2 /* SDL_video_capture.c in Sources */, A7D8B96623E2514400DCD162 /* SDL_strtokr.c in Sources */, 560572092473687900B46B66 /* SDL_syslocale.m in Sources */, F3820717284F3609004DD584 /* controller_type.c in Sources */, @@ -8418,6 +8520,7 @@ 75E0915A241EA924004729E1 /* SDL_virtualjoystick.c in Sources */, A7D8ABEB23E2514100DCD162 /* SDL_nullvideo.c in Sources */, A7D8AB6723E2514100DCD162 /* SDL_offscreenevents.c in Sources */, + A16680CA29967BD100318FE2 /* SDL_video_capture.c in Sources */, A7D8ABF123E2514100DCD162 /* SDL_nullevents.c in Sources */, A7D8B81823E2514400DCD162 /* SDL_audiodev.c in Sources */, A7D8AF0C23E2514100DCD162 /* SDL_cocoaclipboard.m in Sources */, @@ -8440,6 +8543,7 @@ A7D8AED623E2514100DCD162 /* SDL_cocoakeyboard.m in Sources */, A7D8AB1623E2514100DCD162 /* SDL_dynapi.c in Sources */, A7D8BA8523E2514400DCD162 /* SDL_shaders_gl.c in Sources */, + A16680D329967BD100318FE2 /* SDL_video_capture_apple.m in Sources */, A7D8BAF123E2514500DCD162 /* e_log.c in Sources */, A7D8AED023E2514100DCD162 /* SDL_cocoamessagebox.m in Sources */, F376F6552559B4E300CFC0BC /* SDL_hidapi.c in Sources */, @@ -8612,6 +8716,7 @@ A7D8BA3A23E2514400DCD162 /* SDL_d3dmath.c in Sources */, A7D8ABEE23E2514100DCD162 /* SDL_nullvideo.c in Sources */, A7D8AB6A23E2514100DCD162 /* SDL_offscreenevents.c in Sources */, + A16680DF29967C0A00318FE2 /* SDL_video_capture.c in Sources */, A7D8ABF423E2514100DCD162 /* SDL_nullevents.c in Sources */, A7D8B81B23E2514400DCD162 /* SDL_audiodev.c in Sources */, A7D8AF0F23E2514100DCD162 /* SDL_cocoaclipboard.m in Sources */, @@ -8634,6 +8739,7 @@ A7D8BAF423E2514500DCD162 /* e_log.c in Sources */, A7D8AED323E2514100DCD162 /* SDL_cocoamessagebox.m in Sources */, A7D8BA2E23E2514400DCD162 /* SDL_blendfillrect.c in Sources */, + A16680E029967C0A00318FE2 /* SDL_video_capture_apple.m in Sources */, A7D8AEE523E2514100DCD162 /* SDL_cocoashape.m in Sources */, A7D8AEBB23E2514100DCD162 /* SDL_cocoamouse.m in Sources */, F376F6762559B4E500CFC0BC /* SDL_hidapi.c in Sources */, @@ -8806,6 +8912,7 @@ A7D8AB6C23E2514100DCD162 /* SDL_offscreenevents.c in Sources */, A7D8ACAA23E2514100DCD162 /* SDL_uikitview.m in Sources */, A7D8ABF623E2514100DCD162 /* SDL_nullevents.c in Sources */, + A16680E829967C0B00318FE2 /* SDL_video_capture.c in Sources */, A7D8B81D23E2514400DCD162 /* SDL_audiodev.c in Sources */, A7D8AF1123E2514100DCD162 /* SDL_cocoaclipboard.m in Sources */, A7D8ABD223E2514100DCD162 /* SDL_blit_slow.c in Sources */, @@ -8828,6 +8935,7 @@ A7D8BA8A23E2514400DCD162 /* SDL_shaders_gl.c in Sources */, A7D8BAF623E2514500DCD162 /* e_log.c in Sources */, A7D8AED523E2514100DCD162 /* SDL_cocoamessagebox.m in Sources */, + A16680E929967C0B00318FE2 /* SDL_video_capture_apple.m in Sources */, A7D8BA3023E2514400DCD162 /* SDL_blendfillrect.c in Sources */, A7D8ACE023E2514100DCD162 /* SDL_uikitvideo.m in Sources */, F376F68D2559B4E900CFC0BC /* SDL_hidapi.c in Sources */, diff --git a/include/SDL3/SDL.h b/include/SDL3/SDL.h index cb51cd7ac..a8c68aa92 100644 --- a/include/SDL3/SDL.h +++ b/include/SDL3/SDL.h @@ -75,6 +75,7 @@ #include #include #include +#include "SDL3/SDL_video_capture.h" #include #endif /* SDL_h_ */ diff --git a/include/SDL3/SDL_video_capture.h b/include/SDL3/SDL_video_capture.h new file mode 100644 index 000000000..85f1fc4b6 --- /dev/null +++ b/include/SDL3/SDL_video_capture.h @@ -0,0 +1,302 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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 +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * SDL VideoCapture Device IDs. + */ +typedef Sint32 SDL_VideoCaptureDeviceID; +#define SDL_VIDEO_CAPTURE_INVALID -1 + +#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; /**< Video data format */ + Uint32 width; /**< Width */ + Uint32 height; /**< Height */ +} SDL_VideoCaptureSpec; + +/** + * SDL Video Capture Status + */ +typedef enum +{ + 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 +{ + int num_planes; /**< Number of planes */ + Uint8 *data[3]; /**< Pointer to data of i-th plane */ + int row_stride[3]; /**< Row stride of i-th plane */ + int fd; /**< DMABUF file descriptor if available (-1 if not) */ + void *clientbuffer; /**< Android clientbuffer if available (NULL if not) */ + void *internal; /**< Private field */ +} SDL_VideoCaptureFrame; + +/** + * Open a Video Capture device + * + * \param device_name device name to open. Can be NULL. + * + * \returns device id, or SDL_VIDEO_CAPTURE_INVALID on error + * + * \sa SDL_GetVideoCaptureDeviceName + * \sa SDL_GetNumVideoCaptureDevices + */ +extern DECLSPEC SDL_VideoCaptureDeviceID SDLCALL SDL_OpenVideoCapture(const char *device_name); + +/** + * Set specification + * + * \param id video capture opened device id + * \param desired desired video capture spec + * \param obtained obtained video capture spec + * \param allowed_changes allow changes or not + * + * \returns 0 on success, negative value on error + */ +extern DECLSPEC int SDLCALL SDL_SetVideoCaptureSpec(SDL_VideoCaptureDeviceID id, + const SDL_VideoCaptureSpec *desired, + SDL_VideoCaptureSpec *obtained, + int allowed_changes); + +/** + * Open a Video Capture device and set specification + * + * \param device_name device name to open. Can be NULL. + * \param desired desired video capture spec + * \param obtained obtained video capture spec + * \param allowed_changes allow changes or not + * + * \returns device id, or SDL_VIDEO_CAPTURE_INVALID on error + */ +extern DECLSPEC SDL_VideoCaptureDeviceID SDLCALL SDL_OpenVideoCaptureWithSpec( + const char *device_name, + const SDL_VideoCaptureSpec *desired, + SDL_VideoCaptureSpec *obtained, + int allowed_changes); +/** + * Get device name + * + * \param index device index between 0 and num -1 + * + * \returns device name, shouldn't be freed + * + * \sa SDL_GetNumVideoCaptureDevices + */ +extern DECLSPEC const char * SDLCALL SDL_GetVideoCaptureDeviceName(int index); + +/** + * Use this function to get the number of video capture devices. + * + * \returns the number of video capture devices + */ +extern DECLSPEC int SDLCALL SDL_GetNumVideoCaptureDevices(void); + +/** + * Get the obtained video capture spec + * + * \param id video capture opened device id + * \param spec The SDL_VideoCaptureSpec to be initialized by this function. + * + * \returns 0 on success, negative value on error + */ +extern DECLSPEC int SDLCALL SDL_GetVideoCaptureSpec(SDL_VideoCaptureDeviceID id, SDL_VideoCaptureSpec *spec); + + +#define SDL_VIDEO_CAPTURE_TYPE_COMPRESSED 1 +#define SDL_VIDEO_CAPTURE_TYPE_EMULATED 2 +/** + * Get frame format of video capture device. + * The value can be used to fill SDL_VideoCaptureSpec structure. + * + * \param id video capture opened device id + * \param index format between 0 and num -1 + * \param format pointer output format (SDL_PixelFormatEnum) + * \param type pointer output type + * + * \returns 0 on success, negative value on error + * + * \sa SDL_GetNumVideoCaptureFormats + */ +extern DECLSPEC int SDLCALL SDL_GetVideoCaptureFormat(SDL_VideoCaptureDeviceID id, int index, Uint32 *format, Uint32 *type); + +/** + * Number of available formats for the device + * + * \param id video capture opened device id + * + * \returns number of formats, negative value on error + * + * \sa SDL_GetVideoCaptureFormat + */ +extern DECLSPEC int SDLCALL SDL_GetNumVideoCaptureFormats(SDL_VideoCaptureDeviceID id); + +/** + * Get frame sizes of the device and the specified input format. + * The value can be used to fill SDL_VideoCaptureSpec structure. + * + * \param id video capture opened device id + * \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, negative value on error + * + * \sa SDL_GetNumVideoCaptureFrameSizes + */ +extern DECLSPEC int SDLCALL SDL_GetVideoCaptureFrameSize(SDL_VideoCaptureDeviceID id, Uint32 format, int index, Uint32 *width, Uint32 *height); + +/** + * Number of different framesizes available for the device and pixel format. + * + * \param id video capture opened device id + * \param format frame pixel format (SDL_PixelFormatEnum) + * + * \returns number of framesize, negative value on error + * + * \sa SDL_GetVideoCaptureFrameSize + */ +extern DECLSPEC int SDLCALL SDL_GetNumVideoCaptureFrameSizes(SDL_VideoCaptureDeviceID id, Uint32 format); + + +/** + * Get video capture status + * + * \param id video capture opened device id + * + * \returns 0 on success, negative value on error + * + * \sa SDL_VideoCaptureStatus + */ +extern DECLSPEC SDL_VideoCaptureStatus SDLCALL SDL_GetVideoCaptureStatus(SDL_VideoCaptureDeviceID id); + +/** + * Start video capture + * + * \param id video capture opened device id + * + * \returns 0 on success, negative value on error + * + * \sa SDL_StopVideoCapture + */ +extern DECLSPEC int SDLCALL SDL_StartVideoCapture(SDL_VideoCaptureDeviceID id); + +/** + * 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_VideoCaptureReleaseFrame + * + * \param id video capture opened device id + * \param frame pointer to get the frame + * \param ticks pointer to get the ticks when this was read from driver + * + * \returns 0 on success, negative value on error + * + * \sa SDL_VideoCaptureReleaseFrame + */ +extern DECLSPEC int SDLCALL SDL_VideoCaptureAcquireFrame(SDL_VideoCaptureDeviceID id, SDL_VideoCaptureFrame *frame, Uint64 *ticks); + +/** + * Release a frame. Let the back-end re-use the internal buffer for video capture. + * + * \param id video capture opened device id + * \param frame frame pointer. + * + * \returns 0 on success, negative value on error + * + * \sa SDL_VideoCaptureAcquireFrame + */ +extern DECLSPEC int SDLCALL SDL_VideoCaptureReleaseFrame(SDL_VideoCaptureDeviceID id, SDL_VideoCaptureFrame *frame); + +/** + * Stop Video Capture + * + * \param id video capture opened device id + * + * \returns 0 on success, negative value on error + * + * \sa SDL_StartVideoCapture + */ +extern DECLSPEC int SDLCALL SDL_StopVideoCapture(SDL_VideoCaptureDeviceID id); + +/** + * Use this function to shut down video_capture processing and close the video_capture device. + * + * The device ID is invalid as soon as the device is closed, and is eligible + * for reuse in a new SDL_OpenVideoCaptureWithSpec() call immediately. + * + * \param id video capture opened device id + * + * \sa SDL_OpenVideoCaptureWithSpec + * \sa SDL_OpenVideoCapture + */ +extern DECLSPEC void SDLCALL SDL_CloseVideoCapture(SDL_VideoCaptureDeviceID id); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include + +#endif /* SDL_video_capture_h_ */ diff --git a/include/build_config/SDL_build_config_ios.h b/include/build_config/SDL_build_config_ios.h index a523a0883..7db933bc5 100644 --- a/include/build_config/SDL_build_config_ios.h +++ b/include/build_config/SDL_build_config_ios.h @@ -199,6 +199,8 @@ #define SDL_VIDEO_METAL 1 #endif +#define HAVE_COREMEDIA 1 + /* Enable system power support */ #define SDL_POWER_UIKIT 1 diff --git a/include/build_config/SDL_build_config_macos.h b/include/build_config/SDL_build_config_macos.h index df6d679e1..d7df852ae 100644 --- a/include/build_config/SDL_build_config_macos.h +++ b/include/build_config/SDL_build_config_macos.h @@ -260,6 +260,8 @@ #endif #endif +#define HAVE_COREMEDIA 1 + /* Enable system power support */ #define SDL_POWER_MACOSX 1 diff --git a/src/video/SDL_sysvideocapture.h b/src/video/SDL_sysvideocapture.h new file mode 100644 index 000000000..61292b385 --- /dev/null +++ b/src/video/SDL_sysvideocapture.h @@ -0,0 +1,91 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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" + +#define _THIS SDL_VideoCaptureDevice *_this + +/* 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 */ + SDL_VideoCaptureDeviceID id; + + /* The device's current video_capture specification */ + SDL_VideoCaptureSpec spec; + + /* Device name */ + char *dev_name; + + /* Current state flags */ + SDL_atomic_t shutdown; + SDL_atomic_t 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(_THIS); +extern void CloseDevice(_THIS); + +extern int InitDevice(_THIS); + +extern int GetDeviceSpec(_THIS, SDL_VideoCaptureSpec *spec); + +extern int StartCapture(_THIS); +extern int StopCapture(_THIS); + +extern int AcquireFrame(_THIS, SDL_VideoCaptureFrame *frame, Uint64 *ticks); +extern int ReleaseFrame(_THIS, SDL_VideoCaptureFrame *frame); + +extern int GetNumFormats(_THIS); +extern int GetFormat(_THIS, int index, Uint32 *format, Uint32 *type); + +extern int GetNumFrameSizes(_THIS, Uint32 format); +extern int GetFrameSize(_THIS, Uint32 format, int index, Uint32 *width, Uint32 *height); + +extern int GetDeviceName(int index, char *buf, int size); +extern int GetNumDevices(void); + + + +#endif /* SDL_sysvideocapture_h_ */ diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index e2e2673af..ac702ea77 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -30,6 +30,7 @@ #include "SDL_video_c.h" #include "../events/SDL_events_c.h" #include "../timer/SDL_timer_c.h" +#include "SDL_video_capture_c.h" #include @@ -420,6 +421,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' */ @@ -448,6 +450,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; @@ -541,6 +547,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(); } @@ -3283,6 +3292,7 @@ void SDL_VideoQuit(void) } /* Halt event processing before doing anything else */ + SDL_QuitVideoCapture(); SDL_QuitTouch(); SDL_QuitMouse(); SDL_QuitKeyboard(); diff --git a/src/video/SDL_video_capture.c b/src/video/SDL_video_capture.c new file mode 100644 index 000000000..5efd52720 --- /dev/null +++ b/src/video/SDL_video_capture.c @@ -0,0 +1,2491 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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 "../thread/SDL_systhread.h" + +#define DEBUG_VIDEO_CAPTURE_CAPTURE 1 + + +/* list node entries to share frames between SDL and user app */ +typedef struct entry_t +{ + SDL_VideoCaptureFrame frame; + Uint64 ticks; +} entry_t; + +static SDL_VideoCaptureDevice *open_devices[16]; + +static SDL_VideoCaptureDevice * +get_video_capture_device(SDL_VideoCaptureDeviceID id) +{ + Uint32 i = id; + if (i >= SDL_arraysize(open_devices) -1 || open_devices[i] == NULL) { + SDL_SetError("Invalid video capture device ID"); + return NULL; + } + return open_devices[i]; +} + +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); + } + + open_devices[device->id] = NULL; + + CloseDevice(device); + + { + entry_t *entry = NULL; + while (device->buffer_queue != NULL) { + SDL_ListPop(&device->buffer_queue, (void**)&entry); + if (entry) { + SDL_free(entry); + } + } + } + + SDL_free(device->dev_name); + SDL_free(device); +} + +#if __IPHONEOS__ || __MACOS__ +/* unused */ +#else +static void +size_buffer(Uint32 width, Uint32 height, Uint32 format, Uint32 *size, Uint32 *pitch) +{ + const int sz_plane = width * height; + const int sz_plane_chroma = ((width + 1) / 2) * ((height + 1) / 2); + const int sz_plane_packed = ((width + 1) / 2) * height; + + switch (format) + { + case SDL_PIXELFORMAT_YV12: /**< Planar mode: Y + V + U (3 planes) */ + case SDL_PIXELFORMAT_IYUV: /**< Planar mode: Y + U + V (3 planes) */ + *size = sz_plane + sz_plane_chroma + sz_plane_chroma; + *pitch = width; + break; + + case SDL_PIXELFORMAT_YUY2: /**< Packed mode: Y0+U0+Y1+V0 (1 plane) */ + case SDL_PIXELFORMAT_UYVY: /**< Packed mode: U0+Y0+V0+Y1 (1 plane) */ + case SDL_PIXELFORMAT_YVYU: /**< Packed mode: Y0+V0+Y1+U0 (1 plane) */ + *size = 4 * sz_plane_packed; + *pitch = ((width + 1)/2) * 4; + break; + + case SDL_PIXELFORMAT_NV12: /**< Planar mode: Y + U/V interleaved (2 planes) */ + case SDL_PIXELFORMAT_NV21: /**< Planar mode: Y + V/U interleaved (2 planes) */ + *size = sz_plane + sz_plane_chroma + sz_plane_chroma; + *pitch = width; + break; + + default: + { + int bpp; + Uint32 Rmask, Gmask, Bmask, Amask; + SDL_GetMasksForPixelFormatEnum(format, &bpp, &Rmask, &Gmask, &Bmask, &Amask); + *size = width * height * (bpp / 8); + *pitch = width * (bpp / 8); + } + } +} +#endif + +void +SDL_CloseVideoCapture(SDL_VideoCaptureDeviceID id) +{ + SDL_VideoCaptureDevice *device = get_video_capture_device(id); + if (!device) { + SDL_InvalidParamError("id"); + return; + } + close_device(device); +} + +int +SDL_StartVideoCapture(SDL_VideoCaptureDeviceID id) +{ + SDL_VideoCaptureStatus status; + SDL_VideoCaptureDevice *device = get_video_capture_device(id); + if (!device) { + return SDL_InvalidParamError("id"); + } + + if (device->is_spec_set == SDL_FALSE) { + return SDL_SetError("no spec set"); + } + + status = SDL_GetVideoCaptureStatus(id); + if (status != SDL_VIDEO_CAPTURE_INIT) { + return SDL_SetError("invalid state"); + } + + if (StartCapture(device) < 0) { + return -1; + } + + SDL_AtomicSet(&device->enabled, 1); + + return 0; +} + +int +SDL_GetVideoCaptureSpec(SDL_VideoCaptureDeviceID id, SDL_VideoCaptureSpec *spec) +{ + SDL_VideoCaptureDevice *device = get_video_capture_device(id); + if (!device) { + return SDL_InvalidParamError("id"); + } + + if (!spec) { + return SDL_InvalidParamError("spec"); + } + + SDL_zerop(spec); + + return GetDeviceSpec(device, spec); +} + +int +SDL_StopVideoCapture(SDL_VideoCaptureDeviceID id) +{ + SDL_VideoCaptureStatus status; + SDL_VideoCaptureDevice *device = get_video_capture_device(id); + int ret; + if (!device) { + return SDL_InvalidParamError("id"); + } + + status = SDL_GetVideoCaptureStatus(id); + + 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; +} + +/* Check spec has valid format and frame size */ +static int +prepare_video_capturespec(SDL_VideoCaptureDeviceID id, const SDL_VideoCaptureSpec *desired, SDL_VideoCaptureSpec *obtained, int allowed_changes) +{ + /* Check format */ + { + int i, num = SDL_GetNumVideoCaptureFormats(id); + int is_format_valid = 0; + + for (i = 0; i < num; i++) { + Uint32 format, type; + if (SDL_GetVideoCaptureFormat(id, i, &format, &type) == 0) { + if (format == desired->format && format != SDL_PIXELFORMAT_UNKNOWN) { + is_format_valid = 1; + obtained->format = format; + break; + } + } + } + + if (!is_format_valid) { + if (allowed_changes) { + /* Put first non compressed format, eventually emulated, better not emulated */ + for (i = 0; i < num; i++) { + Uint32 format, type; + if (SDL_GetVideoCaptureFormat(id, i, &format, &type) == 0) { + if (type & SDL_VIDEO_CAPTURE_TYPE_COMPRESSED) { + continue; + } + if (format != SDL_PIXELFORMAT_UNKNOWN) { + obtained->format = format; + is_format_valid = 1; + } + if ((type & SDL_VIDEO_CAPTURE_TYPE_EMULATED) == 0) { + 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(id, obtained->format); + int is_framesize_valid = 0; + + for (i = 0; i < num; i++) { + Uint32 w, h; + if (SDL_GetVideoCaptureFrameSize(id, 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) { + Uint32 w, h; + if (SDL_GetVideoCaptureFrameSize(id, 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; +} + +const char * +SDL_GetVideoCaptureDeviceName(int index) +{ + static char buf[256]; + buf[0] = 0; + buf[255] = 0; + if (GetDeviceName(index, buf, sizeof (buf)) < 0) { + buf[0] = 0; + } + return buf; +} + +int +SDL_GetNumVideoCaptureDevices(void) +{ + return GetNumDevices(); +} + +/* Video capture thread function */ +static int SDLCALL +SDL_CaptureVideoThread(void *devicep) +{ + const Uint32 delay = 20; + SDL_VideoCaptureDevice *device = (SDL_VideoCaptureDevice *) devicep; + +#if DEBUG_VIDEO_CAPTURE_CAPTURE + SDL_Log("Start thread 'SDL_CaptureVideo'"); +#endif + + +#if SDL_VIDEO_DRIVER_ANDROID +#if 0 + // TODO + { + /* Set thread priority to THREAD_PRIORITY_VIDEO */ + extern void Android_JNI_VideoCaptureSetThreadPriority(int, int); + Android_JNI_VideoCaptureSetThreadPriority(device->iscapture, device->id); + } +#endif +#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)) { + Uint64 t = 0; + SDL_VideoCaptureFrame f; + int ret; + entry_t *entry; + + SDL_zero(f); + f.fd = -1; + f.clientbuffer = NULL; + + SDL_LockMutex(device->acquiring_lock); + ret = AcquireFrame(device, &f, &t); + 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("error AcquireFrame: %d %s",ret, SDL_GetError()); +#endif + f.num_planes = 0; + t = 0; + } + + + entry = SDL_malloc(sizeof (entry_t)); + if (entry == NULL) { + goto error_mem; + } + + entry->frame = f; + entry->ticks = t; + + 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("End thread 'SDL_CaptureVideo'"); +#endif + return 0; + +error_mem: +#if DEBUG_VIDEO_CAPTURE_CAPTURE + SDL_Log("End thread 'SDL_CaptureVideo' with error: %s", SDL_GetError()); +#endif + SDL_AtomicSet(&device->shutdown, 1); + SDL_OutOfMemory(); + return 0; +} + +SDL_VideoCaptureDeviceID +SDL_OpenVideoCapture(const char *device_name) +{ + Uint32 i; + SDL_VideoCaptureDeviceID id = SDL_VIDEO_CAPTURE_INVALID; + SDL_VideoCaptureDevice *device = 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 < SDL_arraysize(open_devices); i++) { + if (open_devices[i] == NULL) { + id = i; + break; + } + } + + if (id == SDL_VIDEO_CAPTURE_INVALID) { + SDL_SetError("Too many open video capture devices"); + goto error; + } + + /* If app doesn't care about a specific device, let the user override. */ + if (device_name == NULL) { + device_name = SDL_getenv("SDL_VIDEO_CAPTURE_DEVICE_NAME"); + } + + if (device_name == NULL) { + device_name = SDL_GetVideoCaptureDeviceName(0); + } + + if (device_name == NULL) { + SDL_InvalidParamError("device"); + goto error; + } + + device = (SDL_VideoCaptureDevice *) SDL_calloc(1, sizeof (SDL_VideoCaptureDevice)); + if (device == NULL) { + SDL_OutOfMemory(); + goto error; + } + device->id = id; + 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 if necessary */ +// if (!current_video_capture.impl.ProvidesOwnCallbackThread) { + if (1) { + /* Start the video_capture thread */ + /* !!! FIXME: we don't force the video_capture thread stack size here if it calls into user code, but maybe we should? */ + /* buffer queueing callback only needs a few bytes, so make the stack tiny. */ + int is_internal_thread = 1; + const size_t stacksize = is_internal_thread ? 64 * 1024 : 0; + char threadname[64]; + + SDL_snprintf(threadname, sizeof (threadname), "SDLVideoC%d", (int) device->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->id; + +error: + close_device(device); + return SDL_VIDEO_CAPTURE_INVALID; +} + +int +SDL_SetVideoCaptureSpec(SDL_VideoCaptureDeviceID id, + const SDL_VideoCaptureSpec *desired, + SDL_VideoCaptureSpec *obtained, + int allowed_changes) +{ + SDL_VideoCaptureDevice *device = get_video_capture_device(id); + SDL_VideoCaptureSpec _obtained; + SDL_VideoCaptureSpec _desired; + + if (!device) { + return SDL_InvalidParamError("id"); + } + + 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(id, desired, obtained, allowed_changes) < 0) { + return -1; + } + + device->spec = *obtained; + + if (InitDevice(device) < 0) { + return -1; + } + + *obtained = device->spec; + + device->is_spec_set = SDL_TRUE; + + return 0; +} + +int +SDL_VideoCaptureAcquireFrame(SDL_VideoCaptureDeviceID id, SDL_VideoCaptureFrame *frame, Uint64 *ticks) +{ + SDL_VideoCaptureDevice *device = get_video_capture_device(id); + Uint64 _ticks; + + if (!device) { + return SDL_InvalidParamError("id"); + } + + if (!frame) { + return SDL_InvalidParamError("frame"); + } + + SDL_zerop(frame); + frame->fd = -1; + frame->clientbuffer = NULL; + + if (ticks) { + *ticks = 0; + } else { + ticks = &_ticks; + _ticks = 0; + } + + if (device->thread == NULL) { + int ret; + + /* wait a frame */ + while ((ret = AcquireFrame(device, frame, ticks)) == 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; + *ticks = entry->ticks; + SDL_free(entry); + + /* Error from thread */ + if (frame->num_planes == 0 && *ticks == 0) { + SDL_SetError("error from acquisition thread"); + return -1; + } + + + } else { + // Queue is empty. Not an error. + } + } + + return 0; +} + +int +SDL_VideoCaptureReleaseFrame(SDL_VideoCaptureDeviceID id, SDL_VideoCaptureFrame *frame) +{ + SDL_VideoCaptureDevice *device = get_video_capture_device(id); + + if (!device) { + return SDL_InvalidParamError("id"); + } + + if (frame == NULL) { + return SDL_InvalidParamError("frame"); + } + + if (ReleaseFrame(device, frame) < 0) { + return -1; + } + + SDL_zerop(frame); + + return 0; +} + + +int +SDL_GetNumVideoCaptureFormats(SDL_VideoCaptureDeviceID id) +{ + SDL_VideoCaptureDevice *device = get_video_capture_device(id); + if (!device) { + return SDL_InvalidParamError("id"); + } + return GetNumFormats(device); +} + +int +SDL_GetVideoCaptureFormat(SDL_VideoCaptureDeviceID id, int index, Uint32 *format, Uint32 *type) +{ + SDL_VideoCaptureDevice *device = get_video_capture_device(id); + if (!device) { + return SDL_InvalidParamError("id"); + } + if (!format) { + return SDL_InvalidParamError("format"); + } + if (!type) { + return SDL_InvalidParamError("type"); + } + *format = 0; + *type = 0; + return GetFormat(device, index, format, type); +} + +int +SDL_GetNumVideoCaptureFrameSizes(SDL_VideoCaptureDeviceID id, Uint32 format) +{ + SDL_VideoCaptureDevice *device = get_video_capture_device(id); + if (!device) { + return SDL_InvalidParamError("id"); + } + return GetNumFrameSizes(device, format); +} + +int +SDL_GetVideoCaptureFrameSize(SDL_VideoCaptureDeviceID id, Uint32 format, int index, Uint32 *width, Uint32 *height) +{ + SDL_VideoCaptureDevice *device = get_video_capture_device(id); + if (!device) { + return SDL_InvalidParamError("id"); + } + if (!width) { + return SDL_InvalidParamError("width"); + } + if (!height) { + return SDL_InvalidParamError("height"); + } + *width = 0; + *height = 0; + return GetFrameSize(device, format, index, width, height); +} + +SDL_VideoCaptureDeviceID +SDL_OpenVideoCaptureWithSpec( + const char *device_name, + const SDL_VideoCaptureSpec *desired, + SDL_VideoCaptureSpec *obtained, + int allowed_changes) +{ + SDL_VideoCaptureDeviceID id; + + if ((id = SDL_OpenVideoCapture(device_name)) == SDL_VIDEO_CAPTURE_INVALID) { + return SDL_VIDEO_CAPTURE_INVALID; + } + + if (SDL_SetVideoCaptureSpec(id, desired, obtained, allowed_changes) < 0) { + SDL_CloseVideoCapture(id); + return SDL_VIDEO_CAPTURE_INVALID; + } + return id; +} + +SDL_VideoCaptureStatus +SDL_GetVideoCaptureStatus(SDL_VideoCaptureDeviceID id) +{ + SDL_VideoCaptureDevice *device = get_video_capture_device(id); + + 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; +} + +int +SDL_VideoCaptureInit(void) +{ + SDL_zeroa(open_devices); + return 0; +} + +void +SDL_QuitVideoCapture(void) +{ + Uint32 i; + for (i = 0; i < SDL_arraysize(open_devices); i++) { + close_device(open_devices[i]); + } + + SDL_zeroa(open_devices); +} + + +/* --------------------------- + * + * DRIVER CODE using V4L2 + */ + +#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 dmabuf_fd; + int available; /* Is available in userspace */ +}; + +struct SDL_PrivateVideoCaptureData +{ + int fd; + enum io_method io; + Uint32 nb_buffers; + struct buffer *buffers; + int first_start; + int driver_pitch; +}; + +#include +#include +#include /* low-level i/o */ +#include +#include +#include +#include + +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(_THIS, SDL_VideoCaptureFrame *frame) +{ + struct v4l2_buffer buf; + unsigned int i; + + int fd = _this->hidden->fd; + enum io_method io = _this->hidden->io; + int size = (int) _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->row_stride[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 (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->row_stride[0] = _this->hidden->driver_pitch; + frame->fd = _this->hidden->buffers[buf.index].dmabuf_fd; + _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, 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->row_stride[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, frame->data[0]); +#endif + break; + } + + return 1; +} + + +int +ReleaseFrame(_THIS, SDL_VideoCaptureFrame *frame) +{ + struct v4l2_buffer buf; + unsigned 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(_THIS, SDL_VideoCaptureFrame *frame, Uint64 *ticks) +{ + 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){ + if (ticks) { + *ticks = SDL_GetTicks(); + } + } 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(_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(_THIS) +{ + unsigned 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(_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(_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 { + Uint32 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(_THIS, unsigned int 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(_THIS) +{ + int fd = _this->hidden->fd; + Uint32 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(_THIS, unsigned int 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 int +export_dmabuf(_THIS) +{ + int fd = _this->hidden->fd; + Uint32 i; + for (i = 0; i < _this->hidden->nb_buffers; ++i) { + struct v4l2_exportbuffer expbuf; + SDL_zero(expbuf); + expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + expbuf.index = i; + expbuf.flags = O_RDWR; + if (ioctl(fd, VIDIOC_EXPBUF, &expbuf) == -1) { + SDL_SetError("VIDIOC_EXPBUF"); + return -1; + } + _this->hidden->buffers[i].dmabuf_fd = expbuf.fd; + } + 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(_THIS) +{ + int fd = _this->hidden->fd; + Uint32 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(_THIS, int index, Uint32 *format, Uint32 *type) +{ + 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 (fmtdesc.flags & V4L2_FMT_FLAG_EMULATED) { + *type |= SDL_VIDEO_CAPTURE_TYPE_EMULATED; + } + if (fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED) { + *type |= SDL_VIDEO_CAPTURE_TYPE_EMULATED; + } + return 0; + } + + return -1; +} + +int +GetNumFrameSizes(_THIS, Uint32 format) +{ + int fd = _this->hidden->fd; + Uint32 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(_THIS, Uint32 format, int index, Uint32 *width, Uint32 *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) { + Uint32 w; + for (w = frmsizeenum.stepwise.min_width; w <= frmsizeenum.stepwise.max_width; w += frmsizeenum.stepwise.step_width) { + Uint32 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(_THIS, SDL_VideoCaptureSpec *spec) +{ + struct v4l2_format fmt; + int fd = _this->hidden->fd; + Uint32 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(_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; + } + + { + Uint32 i; + _this->hidden->buffers = SDL_calloc(_this->hidden->nb_buffers, sizeof(*_this->hidden->buffers)); + if (!_this->hidden->buffers) { + return SDL_OutOfMemory(); + } + + for (i = 0; i < _this->hidden->nb_buffers; ++i) { + _this->hidden->buffers[i].dmabuf_fd = -1; + } + } + + { + Uint32 sz, pitch; + size_buffer(_this->spec.width, _this->spec.height, _this->spec.format, &sz, &pitch); + + switch (io) { + case IO_METHOD_READ: + ret = alloc_buffer_read(_this, sz); + break; + + case IO_METHOD_MMAP: + ret = alloc_buffer_mmap(_this); + + export_dmabuf(_this); + break; + + case IO_METHOD_USERPTR: + ret = alloc_buffer_userp(_this, sz); + break; + } + } + + if (ret < 0) { + return -1; + } + + return 0; +} + +void +CloseDevice(_THIS) +{ + if (!_this) { + return; + } + + if (_this->hidden) { + if (_this->hidden->buffers) { + unsigned 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 (_this->hidden->buffers[i].dmabuf_fd != -1) { + close(_this->hidden->buffers[i].dmabuf_fd); + } + 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(_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++) { + const char *filename = SDL_GetVideoCaptureDeviceName(num); + SDL_RWops *src = SDL_RWFromFile(filename, "rb"); + if (src == NULL) { + // When file does not exist, an error is set. Clear it. + SDL_ClearError(); + return num; + } + SDL_RWclose(src); + } + return num; +} + +#elif defined(__ANDROID__) && __ANDROID_API__ >= 24 + +/* + * APP_PLATFORM=android-24 + * minSdkVersion=24 + * + * link with: -lcamera2ndk -lmediandk + * + * AndroidManifest.xml: + * + * + * + * for DMA BUF: + * API 26 + * AImage_getHardwareBuffer + * eglGetNativeClientBufferANDROID + * + * + */ + +#include +#include +#include +#include +#include "SDL_egl.h" + +#include "../core/android/SDL_android.h" + + +static ACameraManager *cameraMgr = NULL; +static ACameraIdList *cameraIdList = NULL; + +typedef EGLClientBuffer (*eglGetNativeClientBufferANDROID_t) (const struct AHardwareBuffer *buffer); +static eglGetNativeClientBufferANDROID_t eglGetNativeClientBufferANDROID = 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"); + } + + eglGetNativeClientBufferANDROID = (eglGetNativeClientBufferANDROID_t) SDL_GL_GetProcAddress("eglGetNativeClientBufferANDROID"); + if (eglGetNativeClientBufferANDROID == NULL) { + SDL_Log("Cannot load function: eglGetNativeClientBufferANDROID"); + } + } +} + +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(_THIS) +{ + camera_status_t res; + + _this->hidden = (struct SDL_PrivateVideoCaptureData *) SDL_calloc(1, sizeof (struct SDL_PrivateVideoCaptureData)); + if (_this->hidden == NULL) { + SDL_OutOfMemory(); + return -1; + } + + 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) { + goto error; + } + + return 0; + +error: + return -1; +} + +void +CloseDevice(_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; + } + + + { + Uint32 i; + int all_closed = 1; + for (i = 0; i < SDL_arraysize(open_devices); i++) { + if (open_devices[i]) { + all_closed = 0; + break; + } + } + + if (all_closed) { + delete_cameraMgr(); + } + } + +} + +int +InitDevice(_THIS) +{ + Uint32 sz, p; + size_buffer(_this->spec.width, _this->spec.height, _this->spec.format, &sz, &p); + SDL_Log("size_buffer: %d x %d", _this->spec.width, _this->spec.height); + return 0; +} + +int +GetDeviceSpec(_THIS, SDL_VideoCaptureSpec *spec) +{ + if (spec) { + *spec = _this->spec; + return 0; + } + return -1; +} + +int +StartCapture(_THIS) +{ + camera_status_t res; + media_status_t res2; + ANativeWindow *window = NULL; + + 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; + + } + + ACaptureSessionOutput *sessionOutput; + 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; + } + + + + + ACameraOutputTarget *outputTarget; + res = ACameraOutputTarget_create(window, &outputTarget); + if (res != ACAMERA_OK) { + SDL_SetError("Error ACameraOutputTarget_create"); + goto error; + } + + + ACaptureRequest *request; + 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(_THIS) +{ + ACameraCaptureSession_close(_this->hidden->session); + _this->hidden->session = NULL; + return 0; +} + +int +AcquireFrame(_THIS, SDL_VideoCaptureFrame *frame, Uint64 *ticks) +{ + media_status_t res; + AImage *image; + res = AImageReader_acquireNextImage(_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); + + *ticks = SDL_GetTicks64(); + + 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->row_stride[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; + } + } + +#if __ANDROID_API__ >= 26 + { + AHardwareBuffer *buffer = NULL; + AImage_getHardwareBuffer(image, &buffer); + if (buffer && eglGetNativeClientBufferANDROID) { + frame->clientbuffer = eglGetNativeClientBufferANDROID(buffer); + } + } +#endif + frame->internal = (void*)image; + return 0; + } + + SDL_SetError("Error AImageReader_acquireNextImage"); + return -1; +} + +int +ReleaseFrame(_THIS, SDL_VideoCaptureFrame *frame) +{ + if (frame->internal){ + AImage_delete((AImage *)frame->internal); + } + return 0; +} + +int +GetNumFormats(_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) { + int32_t w = entry.data.i32[i + 1]; + int32_t 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(_THIS, int index, Uint32 *format, Uint32 *type) +{ + 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); + *type = 0; + } + + i2++; + + } + return 0; +} + +int +GetNumFrameSizes(_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(_THIS, Uint32 format, int index, Uint32 *width, Uint32 *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]; + int32_t w = entry.data.i32[i + 1]; + int32_t 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; +} + + +#elif __IPHONEOS__ || __MACOS__ + /* SDL_video_capture_apple.m */ +#else +int +OpenDevice(_THIS) +{ + return SDL_SetError("not implemented"); +} + +void +CloseDevice(_THIS) +{ + return; +} + +int +InitDevice(_THIS) +{ + Uint32 sz, p; + size_buffer(_this->spec.width, _this->spec.height, _this->spec.format, &sz, &p); + SDL_Log("size_buffer: %" SDL_PRIu32 " x %" SDL_PRIu32 "", _this->spec.width, _this->spec.height); + return -1; +} + +int +GetDeviceSpec(_THIS, SDL_VideoCaptureSpec *spec) +{ + return -1; +} + +int +StartCapture(_THIS) +{ + return -1; +} + +int +StopCapture(_THIS) +{ + return -1; +} + +int +AcquireFrame(_THIS, SDL_VideoCaptureFrame *frame, Uint64 *ticks) +{ + return -1; +} + +int +ReleaseFrame(_THIS, SDL_VideoCaptureFrame *frame) +{ + return -1; +} + +int +GetNumFormats(_THIS) +{ + return -1; +} + +int +GetFormat(_THIS, int index, Uint32 *format, Uint32 *type) +{ + return -1; +} + +int +GetNumFrameSizes(_THIS, Uint32 format) +{ + return -1; +} + +int +GetFrameSize(_THIS, Uint32 format, int index, Uint32 *width, Uint32 *height) +{ + return -1; +} + +int +GetDeviceName(int index, char *buf, int size) +{ + return -1; +} + +int +GetNumDevices(void) +{ + return -1; +} +#endif diff --git a/src/video/SDL_video_capture_apple.m b/src/video/SDL_video_capture_apple.m new file mode 100644 index 000000000..87ec4555b --- /dev/null +++ b/src/video/SDL_video_capture_apple.m @@ -0,0 +1,578 @@ +/* + 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" + +#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(__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(_THIS) { + return -1; +} +int OpenDevice(_THIS) { + return -1; +} +int AcquireFrame(_THIS, SDL_VideoCaptureFrame *frame, Uint64 *ticks) { + return -1; +} +void CloseDevice(_THIS) { +} +int GetDeviceName(int index, char *buf, int size) { + return -1; +} +int GetDeviceSpec(_THIS, SDL_VideoCaptureSpec *spec) { + return -1; +} +int GetFormat(_THIS, int index, Uint32 *format, Uint32 *type) { + return -1; +} +int GetFrameSize(_THIS, Uint32 format, int index, Uint32 *width, Uint32 *height) { + return -1; +} +int GetNumDevices(void) { + return 0; +} +int GetNumFormats(_THIS) { + return 0; +} +int GetNumFrameSizes(_THIS, Uint32 format) { + return 0; +} +int ReleaseFrame(_THIS, SDL_VideoCaptureFrame *frame) { + return 0; +} +int StartCapture(_THIS) { + return 0; +} +int StopCapture(_THIS) { + return 0; +} + +#else + +#import +#import + +/* + * Need to link with:: CoreMedia CoreVideo + * + * Add in pInfo.list: + * NSCameraUsageDescription Access camera + * + * + * MACOSX: + * Add to the Code Sign Entitlement file: + * com.apple.security.device.camera + * + */ + +@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 * +discover_devices() +{ + NSArray *deviceType = @[AVCaptureDeviceTypeBuiltInWideAngleCamera]; + + AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession + discoverySessionWithDeviceTypes:deviceType + mediaType:AVMediaTypeVideo + position:AVCaptureDevicePositionUnspecified]; + + NSArray *devices = discoverySession.devices; + + if ([devices count] > 0) { + return devices; + } else { + AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if (captureDevice == nil) { + return devices; + } else { + NSArray *default_device = @[ captureDevice ]; + return default_device; + } + } + + return devices; +} + +static AVCaptureDevice * +get_device_by_name(const char *dev_name) +{ + NSArray *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]; + + if (SDL_strcmp("420v", str) == 0) return SDL_PIXELFORMAT_NV12; + 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 = ""; + + if (fmt == SDL_PIXELFORMAT_NV12) str = "420v"; + if (fmt == SDL_PIXELFORMAT_UYVY) str = "yuvs"; + + NSString *result = [[NSString alloc] initWithUTF8String: str]; + + return result; +} + + +@interface MySampleBufferDelegate : NSObject + @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(_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(_THIS) +{ + if (!_this) { + return; + } + + if (_this->hidden) { + AVCaptureSession *session = _this->hidden->session; + + if (session) { + AVCaptureInput *input = [session.inputs objectAtIndex:0]; + [session removeInput:input]; + AVCaptureVideoDataOutput *output = (AVCaptureVideoDataOutput*)[session.outputs objectAtIndex:0]; + [session removeOutput:output]; + // TODO clenaupp + } + + if (_this->hidden->frame_queue) { + CFRelease(_this->hidden->frame_queue); + } + + SDL_free(_this->hidden); + _this->hidden = NULL; + } +} + +int +InitDevice(_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 *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]; + + // Specify the pixel format + output.videoSettings = + [NSDictionary dictionaryWithObject: + [NSNumber numberWithInt:kCVPixelFormatType_422YpCbCr8] + forKey:(id)kCVPixelBufferPixelFormatTypeKey]; + + _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(_THIS, SDL_VideoCaptureSpec *spec) +{ + if (spec) { + *spec = _this->spec; + return 0; + } + return -1; +} + +int +StartCapture(_THIS) +{ + [_this->hidden->session startRunning]; + return 0; +} + +int +StopCapture(_THIS) +{ + [_this->hidden->session stopRunning]; + return 0; +} + +int +AcquireFrame(_THIS, SDL_VideoCaptureFrame *frame, Uint64 *ticks) +{ + if (CMSimpleQueueGetCount(_this->hidden->frame_queue) > 0) { + CMSampleBufferRef sampleBuffer = (CMSampleBufferRef)CMSimpleQueueDequeue(_this->hidden->frame_queue); + frame->internal = (void *) sampleBuffer; + *ticks = SDL_GetTicks(); + + int i = 0; + CVImageBufferRef image = CMSampleBufferGetImageBuffer(sampleBuffer); + int numPlanes = CVPixelBufferGetPlaneCount(image); + int 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->row_stride[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->row_stride[i] = rowStride; + } + } + + /* Unlocked when frame is released */ + + } else { + // no frame + SDL_Delay(20); // TODO fix some delay + } + return 0; +} + +int +ReleaseFrame(_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(_THIS) +{ + AVCaptureDevice *device = get_device_by_name(_this->dev_name); + if (device) { + // LIST FORMATS + NSMutableOrderedSet *array_formats = [NSMutableOrderedSet new]; + NSArray *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(_THIS, int index, Uint32 *format, Uint32 *type) +{ + AVCaptureDevice *device = get_device_by_name(_this->dev_name); + if (device) { + // LIST FORMATS + NSMutableOrderedSet *array_formats = [NSMutableOrderedSet new]; + NSArray *formats = [device formats]; + for (AVCaptureDeviceFormat *f in formats) { + CMFormatDescriptionRef formatDescription = [f formatDescription]; + FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription); + NSString *str = fourcc_to_nstring(mediaSubType); + [array_formats addObject:str]; + } + + NSString *str = array_formats[index]; + *format = nsfourcc_to_sdlformat(str); + + return 0; + } + return -1; +} + +int +GetNumFrameSizes(_THIS, Uint32 format) +{ + AVCaptureDevice *device = get_device_by_name(_this->dev_name); + if (device) { + NSString *fmt = sdlformat_to_nsfourcc(format); + int count = 0; + + NSArray *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(_THIS, Uint32 format, int index, Uint32 *width, Uint32 *height) +{ + AVCaptureDevice *device = get_device_by_name(_this->dev_name); + if (device) { + NSString *fmt = sdlformat_to_nsfourcc(format); + int count = 0; + + NSArray *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 *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 *devices = discover_devices(); + return [devices count]; +} + +#endif /* HAVE_COREMEDIA */ + +/* vi: set ts=4 sw=4 expandtab: */ + diff --git a/src/video/SDL_video_capture_c.h b/src/video/SDL_video_capture_c.h new file mode 100644 index 000000000..5f39ba651 --- /dev/null +++ b/src/video/SDL_video_capture_c.h @@ -0,0 +1,33 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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_ */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e8bb837f1..d9ea90365 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -190,6 +190,7 @@ add_sdl_test_executable(teststreaming NEEDS_RESOURCES TESTUTILS teststreaming.c) add_sdl_test_executable(testtimer NONINTERACTIVE testtimer.c) add_sdl_test_executable(testurl testurl.c) add_sdl_test_executable(testver NONINTERACTIVE testver.c) +add_sdl_test_executable(testvideocapture testvideocapture.c) add_sdl_test_executable(testviewport NEEDS_RESOURCES TESTUTILS testviewport.c) add_sdl_test_executable(testwm testwm.c) add_sdl_test_executable(testyuv NEEDS_RESOURCES testyuv.c testyuv_cvt.c) diff --git a/test/testvideocapture.c b/test/testvideocapture.c new file mode 100644 index 000000000..bf5e5915c --- /dev/null +++ b/test/testvideocapture.c @@ -0,0 +1,787 @@ +/* + Copyright (C) 1997-2022 Sam Lantinga + + 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.h" +#include "SDL3/SDL_video_capture.h" +#include + +/* Enable DMABUF to compile (linux + v4l2) */ +#define USE_DMABUF 0 + +#if USE_DMABUF +# include "SDL_egl.h" +# include "SDL_opengles2.h" +# include "drm/drm_fourcc.h" +#endif + +#ifdef __EMSCRIPTEN__ +#include +#endif + +static const char *usage = "\ + \n\ + =========================================================================\n\ + \n\ +Use keyboards:\n\ + o: open first video capture device. (close previously opened)\n\ + l: 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, const char *str) +{ + Uint64 now = SDL_GetTicks(); + Uint64 deadline; + m->frame_counter++; + if (m->check_delay == 0) { + m->check_delay = 1500; + } + if (str == NULL) { + str = ""; + } + 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); + + SDL_Log("%s %2.2f frames per second\n", str, m->last_fps); + m->next_check = now + m->check_delay; + m->frame_counter = 0; + } +} + +/* + +--1--+ + | | + 4 6 + |--2--| + 5 7 + | | + +--3--+ +*/ +static const +SDL_FPoint segments[9][2] = { + { {0, 0}, {10, 1} }, /* 1 */ + { {0, 10}, {10, 11} }, /* 2 */ + { {0, 20}, {10, 21} }, /* 3 */ + + { {0, 0}, {1, 11} }, /* 4 */ + { {0, 10}, {1, 21} }, /* 5 */ + + { {9, 0}, {10, 11} }, /* 6 */ + { {9, 10}, {10, 21} }, /* 7 */ + { {3, 18}, {5, 21} }, /* . */ + { {4, 0}, {5, 21} } /* | */ +}; + +static void +draw_string(SDL_Renderer *renderer, const char *c, float x0, float y0) +{ + while (*c) { + int seg = 0; + int val = *c; + if (val >= 'A' && val <= 'Z') { + val = 'a' + val - 'A'; + } + switch (val) { + case '0': seg = 167345; break; + case '1': seg = 67; break; + case '2': seg = 16253; break; + case '3': seg = 16732; break; + case '4': seg = 4267; break; + case '5': seg = 14273; break; + case '6': seg = 145372; break; + case '7': seg = 167; break; + case '8': seg = 1234567; break; + case '9': seg = 123467; break; + case '.': seg = 8; break; + case '-': seg = 2; break; + case '_': seg = 3; break; + case '+': seg = 29; break; + case '|': seg = 9; break; + case '[': seg = 1453; break; + case ']': seg = 1673; break; + case 'a': seg = 124567; break; + case 'b': seg = 1234567; break; + case 'c': seg = 1453; break; + case 'd': seg = 167345; break; + case 'e': seg = 12345; break; + case 'f': seg = 1425; break; + case 'g': seg = 123457; break; + case 'h': seg = 24567; break; + case 'i': seg = 9; break; + case 'j': seg = 6735; break; + case 'k': seg = 24567; break; + case 'l': seg = 453; break; + case 'm': seg = 145679; break; + case 'n': seg = 14567; break; + case 'o': seg = 134567; break; + case 'p': seg = 14256; break; + case 'q': seg = 134567; break; + case 'r': seg = 145; break; + case 's': seg = 14273; break; + case 't': seg = 19; break; + case 'u': seg = 45367; break; + case 'v': seg = 45367; break; + case 'w': seg = 345679; break; + case 'x': seg = 24567; break; + case 'y': seg = 23467; break; + case 'z': seg = 16253; break; + } + while (seg) { + SDL_FRect r; + SDL_FPoint p1, p2; + int s = seg - 10 * (seg/10); + p1 = segments[s - 1][0]; + p2 = segments[s - 1][1]; + r.x = p1.x + x0; + r.y = p1.y + y0; + r.w = p2.x - p1.x; + r.h = p2.y - p1.y; + SDL_RenderRect(renderer, &r); + seg /= 10; + } + x0 += 12; + c++; + } +} + +#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 + +int main(int argc, char **argv) +{ + SDL_Window *window = NULL; + SDL_Renderer *renderer = NULL; + SDL_Texture *texture = NULL; + int texture_updated = 0; + SDL_Event evt; + int quit = 1; + + SDL_VideoCaptureDeviceID id = 0; + SDL_VideoCaptureSpec obtained; + + SDL_FRect r_playstop = { 50, 50, 120, 50 }; + SDL_FRect r_close = { 50 + (120 + 50) * 1, 50, 120, 50 }; + + SDL_FRect r_open0 = { 50 + (120 + 50) * 2, 50, 120, 50 }; + SDL_FRect r_open1 = { 50 + (120 + 50) * 2, 150, 120, 50 }; + + SDL_FRect r_format = { 50 + (120 + 50) * 3, 50, 120, 50 }; + SDL_FRect r_listdev = { 50 + (120 + 50) * 4, 50, 120, 50 }; + + int start = 1; + SDL_VideoCaptureFrame frame_current; + +#if USE_DMABUF + EGLDisplay display; + + typedef EGLDisplay (*eglGetCurrentDisplay_t)(); + typedef EGLImageKHR (*eglCreateImageKHR_t)(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list); + typedef EGLBoolean (*eglDestroyImageKHR_t)(EGLDisplay dpy, EGLImageKHR image); + typedef void (*glActiveTexture_t)(GLenum texture); + typedef void (*glEGLImageTargetTexture2DOES_t)(GLenum target, GLeglImageOES image); + typedef EGLint (*eglGetError_t)(void); + + eglGetCurrentDisplay_t eglGetCurrentDisplay; + eglCreateImageKHR_t eglCreateImageKHR; + eglDestroyImageKHR_t eglDestroyImageKHR; + glActiveTexture_t glActiveTexture; + glEGLImageTargetTexture2DOES_t glEGLImageTargetTexture2DOES; + eglGetError_t eglGetError; + +#endif + + measure_fps_t fps_main; + measure_fps_t fps_capture; + + SDL_zero(fps_main); + SDL_zero(fps_capture); + SDL_zero(frame_current); + + SDL_Log("%s", usage); + + /* Enable standard application logging */ + SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); + +#if USE_DMABUF + SDL_SetHint("SDL_RENDER_OPENGL_NV12_RG_SHADER", "1"); + SDL_SetHint("SDL_VIDEO_X11_FORCE_EGL", "1"); /* Don't use GLX */ + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengles2"); +#endif + + /* 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; + } + + if (!(window = SDL_CreateWindow("Local Video", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1000, 800, 0))) { + 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; + } + +#if USE_DMABUF + +#define LOAL_FUNC(f) \ + f = (f ##_t)SDL_GL_GetProcAddress(#f); \ + if (f == NULL) { \ + SDL_Log("Cannot load function: " #f ); \ + return 0; \ + } \ + + LOAL_FUNC(eglGetError) + LOAL_FUNC(eglGetCurrentDisplay) + LOAL_FUNC(eglCreateImageKHR) + LOAL_FUNC(eglDestroyImageKHR) + LOAL_FUNC(glActiveTexture) + LOAL_FUNC(glEGLImageTargetTexture2DOES) + + display = eglGetCurrentDisplay(); + if (display == EGL_NO_DISPLAY) { + SDL_Log("no dispay"); + return 0; + } +#endif + + + SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE); + + id = SDL_OpenVideoCapture(NULL); + + if (id == SDL_VIDEO_CAPTURE_INVALID) { + SDL_Log("Error SDL_OpenVideoCapture: %s", SDL_GetError()); + } + + { + /* List formats */ + int i, num = SDL_GetNumVideoCaptureFormats(id); + for (i = 0; i < num; i++) { + Uint32 format, type; + SDL_GetVideoCaptureFormat(id, i, &format, &type); + SDL_Log("format %d/%d type=%08" SDL_PRIx32 ": %s", i, num, type, SDL_GetPixelFormatName(format)); + { + Uint32 w, h; + int j, num2 = SDL_GetNumVideoCaptureFrameSizes(id, format); + for (j = 0; j < num2; j++) { + SDL_GetVideoCaptureFrameSize(id, format, j, &w, &h); + SDL_Log(" framesizes %d/%d : %" SDL_PRIu32 " x %" SDL_PRIu32 "", j, num2, w, h); + } + } + } + } + + /* Set Spec */ + { + int ret; + /* forced_format */ + SDL_VideoCaptureSpec desired; + SDL_zero(desired); + desired.width = 640 * 2; + desired.height = 360 * 2; + ret = SDL_SetVideoCaptureSpec(id, &desired, &obtained, SDL_VIDEO_CAPTURE_ALLOW_ANY_CHANGE); + + if (ret < 0) { + SDL_SetVideoCaptureSpec(id, NULL, &obtained, 0); + } + } + + SDL_Log("Open capture video device. Obtained spec: size=%" SDL_PRIu32 "x%" SDL_PRIu32 " format=%s", + obtained.width, obtained.height, SDL_GetPixelFormatName(obtained.format)); + + { + SDL_VideoCaptureSpec spec; + if (SDL_GetVideoCaptureSpec(id, &spec) == 0) { + SDL_Log("Read spec: size=%" SDL_PRIu32 "x%" SDL_PRIu32 " format=%s", + spec.width, spec.height, SDL_GetPixelFormatName(spec.format)); + } else { + SDL_Log("Error read spec: %s", SDL_GetError()); + } + } + + if (SDL_StartVideoCapture(id) < 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_open0); + SDL_RenderFillRect(renderer, &r_open1); + SDL_RenderFillRect(renderer, &r_format); + SDL_RenderFillRect(renderer, &r_listdev); + + SDL_SetRenderDrawColor(renderer, 0xcc, 0xcc, 0xcc, 255); + + draw_string(renderer, "play stop", r_playstop.x + 5, r_playstop.y + 5); + draw_string(renderer, "close", r_close.x + 5, r_close.y + 5); + draw_string(renderer, "open dev0", r_open0.x + 5, r_open0.y + 5); + draw_string(renderer, "open dev1", r_open1.x + 5, r_open1.y + 5); + draw_string(renderer, "formats", r_format.x + 5, r_format.y + 5); + draw_string(renderer, "devices", r_listdev.x + 5, r_listdev.y + 5); + + + + while (SDL_PollEvent(&evt) == 1) { + int sym = 0; + int opendev = 0; + switch (evt.type) + { + case SDL_EVENT_KEY_DOWN: + { + sym = evt.key.keysym.sym; + break; + } + case SDL_EVENT_QUIT: + { + quit = 0; + SDL_Log("Ctlr+C : Quit!"); + } + break; + + case SDL_EVENT_MOUSE_BUTTON_DOWN: + { + SDL_FRect *r = NULL; + SDL_FPoint pt; + pt.x = evt.button.x; + pt.y = evt.button.y; + 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_open0)) { + r = &r_open0; + sym = SDLK_o; + opendev = 0; + } + + if (SDL_PointInRectFloat(&pt, &r_open1)) { + r = &r_open1; + sym = SDLK_o; + opendev = 1; + } + + 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); + } + } + + break; + } + + if (sym == SDLK_c) { + if (frame_current.num_planes) { + SDL_VideoCaptureReleaseFrame(id, &frame_current); + } + SDL_CloseVideoCapture(id); + id = SDL_VIDEO_CAPTURE_INVALID; + SDL_Log("Close"); + } + + if (sym == SDLK_o) { + if (id != SDL_VIDEO_CAPTURE_INVALID) { + SDL_Log("Close previous .."); + if (frame_current.num_planes) { + SDL_VideoCaptureReleaseFrame(id, &frame_current); + } + SDL_CloseVideoCapture(id); + } + + texture_updated = 0; + + SDL_ClearError(); + + if (opendev == 0) { + id = SDL_OpenVideoCaptureWithSpec(NULL, &obtained, &obtained, SDL_VIDEO_CAPTURE_ALLOW_ANY_CHANGE); + } else { + SDL_Log("Try to open:%s", SDL_GetVideoCaptureDeviceName(opendev)); + id = SDL_OpenVideoCaptureWithSpec(SDL_GetVideoCaptureDeviceName(opendev), &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 id:%" SDL_PRIu32 " %s", id, SDL_GetError()); + start = 0; + } + + if (sym == SDLK_l) { + int num = SDL_GetNumVideoCaptureDevices(); + int i; + SDL_Log("Num devices : %d", num); + for (i = 0; i < num; i++) { + SDL_Log("Device %d/%d : %s", i, num, SDL_GetVideoCaptureDeviceName(i)); + } + } + + if (sym == SDLK_i) { + SDL_VideoCaptureStatus status = SDL_GetVideoCaptureStatus(id); + 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) { + start = !start; + + if (start) { + SDL_Log("Start"); + SDL_StartVideoCapture(id); + } else { + SDL_Log("Stop"); + SDL_StopVideoCapture(id); + } + + } + + if (sym == SDLK_f) { + SDL_Log("List formats"); + + if (id == SDL_VIDEO_CAPTURE_INVALID) { + id = SDL_OpenVideoCapture(NULL); + } + + /* List formats */ + { + int i, num = SDL_GetNumVideoCaptureFormats(id); + for (i = 0; i < num; i++) { + Uint32 format, type; + SDL_GetVideoCaptureFormat(id, i, &format, &type); + SDL_Log("format %d/%d type=%08" SDL_PRIx32 ": %s", i, num, type, SDL_GetPixelFormatName(format)); + { + Uint32 w, h; + int j, num2 = SDL_GetNumVideoCaptureFrameSizes(id, format); + for (j = 0; j < num2; j++) { + SDL_GetVideoCaptureFrameSize(id, format, j, &w, &h); + SDL_Log(" framesizes %d/%d : %" SDL_PRIu32 " x %" SDL_PRIu32 "", j, num2, w, h); + } + } + } + } + } + if (sym == SDLK_ESCAPE || sym == SDLK_AC_BACK) { + quit = 0; + SDL_Log("Key : Escape!"); + } + + if (sym == SDLK_h || sym == SDLK_F1) { + SDL_Log("%s", usage); + } + } + + if (id == SDL_VIDEO_CAPTURE_INVALID) { + /* device has been closed */ + frame_current.num_planes = 0; + texture_updated = 0; + } else { + Uint64 ticks; + int ret; + SDL_VideoCaptureFrame frame_next; + SDL_zero(frame_next); + + ret = SDL_VideoCaptureAcquireFrame(id, &frame_next, &ticks); + if (ret < 0) { + SDL_Log("err SDL_VideoCaptureAcquireFrame: %s", SDL_GetError()); + } +#if 1 + if (frame_next.num_planes) { + SDL_Log("frame: %p at %" SDL_PRIu64, (void*)frame_next.data[0], ticks); + } +#endif + + if (frame_next.num_planes) { + + update_fps(&fps_capture, "Capture"); + + if (frame_current.num_planes) { + ret = SDL_VideoCaptureReleaseFrame(id, &frame_current); + if (ret < 0) { + SDL_Log("err SDL_VideoCaptureReleaseFrame: %s", SDL_GetError()); + } + } + frame_current = frame_next; + texture_updated = 0; + } + } + + /* 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); + + /* Update SDL_Texture with last video frame (only once per new frame) */ + if (frame_current.num_planes && texture_updated == 0) { +#if USE_DMABUF + int use_sw = 0; +#else + int use_sw = 1; +#endif + + /* Create texture with appropriate format (for DMABUF or not) */ + if (texture == NULL) { + Uint32 format = obtained.format; +#if USE_DMABUF + if ((frame_current.fd == -1 && frame_current.clientbuffer == NULL) || use_sw) { + /* keep format */ + } else { + format = SDL_PIXELFORMAT_EXTERNAL_OES; + } +#endif + 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; + } + } + + if ((frame_current.fd == -1 && frame_current.clientbuffer == NULL) || use_sw) { + /* Use software data */ + if (frame_current.num_planes == 1) { + SDL_UpdateTexture(texture, NULL, + frame_current.data[0], frame_current.row_stride[0]); + } else if (frame_current.num_planes == 2) { + SDL_UpdateNVTexture(texture, NULL, + frame_current.data[0], frame_current.row_stride[0], + frame_current.data[1], frame_current.row_stride[1]); + } else if (frame_current.num_planes == 3) { + SDL_UpdateYUVTexture(texture, NULL, frame_current.data[0], frame_current.row_stride[0], + frame_current.data[1], frame_current.row_stride[1], + frame_current.data[2], frame_current.row_stride[2]); + } + texture_updated = 1; + } else { + /* Use DMABUF */ +#if USE_DMABUF + int j = 0; + EGLImage image; + EGLint img_attr[64]; + +#if __ANDROID__ + img_attr[j] = EGL_NONE; + image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, frame_current.clientbuffer, img_attr); +#else + /* V4L2_PIX_FMT_YUYV / SDL_PIXELFORMAT_EXTERNAL_OES */ + img_attr[j++] = EGL_WIDTH; + img_attr[j++] = obtained.width; + + img_attr[j++] = EGL_HEIGHT; + img_attr[j++] = obtained.height; + + img_attr[j++] = EGL_LINUX_DRM_FOURCC_EXT; + img_attr[j++] = DRM_FORMAT_YUYV; + + img_attr[j++] = EGL_DMA_BUF_PLANE0_FD_EXT; + img_attr[j++] = frame_current.fd; + img_attr[j++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT; + img_attr[j++] = 0; + img_attr[j++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; + img_attr[j++] = frame_current.row_stride[0]; + + img_attr[j++] = EGL_NONE; + + image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr); +#endif + + if (image == EGL_NO_IMAGE_KHR) { + SDL_Log("error eglCreateImageKHR : eglGetError: %08" SDL_PRIx32 "", eglGetError()); + } else { + SDL_Log("ImageKHR: %p fd: %" SDL_PRIu32 "", image, frame_current.fd); + + SDL_GL_BindTexture(texture, NULL, NULL); + glActiveTexture(GL_TEXTURE0); + glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image); + SDL_GL_UnbindTexture(texture); + + eglDestroyImageKHR(display, image); + texture_updated = 1; + } +#endif + } + } + + /* RenderCopy the SDL_Texture */ + if (texture_updated == 1) { +#if 0 + SDL_Rect dst; + dst.x = 10; + dst.y = 120; + dst.w = obtained.width; + dst.h = obtained.height; + + SDL_RenderCopy(renderer, texture, NULL, &dst); +#else + { /* Scale texture to fit the screen */ + int w, h; + int tw, th; + SDL_FRect d; + SDL_GetWindowSize(window, &w, &h); + SDL_QueryTexture(texture, NULL, NULL, &tw, &th); + + if (tw > w - 20) { + float scale = (float) (w - 20) / (float) tw; + tw = w - 20; + th = (int)((float) th * scale); + } + d.x = 10; + d.y = (float)(h - th); + d.w = (float)tw; + d.h = (float)(th - 10); + SDL_RenderTexture(renderer, texture, NULL, &d); + } +#endif + } + + /* display FPS */ + { + char buf[128]; + SDL_snprintf(buf, sizeof(buf), "%2.2f fps", fps_capture.last_fps); + draw_string(renderer, buf, 10, 10); + } + + /* display status */ + { + const char *status = "no status"; + if (id != SDL_VIDEO_CAPTURE_INVALID) { + SDL_VideoCaptureStatus s = SDL_GetVideoCaptureStatus(id); + 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"; + } + } + draw_string(renderer, status, 200, 10); + } + + /* display last error */ + { + draw_string(renderer, SDL_GetError(), 400, 10); + } + + /* display spec */ + { + char buf[128]; + SDL_snprintf(buf, sizeof(buf), "%" SDL_PRIu32 " x %" SDL_PRIu32 " %s", obtained.width, obtained.height, SDL_GetPixelFormatName(obtained.format)); + draw_string(renderer, buf, 10, 200); + } + + /* 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); + draw_string(renderer, buf, 800, 10); + } + } +#endif + + + SDL_RenderPresent(renderer); + + update_fps(&fps_main, "Main"); + + } + + if (SDL_StopVideoCapture(id) < 0) { + SDL_Log("error SDL_StopVideoCapture(): %s", SDL_GetError()); + } + + if (frame_current.num_planes) { + SDL_VideoCaptureReleaseFrame(id, &frame_current); + } + + SDL_CloseVideoCapture(id); + + if (texture) SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + + SDL_Quit(); + + return 0; +}