mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-07-16 06:00:31 +00:00
Add SDL Video Capture, with back-end for linux/macos/ios/android
This commit is contained in:
@@ -923,6 +923,22 @@ SDL3_0.0.0 {
|
||||
SDL_SetPropertyWithCleanup;
|
||||
SDL_SetX11EventHook;
|
||||
SDL_GetGlobalProperties;
|
||||
SDL_OpenVideoCapture;
|
||||
SDL_SetVideoCaptureSpec;
|
||||
SDL_OpenVideoCaptureWithSpec;
|
||||
SDL_GetVideoCaptureDeviceName;
|
||||
SDL_GetVideoCaptureSpec;
|
||||
SDL_GetVideoCaptureFormat;
|
||||
SDL_GetNumVideoCaptureFormats;
|
||||
SDL_GetVideoCaptureFrameSize;
|
||||
SDL_GetNumVideoCaptureFrameSizes;
|
||||
SDL_GetVideoCaptureStatus;
|
||||
SDL_StartVideoCapture;
|
||||
SDL_AcquireVideoCaptureFrame;
|
||||
SDL_ReleaseVideoCaptureFrame;
|
||||
SDL_StopVideoCapture;
|
||||
SDL_CloseVideoCapture;
|
||||
SDL_GetVideoCaptureDevices;
|
||||
# extra symbols go here (don't modify this line)
|
||||
local: *;
|
||||
};
|
||||
|
||||
@@ -948,3 +948,19 @@
|
||||
#define SDL_SetPropertyWithCleanup SDL_SetPropertyWithCleanup_REAL
|
||||
#define SDL_SetX11EventHook SDL_SetX11EventHook_REAL
|
||||
#define SDL_GetGlobalProperties SDL_GetGlobalProperties_REAL
|
||||
#define SDL_OpenVideoCapture SDL_OpenVideoCapture_REAL
|
||||
#define SDL_SetVideoCaptureSpec SDL_SetVideoCaptureSpec_REAL
|
||||
#define SDL_OpenVideoCaptureWithSpec SDL_OpenVideoCaptureWithSpec_REAL
|
||||
#define SDL_GetVideoCaptureDeviceName SDL_GetVideoCaptureDeviceName_REAL
|
||||
#define SDL_GetVideoCaptureSpec SDL_GetVideoCaptureSpec_REAL
|
||||
#define SDL_GetVideoCaptureFormat SDL_GetVideoCaptureFormat_REAL
|
||||
#define SDL_GetNumVideoCaptureFormats SDL_GetNumVideoCaptureFormats_REAL
|
||||
#define SDL_GetVideoCaptureFrameSize SDL_GetVideoCaptureFrameSize_REAL
|
||||
#define SDL_GetNumVideoCaptureFrameSizes SDL_GetNumVideoCaptureFrameSizes_REAL
|
||||
#define SDL_GetVideoCaptureStatus SDL_GetVideoCaptureStatus_REAL
|
||||
#define SDL_StartVideoCapture SDL_StartVideoCapture_REAL
|
||||
#define SDL_AcquireVideoCaptureFrame SDL_AcquireVideoCaptureFrame_REAL
|
||||
#define SDL_ReleaseVideoCaptureFrame SDL_ReleaseVideoCaptureFrame_REAL
|
||||
#define SDL_StopVideoCapture SDL_StopVideoCapture_REAL
|
||||
#define SDL_CloseVideoCapture SDL_CloseVideoCapture_REAL
|
||||
#define SDL_GetVideoCaptureDevices SDL_GetVideoCaptureDevices_REAL
|
||||
|
||||
@@ -981,3 +981,19 @@ SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetDisplayProperties,(SDL_DisplayID a),(a),
|
||||
SDL_DYNAPI_PROC(int,SDL_SetPropertyWithCleanup,(SDL_PropertiesID a, const char *b, void *c, void (SDLCALL *d)(void *userdata, void *value), void *e),(a,b,c,d,e),return)
|
||||
SDL_DYNAPI_PROC(void,SDL_SetX11EventHook,(SDL_X11EventHook a, void *b),(a,b),)
|
||||
SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGlobalProperties,(void),(),return)
|
||||
SDL_DYNAPI_PROC(SDL_VideoCaptureDevice*,SDL_OpenVideoCapture,(SDL_VideoCaptureDeviceID a),(a),return)
|
||||
SDL_DYNAPI_PROC(int,SDL_SetVideoCaptureSpec,(SDL_VideoCaptureDevice *a, const SDL_VideoCaptureSpec *b, SDL_VideoCaptureSpec *c, int d),(a,b,c,d),return)
|
||||
SDL_DYNAPI_PROC(SDL_VideoCaptureDevice*,SDL_OpenVideoCaptureWithSpec,(SDL_VideoCaptureDeviceID a, const SDL_VideoCaptureSpec *b, SDL_VideoCaptureSpec *c, int d),(a,b,c,d),return)
|
||||
SDL_DYNAPI_PROC(const char*,SDL_GetVideoCaptureDeviceName,(SDL_VideoCaptureDeviceID a),(a),return)
|
||||
SDL_DYNAPI_PROC(int,SDL_GetVideoCaptureSpec,(SDL_VideoCaptureDevice *a, SDL_VideoCaptureSpec *b),(a,b),return)
|
||||
SDL_DYNAPI_PROC(int,SDL_GetVideoCaptureFormat,(SDL_VideoCaptureDevice *a, int b, Uint32 *c),(a,b,c),return)
|
||||
SDL_DYNAPI_PROC(int,SDL_GetNumVideoCaptureFormats,(SDL_VideoCaptureDevice *a),(a),return)
|
||||
SDL_DYNAPI_PROC(int,SDL_GetVideoCaptureFrameSize,(SDL_VideoCaptureDevice *a, Uint32 b, int c, int *d, int *e),(a,b,c,d,e),return)
|
||||
SDL_DYNAPI_PROC(int,SDL_GetNumVideoCaptureFrameSizes,(SDL_VideoCaptureDevice *a, Uint32 b),(a,b),return)
|
||||
SDL_DYNAPI_PROC(SDL_VideoCaptureStatus,SDL_GetVideoCaptureStatus,(SDL_VideoCaptureDevice *a),(a),return)
|
||||
SDL_DYNAPI_PROC(int,SDL_StartVideoCapture,(SDL_VideoCaptureDevice *a),(a),return)
|
||||
SDL_DYNAPI_PROC(int,SDL_AcquireVideoCaptureFrame,(SDL_VideoCaptureDevice *a, SDL_VideoCaptureFrame *b),(a,b),return)
|
||||
SDL_DYNAPI_PROC(int,SDL_ReleaseVideoCaptureFrame,(SDL_VideoCaptureDevice *a, SDL_VideoCaptureFrame *b),(a,b),return)
|
||||
SDL_DYNAPI_PROC(int,SDL_StopVideoCapture,(SDL_VideoCaptureDevice *a),(a),return)
|
||||
SDL_DYNAPI_PROC(void,SDL_CloseVideoCapture,(SDL_VideoCaptureDevice *a),(a),)
|
||||
SDL_DYNAPI_PROC(SDL_VideoCaptureDeviceID*,SDL_GetVideoCaptureDevices,(int *a),(a),return)
|
||||
|
||||
90
src/video/SDL_sysvideocapture.h
Normal file
90
src/video/SDL_sysvideocapture.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "../SDL_internal.h"
|
||||
|
||||
#ifndef SDL_sysvideocapture_h_
|
||||
#define SDL_sysvideocapture_h_
|
||||
|
||||
#include "../SDL_list.h"
|
||||
|
||||
/* The SDL video_capture driver */
|
||||
typedef struct SDL_VideoCaptureDevice SDL_VideoCaptureDevice;
|
||||
|
||||
/* Define the SDL video_capture driver structure */
|
||||
struct SDL_VideoCaptureDevice
|
||||
{
|
||||
/* * * */
|
||||
/* Data common to all devices */
|
||||
|
||||
/* The device's current video_capture specification */
|
||||
SDL_VideoCaptureSpec spec;
|
||||
|
||||
/* Device name */
|
||||
char *dev_name;
|
||||
|
||||
/* Current state flags */
|
||||
SDL_AtomicInt shutdown;
|
||||
SDL_AtomicInt enabled;
|
||||
SDL_bool is_spec_set;
|
||||
|
||||
/* A mutex for locking the queue buffers */
|
||||
SDL_Mutex *device_lock;
|
||||
SDL_Mutex *acquiring_lock;
|
||||
|
||||
/* A thread to feed the video_capture device */
|
||||
SDL_Thread *thread;
|
||||
SDL_threadID threadid;
|
||||
|
||||
/* Queued buffers (if app not using callback). */
|
||||
SDL_ListNode *buffer_queue;
|
||||
|
||||
/* * * */
|
||||
/* Data private to this driver */
|
||||
struct SDL_PrivateVideoCaptureData *hidden;
|
||||
};
|
||||
|
||||
extern int OpenDevice(SDL_VideoCaptureDevice *_this);
|
||||
extern void CloseDevice(SDL_VideoCaptureDevice *_this);
|
||||
|
||||
extern int InitDevice(SDL_VideoCaptureDevice *_this);
|
||||
|
||||
extern int GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec);
|
||||
|
||||
extern int StartCapture(SDL_VideoCaptureDevice *_this);
|
||||
extern int StopCapture(SDL_VideoCaptureDevice *_this);
|
||||
|
||||
extern int AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame);
|
||||
extern int ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame);
|
||||
|
||||
extern int GetNumFormats(SDL_VideoCaptureDevice *_this);
|
||||
extern int GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format);
|
||||
|
||||
extern int GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format);
|
||||
extern int GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height);
|
||||
|
||||
extern int GetDeviceName(int index, char *buf, int size);
|
||||
extern int GetNumDevices(void);
|
||||
|
||||
|
||||
extern SDL_bool check_all_device_closed(void);
|
||||
extern SDL_bool check_device_playing(void);
|
||||
|
||||
#endif /* SDL_sysvideocapture_h_ */
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "SDL_video_c.h"
|
||||
#include "../events/SDL_events_c.h"
|
||||
#include "../timer/SDL_timer_c.h"
|
||||
#include "SDL_video_capture_c.h"
|
||||
|
||||
#ifdef SDL_VIDEO_OPENGL
|
||||
#include <SDL3/SDL_opengl.h>
|
||||
@@ -443,6 +444,7 @@ int SDL_VideoInit(const char *driver_name)
|
||||
SDL_bool init_keyboard = SDL_FALSE;
|
||||
SDL_bool init_mouse = SDL_FALSE;
|
||||
SDL_bool init_touch = SDL_FALSE;
|
||||
SDL_bool init_video_capture = SDL_FALSE;
|
||||
int i = 0;
|
||||
|
||||
/* Check to make sure we don't overwrite '_this' */
|
||||
@@ -471,6 +473,10 @@ int SDL_VideoInit(const char *driver_name)
|
||||
goto pre_driver_error;
|
||||
}
|
||||
init_touch = SDL_TRUE;
|
||||
if (SDL_VideoCaptureInit() < 0) {
|
||||
goto pre_driver_error;
|
||||
}
|
||||
init_video_capture = SDL_TRUE;
|
||||
|
||||
/* Select the proper video driver */
|
||||
video = NULL;
|
||||
@@ -565,6 +571,9 @@ int SDL_VideoInit(const char *driver_name)
|
||||
|
||||
pre_driver_error:
|
||||
SDL_assert(_this == NULL);
|
||||
if (init_video_capture) {
|
||||
SDL_QuitVideoCapture();
|
||||
}
|
||||
if (init_touch) {
|
||||
SDL_QuitTouch();
|
||||
}
|
||||
@@ -3684,6 +3693,7 @@ void SDL_VideoQuit(void)
|
||||
SDL_ClearClipboardData();
|
||||
|
||||
/* Halt event processing before doing anything else */
|
||||
SDL_QuitVideoCapture();
|
||||
SDL_QuitTouch();
|
||||
SDL_QuitMouse();
|
||||
SDL_QuitKeyboard();
|
||||
|
||||
948
src/video/SDL_video_capture.c
Normal file
948
src/video/SDL_video_capture.c
Normal file
@@ -0,0 +1,948 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL3/SDL.h"
|
||||
#include "SDL3/SDL_video_capture.h"
|
||||
#include "SDL_sysvideocapture.h"
|
||||
#include "SDL_video_capture_c.h"
|
||||
#include "SDL_pixels_c.h"
|
||||
#include "../thread/SDL_systhread.h"
|
||||
|
||||
#define DEBUG_VIDEO_CAPTURE_CAPTURE 1
|
||||
|
||||
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
/* list node entries to share frames between SDL and user app */
|
||||
typedef struct entry_t
|
||||
{
|
||||
SDL_VideoCaptureFrame frame;
|
||||
} entry_t;
|
||||
|
||||
static SDL_VideoCaptureDevice *open_devices[16];
|
||||
|
||||
static void
|
||||
close_device(SDL_VideoCaptureDevice *device)
|
||||
{
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_AtomicSet(&device->shutdown, 1);
|
||||
SDL_AtomicSet(&device->enabled, 1);
|
||||
|
||||
if (device->thread != NULL) {
|
||||
SDL_WaitThread(device->thread, NULL);
|
||||
}
|
||||
if (device->device_lock != NULL) {
|
||||
SDL_DestroyMutex(device->device_lock);
|
||||
}
|
||||
if (device->acquiring_lock != NULL) {
|
||||
SDL_DestroyMutex(device->acquiring_lock);
|
||||
}
|
||||
|
||||
{
|
||||
int i, n = SDL_arraysize(open_devices);
|
||||
for (i = 0; i < n; i++) {
|
||||
if (open_devices[i] == device) {
|
||||
open_devices[i] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
entry_t *entry = NULL;
|
||||
while (device->buffer_queue != NULL) {
|
||||
SDL_ListPop(&device->buffer_queue, (void**)&entry);
|
||||
if (entry) {
|
||||
SDL_VideoCaptureFrame f = entry->frame;
|
||||
/* Release frames not acquired, if any */
|
||||
if (f.timestampNS) {
|
||||
ReleaseFrame(device, &f);
|
||||
}
|
||||
SDL_free(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CloseDevice(device);
|
||||
|
||||
SDL_free(device->dev_name);
|
||||
SDL_free(device);
|
||||
}
|
||||
|
||||
/* Tell if all device are closed */
|
||||
SDL_bool check_all_device_closed(void)
|
||||
{
|
||||
int i, n = SDL_arraysize(open_devices);
|
||||
int all_closed = SDL_TRUE;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (open_devices[i]) {
|
||||
all_closed = SDL_FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return all_closed;
|
||||
}
|
||||
|
||||
/* Tell if at least one device is in playing state */
|
||||
SDL_bool check_device_playing(void)
|
||||
{
|
||||
int i, n = SDL_arraysize(open_devices);
|
||||
for (i = 0; i < n; i++) {
|
||||
if (open_devices[i]) {
|
||||
if (SDL_GetVideoCaptureStatus(open_devices[i]) == SDL_VIDEO_CAPTURE_PLAYING) {
|
||||
return SDL_TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
|
||||
#endif /* SDL_VIDEO_CAPTURE */
|
||||
|
||||
void
|
||||
SDL_CloseVideoCapture(SDL_VideoCaptureDevice *device)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
if (!device) {
|
||||
SDL_InvalidParamError("device");
|
||||
return;
|
||||
}
|
||||
close_device(device);
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
SDL_StartVideoCapture(SDL_VideoCaptureDevice *device)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
SDL_VideoCaptureStatus status;
|
||||
int result;
|
||||
if (!device) {
|
||||
return SDL_InvalidParamError("device");
|
||||
}
|
||||
|
||||
if (device->is_spec_set == SDL_FALSE) {
|
||||
return SDL_SetError("no spec set");
|
||||
}
|
||||
|
||||
status = SDL_GetVideoCaptureStatus(device);
|
||||
if (status != SDL_VIDEO_CAPTURE_INIT) {
|
||||
return SDL_SetError("invalid state");
|
||||
}
|
||||
|
||||
result = StartCapture(device);
|
||||
if (result < 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
SDL_AtomicSet(&device->enabled, 1);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return SDL_Unsupported();
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
SDL_GetVideoCaptureSpec(SDL_VideoCaptureDevice *device, SDL_VideoCaptureSpec *spec)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
if (!device) {
|
||||
return SDL_InvalidParamError("device");
|
||||
}
|
||||
|
||||
if (!spec) {
|
||||
return SDL_InvalidParamError("spec");
|
||||
}
|
||||
|
||||
SDL_zerop(spec);
|
||||
|
||||
return GetDeviceSpec(device, spec);
|
||||
#else
|
||||
return SDL_Unsupported();
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
SDL_StopVideoCapture(SDL_VideoCaptureDevice *device)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
SDL_VideoCaptureStatus status;
|
||||
int ret;
|
||||
if (!device) {
|
||||
return SDL_InvalidParamError("device");
|
||||
}
|
||||
|
||||
status = SDL_GetVideoCaptureStatus(device);
|
||||
|
||||
if (status != SDL_VIDEO_CAPTURE_PLAYING) {
|
||||
return SDL_SetError("invalid state");
|
||||
}
|
||||
|
||||
SDL_AtomicSet(&device->enabled, 0);
|
||||
SDL_AtomicSet(&device->shutdown, 1);
|
||||
|
||||
SDL_LockMutex(device->acquiring_lock);
|
||||
ret = StopCapture(device);
|
||||
SDL_UnlockMutex(device->acquiring_lock);
|
||||
|
||||
if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return SDL_Unsupported();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
|
||||
/* Check spec has valid format and frame size */
|
||||
static int
|
||||
prepare_video_capturespec(SDL_VideoCaptureDevice *device, const SDL_VideoCaptureSpec *desired, SDL_VideoCaptureSpec *obtained, int allowed_changes)
|
||||
{
|
||||
/* Check format */
|
||||
{
|
||||
int i, num = SDL_GetNumVideoCaptureFormats(device);
|
||||
int is_format_valid = 0;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
Uint32 format;
|
||||
if (SDL_GetVideoCaptureFormat(device, i, &format) == 0) {
|
||||
if (format == desired->format && format != SDL_PIXELFORMAT_UNKNOWN) {
|
||||
is_format_valid = 1;
|
||||
obtained->format = format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_format_valid) {
|
||||
if (allowed_changes) {
|
||||
for (i = 0; i < num; i++) {
|
||||
Uint32 format;
|
||||
if (SDL_GetVideoCaptureFormat(device, i, &format) == 0) {
|
||||
if (format != SDL_PIXELFORMAT_UNKNOWN) {
|
||||
obtained->format = format;
|
||||
is_format_valid = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
SDL_SetError("Not allowed to change the format");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_format_valid) {
|
||||
SDL_SetError("Invalid format");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check frame size */
|
||||
{
|
||||
int i, num = SDL_GetNumVideoCaptureFrameSizes(device, obtained->format);
|
||||
int is_framesize_valid = 0;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
int w, h;
|
||||
if (SDL_GetVideoCaptureFrameSize(device, obtained->format, i, &w, &h) == 0) {
|
||||
if (desired->width == w && desired->height == h) {
|
||||
is_framesize_valid = 1;
|
||||
obtained->width = w;
|
||||
obtained->height = h;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_framesize_valid) {
|
||||
if (allowed_changes) {
|
||||
int w, h;
|
||||
if (SDL_GetVideoCaptureFrameSize(device, obtained->format, 0, &w, &h) == 0) {
|
||||
is_framesize_valid = 1;
|
||||
obtained->width = w;
|
||||
obtained->height = h;
|
||||
}
|
||||
} else {
|
||||
SDL_SetError("Not allowed to change the frame size");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_framesize_valid) {
|
||||
SDL_SetError("Invalid frame size");
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* SDL_VIDEO_CAPTURE */
|
||||
|
||||
const char *
|
||||
SDL_GetVideoCaptureDeviceName(SDL_VideoCaptureDeviceID instance_id)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
int index = instance_id - 1;
|
||||
static char buf[256];
|
||||
buf[0] = 0;
|
||||
buf[255] = 0;
|
||||
|
||||
if (instance_id == 0) {
|
||||
SDL_InvalidParamError("instance_id");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (GetDeviceName(index, buf, sizeof (buf)) < 0) {
|
||||
buf[0] = 0;
|
||||
}
|
||||
return buf;
|
||||
#else
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
SDL_VideoCaptureDeviceID *
|
||||
SDL_GetVideoCaptureDevices(int *count)
|
||||
{
|
||||
|
||||
int i;
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
int num = GetNumDevices();
|
||||
#else
|
||||
int num = 0;
|
||||
#endif
|
||||
SDL_VideoCaptureDeviceID *ret;
|
||||
|
||||
ret = (SDL_VideoCaptureDeviceID *)SDL_malloc((num + 1) * sizeof(*ret));
|
||||
|
||||
if (ret == NULL) {
|
||||
SDL_OutOfMemory();
|
||||
if (count) {
|
||||
*count = 0;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
ret[i] = i + 1;
|
||||
}
|
||||
ret[num] = 0;
|
||||
|
||||
if (count) {
|
||||
*count = num;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
|
||||
/* Video capture thread function */
|
||||
static int SDLCALL
|
||||
SDL_CaptureVideoThread(void *devicep)
|
||||
{
|
||||
const int delay = 20;
|
||||
SDL_VideoCaptureDevice *device = (SDL_VideoCaptureDevice *) devicep;
|
||||
|
||||
#if DEBUG_VIDEO_CAPTURE_CAPTURE
|
||||
SDL_Log("Start thread 'SDL_CaptureVideo'");
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef SDL_VIDEO_DRIVER_ANDROID
|
||||
// TODO
|
||||
/*
|
||||
{
|
||||
// Set thread priority to THREAD_PRIORITY_VIDEO
|
||||
extern void Android_JNI_VideoCaptureSetThreadPriority(int, int);
|
||||
Android_JNI_VideoCaptureSetThreadPriority(device->iscapture, device);
|
||||
}*/
|
||||
#else
|
||||
/* The video_capture mixing is always a high priority thread */
|
||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||
#endif
|
||||
|
||||
/* Perform any thread setup */
|
||||
device->threadid = SDL_ThreadID();
|
||||
|
||||
/* Init state */
|
||||
while (!SDL_AtomicGet(&device->enabled)) {
|
||||
SDL_Delay(delay);
|
||||
}
|
||||
|
||||
/* Loop, filling the video_capture buffers */
|
||||
while (!SDL_AtomicGet(&device->shutdown)) {
|
||||
SDL_VideoCaptureFrame f;
|
||||
int ret;
|
||||
entry_t *entry;
|
||||
|
||||
SDL_zero(f);
|
||||
|
||||
SDL_LockMutex(device->acquiring_lock);
|
||||
ret = AcquireFrame(device, &f);
|
||||
SDL_UnlockMutex(device->acquiring_lock);
|
||||
|
||||
if (ret == 0) {
|
||||
if (f.num_planes == 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
/* Flag it as an error */
|
||||
#if DEBUG_VIDEO_CAPTURE_CAPTURE
|
||||
SDL_Log("dev[%p] error AcquireFrame: %d %s", (void *)device, ret, SDL_GetError());
|
||||
#endif
|
||||
f.num_planes = 0;
|
||||
}
|
||||
|
||||
|
||||
entry = SDL_malloc(sizeof (entry_t));
|
||||
if (entry == NULL) {
|
||||
goto error_mem;
|
||||
}
|
||||
|
||||
entry->frame = f;
|
||||
|
||||
SDL_LockMutex(device->device_lock);
|
||||
ret = SDL_ListAdd(&device->buffer_queue, entry);
|
||||
SDL_UnlockMutex(device->device_lock);
|
||||
|
||||
if (ret < 0) {
|
||||
SDL_free(entry);
|
||||
goto error_mem;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG_VIDEO_CAPTURE_CAPTURE
|
||||
SDL_Log("dev[%p] End thread 'SDL_CaptureVideo'", (void *)device);
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
error_mem:
|
||||
#if DEBUG_VIDEO_CAPTURE_CAPTURE
|
||||
SDL_Log("dev[%p] End thread 'SDL_CaptureVideo' with error: %s", (void *)device, SDL_GetError());
|
||||
#endif
|
||||
SDL_AtomicSet(&device->shutdown, 1);
|
||||
SDL_OutOfMemory();
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
SDL_VideoCaptureDevice *
|
||||
SDL_OpenVideoCapture(SDL_VideoCaptureDeviceID instance_id)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
int i, n = SDL_arraysize(open_devices);
|
||||
int id = -1;
|
||||
SDL_VideoCaptureDevice *device = NULL;
|
||||
const char *device_name = NULL;
|
||||
|
||||
if (!SDL_WasInit(SDL_INIT_VIDEO)) {
|
||||
SDL_SetError("Video subsystem is not initialized");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* !!! FIXME: there is a race condition here if two devices open from two threads at once. */
|
||||
/* Find an available device ID... */
|
||||
for (i = 0; i < n; i++) {
|
||||
if (open_devices[i] == NULL) {
|
||||
id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (id == -1) {
|
||||
SDL_SetError("Too many open video capture devices");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (instance_id != 0) {
|
||||
device_name = SDL_GetVideoCaptureDeviceName(instance_id);
|
||||
if (device_name == NULL) {
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
SDL_VideoCaptureDeviceID *devices = SDL_GetVideoCaptureDevices(NULL);
|
||||
if (devices && devices[0]) {
|
||||
device_name = SDL_GetVideoCaptureDeviceName(devices[0]);
|
||||
SDL_free(devices);
|
||||
}
|
||||
}
|
||||
|
||||
/* Let the user override. */
|
||||
{
|
||||
const char *dev = SDL_getenv("SDL_VIDEO_CAPTURE_DEVICE_NAME");
|
||||
if (dev && dev[0]) {
|
||||
device_name = dev;
|
||||
}
|
||||
}
|
||||
|
||||
if (device_name == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
device = (SDL_VideoCaptureDevice *) SDL_calloc(1, sizeof (SDL_VideoCaptureDevice));
|
||||
if (device == NULL) {
|
||||
SDL_OutOfMemory();
|
||||
goto error;
|
||||
}
|
||||
device->dev_name = SDL_strdup(device_name);
|
||||
|
||||
|
||||
SDL_AtomicSet(&device->shutdown, 0);
|
||||
SDL_AtomicSet(&device->enabled, 0);
|
||||
|
||||
device->device_lock = SDL_CreateMutex();
|
||||
if (device->device_lock == NULL) {
|
||||
SDL_SetError("Couldn't create acquiring_lock");
|
||||
goto error;
|
||||
}
|
||||
|
||||
device->acquiring_lock = SDL_CreateMutex();
|
||||
if (device->acquiring_lock == NULL) {
|
||||
SDL_SetError("Couldn't create acquiring_lock");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (OpenDevice(device) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* empty */
|
||||
device->buffer_queue = NULL;
|
||||
open_devices[id] = device; /* add it to our list of open devices. */
|
||||
|
||||
|
||||
/* Start the video_capture thread */
|
||||
{
|
||||
const size_t stacksize = 64 * 1024;
|
||||
char threadname[64];
|
||||
|
||||
SDL_snprintf(threadname, sizeof (threadname), "SDLVideoC%d", id);
|
||||
device->thread = SDL_CreateThreadInternal(SDL_CaptureVideoThread, threadname, stacksize, device);
|
||||
|
||||
if (device->thread == NULL) {
|
||||
SDL_SetError("Couldn't create video_capture thread");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
return device;
|
||||
|
||||
error:
|
||||
close_device(device);
|
||||
return NULL;
|
||||
#else
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
#endif /* SDL_VIDEO_CAPTURE */
|
||||
}
|
||||
|
||||
int
|
||||
SDL_SetVideoCaptureSpec(SDL_VideoCaptureDevice *device,
|
||||
const SDL_VideoCaptureSpec *desired,
|
||||
SDL_VideoCaptureSpec *obtained,
|
||||
int allowed_changes)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
SDL_VideoCaptureSpec _obtained;
|
||||
SDL_VideoCaptureSpec _desired;
|
||||
int result;
|
||||
|
||||
if (!device) {
|
||||
return SDL_InvalidParamError("device");
|
||||
}
|
||||
|
||||
if (device->is_spec_set == SDL_TRUE) {
|
||||
return SDL_SetError("already configured");
|
||||
}
|
||||
|
||||
if (!desired) {
|
||||
SDL_zero(_desired);
|
||||
desired = &_desired;
|
||||
allowed_changes = SDL_VIDEO_CAPTURE_ALLOW_ANY_CHANGE;
|
||||
} else {
|
||||
/* in case desired == obtained */
|
||||
_desired = *desired;
|
||||
desired = &_desired;
|
||||
}
|
||||
|
||||
if (!obtained) {
|
||||
obtained = &_obtained;
|
||||
}
|
||||
|
||||
SDL_zerop(obtained);
|
||||
|
||||
if (prepare_video_capturespec(device, desired, obtained, allowed_changes) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
device->spec = *obtained;
|
||||
|
||||
result = InitDevice(device);
|
||||
if (result < 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
*obtained = device->spec;
|
||||
|
||||
device->is_spec_set = SDL_TRUE;
|
||||
|
||||
return 0;
|
||||
#else
|
||||
SDL_zero(*obtained);
|
||||
return SDL_Unsupported();
|
||||
#endif /* SDL_VIDEO_CAPTURE */
|
||||
}
|
||||
|
||||
int
|
||||
SDL_AcquireVideoCaptureFrame(SDL_VideoCaptureDevice *device, SDL_VideoCaptureFrame *frame)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
if (!device) {
|
||||
return SDL_InvalidParamError("device");
|
||||
}
|
||||
|
||||
if (!frame) {
|
||||
return SDL_InvalidParamError("frame");
|
||||
}
|
||||
|
||||
SDL_zerop(frame);
|
||||
|
||||
if (device->thread == NULL) {
|
||||
int ret;
|
||||
|
||||
/* Wait for a frame */
|
||||
while ((ret = AcquireFrame(device, frame)) == 0) {
|
||||
if (frame->num_planes) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
} else {
|
||||
entry_t *entry = NULL;
|
||||
|
||||
SDL_LockMutex(device->device_lock);
|
||||
SDL_ListPop(&device->buffer_queue, (void**)&entry);
|
||||
SDL_UnlockMutex(device->device_lock);
|
||||
|
||||
if (entry) {
|
||||
*frame = entry->frame;
|
||||
SDL_free(entry);
|
||||
|
||||
/* Error from thread */
|
||||
if (frame->num_planes == 0 && frame->timestampNS == 0) {
|
||||
return SDL_SetError("error from acquisition thread");
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
/* Queue is empty. Not an error. */
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return SDL_Unsupported();
|
||||
#endif /* SDL_VIDEO_CAPTURE */
|
||||
}
|
||||
|
||||
int
|
||||
SDL_ReleaseVideoCaptureFrame(SDL_VideoCaptureDevice *device, SDL_VideoCaptureFrame *frame)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
if (!device) {
|
||||
return SDL_InvalidParamError("device");
|
||||
}
|
||||
|
||||
if (frame == NULL) {
|
||||
return SDL_InvalidParamError("frame");
|
||||
}
|
||||
|
||||
if (ReleaseFrame(device, frame) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_zerop(frame);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return SDL_Unsupported();
|
||||
#endif /* SDL_VIDEO_CAPTURE */
|
||||
}
|
||||
|
||||
int
|
||||
SDL_GetNumVideoCaptureFormats(SDL_VideoCaptureDevice *device)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
if (!device) {
|
||||
return SDL_InvalidParamError("device");
|
||||
}
|
||||
return GetNumFormats(device);
|
||||
#else
|
||||
return 0;
|
||||
#endif /* SDL_VIDEO_CAPTURE */
|
||||
}
|
||||
|
||||
int
|
||||
SDL_GetVideoCaptureFormat(SDL_VideoCaptureDevice *device, int index, Uint32 *format)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
if (!device) {
|
||||
return SDL_InvalidParamError("device");
|
||||
}
|
||||
if (!format) {
|
||||
return SDL_InvalidParamError("format");
|
||||
}
|
||||
*format = 0;
|
||||
return GetFormat(device, index, format);
|
||||
#else
|
||||
return SDL_Unsupported();
|
||||
#endif /* SDL_VIDEO_CAPTURE */
|
||||
}
|
||||
|
||||
int
|
||||
SDL_GetNumVideoCaptureFrameSizes(SDL_VideoCaptureDevice *device, Uint32 format)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
if (!device) {
|
||||
return SDL_InvalidParamError("device");
|
||||
}
|
||||
return GetNumFrameSizes(device, format);
|
||||
#else
|
||||
return 0;
|
||||
#endif /* SDL_VIDEO_CAPTURE */
|
||||
}
|
||||
|
||||
int
|
||||
SDL_GetVideoCaptureFrameSize(SDL_VideoCaptureDevice *device, Uint32 format, int index, int *width, int *height)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
if (!device) {
|
||||
return SDL_InvalidParamError("device");
|
||||
}
|
||||
if (!width) {
|
||||
return SDL_InvalidParamError("width");
|
||||
}
|
||||
if (!height) {
|
||||
return SDL_InvalidParamError("height");
|
||||
}
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
return GetFrameSize(device, format, index, width, height);
|
||||
#else
|
||||
return SDL_Unsupported();
|
||||
#endif
|
||||
}
|
||||
|
||||
SDL_VideoCaptureDevice *
|
||||
SDL_OpenVideoCaptureWithSpec(
|
||||
SDL_VideoCaptureDeviceID instance_id,
|
||||
const SDL_VideoCaptureSpec *desired,
|
||||
SDL_VideoCaptureSpec *obtained,
|
||||
int allowed_changes)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
SDL_VideoCaptureDevice *device;
|
||||
|
||||
if ((device = SDL_OpenVideoCapture(instance_id)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (SDL_SetVideoCaptureSpec(device, desired, obtained, allowed_changes) < 0) {
|
||||
SDL_CloseVideoCapture(device);
|
||||
return NULL;
|
||||
}
|
||||
return device;
|
||||
#else
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
SDL_VideoCaptureStatus
|
||||
SDL_GetVideoCaptureStatus(SDL_VideoCaptureDevice *device)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
if (device == NULL) {
|
||||
return SDL_VIDEO_CAPTURE_INIT;
|
||||
}
|
||||
|
||||
if (device->is_spec_set == SDL_FALSE) {
|
||||
return SDL_VIDEO_CAPTURE_INIT;
|
||||
}
|
||||
|
||||
if (SDL_AtomicGet(&device->shutdown)) {
|
||||
return SDL_VIDEO_CAPTURE_STOPPED;
|
||||
}
|
||||
|
||||
if (SDL_AtomicGet(&device->enabled)) {
|
||||
return SDL_VIDEO_CAPTURE_PLAYING;
|
||||
}
|
||||
return SDL_VIDEO_CAPTURE_INIT;
|
||||
#else
|
||||
SDL_Unsupported();
|
||||
return SDL_VIDEO_CAPTURE_FAIL;
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
SDL_VideoCaptureInit(void)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
SDL_zeroa(open_devices);
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
SDL_QuitVideoCapture(void)
|
||||
{
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
int i, n = SDL_arraysize(open_devices);
|
||||
for (i = 0; i < n; i++) {
|
||||
close_device(open_devices[i]);
|
||||
}
|
||||
|
||||
SDL_zeroa(open_devices);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
|
||||
/* See SDL_video_capture_v4l2.c */
|
||||
|
||||
#elif defined(__ANDROID__) && __ANDROID_API__ >= 24
|
||||
|
||||
/* See SDL_android_video_capture.c */
|
||||
|
||||
#elif defined(__IPHONEOS__) || defined(__MACOS__)
|
||||
|
||||
/* See SDL_video_capture_apple.m */
|
||||
#else
|
||||
|
||||
int
|
||||
OpenDevice(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
return SDL_SetError("not implemented");
|
||||
}
|
||||
|
||||
void
|
||||
CloseDevice(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
InitDevice(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
size_t size, pitch;
|
||||
SDL_CalculateSize(_this->spec.format, _this->spec.width, _this->spec.height, &size, &pitch, SDL_FALSE);
|
||||
SDL_Log("Buffer size: %d x %d", _this->spec.width, _this->spec.height);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
int
|
||||
StartCapture(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
int
|
||||
StopCapture(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetNumFormats(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetDeviceName(int index, char *buf, int size)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetNumDevices(void)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SDL_VIDEO_CAPTURE */
|
||||
615
src/video/SDL_video_capture_apple.m
Normal file
615
src/video/SDL_video_capture_apple.m
Normal file
@@ -0,0 +1,615 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 2021 Valve Corporation
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
|
||||
#include "SDL3/SDL.h"
|
||||
#include "SDL3/SDL_video_capture.h"
|
||||
#include "SDL_sysvideocapture.h"
|
||||
#include "SDL_video_capture_c.h"
|
||||
#include "../thread/SDL_systhread.h"
|
||||
|
||||
#if defined(HAVE_COREMEDIA) && defined(__MACOS__) && (__MAC_OS_X_VERSION_MAX_ALLOWED < 101500)
|
||||
/* AVCaptureDeviceTypeBuiltInWideAngleCamera requires macOS SDK 10.15 */
|
||||
#undef HAVE_COREMEDIA
|
||||
#endif
|
||||
|
||||
#if TARGET_OS_TV
|
||||
#undef HAVE_COREMEDIA
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_COREMEDIA
|
||||
int InitDevice(SDL_VideoCaptureDevice *_this) {
|
||||
return -1;
|
||||
}
|
||||
int OpenDevice(SDL_VideoCaptureDevice *_this) {
|
||||
return -1;
|
||||
}
|
||||
int AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) {
|
||||
return -1;
|
||||
}
|
||||
void CloseDevice(SDL_VideoCaptureDevice *_this) {
|
||||
}
|
||||
int GetDeviceName(int index, char *buf, int size) {
|
||||
return -1;
|
||||
}
|
||||
int GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec) {
|
||||
return -1;
|
||||
}
|
||||
int GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format) {
|
||||
return -1;
|
||||
}
|
||||
int GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height) {
|
||||
return -1;
|
||||
}
|
||||
int GetNumDevices(void) {
|
||||
return 0;
|
||||
}
|
||||
int GetNumFormats(SDL_VideoCaptureDevice *_this) {
|
||||
return 0;
|
||||
}
|
||||
int GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format) {
|
||||
return 0;
|
||||
}
|
||||
int ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) {
|
||||
return 0;
|
||||
}
|
||||
int StartCapture(SDL_VideoCaptureDevice *_this) {
|
||||
return 0;
|
||||
}
|
||||
int StopCapture(SDL_VideoCaptureDevice *_this) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <CoreMedia/CoreMedia.h>
|
||||
|
||||
/*
|
||||
* Need to link with:: CoreMedia CoreVideo
|
||||
*
|
||||
* Add in pInfo.list:
|
||||
* <key>NSCameraUsageDescription</key> <string>Access camera</string>
|
||||
*
|
||||
*
|
||||
* MACOSX:
|
||||
* Add to the Code Sign Entitlement file:
|
||||
* <key>com.apple.security.device.camera</key> <true/>
|
||||
*
|
||||
*
|
||||
* IOS:
|
||||
*
|
||||
* - Need to link with:: CoreMedia CoreVideo
|
||||
* - Add #define SDL_VIDEO_CAPTURE 1
|
||||
* to SDL_build_config_ios.h
|
||||
*/
|
||||
|
||||
@class MySampleBufferDelegate;
|
||||
|
||||
struct SDL_PrivateVideoCaptureData
|
||||
{
|
||||
dispatch_queue_t queue;
|
||||
MySampleBufferDelegate *delegate;
|
||||
AVCaptureSession *session;
|
||||
CMSimpleQueueRef frame_queue;
|
||||
};
|
||||
|
||||
static NSString *
|
||||
fourcc_to_nstring(Uint32 code)
|
||||
{
|
||||
Uint8 buf[4];
|
||||
*(Uint32 *)buf = code;
|
||||
return [NSString stringWithFormat:@"%c%c%c%c", buf[3], buf[2], buf[1], buf[0]];
|
||||
}
|
||||
|
||||
static NSArray<AVCaptureDevice *> *
|
||||
discover_devices()
|
||||
{
|
||||
NSArray *deviceType = @[AVCaptureDeviceTypeBuiltInWideAngleCamera];
|
||||
|
||||
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
|
||||
discoverySessionWithDeviceTypes:deviceType
|
||||
mediaType:AVMediaTypeVideo
|
||||
position:AVCaptureDevicePositionUnspecified];
|
||||
|
||||
NSArray<AVCaptureDevice *> *devices = discoverySession.devices;
|
||||
|
||||
if ([devices count] > 0) {
|
||||
return devices;
|
||||
} else {
|
||||
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
|
||||
if (captureDevice == nil) {
|
||||
return devices;
|
||||
} else {
|
||||
NSArray<AVCaptureDevice *> *default_device = @[ captureDevice ];
|
||||
return default_device;
|
||||
}
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
static AVCaptureDevice *
|
||||
get_device_by_name(const char *dev_name)
|
||||
{
|
||||
NSArray<AVCaptureDevice *> *devices = discover_devices();
|
||||
|
||||
for (AVCaptureDevice *device in devices) {
|
||||
char buf[1024];
|
||||
NSString *cameraID = [device localizedName];
|
||||
const char *str = [cameraID UTF8String];
|
||||
SDL_snprintf(buf, sizeof (buf) - 1, "%s", str);
|
||||
if (SDL_strcmp(buf, dev_name) == 0) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
static Uint32
|
||||
nsfourcc_to_sdlformat(NSString *nsfourcc)
|
||||
{
|
||||
const char *str = [nsfourcc UTF8String];
|
||||
|
||||
/* FIXME
|
||||
* on IOS this mode gives 2 planes, and it's NV12
|
||||
* on macos, 1 plane/ YVYU
|
||||
*
|
||||
*/
|
||||
#ifdef __MACOS__
|
||||
if (SDL_strcmp("420v", str) == 0) return SDL_PIXELFORMAT_YVYU;
|
||||
#else
|
||||
if (SDL_strcmp("420v", str) == 0) return SDL_PIXELFORMAT_NV12;
|
||||
#endif
|
||||
if (SDL_strcmp("yuvs", str) == 0) return SDL_PIXELFORMAT_UYVY;
|
||||
if (SDL_strcmp("420f", str) == 0) return SDL_PIXELFORMAT_UNKNOWN;
|
||||
|
||||
SDL_Log("Unknown format '%s'", str);
|
||||
|
||||
return SDL_PIXELFORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
static NSString *
|
||||
sdlformat_to_nsfourcc(Uint32 fmt)
|
||||
{
|
||||
const char *str = "";
|
||||
NSString *result;
|
||||
|
||||
#ifdef __MACOS__
|
||||
if (fmt == SDL_PIXELFORMAT_YVYU) str = "420v";
|
||||
#else
|
||||
if (fmt == SDL_PIXELFORMAT_NV12) str = "420v";
|
||||
#endif
|
||||
if (fmt == SDL_PIXELFORMAT_UYVY) str = "yuvs";
|
||||
|
||||
result = [[NSString alloc] initWithUTF8String: str];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@interface MySampleBufferDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
|
||||
@property struct SDL_PrivateVideoCaptureData *hidden;
|
||||
- (void) set: (struct SDL_PrivateVideoCaptureData *) val;
|
||||
@end
|
||||
|
||||
@implementation MySampleBufferDelegate
|
||||
|
||||
- (void) set: (struct SDL_PrivateVideoCaptureData *) val {
|
||||
_hidden = val;
|
||||
}
|
||||
|
||||
- (void) captureOutput:(AVCaptureOutput *)output
|
||||
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
||||
fromConnection:(AVCaptureConnection *) connection {
|
||||
CFRetain(sampleBuffer);
|
||||
CMSimpleQueueEnqueue(_hidden->frame_queue, sampleBuffer);
|
||||
}
|
||||
|
||||
- (void)captureOutput:(AVCaptureOutput *)output
|
||||
didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
||||
fromConnection:(AVCaptureConnection *)connection {
|
||||
SDL_Log("Drop frame..");
|
||||
}
|
||||
@end
|
||||
|
||||
int
|
||||
OpenDevice(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
_this->hidden = (struct SDL_PrivateVideoCaptureData *) SDL_calloc(1, sizeof (struct SDL_PrivateVideoCaptureData));
|
||||
if (_this->hidden == NULL) {
|
||||
SDL_OutOfMemory();
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
CloseDevice(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
if (!_this) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_this->hidden) {
|
||||
AVCaptureSession *session = _this->hidden->session;
|
||||
|
||||
if (session) {
|
||||
AVCaptureInput *input;
|
||||
AVCaptureVideoDataOutput *output;
|
||||
input = [session.inputs objectAtIndex:0];
|
||||
[session removeInput:input];
|
||||
output = (AVCaptureVideoDataOutput*)[session.outputs objectAtIndex:0];
|
||||
[session removeOutput:output];
|
||||
// TODO more cleanup ?
|
||||
}
|
||||
|
||||
if (_this->hidden->frame_queue) {
|
||||
CFRelease(_this->hidden->frame_queue);
|
||||
}
|
||||
|
||||
SDL_free(_this->hidden);
|
||||
_this->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
InitDevice(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
NSString *fmt = sdlformat_to_nsfourcc(_this->spec.format);
|
||||
int w = _this->spec.width;
|
||||
int h = _this->spec.height;
|
||||
|
||||
NSError *error = nil;
|
||||
AVCaptureDevice *device = nil;
|
||||
AVCaptureDeviceInput *input = nil;
|
||||
AVCaptureVideoDataOutput *output = nil;
|
||||
|
||||
AVCaptureDeviceFormat *spec_format = nil;
|
||||
|
||||
#ifdef __MACOS__
|
||||
if (@available(macOS 10.15, *)) {
|
||||
/* good. */
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
device = get_device_by_name(_this->dev_name);
|
||||
if (!device) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
_this->hidden->session = [[AVCaptureSession alloc] init];
|
||||
if (_this->hidden->session == nil) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
[_this->hidden->session setSessionPreset:AVCaptureSessionPresetHigh];
|
||||
|
||||
// Pick format that matches the spec
|
||||
{
|
||||
NSArray<AVCaptureDeviceFormat *> *formats = [device formats];
|
||||
for (AVCaptureDeviceFormat *format in formats) {
|
||||
CMFormatDescriptionRef formatDescription = [format formatDescription];
|
||||
FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription);
|
||||
NSString *str = fourcc_to_nstring(mediaSubType);
|
||||
if (str == fmt) {
|
||||
CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDescription);
|
||||
if (dim.width == w && dim.height == h) {
|
||||
spec_format = format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (spec_format == nil) {
|
||||
SDL_SetError("format not found");
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Set format
|
||||
if ([device lockForConfiguration:NULL] == YES) {
|
||||
device.activeFormat = spec_format;
|
||||
[device unlockForConfiguration];
|
||||
} else {
|
||||
SDL_SetError("Cannot lockForConfiguration");
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Input
|
||||
input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
|
||||
if (!input) {
|
||||
SDL_SetError("Cannot create AVCaptureDeviceInput");
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Output
|
||||
output = [[AVCaptureVideoDataOutput alloc] init];
|
||||
|
||||
#ifdef __MACOS__
|
||||
// FIXME this now fail on ios ... but not using anything works...
|
||||
|
||||
// Specify the pixel format
|
||||
output.videoSettings =
|
||||
[NSDictionary dictionaryWithObject:
|
||||
[NSNumber numberWithInt:kCVPixelFormatType_422YpCbCr8]
|
||||
forKey:(id)kCVPixelBufferPixelFormatTypeKey];
|
||||
#endif
|
||||
|
||||
_this->hidden->delegate = [[MySampleBufferDelegate alloc] init];
|
||||
[_this->hidden->delegate set:_this->hidden];
|
||||
|
||||
|
||||
CMSimpleQueueCreate(kCFAllocatorDefault, 30 /* buffers */, &_this->hidden->frame_queue);
|
||||
if (_this->hidden->frame_queue == nil) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
_this->hidden->queue = dispatch_queue_create("my_queue", NULL);
|
||||
[output setSampleBufferDelegate:_this->hidden->delegate queue:_this->hidden->queue];
|
||||
|
||||
|
||||
if ([_this->hidden->session canAddInput:input] ){
|
||||
[_this->hidden->session addInput:input];
|
||||
} else {
|
||||
SDL_SetError("Cannot add AVCaptureDeviceInput");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ([_this->hidden->session canAddOutput:output] ){
|
||||
[_this->hidden->session addOutput:output];
|
||||
} else {
|
||||
SDL_SetError("Cannot add AVCaptureVideoDataOutput");
|
||||
goto error;
|
||||
}
|
||||
|
||||
[_this->hidden->session commitConfiguration];
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec)
|
||||
{
|
||||
if (spec) {
|
||||
*spec = _this->spec;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
StartCapture(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
[_this->hidden->session startRunning];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
StopCapture(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
[_this->hidden->session stopRunning];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
|
||||
{
|
||||
if (CMSimpleQueueGetCount(_this->hidden->frame_queue) > 0) {
|
||||
int i, numPlanes, planar;
|
||||
CMSampleBufferRef sampleBuffer;
|
||||
CVImageBufferRef image;
|
||||
|
||||
sampleBuffer = (CMSampleBufferRef)CMSimpleQueueDequeue(_this->hidden->frame_queue);
|
||||
frame->internal = (void *) sampleBuffer;
|
||||
frame->timestampNS = SDL_GetTicksNS();
|
||||
|
||||
i = 0;
|
||||
image = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||
numPlanes = CVPixelBufferGetPlaneCount(image);
|
||||
planar = CVPixelBufferIsPlanar(image);
|
||||
|
||||
#if 0
|
||||
int w = CVPixelBufferGetWidth(image);
|
||||
int h = CVPixelBufferGetHeight(image);
|
||||
int sz = CVPixelBufferGetDataSize(image);
|
||||
int pitch = CVPixelBufferGetBytesPerRow(image);
|
||||
SDL_Log("buffer planar=%d count:%d %d x %d sz=%d pitch=%d", planar, numPlanes, w, h, sz, pitch);
|
||||
#endif
|
||||
|
||||
CVPixelBufferLockBaseAddress(image, 0);
|
||||
|
||||
if (planar == 0 && numPlanes == 0) {
|
||||
frame->pitch[0] = CVPixelBufferGetBytesPerRow(image);
|
||||
frame->data[0] = CVPixelBufferGetBaseAddress(image);
|
||||
frame->num_planes = 1;
|
||||
} else {
|
||||
for (i = 0; i < numPlanes && i < 3; i++) {
|
||||
int rowStride = 0;
|
||||
uint8_t *data = NULL;
|
||||
frame->num_planes += 1;
|
||||
|
||||
rowStride = CVPixelBufferGetBytesPerRowOfPlane(image, i);
|
||||
data = CVPixelBufferGetBaseAddressOfPlane(image, i);
|
||||
frame->data[i] = data;
|
||||
frame->pitch[i] = rowStride;
|
||||
}
|
||||
}
|
||||
|
||||
/* Unlocked when frame is released */
|
||||
|
||||
} else {
|
||||
// no frame
|
||||
SDL_Delay(20); // TODO fix some delay
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
|
||||
{
|
||||
if (frame->internal){
|
||||
CMSampleBufferRef sampleBuffer = (CMSampleBufferRef) frame->internal;
|
||||
|
||||
CVImageBufferRef image = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||
CVPixelBufferUnlockBaseAddress(image, 0);
|
||||
|
||||
CFRelease(sampleBuffer);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
GetNumFormats(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
AVCaptureDevice *device = get_device_by_name(_this->dev_name);
|
||||
if (device) {
|
||||
// LIST FORMATS
|
||||
NSMutableOrderedSet<NSString *> *array_formats = [NSMutableOrderedSet new];
|
||||
NSArray<AVCaptureDeviceFormat *> *formats = [device formats];
|
||||
for (AVCaptureDeviceFormat *format in formats) {
|
||||
// NSLog(@"%@", formats);
|
||||
CMFormatDescriptionRef formatDescription = [format formatDescription];
|
||||
//NSLog(@"%@", formatDescription);
|
||||
FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription);
|
||||
NSString *str = fourcc_to_nstring(mediaSubType);
|
||||
[array_formats addObject:str];
|
||||
}
|
||||
return [array_formats count];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format)
|
||||
{
|
||||
AVCaptureDevice *device = get_device_by_name(_this->dev_name);
|
||||
if (device) {
|
||||
// LIST FORMATS
|
||||
NSMutableOrderedSet<NSString *> *array_formats = [NSMutableOrderedSet new];
|
||||
NSArray<AVCaptureDeviceFormat *> *formats = [device formats];
|
||||
NSString *str;
|
||||
|
||||
for (AVCaptureDeviceFormat *f in formats) {
|
||||
FourCharCode mediaSubType;
|
||||
CMFormatDescriptionRef formatDescription;
|
||||
|
||||
formatDescription = [f formatDescription];
|
||||
mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription);
|
||||
str = fourcc_to_nstring(mediaSubType);
|
||||
[array_formats addObject:str];
|
||||
}
|
||||
|
||||
str = array_formats[index];
|
||||
*format = nsfourcc_to_sdlformat(str);
|
||||
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format)
|
||||
{
|
||||
AVCaptureDevice *device = get_device_by_name(_this->dev_name);
|
||||
if (device) {
|
||||
NSString *fmt = sdlformat_to_nsfourcc(format);
|
||||
int count = 0;
|
||||
|
||||
NSArray<AVCaptureDeviceFormat *> *formats = [device formats];
|
||||
for (AVCaptureDeviceFormat *f in formats) {
|
||||
CMFormatDescriptionRef formatDescription = [f formatDescription];
|
||||
FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription);
|
||||
NSString *str = fourcc_to_nstring(mediaSubType);
|
||||
|
||||
if (str == fmt) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height)
|
||||
{
|
||||
AVCaptureDevice *device = get_device_by_name(_this->dev_name);
|
||||
if (device) {
|
||||
NSString *fmt = sdlformat_to_nsfourcc(format);
|
||||
int count = 0;
|
||||
|
||||
NSArray<AVCaptureDeviceFormat *> *formats = [device formats];
|
||||
for (AVCaptureDeviceFormat *f in formats) {
|
||||
CMFormatDescriptionRef formatDescription = [f formatDescription];
|
||||
FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription);
|
||||
NSString *str = fourcc_to_nstring(mediaSubType);
|
||||
|
||||
if (str == fmt) {
|
||||
if (index == count) {
|
||||
CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDescription);
|
||||
*width = dim.width;
|
||||
*height = dim.height;
|
||||
return 0;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetDeviceName(int index, char *buf, int size)
|
||||
{
|
||||
NSArray<AVCaptureDevice *> *devices = discover_devices();
|
||||
if (index < [devices count]) {
|
||||
AVCaptureDevice *device = devices[index];
|
||||
NSString *cameraID = [device localizedName];
|
||||
const char *str = [cameraID UTF8String];
|
||||
SDL_snprintf(buf, size, "%s", str);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetNumDevices(void)
|
||||
{
|
||||
NSArray<AVCaptureDevice *> *devices = discover_devices();
|
||||
return [devices count];
|
||||
}
|
||||
|
||||
#endif /* HAVE_COREMEDIA */
|
||||
|
||||
#endif /* SDL_VIDEO_CAPTURE */
|
||||
|
||||
33
src/video/SDL_video_capture_c.h
Normal file
33
src/video/SDL_video_capture_c.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "../SDL_internal.h"
|
||||
#include "../../include/SDL3/SDL_video_capture.h"
|
||||
|
||||
#ifndef SDL_video_capture_c_h_
|
||||
#define SDL_video_capture_c_h_
|
||||
|
||||
/* Initialize the video_capture subsystem */
|
||||
int SDL_VideoCaptureInit(void);
|
||||
|
||||
/* Shutdown the video_capture subsystem */
|
||||
void SDL_QuitVideoCapture(void);
|
||||
|
||||
#endif /* SDL_video_capture_c_h_ */
|
||||
965
src/video/SDL_video_capture_v4l2.c
Normal file
965
src/video/SDL_video_capture_v4l2.c
Normal file
@@ -0,0 +1,965 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_VIDEO_CAPTURE
|
||||
|
||||
#include "SDL3/SDL.h"
|
||||
#include "SDL3/SDL_video_capture.h"
|
||||
#include "SDL_sysvideocapture.h"
|
||||
#include "SDL_video_capture_c.h"
|
||||
#include "SDL_pixels_c.h"
|
||||
#include "../thread/SDL_systhread.h"
|
||||
|
||||
#define DEBUG_VIDEO_CAPTURE_CAPTURE 1
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
|
||||
enum io_method {
|
||||
IO_METHOD_READ,
|
||||
IO_METHOD_MMAP,
|
||||
IO_METHOD_USERPTR
|
||||
};
|
||||
|
||||
struct buffer {
|
||||
void *start;
|
||||
size_t length;
|
||||
int available; /* Is available in userspace */
|
||||
};
|
||||
|
||||
struct SDL_PrivateVideoCaptureData
|
||||
{
|
||||
int fd;
|
||||
enum io_method io;
|
||||
int nb_buffers;
|
||||
struct buffer *buffers;
|
||||
int first_start;
|
||||
int driver_pitch;
|
||||
};
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h> /* low-level i/o */
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
static int
|
||||
xioctl(int fh, int request, void *arg)
|
||||
{
|
||||
int r;
|
||||
|
||||
do {
|
||||
r = ioctl(fh, request, arg);
|
||||
} while (r == -1 && errno == EINTR);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/* -1:error 1:frame 0:no frame*/
|
||||
static int
|
||||
acquire_frame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
|
||||
{
|
||||
struct v4l2_buffer buf;
|
||||
int i;
|
||||
|
||||
int fd = _this->hidden->fd;
|
||||
enum io_method io = _this->hidden->io;
|
||||
size_t size = _this->hidden->buffers[0].length;
|
||||
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
if (read(fd, _this->hidden->buffers[0].start, size) == -1) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
return 0;
|
||||
|
||||
case EIO:
|
||||
/* Could ignore EIO, see spec. */
|
||||
|
||||
/* fall through */
|
||||
|
||||
default:
|
||||
return SDL_SetError("read");
|
||||
}
|
||||
}
|
||||
|
||||
frame->num_planes = 1;
|
||||
frame->data[0] = _this->hidden->buffers[0].start;
|
||||
frame->pitch[0] = _this->hidden->driver_pitch;
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
return 0;
|
||||
|
||||
case EIO:
|
||||
/* Could ignore EIO, see spec. */
|
||||
|
||||
/* fall through */
|
||||
|
||||
default:
|
||||
return SDL_SetError("VIDIOC_DQBUF: %d", errno);
|
||||
}
|
||||
}
|
||||
|
||||
if ((int)buf.index < 0 || (int)buf.index >= _this->hidden->nb_buffers) {
|
||||
return SDL_SetError("invalid buffer index");
|
||||
}
|
||||
|
||||
frame->num_planes = 1;
|
||||
frame->data[0] = _this->hidden->buffers[buf.index].start;
|
||||
frame->pitch[0] = _this->hidden->driver_pitch;
|
||||
_this->hidden->buffers[buf.index].available = 1;
|
||||
|
||||
#if DEBUG_VIDEO_CAPTURE_CAPTURE
|
||||
SDL_Log("debug mmap: image %d/%d num_planes:%d data[0]=%p", buf.index, _this->hidden->nb_buffers, frame->num_planes, (void*)frame->data[0]);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_USERPTR;
|
||||
|
||||
if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
return 0;
|
||||
|
||||
case EIO:
|
||||
/* Could ignore EIO, see spec. */
|
||||
|
||||
/* fall through */
|
||||
|
||||
default:
|
||||
return SDL_SetError("VIDIOC_DQBUF");
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
|
||||
if (buf.m.userptr == (unsigned long)_this->hidden->buffers[i].start && buf.length == size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= _this->hidden->nb_buffers) {
|
||||
return SDL_SetError("invalid buffer index");
|
||||
}
|
||||
|
||||
frame->num_planes = 1;
|
||||
frame->data[0] = (void*)buf.m.userptr;
|
||||
frame->pitch[0] = _this->hidden->driver_pitch;
|
||||
_this->hidden->buffers[i].available = 1;
|
||||
#if DEBUG_VIDEO_CAPTURE_CAPTURE
|
||||
SDL_Log("debug userptr: image %d/%d num_planes:%d data[0]=%p", buf.index, _this->hidden->nb_buffers, frame->num_planes, (void*)frame->data[0]);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
|
||||
{
|
||||
struct v4l2_buffer buf;
|
||||
int i;
|
||||
int fd = _this->hidden->fd;
|
||||
enum io_method io = _this->hidden->io;
|
||||
|
||||
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
|
||||
if (frame->num_planes && frame->data[0] == _this->hidden->buffers[i].start) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= _this->hidden->nb_buffers) {
|
||||
return SDL_SetError("invalid buffer index");
|
||||
}
|
||||
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = i;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
|
||||
return SDL_SetError("VIDIOC_QBUF");
|
||||
}
|
||||
_this->hidden->buffers[i].available = 0;
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_USERPTR;
|
||||
buf.index = i;
|
||||
buf.m.userptr = (unsigned long)frame->data[0];
|
||||
buf.length = (int) _this->hidden->buffers[i].length;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
|
||||
return SDL_SetError("VIDIOC_QBUF");
|
||||
}
|
||||
_this->hidden->buffers[i].available = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
|
||||
{
|
||||
fd_set fds;
|
||||
struct timeval tv;
|
||||
int ret;
|
||||
|
||||
int fd = _this->hidden->fd;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(fd, &fds);
|
||||
|
||||
/* Timeout. */
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 300 * 1000;
|
||||
|
||||
ret = select(fd + 1, &fds, NULL, NULL, &tv);
|
||||
|
||||
if (ret == -1) {
|
||||
if (errno == EINTR) {
|
||||
#if DEBUG_VIDEO_CAPTURE_CAPTURE
|
||||
SDL_Log("continue ..");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
return SDL_SetError("select");
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
/* Timeout. Not an error */
|
||||
SDL_SetError("timeout select");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = acquire_frame(_this, frame);
|
||||
if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ret == 1){
|
||||
frame->timestampNS = SDL_GetTicksNS();
|
||||
} else if (ret == 0) {
|
||||
#if DEBUG_VIDEO_CAPTURE_CAPTURE
|
||||
SDL_Log("No frame continue: %s", SDL_GetError());
|
||||
#endif
|
||||
}
|
||||
|
||||
/* EAGAIN - continue select loop. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
StopCapture(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
enum v4l2_buf_type type;
|
||||
int fd = _this->hidden->fd;
|
||||
enum io_method io = _this->hidden->io;
|
||||
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
case IO_METHOD_USERPTR:
|
||||
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (xioctl(fd, VIDIOC_STREAMOFF, &type) == -1) {
|
||||
return SDL_SetError("VIDIOC_STREAMOFF");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
enqueue_buffers(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
int i;
|
||||
int fd = _this->hidden->fd;
|
||||
enum io_method io = _this->hidden->io;
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
|
||||
if (_this->hidden->buffers[i].available == 0) {
|
||||
struct v4l2_buffer buf;
|
||||
|
||||
SDL_zero(buf);
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = i;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
|
||||
return SDL_SetError("VIDIOC_QBUF");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
|
||||
if (_this->hidden->buffers[i].available == 0) {
|
||||
struct v4l2_buffer buf;
|
||||
|
||||
SDL_zero(buf);
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_USERPTR;
|
||||
buf.index = i;
|
||||
buf.m.userptr = (unsigned long)_this->hidden->buffers[i].start;
|
||||
buf.length = (int) _this->hidden->buffers[i].length;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
|
||||
return SDL_SetError("VIDIOC_QBUF");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pre_enqueue_buffers(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
struct v4l2_requestbuffers req;
|
||||
int fd = _this->hidden->fd;
|
||||
enum io_method io = _this->hidden->io;
|
||||
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
{
|
||||
SDL_zero(req);
|
||||
req.count = _this->hidden->nb_buffers;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
if (xioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
|
||||
if (errno == EINVAL) {
|
||||
return SDL_SetError("Does not support memory mapping");
|
||||
} else {
|
||||
return SDL_SetError("VIDIOC_REQBUFS");
|
||||
}
|
||||
}
|
||||
|
||||
if (req.count < 2) {
|
||||
return SDL_SetError("Insufficient buffer memory");
|
||||
}
|
||||
|
||||
_this->hidden->nb_buffers = req.count;
|
||||
}
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
{
|
||||
SDL_zero(req);
|
||||
req.count = _this->hidden->nb_buffers;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_USERPTR;
|
||||
|
||||
if (xioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
|
||||
if (errno == EINVAL) {
|
||||
return SDL_SetError("Does not support user pointer i/o");
|
||||
} else {
|
||||
return SDL_SetError("VIDIOC_REQBUFS");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
StartCapture(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
enum v4l2_buf_type type;
|
||||
|
||||
int fd = _this->hidden->fd;
|
||||
enum io_method io = _this->hidden->io;
|
||||
|
||||
|
||||
if (_this->hidden->first_start == 0) {
|
||||
_this->hidden->first_start = 1;
|
||||
} else {
|
||||
int old = _this->hidden->nb_buffers;
|
||||
// TODO mmap; doesn't work with stop->start
|
||||
#if 1
|
||||
/* Can change nb_buffers for mmap */
|
||||
if (pre_enqueue_buffers(_this) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (old != _this->hidden->nb_buffers) {
|
||||
SDL_SetError("different nb of buffers requested");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
_this->hidden->first_start = 1;
|
||||
}
|
||||
|
||||
if (enqueue_buffers(_this) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
case IO_METHOD_USERPTR:
|
||||
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (xioctl(fd, VIDIOC_STREAMON, &type) == -1) {
|
||||
return SDL_SetError("VIDIOC_STREAMON");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int alloc_buffer_read(SDL_VideoCaptureDevice *_this, size_t buffer_size)
|
||||
{
|
||||
_this->hidden->buffers[0].length = buffer_size;
|
||||
_this->hidden->buffers[0].start = SDL_calloc(1, buffer_size);
|
||||
|
||||
if (!_this->hidden->buffers[0].start) {
|
||||
return SDL_OutOfMemory();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
alloc_buffer_mmap(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
int fd = _this->hidden->fd;
|
||||
int i;
|
||||
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
|
||||
struct v4l2_buffer buf;
|
||||
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = i;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
|
||||
return SDL_SetError("VIDIOC_QUERYBUF");
|
||||
}
|
||||
|
||||
_this->hidden->buffers[i].length = buf.length;
|
||||
_this->hidden->buffers[i].start =
|
||||
mmap(NULL /* start anywhere */,
|
||||
buf.length,
|
||||
PROT_READ | PROT_WRITE /* required */,
|
||||
MAP_SHARED /* recommended */,
|
||||
fd, buf.m.offset);
|
||||
|
||||
if (MAP_FAILED == _this->hidden->buffers[i].start) {
|
||||
return SDL_SetError("mmap");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
alloc_buffer_userp(SDL_VideoCaptureDevice *_this, size_t buffer_size)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
|
||||
_this->hidden->buffers[i].length = buffer_size;
|
||||
_this->hidden->buffers[i].start = SDL_calloc(1, buffer_size);
|
||||
|
||||
if (!_this->hidden->buffers[i].start) {
|
||||
return SDL_OutOfMemory();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Uint32
|
||||
format_v4l2_2_sdl(Uint32 fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define CASE(x, y) case x: return y
|
||||
CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2);
|
||||
CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_UNKNOWN);
|
||||
#undef CASE
|
||||
default:
|
||||
SDL_Log("Unknown format V4L2_PIX_FORMAT '%d'", fmt);
|
||||
return SDL_PIXELFORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static Uint32
|
||||
format_sdl_2_v4l2(Uint32 fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define CASE(y, x) case x: return y
|
||||
CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2);
|
||||
CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_UNKNOWN);
|
||||
#undef CASE
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
GetNumFormats(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
int fd = _this->hidden->fd;
|
||||
int i = 0;
|
||||
struct v4l2_fmtdesc fmtdesc;
|
||||
|
||||
SDL_zero(fmtdesc);
|
||||
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
while (ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc) == 0) {
|
||||
fmtdesc.index++;
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
int
|
||||
GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format)
|
||||
{
|
||||
int fd = _this->hidden->fd;
|
||||
struct v4l2_fmtdesc fmtdesc;
|
||||
|
||||
SDL_zero(fmtdesc);
|
||||
fmtdesc.index = index;
|
||||
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc) == 0) {
|
||||
*format = format_v4l2_2_sdl(fmtdesc.pixelformat);
|
||||
|
||||
#if DEBUG_VIDEO_CAPTURE_CAPTURE
|
||||
if (fmtdesc.flags & V4L2_FMT_FLAG_EMULATED) {
|
||||
SDL_Log("%s format emulated", SDL_GetPixelFormatName(*format));
|
||||
}
|
||||
if (fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED) {
|
||||
SDL_Log("%s format compressed", SDL_GetPixelFormatName(*format));
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format)
|
||||
{
|
||||
int fd = _this->hidden->fd;
|
||||
int i = 0;
|
||||
struct v4l2_frmsizeenum frmsizeenum;
|
||||
|
||||
SDL_zero(frmsizeenum);
|
||||
frmsizeenum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
frmsizeenum.pixel_format = format_sdl_2_v4l2(format);
|
||||
while (ioctl(fd,VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == 0) {
|
||||
frmsizeenum.index++;
|
||||
if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
|
||||
i++;
|
||||
} else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
|
||||
i += (1 + (frmsizeenum.stepwise.max_width - frmsizeenum.stepwise.min_width) / frmsizeenum.stepwise.step_width)
|
||||
* (1 + (frmsizeenum.stepwise.max_height - frmsizeenum.stepwise.min_height) / frmsizeenum.stepwise.step_height);
|
||||
} else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) {
|
||||
SDL_SetError("V4L2_FRMSIZE_TYPE_CONTINUOUS not handled");
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
int
|
||||
GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height)
|
||||
{
|
||||
int fd = _this->hidden->fd;
|
||||
struct v4l2_frmsizeenum frmsizeenum;
|
||||
int i = 0;
|
||||
|
||||
SDL_zero(frmsizeenum);
|
||||
frmsizeenum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
frmsizeenum.pixel_format = format_sdl_2_v4l2(format);
|
||||
while (ioctl(fd,VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == 0) {
|
||||
frmsizeenum.index++;
|
||||
|
||||
if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
|
||||
if (i == index) {
|
||||
*width = frmsizeenum.discrete.width;
|
||||
*height = frmsizeenum.discrete.height;
|
||||
return 0;
|
||||
}
|
||||
i++;
|
||||
} else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
|
||||
unsigned int w;
|
||||
for (w = frmsizeenum.stepwise.min_width; w <= frmsizeenum.stepwise.max_width; w += frmsizeenum.stepwise.step_width) {
|
||||
unsigned int h;
|
||||
for (h = frmsizeenum.stepwise.min_height; h <= frmsizeenum.stepwise.max_height; h += frmsizeenum.stepwise.step_height) {
|
||||
if (i == index) {
|
||||
*width = h;
|
||||
*height = w;
|
||||
return 0;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
} else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) {
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
dbg_v4l2_pixelformat(const char *str, int f) {
|
||||
SDL_Log("%s V4L2_format=%d %c%c%c%c", str, f,
|
||||
(f >> 0) & 0xff,
|
||||
(f >> 8) & 0xff,
|
||||
(f >> 16) & 0xff,
|
||||
(f >> 24) & 0xff);
|
||||
}
|
||||
|
||||
int
|
||||
GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec)
|
||||
{
|
||||
struct v4l2_format fmt;
|
||||
int fd = _this->hidden->fd;
|
||||
unsigned int min;
|
||||
|
||||
SDL_zero(fmt);
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
/* Preserve original settings as set by v4l2-ctl for example */
|
||||
if (xioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
|
||||
return SDL_SetError("Error VIDIOC_G_FMT");
|
||||
}
|
||||
|
||||
/* Buggy driver paranoia. */
|
||||
min = fmt.fmt.pix.width * 2;
|
||||
if (fmt.fmt.pix.bytesperline < min) {
|
||||
fmt.fmt.pix.bytesperline = min;
|
||||
}
|
||||
min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
|
||||
if (fmt.fmt.pix.sizeimage < min) {
|
||||
fmt.fmt.pix.sizeimage = min;
|
||||
}
|
||||
|
||||
//spec->width = fmt.fmt.pix.width;
|
||||
//spec->height = fmt.fmt.pix.height;
|
||||
_this->hidden->driver_pitch = fmt.fmt.pix.bytesperline;
|
||||
//spec->format = format_v4l2_2_sdl(fmt.fmt.pix.pixelformat);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
InitDevice(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
struct v4l2_cropcap cropcap;
|
||||
struct v4l2_crop crop;
|
||||
|
||||
int fd = _this->hidden->fd;
|
||||
enum io_method io = _this->hidden->io;
|
||||
int ret = -1;
|
||||
|
||||
/* Select video input, video standard and tune here. */
|
||||
SDL_zero(cropcap);
|
||||
|
||||
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
if (xioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0) {
|
||||
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
crop.c = cropcap.defrect; /* reset to default */
|
||||
|
||||
if (xioctl(fd, VIDIOC_S_CROP, &crop) == -1) {
|
||||
switch (errno) {
|
||||
case EINVAL:
|
||||
/* Cropping not supported. */
|
||||
break;
|
||||
default:
|
||||
/* Errors ignored. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Errors ignored. */
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
struct v4l2_format fmt;
|
||||
SDL_zero(fmt);
|
||||
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
fmt.fmt.pix.width = _this->spec.width;
|
||||
fmt.fmt.pix.height = _this->spec.height;
|
||||
|
||||
|
||||
fmt.fmt.pix.pixelformat = format_sdl_2_v4l2(_this->spec.format);
|
||||
// fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
|
||||
fmt.fmt.pix.field = V4L2_FIELD_ANY;
|
||||
|
||||
#if DEBUG_VIDEO_CAPTURE_CAPTURE
|
||||
SDL_Log("set SDL format %s", SDL_GetPixelFormatName(_this->spec.format));
|
||||
dbg_v4l2_pixelformat("set format", fmt.fmt.pix.pixelformat);
|
||||
#endif
|
||||
|
||||
if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
|
||||
return SDL_SetError("Error VIDIOC_S_FMT");
|
||||
}
|
||||
}
|
||||
|
||||
GetDeviceSpec(_this, &_this->spec);
|
||||
|
||||
if (pre_enqueue_buffers(_this) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
{
|
||||
_this->hidden->buffers = SDL_calloc(_this->hidden->nb_buffers, sizeof(*_this->hidden->buffers));
|
||||
if (!_this->hidden->buffers) {
|
||||
return SDL_OutOfMemory();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
size_t size, pitch;
|
||||
SDL_CalculateSize(_this->spec.format, _this->spec.width, _this->spec.height, &size, &pitch, SDL_FALSE);
|
||||
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
ret = alloc_buffer_read(_this, size);
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
ret = alloc_buffer_mmap(_this);
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
ret = alloc_buffer_userp(_this, size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
CloseDevice(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
if (!_this) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_this->hidden) {
|
||||
if (_this->hidden->buffers) {
|
||||
int i;
|
||||
enum io_method io = _this->hidden->io;
|
||||
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
SDL_free(_this->hidden->buffers[0].start);
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
|
||||
if (munmap(_this->hidden->buffers[i].start, _this->hidden->buffers[i].length) == -1) {
|
||||
SDL_SetError("munmap");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
for (i = 0; i < _this->hidden->nb_buffers; ++i) {
|
||||
SDL_free(_this->hidden->buffers[i].start);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_free(_this->hidden->buffers);
|
||||
}
|
||||
|
||||
if (_this->hidden->fd != -1) {
|
||||
if (close(_this->hidden->fd)) {
|
||||
SDL_SetError("close video capture device");
|
||||
}
|
||||
}
|
||||
SDL_free(_this->hidden);
|
||||
|
||||
_this->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
OpenDevice(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
struct stat st;
|
||||
struct v4l2_capability cap;
|
||||
int fd;
|
||||
enum io_method io;
|
||||
|
||||
_this->hidden = (struct SDL_PrivateVideoCaptureData *) SDL_calloc(1, sizeof (struct SDL_PrivateVideoCaptureData));
|
||||
if (_this->hidden == NULL) {
|
||||
SDL_OutOfMemory();
|
||||
return -1;
|
||||
}
|
||||
|
||||
_this->hidden->fd = -1;
|
||||
|
||||
if (stat(_this->dev_name, &st) == -1) {
|
||||
SDL_SetError("Cannot identify '%s': %d, %s", _this->dev_name, errno, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!S_ISCHR(st.st_mode)) {
|
||||
SDL_SetError("%s is no device", _this->dev_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = open(_this->dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);
|
||||
if (fd == -1) {
|
||||
SDL_SetError("Cannot open '%s': %d, %s", _this->dev_name, errno, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
_this->hidden->fd = fd;
|
||||
_this->hidden->io = IO_METHOD_MMAP;
|
||||
// _this->hidden->io = IO_METHOD_USERPTR;
|
||||
// _this->hidden->io = IO_METHOD_READ;
|
||||
//
|
||||
if (_this->hidden->io == IO_METHOD_READ) {
|
||||
_this->hidden->nb_buffers = 1;
|
||||
} else {
|
||||
_this->hidden->nb_buffers = 8; /* Number of image as internal buffer, */
|
||||
}
|
||||
io = _this->hidden->io;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
|
||||
if (errno == EINVAL) {
|
||||
return SDL_SetError("%s is no V4L2 device", _this->dev_name);
|
||||
} else {
|
||||
return SDL_SetError("Error VIDIOC_QUERYCAP errno=%d device%s is no V4L2 device", errno, _this->dev_name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
|
||||
return SDL_SetError("%s is no video capture device", _this->dev_name);
|
||||
}
|
||||
|
||||
#if 0
|
||||
if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
|
||||
SDL_Log("%s is video capture device - single plane", _this->dev_name);
|
||||
}
|
||||
if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE)) {
|
||||
SDL_Log("%s is video capture device - multiple planes", _this->dev_name);
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
if (!(cap.capabilities & V4L2_CAP_READWRITE)) {
|
||||
return SDL_SetError("%s does not support read i/o", _this->dev_name);
|
||||
}
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
case IO_METHOD_USERPTR:
|
||||
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
|
||||
return SDL_SetError("%s does not support streaming i/o", _this->dev_name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int
|
||||
GetDeviceName(int index, char *buf, int size) {
|
||||
SDL_snprintf(buf, size, "/dev/video%d", index);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
GetNumDevices(void) {
|
||||
int num;
|
||||
for (num = 0; num < 128; num++) {
|
||||
static char buf[256];
|
||||
buf[0] = 0;
|
||||
buf[255] = 0;
|
||||
GetDeviceName(num, buf, sizeof (buf));
|
||||
SDL_RWops *src = SDL_RWFromFile(buf, "rb");
|
||||
if (src == NULL) {
|
||||
// When file does not exist, an error is set. Clear it.
|
||||
SDL_ClearError();
|
||||
return num;
|
||||
}
|
||||
SDL_RWclose(src);
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* SDL_VIDEO_CAPTURE */
|
||||
678
src/video/android/SDL_android_video_capture.c
Normal file
678
src/video/android/SDL_android_video_capture.c
Normal file
@@ -0,0 +1,678 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL3/SDL.h"
|
||||
#include "SDL3/SDL_video_capture.h"
|
||||
#include "../SDL_sysvideocapture.h"
|
||||
#include "../SDL_video_capture_c.h"
|
||||
#include "../SDL_pixels_c.h"
|
||||
#include "../../thread/SDL_systhread.h"
|
||||
|
||||
#define DEBUG_VIDEO_CAPTURE_CAPTURE 1
|
||||
|
||||
#if defined(__ANDROID__) && __ANDROID_API__ >= 24
|
||||
|
||||
/*
|
||||
* APP_PLATFORM=android-24
|
||||
* minSdkVersion=24
|
||||
*
|
||||
* link with: -lcamera2ndk -lmediandk
|
||||
*
|
||||
* AndroidManifest.xml:
|
||||
* <uses-permission android:name="android.permission.CAMERA"></uses-permission>
|
||||
* <uses-feature android:name="android.hardware.camera" />
|
||||
*
|
||||
*
|
||||
* Add: #define SDL_VIDEO_CAPTURE 1
|
||||
* in: include/build_config/SDL_build_config_android.h
|
||||
*
|
||||
*
|
||||
* Very likely SDL must be build with YUV support (done by default)
|
||||
*
|
||||
* https://developer.android.com/reference/android/hardware/camera2/CameraManager
|
||||
* "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler),
|
||||
* before configuring sessions on any of the camera devices. * "
|
||||
*/
|
||||
|
||||
#include <camera/NdkCameraDevice.h>
|
||||
#include <camera/NdkCameraManager.h>
|
||||
#include <media/NdkImage.h>
|
||||
#include <media/NdkImageReader.h>
|
||||
|
||||
#include "../../core/android/SDL_android.h"
|
||||
|
||||
|
||||
static ACameraManager *cameraMgr = NULL;
|
||||
static ACameraIdList *cameraIdList = NULL;
|
||||
|
||||
static void
|
||||
create_cameraMgr(void)
|
||||
{
|
||||
if (cameraMgr == NULL) {
|
||||
|
||||
if (!Android_JNI_RequestPermission("android.permission.CAMERA")) {
|
||||
SDL_SetError("This app doesn't have CAMERA permission");
|
||||
return;
|
||||
}
|
||||
|
||||
cameraMgr = ACameraManager_create();
|
||||
if (cameraMgr == NULL) {
|
||||
SDL_Log("Error creating ACameraManager");
|
||||
} else {
|
||||
SDL_Log("Create ACameraManager");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
delete_cameraMgr(void)
|
||||
{
|
||||
if (cameraIdList) {
|
||||
ACameraManager_deleteCameraIdList(cameraIdList);
|
||||
cameraIdList = NULL;
|
||||
}
|
||||
|
||||
if (cameraMgr) {
|
||||
ACameraManager_delete(cameraMgr);
|
||||
cameraMgr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
struct SDL_PrivateVideoCaptureData
|
||||
{
|
||||
ACameraDevice *device;
|
||||
ACameraCaptureSession *session;
|
||||
ACameraDevice_StateCallbacks dev_callbacks;
|
||||
ACameraCaptureSession_stateCallbacks capture_callbacks;
|
||||
ACaptureSessionOutputContainer *sessionOutputContainer;
|
||||
AImageReader *reader;
|
||||
int num_formats;
|
||||
int count_formats[6]; // see format_2_id
|
||||
};
|
||||
|
||||
|
||||
/**/
|
||||
#define FORMAT_SDL SDL_PIXELFORMAT_NV12
|
||||
|
||||
static int
|
||||
format_2_id(int fmt) {
|
||||
switch (fmt) {
|
||||
#define CASE(x, y) case x: return y
|
||||
CASE(FORMAT_SDL, 0);
|
||||
CASE(SDL_PIXELFORMAT_RGB565, 1);
|
||||
CASE(SDL_PIXELFORMAT_RGB888, 2);
|
||||
CASE(SDL_PIXELFORMAT_RGBA8888, 3);
|
||||
CASE(SDL_PIXELFORMAT_RGBX8888, 4);
|
||||
CASE(SDL_PIXELFORMAT_UNKNOWN, 5);
|
||||
#undef CASE
|
||||
default:
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
id_2_format(int fmt) {
|
||||
switch (fmt) {
|
||||
#define CASE(x, y) case y: return x
|
||||
CASE(FORMAT_SDL, 0);
|
||||
CASE(SDL_PIXELFORMAT_RGB565, 1);
|
||||
CASE(SDL_PIXELFORMAT_RGB888, 2);
|
||||
CASE(SDL_PIXELFORMAT_RGBA8888, 3);
|
||||
CASE(SDL_PIXELFORMAT_RGBX8888, 4);
|
||||
CASE(SDL_PIXELFORMAT_UNKNOWN, 5);
|
||||
#undef CASE
|
||||
default:
|
||||
return SDL_PIXELFORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static Uint32
|
||||
format_android_2_sdl(Uint32 fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define CASE(x, y) case x: return y
|
||||
CASE(AIMAGE_FORMAT_YUV_420_888, FORMAT_SDL);
|
||||
CASE(AIMAGE_FORMAT_RGB_565, SDL_PIXELFORMAT_RGB565);
|
||||
CASE(AIMAGE_FORMAT_RGB_888, SDL_PIXELFORMAT_RGB888);
|
||||
CASE(AIMAGE_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888);
|
||||
CASE(AIMAGE_FORMAT_RGBX_8888, SDL_PIXELFORMAT_RGBX8888);
|
||||
|
||||
CASE(AIMAGE_FORMAT_RGBA_FP16, SDL_PIXELFORMAT_UNKNOWN); // 64bits
|
||||
CASE(AIMAGE_FORMAT_RAW_PRIVATE, SDL_PIXELFORMAT_UNKNOWN);
|
||||
CASE(AIMAGE_FORMAT_JPEG, SDL_PIXELFORMAT_UNKNOWN);
|
||||
#undef CASE
|
||||
default:
|
||||
SDL_Log("Unknown format AIMAGE_FORMAT '%d'", fmt);
|
||||
return SDL_PIXELFORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static Uint32
|
||||
format_sdl_2_android(Uint32 fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define CASE(x, y) case y: return x
|
||||
CASE(AIMAGE_FORMAT_YUV_420_888, FORMAT_SDL);
|
||||
CASE(AIMAGE_FORMAT_RGB_565, SDL_PIXELFORMAT_RGB565);
|
||||
CASE(AIMAGE_FORMAT_RGB_888, SDL_PIXELFORMAT_RGB888);
|
||||
CASE(AIMAGE_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888);
|
||||
CASE(AIMAGE_FORMAT_RGBX_8888, SDL_PIXELFORMAT_RGBX8888);
|
||||
#undef CASE
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
onDisconnected(void *context, ACameraDevice *device)
|
||||
{
|
||||
// SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context;
|
||||
SDL_Log("CB onDisconnected");
|
||||
}
|
||||
|
||||
static void
|
||||
onError(void *context, ACameraDevice *device, int error)
|
||||
{
|
||||
// SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context;
|
||||
SDL_Log("CB onError");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
onClosed(void* context, ACameraCaptureSession *session)
|
||||
{
|
||||
// SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context;
|
||||
SDL_Log("CB onClosed");
|
||||
}
|
||||
|
||||
static void
|
||||
onReady(void* context, ACameraCaptureSession *session)
|
||||
{
|
||||
// SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context;
|
||||
SDL_Log("CB onReady");
|
||||
}
|
||||
|
||||
static void
|
||||
onActive(void* context, ACameraCaptureSession *session)
|
||||
{
|
||||
// SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context;
|
||||
SDL_Log("CB onActive");
|
||||
}
|
||||
|
||||
int
|
||||
OpenDevice(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
camera_status_t res;
|
||||
|
||||
/* Cannot open a second camera, while the first one is opened.
|
||||
* If you want to play several camera, they must all be opened first, then played.
|
||||
*
|
||||
* https://developer.android.com/reference/android/hardware/camera2/CameraManager
|
||||
* "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler),
|
||||
* before configuring sessions on any of the camera devices. * "
|
||||
*
|
||||
*/
|
||||
if (check_device_playing()) {
|
||||
return SDL_SetError("A camera is already playing");
|
||||
}
|
||||
|
||||
_this->hidden = (struct SDL_PrivateVideoCaptureData *) SDL_calloc(1, sizeof (struct SDL_PrivateVideoCaptureData));
|
||||
if (_this->hidden == NULL) {
|
||||
return SDL_OutOfMemory();
|
||||
}
|
||||
|
||||
create_cameraMgr();
|
||||
|
||||
_this->hidden->dev_callbacks.context = (void *) _this;
|
||||
_this->hidden->dev_callbacks.onDisconnected = onDisconnected;
|
||||
_this->hidden->dev_callbacks.onError = onError;
|
||||
|
||||
res = ACameraManager_openCamera(cameraMgr, _this->dev_name, &_this->hidden->dev_callbacks, &_this->hidden->device);
|
||||
if (res != ACAMERA_OK) {
|
||||
return SDL_SetError("Failed to open camera");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
CloseDevice(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
if (_this && _this->hidden) {
|
||||
if (_this->hidden->session) {
|
||||
ACameraCaptureSession_close(_this->hidden->session);
|
||||
}
|
||||
|
||||
if (_this->hidden->sessionOutputContainer) {
|
||||
ACaptureSessionOutputContainer_free(_this->hidden->sessionOutputContainer);
|
||||
}
|
||||
|
||||
if (_this->hidden->reader) {
|
||||
AImageReader_delete(_this->hidden->reader);
|
||||
}
|
||||
|
||||
if (_this->hidden->device) {
|
||||
ACameraDevice_close(_this->hidden->device);
|
||||
}
|
||||
|
||||
SDL_free(_this->hidden);
|
||||
|
||||
_this->hidden = NULL;
|
||||
}
|
||||
|
||||
if (check_all_device_closed()) {
|
||||
delete_cameraMgr();
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
InitDevice(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
size_t size, pitch;
|
||||
SDL_CalculateSize(_this->spec.format, _this->spec.width, _this->spec.height, &size, &pitch, SDL_FALSE);
|
||||
SDL_Log("Buffer size: %d x %d", _this->spec.width, _this->spec.height);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec)
|
||||
{
|
||||
if (spec) {
|
||||
*spec = _this->spec;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
StartCapture(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
camera_status_t res;
|
||||
media_status_t res2;
|
||||
ANativeWindow *window = NULL;
|
||||
ACaptureSessionOutput *sessionOutput;
|
||||
ACameraOutputTarget *outputTarget;
|
||||
ACaptureRequest *request;
|
||||
|
||||
res2 = AImageReader_new(_this->spec.width, _this->spec.height, format_sdl_2_android(_this->spec.format), 10 /* nb buffers */, &_this->hidden->reader);
|
||||
if (res2 != AMEDIA_OK) {
|
||||
SDL_SetError("Error AImageReader_new");
|
||||
goto error;
|
||||
}
|
||||
res2 = AImageReader_getWindow(_this->hidden->reader, &window);
|
||||
if (res2 != AMEDIA_OK) {
|
||||
SDL_SetError("Error AImageReader_new");
|
||||
goto error;
|
||||
|
||||
}
|
||||
|
||||
|
||||
res = ACaptureSessionOutput_create(window, &sessionOutput);
|
||||
if (res != ACAMERA_OK) {
|
||||
SDL_SetError("Error ACaptureSessionOutput_create");
|
||||
goto error;
|
||||
}
|
||||
res = ACaptureSessionOutputContainer_create(&_this->hidden->sessionOutputContainer);
|
||||
if (res != ACAMERA_OK) {
|
||||
SDL_SetError("Error ACaptureSessionOutputContainer_create");
|
||||
goto error;
|
||||
}
|
||||
res = ACaptureSessionOutputContainer_add(_this->hidden->sessionOutputContainer, sessionOutput);
|
||||
if (res != ACAMERA_OK) {
|
||||
SDL_SetError("Error ACaptureSessionOutputContainer_add");
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
res = ACameraOutputTarget_create(window, &outputTarget);
|
||||
if (res != ACAMERA_OK) {
|
||||
SDL_SetError("Error ACameraOutputTarget_create");
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
res = ACameraDevice_createCaptureRequest(_this->hidden->device, TEMPLATE_RECORD, &request);
|
||||
if (res != ACAMERA_OK) {
|
||||
SDL_SetError("Error ACameraDevice_createCaptureRequest");
|
||||
goto error;
|
||||
}
|
||||
|
||||
res = ACaptureRequest_addTarget(request, outputTarget);
|
||||
if (res != ACAMERA_OK) {
|
||||
SDL_SetError("Error ACaptureRequest_addTarget");
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
_this->hidden->capture_callbacks.context = (void *) _this;
|
||||
_this->hidden->capture_callbacks.onClosed = onClosed;
|
||||
_this->hidden->capture_callbacks.onReady = onReady;
|
||||
_this->hidden->capture_callbacks.onActive = onActive;
|
||||
|
||||
res = ACameraDevice_createCaptureSession(_this->hidden->device,
|
||||
_this->hidden->sessionOutputContainer,
|
||||
&_this->hidden->capture_callbacks,
|
||||
&_this->hidden->session);
|
||||
if (res != ACAMERA_OK) {
|
||||
SDL_SetError("Error ACameraDevice_createCaptureSession");
|
||||
goto error;
|
||||
}
|
||||
|
||||
res = ACameraCaptureSession_setRepeatingRequest(_this->hidden->session, NULL, 1, &request, NULL);
|
||||
if (res != ACAMERA_OK) {
|
||||
SDL_SetError("Error ACameraDevice_createCaptureSession");
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
StopCapture(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
ACameraCaptureSession_close(_this->hidden->session);
|
||||
_this->hidden->session = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
|
||||
{
|
||||
media_status_t res;
|
||||
AImage *image;
|
||||
res = AImageReader_acquireNextImage(_this->hidden->reader, &image);
|
||||
/* We could also use this one:
|
||||
res = AImageReader_acquireLatestImage(_this->hidden->reader, &image);
|
||||
*/
|
||||
if (res == AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE ) {
|
||||
|
||||
SDL_Delay(20); // TODO fix some delay
|
||||
#if DEBUG_VIDEO_CAPTURE_CAPTURE
|
||||
// SDL_Log("AImageReader_acquireNextImage: AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE");
|
||||
#endif
|
||||
return 0;
|
||||
} else if (res == AMEDIA_OK ) {
|
||||
int i = 0;
|
||||
int32_t numPlanes = 0;
|
||||
AImage_getNumberOfPlanes(image, &numPlanes);
|
||||
|
||||
frame->timestampNS = SDL_GetTicksNS();
|
||||
|
||||
for (i = 0; i < numPlanes && i < 3; i++) {
|
||||
int dataLength = 0;
|
||||
int rowStride = 0;
|
||||
uint8_t *data = NULL;
|
||||
frame->num_planes += 1;
|
||||
AImage_getPlaneRowStride(image, i, &rowStride);
|
||||
res = AImage_getPlaneData(image, i, &data, &dataLength);
|
||||
if (res == AMEDIA_OK) {
|
||||
frame->data[i] = data;
|
||||
frame->pitch[i] = rowStride;
|
||||
}
|
||||
}
|
||||
|
||||
if (frame->num_planes == 3) {
|
||||
/* plane 2 and 3 are interleaved NV12. SDL only takes two planes for this format */
|
||||
int pixelStride = 0;
|
||||
AImage_getPlanePixelStride(image, 1, &pixelStride);
|
||||
if (pixelStride == 2) {
|
||||
frame->num_planes -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
frame->internal = (void*)image;
|
||||
return 0;
|
||||
} else if (res == AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED) {
|
||||
SDL_SetError("AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED");
|
||||
} else {
|
||||
SDL_SetError("AImageReader_acquireNextImage: %d", res);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame)
|
||||
{
|
||||
if (frame->internal){
|
||||
AImage_delete((AImage *)frame->internal);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
GetNumFormats(SDL_VideoCaptureDevice *_this)
|
||||
{
|
||||
camera_status_t res;
|
||||
int i;
|
||||
int unknown = 0;
|
||||
ACameraMetadata *metadata;
|
||||
ACameraMetadata_const_entry entry;
|
||||
|
||||
if (_this->hidden->num_formats != 0) {
|
||||
return _this->hidden->num_formats;
|
||||
}
|
||||
|
||||
res = ACameraManager_getCameraCharacteristics(cameraMgr, _this->dev_name, &metadata);
|
||||
if (res != ACAMERA_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = ACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry);
|
||||
if (res != ACAMERA_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_Log("got entry ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS");
|
||||
|
||||
for (i = 0; i < entry.count; i += 4) {
|
||||
int32_t format = entry.data.i32[i + 0];
|
||||
int32_t type = entry.data.i32[i + 3];
|
||||
Uint32 fmt;
|
||||
|
||||
if (type == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fmt = format_android_2_sdl(format);
|
||||
_this->hidden->count_formats[format_2_id(fmt)] += 1;
|
||||
|
||||
#if DEBUG_VIDEO_CAPTURE_CAPTURE
|
||||
if (fmt != SDL_PIXELFORMAT_UNKNOWN) {
|
||||
int w = entry.data.i32[i + 1];
|
||||
int h = entry.data.i32[i + 2];
|
||||
SDL_Log("Got format android 0x%08x -> %s %d x %d", format, SDL_GetPixelFormatName(fmt), w, h);
|
||||
} else {
|
||||
unknown += 1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if DEBUG_VIDEO_CAPTURE_CAPTURE
|
||||
if (unknown) {
|
||||
SDL_Log("Got unknown android");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if ( _this->hidden->count_formats[0]) _this->hidden->num_formats += 1;
|
||||
if ( _this->hidden->count_formats[1]) _this->hidden->num_formats += 1;
|
||||
if ( _this->hidden->count_formats[2]) _this->hidden->num_formats += 1;
|
||||
if ( _this->hidden->count_formats[3]) _this->hidden->num_formats += 1;
|
||||
if ( _this->hidden->count_formats[4]) _this->hidden->num_formats += 1;
|
||||
if ( _this->hidden->count_formats[5]) _this->hidden->num_formats += 1;
|
||||
|
||||
return _this->hidden->num_formats;
|
||||
}
|
||||
|
||||
int
|
||||
GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format)
|
||||
{
|
||||
int i;
|
||||
int i2 = 0;
|
||||
|
||||
if (_this->hidden->num_formats == 0) {
|
||||
GetNumFormats(_this);
|
||||
}
|
||||
|
||||
if (index < 0 || index >= _this->hidden->num_formats) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < SDL_arraysize(_this->hidden->count_formats); i++) {
|
||||
if (_this->hidden->count_formats[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i2 == index) {
|
||||
*format = id_2_format(i);
|
||||
}
|
||||
|
||||
i2++;
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format)
|
||||
{
|
||||
int i, i2 = 0, index;
|
||||
if (_this->hidden->num_formats == 0) {
|
||||
GetNumFormats(_this);
|
||||
}
|
||||
|
||||
index = format_2_id(format);
|
||||
|
||||
for (i = 0; i < SDL_arraysize(_this->hidden->count_formats); i++) {
|
||||
if (_this->hidden->count_formats[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i2 == index) {
|
||||
/* number of resolution for this format */
|
||||
return _this->hidden->count_formats[i];
|
||||
}
|
||||
|
||||
i2++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height)
|
||||
{
|
||||
camera_status_t res;
|
||||
int i, i2 = 0;
|
||||
ACameraMetadata *metadata;
|
||||
ACameraMetadata_const_entry entry;
|
||||
|
||||
if (_this->hidden->num_formats == 0) {
|
||||
GetNumFormats(_this);
|
||||
}
|
||||
|
||||
res = ACameraManager_getCameraCharacteristics(cameraMgr, _this->dev_name, &metadata);
|
||||
if (res != ACAMERA_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = ACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry);
|
||||
if (res != ACAMERA_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < entry.count; i += 4) {
|
||||
int32_t f = entry.data.i32[i + 0];
|
||||
int w = entry.data.i32[i + 1];
|
||||
int h = entry.data.i32[i + 2];
|
||||
int32_t type = entry.data.i32[i + 3];
|
||||
Uint32 fmt;
|
||||
|
||||
if (type == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
fmt = format_android_2_sdl(f);
|
||||
if (fmt != format) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i2 == index) {
|
||||
*width = w;
|
||||
*height = h;
|
||||
return 0;
|
||||
}
|
||||
|
||||
i2++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetDeviceName(int index, char *buf, int size)
|
||||
{
|
||||
create_cameraMgr();
|
||||
|
||||
if (cameraIdList == NULL) {
|
||||
GetNumDevices();
|
||||
}
|
||||
|
||||
if (cameraIdList) {
|
||||
if (index >= 0 && index < cameraIdList->numCameras) {
|
||||
SDL_snprintf(buf, size, "%s", cameraIdList->cameraIds[index]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
GetNumDevices(void)
|
||||
{
|
||||
camera_status_t res;
|
||||
create_cameraMgr();
|
||||
|
||||
if (cameraIdList) {
|
||||
ACameraManager_deleteCameraIdList(cameraIdList);
|
||||
cameraIdList = NULL;
|
||||
}
|
||||
|
||||
res = ACameraManager_getCameraIdList(cameraMgr, &cameraIdList);
|
||||
|
||||
if (res == ACAMERA_OK) {
|
||||
if (cameraIdList) {
|
||||
return cameraIdList->numCameras;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
Reference in New Issue
Block a user