camera: Renamed "video_capture" files to "camera" and moved to own subdir.

This commit is contained in:
Ryan C. Gordon
2023-11-27 19:27:58 -05:00
parent 4d000ae3bd
commit f49ce4a15d
12 changed files with 16 additions and 15 deletions

View File

@@ -1,92 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 SDL_SYS_VideoCaptureInit(void);
extern int SDL_SYS_VideoCaptureQuit(void);
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(SDL_VideoCaptureDeviceID instance_id, char *buf, int size);
extern SDL_VideoCaptureDeviceID *GetVideoCaptureDevices(int *count);
extern SDL_bool check_all_device_closed(void);
extern SDL_bool check_device_playing(void);
#endif /* SDL_sysvideocapture_h_ */

View File

@@ -33,7 +33,7 @@
#include "../SDL_hints_c.h"
#include "../SDL_properties_c.h"
#include "../timer/SDL_timer_c.h"
#include "SDL_video_capture_c.h"
#include "../camera/SDL_camera_c.h"
#ifdef SDL_VIDEO_OPENGL
#include <SDL3/SDL_opengl.h>

View File

@@ -1,968 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 0
#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
static char buf[256];
buf[0] = 0;
buf[255] = 0;
if (instance_id == 0) {
SDL_InvalidParamError("instance_id");
return NULL;
}
if (GetDeviceName(instance_id, buf, sizeof (buf)) < 0) {
buf[0] = 0;
}
return buf;
#else
SDL_Unsupported();
return NULL;
#endif
}
SDL_VideoCaptureDeviceID *
SDL_GetVideoCaptureDevices(int *count)
{
int num = 0;
SDL_VideoCaptureDeviceID *ret = NULL;
#ifdef SDL_VIDEO_CAPTURE
ret = GetVideoCaptureDevices(&num);
#endif
if (ret) {
if (count) {
*count = num;
}
return ret;
}
/* return list of 0 ID, null terminated */
num = 0;
ret = (SDL_VideoCaptureDeviceID *)SDL_malloc((num + 1) * sizeof(*ret));
if (ret == NULL) {
SDL_OutOfMemory();
if (count) {
*count = 0;
}
return NULL;
}
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_GetCurrentThreadID();
/* 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);
}
}
#if 0
// FIXME do we need this ?
/* Let the user override. */
{
const char *dev = SDL_getenv("SDL_VIDEO_CAPTURE_DEVICE_NAME");
if (dev && dev[0]) {
device_name = dev;
}
}
#endif
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);
SDL_SYS_VideoCaptureInit();
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);
SDL_SYS_VideoCaptureQuit();
#endif
}
#ifdef SDL_VIDEO_CAPTURE
#if defined(SDL_PLATFORM_LINUX) && !defined(SDL_PLATFORM_ANDROID)
/* See SDL_video_capture_v4l2.c */
#elif defined(SDL_PLATFORM_ANDROID) && __ANDROID_API__ >= 24
/* See SDL_android_video_capture.c */
#elif defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_MACOS)
/* See SDL_video_capture_apple.m */
#else
int SDL_SYS_VideoCaptureInit(void)
{
return 0;
}
int SDL_SYS_VideoCaptureQuit(void)
{
return 0;
}
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(SDL_VideoCaptureDeviceID instance_id, char *buf, int size)
{
return -1;
}
SDL_VideoCaptureDeviceID *
GetVideoCaptureDevices(int *count)
{
return NULL;
}
#endif
#endif /* SDL_VIDEO_CAPTURE */

View File

@@ -1,659 +0,0 @@
/*
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(SDL_PLATFORM_MACOS) && (__MAC_OS_X_VERSION_MAX_ALLOWED < 101500)
/* AVCaptureDeviceTypeBuiltInWideAngleCamera requires macOS SDK 10.15 */
#undef HAVE_COREMEDIA
#endif
#ifdef SDL_PLATFORM_TVOS
#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(SDL_VideoCaptureDeviceID instance_id, 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;
}
SDL_VideoCaptureDeviceID *GetVideoCaptureDevices(int *count) {
return NULL;
}
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;
}
int SDL_SYS_VideoCaptureInit(void) {
return 0;
}
int SDL_SYS_VideoCaptureQuit(void) {
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 SDL_PLATFORM_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 SDL_PLATFORM_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 SDL_PLATFORM_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 SDL_PLATFORM_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(SDL_VideoCaptureDeviceID instance_id, char *buf, int size)
{
int index = instance_id - 1;
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;
}
static int
GetNumDevices(void)
{
NSArray<AVCaptureDevice *> *devices = discover_devices();
return [devices count];
}
SDL_VideoCaptureDeviceID *GetVideoCaptureDevices(int *count)
{
/* hard-coded list of ID */
int i;
int num = GetNumDevices();
SDL_VideoCaptureDeviceID *ret;
ret = (SDL_VideoCaptureDeviceID *)SDL_malloc((num + 1) * sizeof(*ret));
if (ret == NULL) {
SDL_OutOfMemory();
*count = 0;
return NULL;
}
for (i = 0; i < num; i++) {
ret[i] = i + 1;
}
ret[num] = 0;
*count = num;
return ret;
}
int SDL_SYS_VideoCaptureInit(void)
{
return 0;
}
int SDL_SYS_VideoCaptureQuit(void)
{
return 0;
}
#endif /* HAVE_COREMEDIA */
#endif /* SDL_VIDEO_CAPTURE */

View File

@@ -1,33 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_ */

File diff suppressed because it is too large Load Diff

View File

@@ -1,713 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 0
#if defined(SDL_PLATFORM_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 0 // !!! FIXME: this is getting replaced in a different branch.
if (!Android_JNI_RequestPermission("android.permission.CAMERA")) {
SDL_SetError("This app doesn't have CAMERA permission");
return;
}
#endif
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_XRGB8888, 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_XRGB8888, 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_XRGB8888);
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_XRGB8888);
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;
}
static int GetNumDevices(void);
int
GetDeviceName(SDL_VideoCaptureDeviceID instance_id, char *buf, int size)
{
int index = instance_id - 1;
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;
}
static 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;
}
SDL_VideoCaptureDeviceID *GetVideoCaptureDevices(int *count)
{
/* hard-coded list of ID */
int i;
int num = GetNumDevices();
SDL_VideoCaptureDeviceID *ret;
ret = (SDL_VideoCaptureDeviceID *)SDL_malloc((num + 1) * sizeof(*ret));
if (ret == NULL) {
SDL_OutOfMemory();
*count = 0;
return NULL;
}
for (i = 0; i < num; i++) {
ret[i] = i + 1;
}
ret[num] = 0;
*count = num;
return ret;
}
int SDL_SYS_VideoCaptureInit(void) {
return 0;
}
int SDL_SYS_VideoCaptureQuit(void) {
return 0;
}
#endif