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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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