feat: Add zero-copy SharedArray
type to Frame Processor Plugins (#2383)
* feat: Create `TypedArray` class for Frame Processor Plugins * Type * feat: Pass `VisionCameraProxy` along (BREAKING) * feat: Finish implementation * Log a bit * feat: Successfully convert JSI <> JNI buffers * Wrap buffer * fix: Fix using wrong Runtime * feat: Add docs * add zero copy example * Format C++ * Create iOS base * feat: Finish iOS implementation * chore: Format * fix: Use `NSData` instead of `NSMutableData` * Format * fix: Fix build when Frame Processors are disabled * chore: Rename `TypedArray` to `SharedArray` * fix: Fix Swift typings for Array * Remove a few default inits * fix: Fix Android build * fix: Use `NSInteger` * Update SharedArray.mm * fix: Expose bytes directly on iOS (NSData was immutable)
This commit is contained in:
@@ -19,5 +19,6 @@
|
||||
#if VISION_CAMERA_ENABLE_FRAME_PROCESSORS
|
||||
#import "Frame.h"
|
||||
#import "FrameProcessor.h"
|
||||
#import "SharedArray.h"
|
||||
#import "VisionCameraProxy.h"
|
||||
#endif
|
||||
|
@@ -16,6 +16,8 @@
|
||||
|
||||
- (instancetype _Nonnull)initWithBuffer:(CMSampleBufferRef _Nonnull)buffer orientation:(UIImageOrientation)orientation;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@property(nonatomic, readonly) CMSampleBufferRef _Nonnull buffer;
|
||||
@property(nonatomic, readonly) UIImageOrientation orientation;
|
||||
|
||||
|
@@ -21,6 +21,8 @@
|
||||
|
||||
@interface FrameProcessor : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
#ifdef __cplusplus
|
||||
- (instancetype _Nonnull)initWithWorklet:(std::shared_ptr<RNWorklet::JsiWorklet>)worklet
|
||||
context:(std::shared_ptr<RNWorklet::JsiWorkletContext>)context;
|
||||
|
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#import "Frame.h"
|
||||
#import "VisionCameraProxy.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
@@ -28,9 +29,13 @@
|
||||
* This is called everytime this Frame Processor Plugin is loaded from the JS side (`initFrameProcessorPlugin(..)`).
|
||||
* Optionally override this method to implement custom initialization logic.
|
||||
* - Parameters:
|
||||
* - proxy: The VisionCameraProxy instance for using the Frame Processor Context, e.g. to initialize SharedArrays.
|
||||
* - options: An options dictionary passed from the JS side, or `nil` if none.
|
||||
*/
|
||||
- (instancetype _Nonnull)initWithOptions:(NSDictionary* _Nullable)options;
|
||||
- (instancetype _Nonnull)initWithProxy:(VisionCameraProxyHolder* _Nonnull)proxy
|
||||
withOptions:(NSDictionary* _Nullable)options NS_SWIFT_NAME(init(proxy:options:));
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
* The actual Frame Processor Plugin's implementation that runs when `plugin.call(..)` is called in the JS Frame Processor.
|
||||
@@ -52,10 +57,11 @@
|
||||
|
||||
#define VISION_EXPORT_FRAME_PROCESSOR(frame_processor_class, frame_processor_plugin_name) \
|
||||
+(void)load { \
|
||||
[FrameProcessorPluginRegistry addFrameProcessorPlugin:@ #frame_processor_plugin_name \
|
||||
withInitializer:^FrameProcessorPlugin*(NSDictionary* _Nullable options) { \
|
||||
return [[frame_processor_class alloc] initWithOptions:options]; \
|
||||
}]; \
|
||||
[FrameProcessorPluginRegistry \
|
||||
addFrameProcessorPlugin:@ #frame_processor_plugin_name \
|
||||
withInitializer:^FrameProcessorPlugin*(VisionCameraProxyHolder* _Nonnull proxy, NSDictionary* _Nullable options) { \
|
||||
return [[frame_processor_class alloc] initWithProxy:proxy withOptions:options]; \
|
||||
}]; \
|
||||
}
|
||||
|
||||
#define VISION_EXPORT_SWIFT_FRAME_PROCESSOR(frame_processor_class, frame_processor_plugin_name) \
|
||||
@@ -67,8 +73,9 @@
|
||||
\
|
||||
__attribute__((constructor)) static void VISION_CONCAT(initialize_, frame_processor_plugin_name)(void) { \
|
||||
[FrameProcessorPluginRegistry addFrameProcessorPlugin:@ #frame_processor_plugin_name \
|
||||
withInitializer:^FrameProcessorPlugin* _Nonnull(NSDictionary* _Nullable options) { \
|
||||
return [[frame_processor_class alloc] initWithOptions:options]; \
|
||||
withInitializer:^FrameProcessorPlugin* _Nonnull(VisionCameraProxyHolder* _Nonnull proxy, \
|
||||
NSDictionary* _Nullable options) { \
|
||||
return [[frame_processor_class alloc] initWithProxy:proxy withOptions:options]; \
|
||||
}]; \
|
||||
} \
|
||||
\
|
||||
|
@@ -11,7 +11,7 @@
|
||||
// Base implementation (empty)
|
||||
@implementation FrameProcessorPlugin
|
||||
|
||||
- (instancetype)initWithOptions:(NSDictionary* _Nullable)options {
|
||||
- (instancetype)initWithProxy:(VisionCameraProxyHolder* _Nonnull)proxy withOptions:(NSDictionary* _Nullable)options {
|
||||
self = [super init];
|
||||
return self;
|
||||
}
|
||||
|
@@ -10,14 +10,18 @@
|
||||
|
||||
#import "Frame.h"
|
||||
#import "FrameProcessorPlugin.h"
|
||||
#import "VisionCameraProxy.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface FrameProcessorPluginRegistry : NSObject
|
||||
|
||||
typedef FrameProcessorPlugin* _Nonnull (^PluginInitializerFunction)(NSDictionary* _Nullable options);
|
||||
typedef FrameProcessorPlugin* _Nonnull (^PluginInitializerFunction)(VisionCameraProxyHolder* _Nonnull proxy,
|
||||
NSDictionary* _Nullable options);
|
||||
|
||||
+ (void)addFrameProcessorPlugin:(NSString* _Nonnull)name withInitializer:(PluginInitializerFunction _Nonnull)pluginInitializer;
|
||||
|
||||
+ (FrameProcessorPlugin* _Nullable)getPlugin:(NSString* _Nonnull)name withOptions:(NSDictionary* _Nullable)options;
|
||||
+ (FrameProcessorPlugin* _Nullable)getPlugin:(NSString* _Nonnull)name
|
||||
withProxy:(VisionCameraProxyHolder* _Nonnull)proxy
|
||||
withOptions:(NSDictionary* _Nullable)options;
|
||||
|
||||
@end
|
||||
|
@@ -31,7 +31,9 @@
|
||||
NSLog(@"Successfully registered Frame Processor Plugin \"%@\"!", name);
|
||||
}
|
||||
|
||||
+ (FrameProcessorPlugin*)getPlugin:(NSString* _Nonnull)name withOptions:(NSDictionary* _Nullable)options {
|
||||
+ (FrameProcessorPlugin*)getPlugin:(NSString* _Nonnull)name
|
||||
withProxy:(VisionCameraProxyHolder* _Nonnull)proxy
|
||||
withOptions:(NSDictionary* _Nullable)options {
|
||||
NSLog(@"Looking up Frame Processor Plugin \"%@\"...", name);
|
||||
PluginInitializerFunction initializer = [[FrameProcessorPluginRegistry frameProcessorPlugins] objectForKey:name];
|
||||
if (initializer == nil) {
|
||||
@@ -40,7 +42,7 @@
|
||||
}
|
||||
|
||||
NSLog(@"Frame Processor Plugin \"%@\" found! Initializing...", name);
|
||||
return initializer(options);
|
||||
return initializer(proxy, options);
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#import "../Frame Processor/SharedArray.h"
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <ReactCommon/CallInvoker.h>
|
||||
#import <jsi/jsi.h>
|
||||
@@ -32,6 +33,9 @@ jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime& runtime, NSDictionary*
|
||||
// NSArray -> []
|
||||
jsi::Array convertNSArrayToJSIArray(jsi::Runtime& runtime, NSArray* value);
|
||||
|
||||
// SharedArray -> ArrayBuffer
|
||||
jsi::Object convertSharedArrayToJSIArrayBuffer(jsi::Runtime& runtime, SharedArray* sharedArray);
|
||||
|
||||
// id -> ???
|
||||
jsi::Value convertObjCObjectToJSIValue(jsi::Runtime& runtime, id value);
|
||||
|
||||
|
@@ -18,6 +18,8 @@
|
||||
#import "JSINSObjectConversion.h"
|
||||
#import "../Frame Processor/Frame.h"
|
||||
#import "../Frame Processor/FrameHostObject.h"
|
||||
#import "../Frame Processor/SharedArray.h"
|
||||
#import "JSITypedArray.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <React/RCTBridge.h>
|
||||
#import <ReactCommon/CallInvoker.h>
|
||||
@@ -58,6 +60,11 @@ jsi::Array convertNSArrayToJSIArray(jsi::Runtime& runtime, NSArray* value) {
|
||||
return result;
|
||||
}
|
||||
|
||||
jsi::Object convertSharedArrayToJSIArrayBuffer(jsi::Runtime& runtime, SharedArray* sharedArray) {
|
||||
std::shared_ptr<vision::TypedArrayBase> array = sharedArray.typedArray;
|
||||
return array->getBuffer(runtime);
|
||||
}
|
||||
|
||||
jsi::Value convertObjCObjectToJSIValue(jsi::Runtime& runtime, id value) {
|
||||
if (value == nil) {
|
||||
return jsi::Value::undefined();
|
||||
@@ -77,6 +84,8 @@ jsi::Value convertObjCObjectToJSIValue(jsi::Runtime& runtime, id value) {
|
||||
} else if ([value isKindOfClass:[Frame class]]) {
|
||||
auto frameHostObject = std::make_shared<FrameHostObject>((Frame*)value);
|
||||
return jsi::Object::createFromHostObject(runtime, frameHostObject);
|
||||
} else if ([value isKindOfClass:[SharedArray class]]) {
|
||||
return convertSharedArrayToJSIArrayBuffer(runtime, (SharedArray*)value);
|
||||
}
|
||||
return jsi::Value::undefined();
|
||||
}
|
||||
@@ -132,36 +141,46 @@ NSDictionary* convertJSIObjectToNSDictionary(jsi::Runtime& runtime, const jsi::O
|
||||
|
||||
id convertJSIValueToObjCObject(jsi::Runtime& runtime, const jsi::Value& value, std::shared_ptr<CallInvoker> jsInvoker) {
|
||||
if (value.isUndefined() || value.isNull()) {
|
||||
// undefined/null
|
||||
return nil;
|
||||
}
|
||||
if (value.isBool()) {
|
||||
} else if (value.isBool()) {
|
||||
// bool
|
||||
return @(value.getBool());
|
||||
}
|
||||
if (value.isNumber()) {
|
||||
} else if (value.isNumber()) {
|
||||
// number
|
||||
return @(value.getNumber());
|
||||
}
|
||||
if (value.isString()) {
|
||||
} else if (value.isString()) {
|
||||
// string
|
||||
return convertJSIStringToNSString(runtime, value.getString(runtime));
|
||||
}
|
||||
if (value.isObject()) {
|
||||
} else if (value.isObject()) {
|
||||
// object
|
||||
jsi::Object o = value.getObject(runtime);
|
||||
if (o.isArray(runtime)) {
|
||||
// array[]
|
||||
return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker);
|
||||
}
|
||||
if (o.isFunction(runtime)) {
|
||||
} else if (o.isFunction(runtime)) {
|
||||
// function () => {}
|
||||
return convertJSIFunctionToCallback(runtime, std::move(o.getFunction(runtime)), jsInvoker);
|
||||
}
|
||||
if (o.isHostObject(runtime)) {
|
||||
auto hostObject = o.asHostObject(runtime);
|
||||
auto frame = dynamic_cast<FrameHostObject*>(hostObject.get());
|
||||
if (frame != nullptr) {
|
||||
return frame->frame;
|
||||
} else if (o.isHostObject(runtime)) {
|
||||
if (o.isHostObject<FrameHostObject>(runtime)) {
|
||||
// Frame
|
||||
auto hostObject = o.getHostObject<FrameHostObject>(runtime);
|
||||
return hostObject->frame;
|
||||
} else {
|
||||
throw std::runtime_error("The given HostObject is not supported by a Frame Processor Plugin!");
|
||||
}
|
||||
} else if (o.isArrayBuffer(runtime)) {
|
||||
// ArrayBuffer
|
||||
auto typedArray = std::make_shared<vision::TypedArrayBase>(vision::getTypedArray(runtime, o));
|
||||
return [[SharedArray alloc] initWithRuntime:runtime typedArray:typedArray];
|
||||
} else {
|
||||
// object
|
||||
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker);
|
||||
}
|
||||
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker);
|
||||
}
|
||||
|
||||
throw std::runtime_error("Unsupported jsi::jsi::Value kind");
|
||||
auto stringRepresentation = value.toString(runtime).utf8(runtime);
|
||||
throw std::runtime_error("Failed to convert jsi::Value to JNI value - unsupported type! " + stringRepresentation);
|
||||
}
|
||||
|
||||
RCTResponseSenderBlock convertJSIFunctionToCallback(jsi::Runtime& runtime, const jsi::Function& value,
|
||||
|
48
package/ios/Frame Processor/SharedArray.h
Normal file
48
package/ios/Frame Processor/SharedArray.h
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// SharedArray.h
|
||||
// VisionCamera
|
||||
//
|
||||
// Created by Marc Rousavy on 12.01.24.
|
||||
// Copyright © 2024 mrousavy. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#import "VisionCameraProxy.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#import "JSITypedArray.h"
|
||||
#import <jsi/jsi.h>
|
||||
using namespace facebook;
|
||||
#endif
|
||||
|
||||
// Needs to be in sync with JSITypedArray.h as the index is used
|
||||
typedef NS_ENUM(NSInteger, SharedArrayType) {
|
||||
Int8Array,
|
||||
Int16Array,
|
||||
Int32Array,
|
||||
Uint8Array,
|
||||
Uint8ClampedArray,
|
||||
Uint16Array,
|
||||
Uint32Array,
|
||||
Float32Array,
|
||||
Float64Array,
|
||||
};
|
||||
|
||||
@interface SharedArray : NSObject
|
||||
|
||||
- (instancetype _Nonnull)init NS_UNAVAILABLE;
|
||||
|
||||
- (instancetype _Nonnull)initWithProxy:(VisionCameraProxyHolder* _Nonnull)proxy type:(SharedArrayType)type size:(NSInteger)size;
|
||||
|
||||
#ifdef __cplusplus
|
||||
- (instancetype _Nonnull)initWithRuntime:(jsi::Runtime&)runtime typedArray:(std::shared_ptr<vision::TypedArrayBase>)typedArray;
|
||||
|
||||
- (std::shared_ptr<vision::TypedArrayBase>)typedArray;
|
||||
#endif
|
||||
|
||||
@property(nonatomic, readonly, nonnull) uint8_t* data;
|
||||
@property(nonatomic, readonly) NSInteger count;
|
||||
|
||||
@end
|
58
package/ios/Frame Processor/SharedArray.mm
Normal file
58
package/ios/Frame Processor/SharedArray.mm
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// SharedArray.mm
|
||||
// VisionCamera
|
||||
//
|
||||
// Created by Marc Rousavy on 12.01.24.
|
||||
// Copyright © 2024 mrousavy. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SharedArray.h"
|
||||
#import "JSITypedArray.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <jsi/jsi.h>
|
||||
|
||||
using namespace facebook;
|
||||
|
||||
@implementation SharedArray {
|
||||
uint8_t* _data;
|
||||
NSInteger _count;
|
||||
std::shared_ptr<vision::TypedArrayBase> _array;
|
||||
}
|
||||
|
||||
vision::TypedArrayKind getTypedArrayKind(int unsafeEnumValue) {
|
||||
return static_cast<vision::TypedArrayKind>(unsafeEnumValue);
|
||||
}
|
||||
|
||||
- (instancetype)initWithProxy:(VisionCameraProxyHolder*)proxy type:(SharedArrayType)type size:(NSInteger)size {
|
||||
if (self = [super init]) {
|
||||
jsi::Runtime& runtime = proxy.proxy->getWorkletRuntime();
|
||||
vision::TypedArrayKind kind = getTypedArrayKind((int)type);
|
||||
_array = std::make_shared<vision::TypedArrayBase>(vision::TypedArrayBase(runtime, size, kind));
|
||||
_data = _array->getBuffer(runtime).data(runtime);
|
||||
_count = size;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithRuntime:(jsi::Runtime&)runtime typedArray:(std::shared_ptr<vision::TypedArrayBase>)typedArray {
|
||||
if (self = [super init]) {
|
||||
_array = typedArray;
|
||||
_data = _array->getBuffer(runtime).data(runtime);
|
||||
_count = _array->getBuffer(runtime).size(runtime);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (std::shared_ptr<vision::TypedArrayBase>)typedArray {
|
||||
return _array;
|
||||
}
|
||||
|
||||
- (uint8_t*)data {
|
||||
return _data;
|
||||
}
|
||||
|
||||
- (NSInteger)count {
|
||||
return _count;
|
||||
}
|
||||
|
||||
@end
|
@@ -27,6 +27,10 @@ public:
|
||||
std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime& runtime) override;
|
||||
jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override;
|
||||
|
||||
jsi::Runtime& getWorkletRuntime() {
|
||||
return _workletContext->getWorkletRuntime();
|
||||
}
|
||||
|
||||
private:
|
||||
void setFrameProcessor(jsi::Runtime& runtime, int viewTag, const jsi::Object& frameProcessor);
|
||||
void removeFrameProcessor(jsi::Runtime& runtime, int viewTag);
|
||||
@@ -38,6 +42,18 @@ private:
|
||||
};
|
||||
#endif
|
||||
|
||||
@interface VisionCameraInstaller : NSObject
|
||||
+ (BOOL)installToBridge:(RCTBridge* _Nonnull)bridge;
|
||||
@interface VisionCameraProxyHolder : NSObject
|
||||
|
||||
- (_Nonnull instancetype)initWithProxy:(void* _Nonnull)proxy;
|
||||
|
||||
#ifdef __cplusplus
|
||||
- (VisionCameraProxy* _Nonnull)proxy;
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@interface VisionCameraInstaller : NSObject
|
||||
|
||||
+ (BOOL)installToBridge:(RCTBridge* _Nonnull)bridge;
|
||||
|
||||
@end
|
||||
|
@@ -97,7 +97,8 @@ void VisionCameraProxy::removeFrameProcessor(jsi::Runtime& runtime, int viewTag)
|
||||
jsi::Value VisionCameraProxy::initFrameProcessorPlugin(jsi::Runtime& runtime, std::string name, const jsi::Object& options) {
|
||||
NSString* key = [NSString stringWithUTF8String:name.c_str()];
|
||||
NSDictionary* optionsObjc = JSINSObjectConversion::convertJSIObjectToNSDictionary(runtime, options, _callInvoker);
|
||||
FrameProcessorPlugin* plugin = [FrameProcessorPluginRegistry getPlugin:key withOptions:optionsObjc];
|
||||
VisionCameraProxyHolder* proxy = [[VisionCameraProxyHolder alloc] initWithProxy:this];
|
||||
FrameProcessorPlugin* plugin = [FrameProcessorPluginRegistry getPlugin:key withProxy:proxy withOptions:optionsObjc];
|
||||
if (plugin == nil) {
|
||||
return jsi::Value::undefined();
|
||||
}
|
||||
@@ -145,7 +146,25 @@ jsi::Value VisionCameraProxy::get(jsi::Runtime& runtime, const jsi::PropNameID&
|
||||
return jsi::Value::undefined();
|
||||
}
|
||||
|
||||
@implementation VisionCameraProxyHolder {
|
||||
VisionCameraProxy* _proxy;
|
||||
}
|
||||
|
||||
- (instancetype)initWithProxy:(void*)proxy {
|
||||
if (self = [super init]) {
|
||||
_proxy = (VisionCameraProxy*)proxy;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (VisionCameraProxy*)proxy {
|
||||
return _proxy;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation VisionCameraInstaller
|
||||
|
||||
+ (BOOL)installToBridge:(RCTBridge* _Nonnull)bridge {
|
||||
RCTCxxBridge* cxxBridge = (RCTCxxBridge*)[RCTBridge currentBridge];
|
||||
if (!cxxBridge.runtime) {
|
||||
@@ -160,4 +179,5 @@ jsi::Value VisionCameraProxy::get(jsi::Runtime& runtime, const jsi::PropNameID&
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -105,6 +105,8 @@
|
||||
B81D41EF263C86F900B041FD /* JSINSObjectConversion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSINSObjectConversion.h; sourceTree = "<group>"; };
|
||||
B8207AAC2B0E5DD70002990F /* AVCaptureSession+synchronizeBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureSession+synchronizeBuffer.swift"; sourceTree = "<group>"; };
|
||||
B8207AAE2B0E67460002990F /* AVCaptureVideoDataOutput+recommendedVideoSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureVideoDataOutput+recommendedVideoSettings.swift"; sourceTree = "<group>"; };
|
||||
B82186C72B514B5F00CE68CE /* SharedArray.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SharedArray.h; sourceTree = "<group>"; };
|
||||
B82186C82B514B6D00CE68CE /* SharedArray.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SharedArray.mm; sourceTree = "<group>"; };
|
||||
B83D5EE629377117000AFD2F /* PreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewView.swift; sourceTree = "<group>"; };
|
||||
B8446E4C2ABA147C00E56077 /* CameraDevicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraDevicesManager.swift; sourceTree = "<group>"; };
|
||||
B8446E4F2ABA14C900E56077 /* CameraDevicesManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraDevicesManager.m; sourceTree = "<group>"; };
|
||||
@@ -336,6 +338,8 @@
|
||||
B8994E6B263F03E100069589 /* JSINSObjectConversion.mm */,
|
||||
B85F7AE82A77BB680089C539 /* FrameProcessorPlugin.m */,
|
||||
B89A79692B3EF60F005E0357 /* UIImageOrientation+descriptor.h */,
|
||||
B82186C72B514B5F00CE68CE /* SharedArray.h */,
|
||||
B82186C82B514B6D00CE68CE /* SharedArray.mm */,
|
||||
);
|
||||
path = "Frame Processor";
|
||||
sourceTree = "<group>";
|
||||
|
Reference in New Issue
Block a user