Skip to content

Commit

Permalink
CameraAVFoundation: Move things around reducing boilerplate for upc…
Browse files Browse the repository at this point in the history
…oming changes (#8643)
  • Loading branch information
misl6 committed Mar 18, 2024
1 parent 8cc881c commit 78d5c7d
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 338 deletions.
116 changes: 69 additions & 47 deletions kivy/core/camera/camera_avfoundation.pyx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# distutils: language = c++
'''
AVFoundation Camera
===================
Expand All @@ -7,25 +8,38 @@ Camera implementation using AVFoundation framework for OSX / iOS

__all__ = ['CameraAVFoundation']

cdef extern from "camera_avfoundation_implem.h":
ctypedef void *camera_t
camera_t avf_camera_init(int index, int width, int height)
void avf_camera_deinit(camera_t camera)
bint avf_camera_update(camera_t camera)
void avf_camera_start(camera_t camera)
void avf_camera_stop(camera_t camera)
void avf_camera_get_image(camera_t camera, int *width, int *height, int *rowsize, char **data)
bint avf_camera_attempt_framerate_selection(camera_t camera, int fps)
bint avf_camera_attempt_capture_preset(camera_t camera, char* preset)
bint avf_camera_attempt_start_metadata_analysis(camera_t camera)
void avf_camera_get_metadata(camera_t camera, char **metatype, char **data)
bint avf_camera_have_new_metadata(camera_t camera);
bint avf_camera_set_video_orientation(camera_t camera, int orientation)
int avf_camera_get_device_orientation()
void avf_camera_change_input(camera_t camera, int _cameraNum)
void avf_camera_zoom_level(camera_t camera, float zoomLevel)
char *avf_camera_documents_directory()
void avf_camera_save_pixels(camera_t camera, unsigned char *pixels, int width, int height, char *path, float quality)

cdef extern from "camera_avfoundation_implem.mm":
cppclass CameraMetadata:
char *type
char *data

cppclass CameraFrame:
char * data
unsigned int datasize
unsigned int rowsize
int width
int height

cppclass Camera:
Camera(int cameraNum, int width, int height)
@staticmethod
int getDeviceOrientation()
@staticmethod
char* getDocumentsDirectory()
bint grabFrame(double timeOut)
int startCaptureDevice()
void stopCaptureDevice()
CameraFrame* retrieveFrame()
bint attemptFrameRateSelection(int desiredFrameRate)
bint attemptCapturePreset(char* preset)
void setVideoOrientation(int orientation)
void changeCameraInput(int _cameraNum)
void zoomLevel(float zoomLevel)
bint attemptStartMetadataAnalysis()
bint haveNewMetadata()
CameraMetadata* retrieveMetadata()
void savePixelsToFile(unsigned char *pixels, int width, int height, char *path, float quality)


from kivy.logger import Logger
Expand All @@ -37,7 +51,7 @@ from cython cimport view as cyview


cdef class _AVStorage:
cdef camera_t camera
cdef Camera* camera

def __cinit__(self):
self.camera = NULL
Expand All @@ -59,13 +73,14 @@ class CameraAVFoundation(CameraBase):

def init_camera(self):
cdef _AVStorage storage = <_AVStorage>self._storage
storage.camera = avf_camera_init(
self._index, self.resolution[0], self.resolution[1])
storage.camera = new Camera(
self._index, self.resolution[0], self.resolution[1]
)

def _release_camera(self):
cdef _AVStorage storage = <_AVStorage>self._storage
if storage.camera != NULL:
avf_camera_deinit(storage.camera)
del storage.camera
storage.camera = NULL

@property
Expand All @@ -77,18 +92,25 @@ class CameraAVFoundation(CameraBase):
cdef _AVStorage storage = <_AVStorage>self._storage
cdef int width, height, rowsize
cdef char *data
cdef char *metadata_type
cdef char *metadata_data
cdef CameraFrame* _current_frame
cdef CameraMetadata* _current_metadata
cdef cyview.array cyarr

if self.stopped:
return

if not avf_camera_update(storage.camera):
if not storage.camera.grabFrame(0):
return

_current_frame = storage.camera.retrieveFrame()

if _current_frame == NULL:
return

avf_camera_get_image(storage.camera,
&width, &height, &rowsize, &data)
width = _current_frame.width
height = _current_frame.height
rowsize = _current_frame.rowsize
data = _current_frame.data

if data == NULL:
return
Expand Down Expand Up @@ -116,9 +138,9 @@ class CameraAVFoundation(CameraBase):
self._texture.blit_buffer(cyarr, colorfmt=self._format)
self._copy_to_gpu()
if self._metadata_callback:
if avf_camera_have_new_metadata(storage.camera):
avf_camera_get_metadata(storage.camera, &metadata_type, &metadata_data)
self._metadata_callback(metadata_type, metadata_data)
if storage.camera.haveNewMetadata():
_current_metadata = storage.camera.retrieveMetadata()
self._metadata_callback(_current_metadata.type, _current_metadata.data)

def _copy_to_gpu(self):
self.dispatch('on_texture')
Expand All @@ -129,60 +151,60 @@ class CameraAVFoundation(CameraBase):
if self._update_ev is not None:
self._update_ev.cancel()
self._update_ev = Clock.schedule_interval(self._update, self._scheduled_rate)
avf_camera_start(storage.camera)
storage.camera.startCaptureDevice()

def stop(self):
cdef _AVStorage storage = <_AVStorage>self._storage
super(CameraAVFoundation, self).stop()
if self._update_ev is not None:
self._update_ev.cancel()
self._update_ev = None
avf_camera_stop(storage.camera)
storage.camera.stopCaptureDevice()

def set_framerate(self, framerate):
cdef _AVStorage storage = <_AVStorage>self._storage
if avf_camera_attempt_framerate_selection(storage.camera, framerate):
if storage.camera.attemptFrameRateSelection(framerate):
if self._update_ev is not None:
self._update_ev.cancel()
self._framerate = framerate
self._update_ev = Clock.schedule_interval(self._update, self._scheduled_rate)

def set_preset(self, preset):
cdef _AVStorage storage = <_AVStorage>self._storage
avf_camera_attempt_capture_preset(storage.camera, preset)
storage.camera.attemptCapturePreset(preset)

def set_video_orientation(self, orientation):
cdef _AVStorage storage = <_AVStorage>self._storage
avf_camera_set_video_orientation(storage.camera, orientation)
storage.camera.setVideoOrientation(orientation)

def start_metadata_analysis(self, callback=None):
cdef _AVStorage storage = <_AVStorage>self._storage
self._metadata_callback = callback
avf_camera_attempt_start_metadata_analysis(storage.camera)
storage.camera.attemptStartMetadataAnalysis()

def get_device_orientation(self):
# iOS only
return avf_camera_get_device_orientation()
# iOS only, on macOS this method does nothing
return Camera.getDeviceOrientation()

def change_camera_input(self, index):
# iOS only
# iOS only, on macOS this method does nothing
cdef _AVStorage storage = <_AVStorage>self._storage
avf_camera_change_input(storage.camera, index)
storage.camera.changeCameraInput(index)

def zoom_level(self, level):
# iOS only
# iOS only, on macOS this method does nothing
cdef _AVStorage storage = <_AVStorage>self._storage
avf_camera_zoom_level(storage.camera, level)
storage.camera.zoomLevel(level)

def get_app_documents_directory(self):
# iOS only
return str(avf_camera_documents_directory().decode('utf-8'))
# iOS only, on macOS returns an empty string
return str(Camera.getDocumentsDirectory().decode('utf-8'))

def save_texture(self, texture, filepath = '', quality = 0.9):
# iOS only
# With texture argument only: Save texture to iOS Photos App
# With texture and filepath arguments: Save texture as jpg; filepath root must be app documents directory,
# sub-directories in filepath must exist, filepath last element is filename.jpg
cdef _AVStorage storage = <_AVStorage>self._storage
avf_camera_save_pixels(storage.camera, bytearray(texture.pixels), int(texture.width), int(texture.height),
filepath.encode('utf-8'), quality)
storage.camera.savePixelsToFile(bytearray(texture.pixels), int(texture.width), int(texture.height),
filepath.encode('utf-8'), quality)
150 changes: 131 additions & 19 deletions kivy/core/camera/camera_avfoundation_implem.h
Original file line number Diff line number Diff line change
@@ -1,19 +1,131 @@
typedef void *camera_t;

camera_t avf_camera_init(int index, int width, int height);
void avf_camera_deinit(camera_t camera);
bool avf_camera_update(camera_t camera);
void avf_camera_start(camera_t camera);
void avf_camera_stop(camera_t camera);
void avf_camera_get_image(camera_t camera, int *width, int *height, int *rowsize, char **data);
bool avf_camera_attempt_framerate_selection(camera_t camera, int fps);
bool avf_camera_attempt_capture_preset(camera_t camera, char* preset);
bool avf_camera_attempt_start_metadata_analysis(camera_t camera);
void avf_camera_get_metadata(camera_t camera, char **metatype, char **data);
bool avf_camera_have_new_metadata(camera_t camera);
void avf_camera_set_video_orientation(camera_t camera, int orientation);
int avf_camera_get_device_orientation();
void avf_camera_change_input(camera_t camera, int _cameraNum);
void avf_camera_zoom_level(camera_t camera, float zoomLevel);
char *avf_camera_documents_directory();
void avf_camera_save_pixels(camera_t camera, unsigned char *pixels, int width, int height, char *path, float quality);
#import <AVFoundation/AVFoundation.h>
#import <Foundation/NSException.h>

#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
#import <UIKit/UIKit.h>
#endif


#ifdef WITH_CAMERA_CAPS
typedef enum {
CAM_FRAME_WIDTH,
CAM_FRAME_HEIGHT,
CAM_IOS_DEVICE_FOCUS,
CAM_IOS_DEVICE_EXPOSURE,
CAM_IOS_DEVICE_FLASH,
CAM_IOS_DEVICE_WHITEBALANCE,
CAM_IOS_DEVICE_TORCH
} caps_t;
#endif

class CameraFrame {
public:
CameraFrame(int width, int height);
~CameraFrame();
char *data;
unsigned int datasize;
unsigned int rowsize;
int width;
int height;
};

class CameraMetadata {
public:
CameraMetadata();
~CameraMetadata();
char *type;
char *data;
};

@interface KivyCaptureDelegate : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate>
{
int newFrame;
CVImageBufferRef mCurrentImageBuffer;
CameraFrame* image;
}

- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection;


- (int)updateImage;
- (CameraFrame*)getOutput;

@end

#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR

@interface PhotoAppDelegate : NSObject {}
- (void) thisImage:(UIImage *)image photoAppCallback:(NSError *)error usingContextInfo:(void*)ctxInfo;
@end

@implementation PhotoAppDelegate
- (void) thisImage:(UIImage *)image photoAppCallback:(NSError *)error usingContextInfo:(void*)ctxInfo {
free(ctxInfo);
}
@end

/* AVCaptureMetadataOutput is not available on MacOS */
@interface MetadataDelegate : NSObject <AVCaptureMetadataOutputObjectsDelegate>
{
CameraMetadata* metadata;
int newMetadata;
}
- (void)captureOutput:(AVCaptureOutput *)output
didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects
fromConnection:(AVCaptureConnection *)connection;
- (CameraMetadata*)getOutput;
- (bool)haveNewMetadata;
- (void)setNewMetadata:(bool)newMetadata;
@end
#endif

class Camera {

public:
Camera(int cameraNum, int width, int height);
~Camera();
static int getDeviceOrientation();
static char* getDocumentsDirectory();
bool grabFrame(double timeOut);
CameraFrame* retrieveFrame();
CameraMetadata* retrieveMetadata();
int startCaptureDevice();
void stopCaptureDevice();
bool attemptFrameRateSelection(int desiredFrameRate);
bool attemptCapturePreset(char* preset);
bool attemptStartMetadataAnalysis();
bool haveNewMetadata();
void setVideoOrientation(int orientation);
void changeCameraInput(int _cameraNum);
void zoomLevel(float zoomLevel);
void savePixelsToFile(unsigned char *pixels, int width, int height, char *path, float quality);
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
PhotoAppDelegate *photoapp;
#endif
#ifdef WITH_CAMERA_CAPS
double getProperty(int property_id);
bool setProperty(int property_id, double value);
void setWidthHeight();
#endif

private:
AVCaptureSession *mCaptureSession;
AVCaptureDeviceInput *mCaptureDeviceInput;
AVCaptureVideoDataOutput *mCaptureDecompressedVideoOutput;
AVCaptureDevice *mCaptureDevice;
KivyCaptureDelegate *capture;
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
/* AVCaptureMetadataOutput is not available on MacOS */
AVCaptureMetadataOutput *mMetadataOutput;
MetadataDelegate *metadata;
#endif

int cameraNum;
int width;
int height;
int settingWidth;
int settingHeight;
int started;
};

0 comments on commit 78d5c7d

Please sign in to comment.