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;
+}