feat: Make Frame thread-safe and improve error messages (#2327)
				
					
				
			* fix: Fix multi-Thread access on Java * fix: Thread-lock access on iOS as well * whoops add missing header impl * Update Podfile.lock * fix: Don't use `CFGetRetainCount` * fix: Lock access on iOS as well * C++ format * More detailed error * chore: Move getters into `Frame` * Format c++ * Use enum `orientation` again * format * fix: Synchronize `isValid` on Java * Also log pixelformat * feat: Use Java enums in C++ * Format C++
This commit is contained in:
		| @@ -77,7 +77,9 @@ jsi::Value FrameHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& pr | |||||||
|       } |       } | ||||||
|       auto width = this->frame->getWidth(); |       auto width = this->frame->getWidth(); | ||||||
|       auto height = this->frame->getHeight(); |       auto height = this->frame->getHeight(); | ||||||
|       auto str = std::to_string(width) + " x " + std::to_string(height) + " Frame"; |       auto format = this->frame->getPixelFormat(); | ||||||
|  |       auto formatString = format->getUnionValue(); | ||||||
|  |       auto str = std::to_string(width) + " x " + std::to_string(height) + " " + formatString->toString() + " Frame"; | ||||||
|       return jsi::String::createFromUtf8(runtime, str); |       return jsi::String::createFromUtf8(runtime, str); | ||||||
|     }; |     }; | ||||||
|     return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "toString"), 0, toString); |     return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "toString"), 0, toString); | ||||||
| @@ -141,11 +143,13 @@ jsi::Value FrameHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& pr | |||||||
|     return jsi::Value(this->frame->getIsMirrored()); |     return jsi::Value(this->frame->getIsMirrored()); | ||||||
|   } |   } | ||||||
|   if (name == "orientation") { |   if (name == "orientation") { | ||||||
|     auto string = this->frame->getOrientation(); |     auto orientation = this->frame->getOrientation(); | ||||||
|  |     auto string = orientation->getUnionValue(); | ||||||
|     return jsi::String::createFromUtf8(runtime, string->toStdString()); |     return jsi::String::createFromUtf8(runtime, string->toStdString()); | ||||||
|   } |   } | ||||||
|   if (name == "pixelFormat") { |   if (name == "pixelFormat") { | ||||||
|     auto string = this->frame->getPixelFormat(); |     auto pixelFormat = this->frame->getPixelFormat(); | ||||||
|  |     auto string = pixelFormat->getUnionValue(); | ||||||
|     return jsi::String::createFromUtf8(runtime, string->toStdString()); |     return jsi::String::createFromUtf8(runtime, string->toStdString()); | ||||||
|   } |   } | ||||||
|   if (name == "timestamp") { |   if (name == "timestamp") { | ||||||
|   | |||||||
| @@ -4,6 +4,8 @@ | |||||||
|  |  | ||||||
| #include "JFrame.h" | #include "JFrame.h" | ||||||
|  |  | ||||||
|  | #include "JOrientation.h" | ||||||
|  | #include "JPixelFormat.h" | ||||||
| #include <fbjni/fbjni.h> | #include <fbjni/fbjni.h> | ||||||
| #include <jni.h> | #include <jni.h> | ||||||
|  |  | ||||||
| @@ -39,13 +41,13 @@ jlong JFrame::getTimestamp() const { | |||||||
|   return getTimestampMethod(self()); |   return getTimestampMethod(self()); | ||||||
| } | } | ||||||
|  |  | ||||||
| local_ref<JString> JFrame::getOrientation() const { | local_ref<JOrientation> JFrame::getOrientation() const { | ||||||
|   static const auto getOrientationMethod = getClass()->getMethod<JString()>("getOrientation"); |   static const auto getOrientationMethod = getClass()->getMethod<JOrientation()>("getOrientation"); | ||||||
|   return getOrientationMethod(self()); |   return getOrientationMethod(self()); | ||||||
| } | } | ||||||
|  |  | ||||||
| local_ref<JString> JFrame::getPixelFormat() const { | local_ref<JPixelFormat> JFrame::getPixelFormat() const { | ||||||
|   static const auto getPixelFormatMethod = getClass()->getMethod<JString()>("getPixelFormat"); |   static const auto getPixelFormatMethod = getClass()->getMethod<JPixelFormat()>("getPixelFormat"); | ||||||
|   return getPixelFormatMethod(self()); |   return getPixelFormatMethod(self()); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -77,9 +79,4 @@ void JFrame::decrementRefCount() { | |||||||
|   decrementRefCountMethod(self()); |   decrementRefCountMethod(self()); | ||||||
| } | } | ||||||
|  |  | ||||||
| void JFrame::close() { |  | ||||||
|   static const auto closeMethod = getClass()->getMethod<void()>("close"); |  | ||||||
|   closeMethod(self()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| } // namespace vision | } // namespace vision | ||||||
|   | |||||||
| @@ -4,6 +4,8 @@ | |||||||
|  |  | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  | #include "JOrientation.h" | ||||||
|  | #include "JPixelFormat.h" | ||||||
| #include <fbjni/fbjni.h> | #include <fbjni/fbjni.h> | ||||||
| #include <jni.h> | #include <jni.h> | ||||||
|  |  | ||||||
| @@ -25,15 +27,14 @@ public: | |||||||
|   int getPlanesCount() const; |   int getPlanesCount() const; | ||||||
|   int getBytesPerRow() const; |   int getBytesPerRow() const; | ||||||
|   jlong getTimestamp() const; |   jlong getTimestamp() const; | ||||||
|   local_ref<JString> getOrientation() const; |   local_ref<JOrientation> getOrientation() const; | ||||||
|   local_ref<JString> getPixelFormat() const; |   local_ref<JPixelFormat> getPixelFormat() const; | ||||||
| #if __ANDROID_API__ >= 26 | #if __ANDROID_API__ >= 26 | ||||||
|   AHardwareBuffer* getHardwareBuffer() const; |   AHardwareBuffer* getHardwareBuffer() const; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   void incrementRefCount(); |   void incrementRefCount(); | ||||||
|   void decrementRefCount(); |   void decrementRefCount(); | ||||||
|   void close(); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } // namespace vision | } // namespace vision | ||||||
|   | |||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | // | ||||||
|  | // Created by Marc Rousavy on 29.12.23. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <fbjni/fbjni.h> | ||||||
|  | #include <jni.h> | ||||||
|  |  | ||||||
|  | namespace vision { | ||||||
|  |  | ||||||
|  | using namespace facebook; | ||||||
|  | using namespace jni; | ||||||
|  |  | ||||||
|  | struct JJSUnionValue : public JavaClass<JJSUnionValue> { | ||||||
|  |   static constexpr auto kJavaDescriptor = "Lcom/mrousavy/camera/types/JSUnionValue;"; | ||||||
|  |  | ||||||
|  |   local_ref<JString> getUnionValue() { | ||||||
|  |     const auto getUnionValueMethod = getClass()->getMethod<JString()>("getUnionValue"); | ||||||
|  |     return getUnionValueMethod(self()); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace vision | ||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | // | ||||||
|  | // Created by Marc Rousavy on 29.12.23. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "JJSUnionValue.h" | ||||||
|  | #include <fbjni/fbjni.h> | ||||||
|  | #include <jni.h> | ||||||
|  |  | ||||||
|  | namespace vision { | ||||||
|  |  | ||||||
|  | using namespace facebook; | ||||||
|  | using namespace jni; | ||||||
|  |  | ||||||
|  | struct JOrientation : public JavaClass<JOrientation, JJSUnionValue> { | ||||||
|  |   static constexpr auto kJavaDescriptor = "Lcom/mrousavy/camera/types/Orientation;"; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace vision | ||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | // | ||||||
|  | // Created by Marc Rousavy on 29.12.23. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "JJSUnionValue.h" | ||||||
|  | #include <fbjni/fbjni.h> | ||||||
|  | #include <jni.h> | ||||||
|  |  | ||||||
|  | namespace vision { | ||||||
|  |  | ||||||
|  | using namespace facebook; | ||||||
|  | using namespace jni; | ||||||
|  |  | ||||||
|  | struct JPixelFormat : public JavaClass<JPixelFormat, JJSUnionValue> { | ||||||
|  |   static constexpr auto kJavaDescriptor = "Lcom/mrousavy/camera/types/PixelFormat;"; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace vision | ||||||
| @@ -7,6 +7,7 @@ import com.facebook.proguard.annotations.DoNotStrip; | |||||||
| import com.mrousavy.camera.core.HardwareBuffersNotAvailableError; | import com.mrousavy.camera.core.HardwareBuffersNotAvailableError; | ||||||
| import com.mrousavy.camera.types.PixelFormat; | import com.mrousavy.camera.types.PixelFormat; | ||||||
| import com.mrousavy.camera.types.Orientation; | import com.mrousavy.camera.types.Orientation; | ||||||
|  | import java.lang.IllegalStateException; | ||||||
|  |  | ||||||
| public class Frame { | public class Frame { | ||||||
|     private final Image image; |     private final Image image; | ||||||
| @@ -24,30 +25,44 @@ public class Frame { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public Image getImage() { |     public Image getImage() { | ||||||
|         return image; |         synchronized (this) { | ||||||
|  |             Image img = image; | ||||||
|  |             if (!getIsImageValid(img)) { | ||||||
|  |                 throw new RuntimeException("Frame is already closed! " + | ||||||
|  |                     "Are you trying to access the Image data outside of a Frame Processor's lifetime?\n" + | ||||||
|  |                     "- If you want to use `console.log(frame)`, use `console.log(frame.toString())` instead.\n" + | ||||||
|  |                     "- If you want to do async processing, use `runAsync(...)` instead.\n" + | ||||||
|  |                     "- If you want to use runOnJS, increment it's ref-count: `frame.incrementRefCount()`"); | ||||||
|  |             } | ||||||
|  |             return img; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @SuppressWarnings("unused") |     @SuppressWarnings("unused") | ||||||
|     @DoNotStrip |     @DoNotStrip | ||||||
|     public int getWidth() { |     public int getWidth() { | ||||||
|         return image.getWidth(); |         return getImage().getWidth(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @SuppressWarnings("unused") |     @SuppressWarnings("unused") | ||||||
|     @DoNotStrip |     @DoNotStrip | ||||||
|     public int getHeight() { |     public int getHeight() { | ||||||
|         return image.getHeight(); |         return getImage().getHeight(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @SuppressWarnings("unused") |     @SuppressWarnings("unused") | ||||||
|     @DoNotStrip |     @DoNotStrip | ||||||
|     public boolean getIsValid() { |     public boolean getIsValid() { | ||||||
|  |         return getIsImageValid(getImage()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private boolean getIsImageValid(Image image) { | ||||||
|         try { |         try { | ||||||
|             // will throw an exception if the image is already closed |             // will throw an exception if the image is already closed | ||||||
|             image.getCropRect(); |             synchronized (this) { image.getFormat(); } | ||||||
|             // no exception thrown, image must still be valid. |             // no exception thrown, image must still be valid. | ||||||
|             return true; |             return true; | ||||||
|         } catch (Exception e) { |         } catch (IllegalStateException e) { | ||||||
|             // exception thrown, image has already been closed. |             // exception thrown, image has already been closed. | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| @@ -67,27 +82,26 @@ public class Frame { | |||||||
|  |  | ||||||
|     @SuppressWarnings("unused") |     @SuppressWarnings("unused") | ||||||
|     @DoNotStrip |     @DoNotStrip | ||||||
|     public String getOrientation() { |     public Orientation getOrientation() { | ||||||
|         return orientation.getUnionValue(); |         return orientation; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @SuppressWarnings("unused") |     @SuppressWarnings("unused") | ||||||
|     @DoNotStrip |     @DoNotStrip | ||||||
|     public String getPixelFormat() { |     public PixelFormat getPixelFormat() { | ||||||
|         PixelFormat format = PixelFormat.Companion.fromImageFormat(image.getFormat()); |         return PixelFormat.Companion.fromImageFormat(getImage().getFormat()); | ||||||
|         return format.getUnionValue(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @SuppressWarnings("unused") |     @SuppressWarnings("unused") | ||||||
|     @DoNotStrip |     @DoNotStrip | ||||||
|     public int getPlanesCount() { |     public int getPlanesCount() { | ||||||
|         return image.getPlanes().length; |         return getImage().getPlanes().length; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @SuppressWarnings("unused") |     @SuppressWarnings("unused") | ||||||
|     @DoNotStrip |     @DoNotStrip | ||||||
|     public int getBytesPerRow() { |     public int getBytesPerRow() { | ||||||
|         return image.getPlanes()[0].getRowStride(); |         return getImage().getPlanes()[0].getRowStride(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @SuppressWarnings("unused") |     @SuppressWarnings("unused") | ||||||
| @@ -101,7 +115,7 @@ public class Frame { | |||||||
|             throw new HardwareBuffersNotAvailableError(); |             throw new HardwareBuffersNotAvailableError(); | ||||||
|         } |         } | ||||||
|         if (hardwareBuffer == null) { |         if (hardwareBuffer == null) { | ||||||
|             hardwareBuffer = image.getHardwareBuffer(); |             hardwareBuffer = getImage().getHardwareBuffer(); | ||||||
|         } |         } | ||||||
|         return hardwareBuffer; |         return hardwareBuffer; | ||||||
|     } |     } | ||||||
| @@ -121,17 +135,17 @@ public class Frame { | |||||||
|             refCount--; |             refCount--; | ||||||
|             if (refCount <= 0) { |             if (refCount <= 0) { | ||||||
|                 // If no reference is held on this Image, close it. |                 // If no reference is held on this Image, close it. | ||||||
|                 image.close(); |                 close(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @SuppressWarnings("unused") |     private synchronized void close() { | ||||||
|     @DoNotStrip |         synchronized (this) { | ||||||
|     private void close() { |  | ||||||
|             if (hardwareBuffer != null) { |             if (hardwareBuffer != null) { | ||||||
|                 hardwareBuffer.close(); |                 hardwareBuffer.close(); | ||||||
|             } |             } | ||||||
|             image.close(); |             image.close(); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -484,7 +484,7 @@ PODS: | |||||||
|     - libwebp (~> 1.0) |     - libwebp (~> 1.0) | ||||||
|     - SDWebImage/Core (~> 5.10) |     - SDWebImage/Core (~> 5.10) | ||||||
|   - SocketRocket (0.6.1) |   - SocketRocket (0.6.1) | ||||||
|   - VisionCamera (3.6.16): |   - VisionCamera (3.6.17): | ||||||
|     - React |     - React | ||||||
|     - React-callinvoker |     - React-callinvoker | ||||||
|     - React-Core |     - React-Core | ||||||
| @@ -724,9 +724,9 @@ SPEC CHECKSUMS: | |||||||
|   SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d |   SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d | ||||||
|   SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d |   SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d | ||||||
|   SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 |   SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 | ||||||
|   VisionCamera: 06f5b990082ec783a58dcadc5bac677974f7a580 |   VisionCamera: 361df29347b7b7ecc47b3d173daa17751a11ffc1 | ||||||
|   Yoga: 4c3aa327e4a6a23eeacd71f61c81df1bcdf677d5 |   Yoga: 4c3aa327e4a6a23eeacd71f61c81df1bcdf677d5 | ||||||
|  |  | ||||||
| PODFILE CHECKSUM: 27f53791141a3303d814e09b55770336416ff4eb | PODFILE CHECKSUM: 27f53791141a3303d814e09b55770336416ff4eb | ||||||
|  |  | ||||||
| COCOAPODS: 1.11.3 | COCOAPODS: 1.14.3 | ||||||
|   | |||||||
| @@ -19,4 +19,14 @@ | |||||||
| @property(nonatomic, readonly) CMSampleBufferRef _Nonnull buffer; | @property(nonatomic, readonly) CMSampleBufferRef _Nonnull buffer; | ||||||
| @property(nonatomic, readonly) UIImageOrientation orientation; | @property(nonatomic, readonly) UIImageOrientation orientation; | ||||||
|  |  | ||||||
|  | // Getters | ||||||
|  | - (NSString* _Nonnull)pixelFormat; | ||||||
|  | - (BOOL)isMirrored; | ||||||
|  | - (BOOL)isValid; | ||||||
|  | - (size_t)width; | ||||||
|  | - (size_t)height; | ||||||
|  | - (double)timestamp; | ||||||
|  | - (size_t)bytesPerRow; | ||||||
|  | - (size_t)planesCount; | ||||||
|  |  | ||||||
| @end | @end | ||||||
|   | |||||||
| @@ -20,11 +20,80 @@ | |||||||
|   if (self) { |   if (self) { | ||||||
|     _buffer = buffer; |     _buffer = buffer; | ||||||
|     _orientation = orientation; |     _orientation = orientation; | ||||||
|  |     CFRetain(buffer); | ||||||
|   } |   } | ||||||
|   return self; |   return self; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | - (void)dealloc { | ||||||
|  |   CFRelease(_buffer); | ||||||
|  | } | ||||||
|  |  | ||||||
| @synthesize buffer = _buffer; | @synthesize buffer = _buffer; | ||||||
| @synthesize orientation = _orientation; | @synthesize orientation = _orientation; | ||||||
|  |  | ||||||
|  | - (NSString*)pixelFormat { | ||||||
|  |   CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(_buffer); | ||||||
|  |   FourCharCode mediaType = CMFormatDescriptionGetMediaSubType(format); | ||||||
|  |   switch (mediaType) { | ||||||
|  |     case kCVPixelFormatType_32BGRA: | ||||||
|  |     case kCVPixelFormatType_Lossy_32BGRA: | ||||||
|  |       return @"rgb"; | ||||||
|  |     case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange: | ||||||
|  |     case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: | ||||||
|  |     case kCVPixelFormatType_420YpCbCr10BiPlanarFullRange: | ||||||
|  |     case kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange: | ||||||
|  |     case kCVPixelFormatType_Lossy_420YpCbCr8BiPlanarFullRange: | ||||||
|  |     case kCVPixelFormatType_Lossy_420YpCbCr8BiPlanarVideoRange: | ||||||
|  |     case kCVPixelFormatType_Lossy_420YpCbCr10PackedBiPlanarVideoRange: | ||||||
|  |       return @"yuv"; | ||||||
|  |     default: | ||||||
|  |       return @"unknown"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (BOOL)isMirrored { | ||||||
|  |   switch (_orientation) { | ||||||
|  |     case UIImageOrientationUp: | ||||||
|  |     case UIImageOrientationDown: | ||||||
|  |     case UIImageOrientationLeft: | ||||||
|  |     case UIImageOrientationRight: | ||||||
|  |       return false; | ||||||
|  |     case UIImageOrientationDownMirrored: | ||||||
|  |     case UIImageOrientationUpMirrored: | ||||||
|  |     case UIImageOrientationLeftMirrored: | ||||||
|  |     case UIImageOrientationRightMirrored: | ||||||
|  |       return true; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (BOOL)isValid { | ||||||
|  |   return _buffer != nil && CMSampleBufferIsValid(_buffer); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (size_t)width { | ||||||
|  |   CVPixelBufferRef imageBuffer = CMSampleBufferGetImageBuffer(_buffer); | ||||||
|  |   return CVPixelBufferGetWidth(imageBuffer); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (size_t)height { | ||||||
|  |   CVPixelBufferRef imageBuffer = CMSampleBufferGetImageBuffer(_buffer); | ||||||
|  |   return CVPixelBufferGetHeight(imageBuffer); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (double)timestamp { | ||||||
|  |   CMTime timestamp = CMSampleBufferGetPresentationTimeStamp(_buffer); | ||||||
|  |   return CMTimeGetSeconds(timestamp) * 1000.0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (size_t)bytesPerRow { | ||||||
|  |   CVPixelBufferRef imageBuffer = CMSampleBufferGetImageBuffer(_buffer); | ||||||
|  |   return CVPixelBufferGetBytesPerRow(imageBuffer); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (size_t)planesCount { | ||||||
|  |   CVPixelBufferRef imageBuffer = CMSampleBufferGetImageBuffer(_buffer); | ||||||
|  |   return CVPixelBufferGetPlaneCount(imageBuffer); | ||||||
|  | } | ||||||
|  |  | ||||||
| @end | @end | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ | |||||||
|  |  | ||||||
| #import <CoreMedia/CMSampleBuffer.h> | #import <CoreMedia/CMSampleBuffer.h> | ||||||
| #import <jsi/jsi.h> | #import <jsi/jsi.h> | ||||||
|  | #import <mutex> | ||||||
|  |  | ||||||
| #import "Frame.h" | #import "Frame.h" | ||||||
|  |  | ||||||
| @@ -25,4 +26,11 @@ public: | |||||||
|  |  | ||||||
| public: | public: | ||||||
|   Frame* frame; |   Frame* frame; | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |   Frame* getFrame(); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |   std::mutex _mutex; | ||||||
|  |   size_t _refCount = 0; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ | |||||||
| // | // | ||||||
|  |  | ||||||
| #import "FrameHostObject.h" | #import "FrameHostObject.h" | ||||||
|  | #import "UIImageOrientation+descriptor.h" | ||||||
| #import "WKTJsiHostObject.h" | #import "WKTJsiHostObject.h" | ||||||
| #import <Foundation/Foundation.h> | #import <Foundation/Foundation.h> | ||||||
| #import <jsi/jsi.h> | #import <jsi/jsi.h> | ||||||
| @@ -34,42 +35,70 @@ std::vector<jsi::PropNameID> FrameHostObject::getPropertyNames(jsi::Runtime& rt) | |||||||
|   return result; |   return result; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | Frame* FrameHostObject::getFrame() { | ||||||
|  |   Frame* frame = this->frame; | ||||||
|  |   if (frame == nil || !CMSampleBufferIsValid(frame.buffer)) { | ||||||
|  |     throw std::runtime_error("Frame is already closed! " | ||||||
|  |                              "Are you trying to access the Image data outside of a Frame Processor's lifetime?\n" | ||||||
|  |                              "- If you want to use `console.log(frame)`, use `console.log(frame.toString())` instead.\n" | ||||||
|  |                              "- If you want to do async processing, use `runAsync(...)` instead.\n" | ||||||
|  |                              "- If you want to use runOnJS, increment it's ref-count: `frame.incrementRefCount()`"); | ||||||
|  |   } | ||||||
|  |   return frame; | ||||||
|  | } | ||||||
|  |  | ||||||
| jsi::Value FrameHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) { | jsi::Value FrameHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) { | ||||||
|   auto name = propName.utf8(runtime); |   auto name = propName.utf8(runtime); | ||||||
|  |  | ||||||
|   if (name == "toString") { |   if (name == "toString") { | ||||||
|     auto toString = JSI_HOST_FUNCTION_LAMBDA { |     auto toString = [this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value { | ||||||
|       if (this->frame == nil) { |       // Lock Frame so it cannot be deallocated while we access it | ||||||
|         return jsi::String::createFromUtf8(runtime, "[closed frame]"); |       std::lock_guard lock(this->_mutex); | ||||||
|       } |  | ||||||
|       auto imageBuffer = CMSampleBufferGetImageBuffer(frame.buffer); |  | ||||||
|       auto width = CVPixelBufferGetWidth(imageBuffer); |  | ||||||
|       auto height = CVPixelBufferGetHeight(imageBuffer); |  | ||||||
|  |  | ||||||
|       NSMutableString* string = [NSMutableString stringWithFormat:@"%lu x %lu Frame", width, height]; |       // Print debug description (width, height) | ||||||
|  |       Frame* frame = this->getFrame(); | ||||||
|  |       NSMutableString* string = [NSMutableString stringWithFormat:@"%lu x %lu %@ Frame", frame.width, frame.height, frame.pixelFormat]; | ||||||
|       return jsi::String::createFromUtf8(runtime, string.UTF8String); |       return jsi::String::createFromUtf8(runtime, string.UTF8String); | ||||||
|     }; |     }; | ||||||
|     return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "toString"), 0, toString); |     return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "toString"), 0, toString); | ||||||
|   } |   } | ||||||
|   if (name == "incrementRefCount") { |   if (name == "incrementRefCount") { | ||||||
|     auto incrementRefCount = JSI_HOST_FUNCTION_LAMBDA { |     auto incrementRefCount = [this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, | ||||||
|       // Increment retain count by one so ARC doesn't destroy the Frame Buffer. |                                     size_t count) -> jsi::Value { | ||||||
|       CFRetain(frame.buffer); |       // Lock Frame so it cannot be deallocated while we access it | ||||||
|  |       std::lock_guard lock(this->_mutex); | ||||||
|  |  | ||||||
|  |       // Increment our self-counted ref count by one. | ||||||
|  |       _refCount++; | ||||||
|       return jsi::Value::undefined(); |       return jsi::Value::undefined(); | ||||||
|     }; |     }; | ||||||
|     return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "incrementRefCount"), 0, incrementRefCount); |     return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "incrementRefCount"), 0, incrementRefCount); | ||||||
|   } |   } | ||||||
|   if (name == "decrementRefCount") { |   if (name == "decrementRefCount") { | ||||||
|     auto decrementRefCount = JSI_HOST_FUNCTION_LAMBDA { |     auto decrementRefCount = [this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, | ||||||
|       // Decrement retain count by one. If the retain count is zero, ARC will destroy the Frame |                                     size_t count) -> jsi::Value { | ||||||
|       // Buffer. |       // Lock Frame so it cannot be deallocated while we access it | ||||||
|       CFRelease(frame.buffer); |       std::lock_guard lock(this->_mutex); | ||||||
|  |  | ||||||
|  |       // Decrement our self-counted ref count by one. | ||||||
|  |       _refCount--; | ||||||
|  |       if (_refCount < 1) { | ||||||
|  |         // ARC will then delete the Frame and the underlying Frame Buffer. | ||||||
|  |         this->frame = nil; | ||||||
|  |       } | ||||||
|  |  | ||||||
|       return jsi::Value::undefined(); |       return jsi::Value::undefined(); | ||||||
|     }; |     }; | ||||||
|     return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "decrementRefCount"), 0, decrementRefCount); |     return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "decrementRefCount"), 0, decrementRefCount); | ||||||
|   } |   } | ||||||
|   if (name == "toArrayBuffer") { |   if (name == "toArrayBuffer") { | ||||||
|     auto toArrayBuffer = JSI_HOST_FUNCTION_LAMBDA { |     auto toArrayBuffer = [this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, | ||||||
|  |                                 size_t count) -> jsi::Value { | ||||||
|  |       // Lock Frame so it cannot be deallocated while we access it | ||||||
|  |       std::lock_guard lock(this->_mutex); | ||||||
|  |  | ||||||
|  |       // Get CPU readable Pixel Buffer from Frame and write it to a jsi::ArrayBuffer | ||||||
|  |       Frame* frame = this->getFrame(); | ||||||
|       auto pixelBuffer = CMSampleBufferGetImageBuffer(frame.buffer); |       auto pixelBuffer = CMSampleBufferGetImageBuffer(frame.buffer); | ||||||
|       auto bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer); |       auto bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer); | ||||||
|       auto height = CVPixelBufferGetHeight(pixelBuffer); |       auto height = CVPixelBufferGetHeight(pixelBuffer); | ||||||
| @@ -101,82 +130,69 @@ jsi::Value FrameHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& pr | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (name == "isValid") { |   if (name == "isValid") { | ||||||
|     auto isValid = frame != nil && frame.buffer != nil && CFGetRetainCount(frame.buffer) > 0 && CMSampleBufferIsValid(frame.buffer); |     // Lock Frame so it cannot be deallocated while we access it | ||||||
|     return jsi::Value(isValid); |     std::lock_guard lock(this->_mutex); | ||||||
|  |  | ||||||
|  |     // unsafely access the Frame and try to see if it's valid | ||||||
|  |     Frame* frame = this->frame; | ||||||
|  |     return jsi::Value(frame != nil && frame.isValid); | ||||||
|   } |   } | ||||||
|   if (name == "width") { |   if (name == "width") { | ||||||
|     auto imageBuffer = CMSampleBufferGetImageBuffer(frame.buffer); |     // Lock Frame so it cannot be deallocated while we access it | ||||||
|     auto width = CVPixelBufferGetWidth(imageBuffer); |     std::lock_guard lock(this->_mutex); | ||||||
|     return jsi::Value((double)width); |  | ||||||
|  |     Frame* frame = this->getFrame(); | ||||||
|  |     return jsi::Value((double)frame.width); | ||||||
|   } |   } | ||||||
|   if (name == "height") { |   if (name == "height") { | ||||||
|     auto imageBuffer = CMSampleBufferGetImageBuffer(frame.buffer); |     // Lock Frame so it cannot be deallocated while we access it | ||||||
|     auto height = CVPixelBufferGetHeight(imageBuffer); |     std::lock_guard lock(this->_mutex); | ||||||
|     return jsi::Value((double)height); |  | ||||||
|  |     Frame* frame = this->getFrame(); | ||||||
|  |     return jsi::Value((double)frame.height); | ||||||
|   } |   } | ||||||
|   if (name == "orientation") { |   if (name == "orientation") { | ||||||
|     switch (frame.orientation) { |     // Lock Frame so it cannot be deallocated while we access it | ||||||
|       case UIImageOrientationUp: |     std::lock_guard lock(this->_mutex); | ||||||
|       case UIImageOrientationUpMirrored: |  | ||||||
|         return jsi::String::createFromUtf8(runtime, "portrait"); |     Frame* frame = this->getFrame(); | ||||||
|       case UIImageOrientationDown: |     NSString* orientation = [NSString stringWithParsed:frame.orientation]; | ||||||
|       case UIImageOrientationDownMirrored: |     return jsi::String::createFromUtf8(runtime, orientation.UTF8String); | ||||||
|         return jsi::String::createFromUtf8(runtime, "portrait-upside-down"); |  | ||||||
|       case UIImageOrientationLeft: |  | ||||||
|       case UIImageOrientationLeftMirrored: |  | ||||||
|         return jsi::String::createFromUtf8(runtime, "landscape-left"); |  | ||||||
|       case UIImageOrientationRight: |  | ||||||
|       case UIImageOrientationRightMirrored: |  | ||||||
|         return jsi::String::createFromUtf8(runtime, "landscape-right"); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|   if (name == "isMirrored") { |   if (name == "isMirrored") { | ||||||
|     switch (frame.orientation) { |     // Lock Frame so it cannot be deallocated while we access it | ||||||
|       case UIImageOrientationUp: |     std::lock_guard lock(this->_mutex); | ||||||
|       case UIImageOrientationDown: |  | ||||||
|       case UIImageOrientationLeft: |     Frame* frame = this->getFrame(); | ||||||
|       case UIImageOrientationRight: |     return jsi::Value(frame.isMirrored); | ||||||
|         return jsi::Value(false); |  | ||||||
|       case UIImageOrientationDownMirrored: |  | ||||||
|       case UIImageOrientationUpMirrored: |  | ||||||
|       case UIImageOrientationLeftMirrored: |  | ||||||
|       case UIImageOrientationRightMirrored: |  | ||||||
|         return jsi::Value(true); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|   if (name == "timestamp") { |   if (name == "timestamp") { | ||||||
|     auto timestamp = CMSampleBufferGetPresentationTimeStamp(frame.buffer); |     // Lock Frame so it cannot be deallocated while we access it | ||||||
|     auto seconds = static_cast<double>(CMTimeGetSeconds(timestamp)); |     std::lock_guard lock(this->_mutex); | ||||||
|     return jsi::Value(seconds * 1000.0); |  | ||||||
|  |     Frame* frame = this->getFrame(); | ||||||
|  |     return jsi::Value(frame.timestamp); | ||||||
|   } |   } | ||||||
|   if (name == "pixelFormat") { |   if (name == "pixelFormat") { | ||||||
|     auto format = CMSampleBufferGetFormatDescription(frame.buffer); |     // Lock Frame so it cannot be deallocated while we access it | ||||||
|     auto mediaType = CMFormatDescriptionGetMediaSubType(format); |     std::lock_guard lock(this->_mutex); | ||||||
|     switch (mediaType) { |  | ||||||
|       case kCVPixelFormatType_32BGRA: |     Frame* frame = this->getFrame(); | ||||||
|       case kCVPixelFormatType_Lossy_32BGRA: |     return jsi::String::createFromUtf8(runtime, frame.pixelFormat.UTF8String); | ||||||
|         return jsi::String::createFromUtf8(runtime, "rgb"); |  | ||||||
|       case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange: |  | ||||||
|       case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: |  | ||||||
|       case kCVPixelFormatType_420YpCbCr10BiPlanarFullRange: |  | ||||||
|       case kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange: |  | ||||||
|       case kCVPixelFormatType_Lossy_420YpCbCr8BiPlanarFullRange: |  | ||||||
|       case kCVPixelFormatType_Lossy_420YpCbCr8BiPlanarVideoRange: |  | ||||||
|       case kCVPixelFormatType_Lossy_420YpCbCr10PackedBiPlanarVideoRange: |  | ||||||
|         return jsi::String::createFromUtf8(runtime, "yuv"); |  | ||||||
|       default: |  | ||||||
|         return jsi::String::createFromUtf8(runtime, "unknown"); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|   if (name == "bytesPerRow") { |   if (name == "bytesPerRow") { | ||||||
|     auto imageBuffer = CMSampleBufferGetImageBuffer(frame.buffer); |     // Lock Frame so it cannot be deallocated while we access it | ||||||
|     auto bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); |     std::lock_guard lock(this->_mutex); | ||||||
|     return jsi::Value((double)bytesPerRow); |  | ||||||
|  |     Frame* frame = this->getFrame(); | ||||||
|  |     return jsi::Value((double)frame.bytesPerRow); | ||||||
|   } |   } | ||||||
|   if (name == "planesCount") { |   if (name == "planesCount") { | ||||||
|     auto imageBuffer = CMSampleBufferGetImageBuffer(frame.buffer); |     // Lock Frame so it cannot be deallocated while we access it | ||||||
|     auto planesCount = CVPixelBufferGetPlaneCount(imageBuffer); |     std::lock_guard lock(this->_mutex); | ||||||
|     return jsi::Value((double)planesCount); |  | ||||||
|  |     Frame* frame = this->getFrame(); | ||||||
|  |     return jsi::Value((double)frame.planesCount); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // fallback to base implementation |   // fallback to base implementation | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								package/ios/Frame Processor/UIImageOrientation+descriptor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								package/ios/Frame Processor/UIImageOrientation+descriptor.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | // | ||||||
|  | //  UIImageOrientation+descriptor.h | ||||||
|  | //  VisionCamera | ||||||
|  | // | ||||||
|  | //  Created by Marc Rousavy on 29.12.23. | ||||||
|  | //  Copyright © 2023 mrousavy. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #import <Foundation/Foundation.h> | ||||||
|  | #import <UIKit/UIImage.h> | ||||||
|  |  | ||||||
|  | @interface NSString (UIImageOrientationJSDescriptor) | ||||||
|  |  | ||||||
|  | + (NSString*)stringWithParsed:(UIImageOrientation)orientation; | ||||||
|  |  | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | @implementation NSString (UIImageOrientationJSDescriptor) | ||||||
|  |  | ||||||
|  | + (NSString*)stringWithParsed:(UIImageOrientation)orientation { | ||||||
|  |   switch (orientation) { | ||||||
|  |     case UIImageOrientationUp: | ||||||
|  |     case UIImageOrientationUpMirrored: | ||||||
|  |       return @"portrait"; | ||||||
|  |     case UIImageOrientationDown: | ||||||
|  |     case UIImageOrientationDownMirrored: | ||||||
|  |       return @"portrait-upside-down"; | ||||||
|  |     case UIImageOrientationLeft: | ||||||
|  |     case UIImageOrientationLeftMirrored: | ||||||
|  |       return @"landscape-left"; | ||||||
|  |     case UIImageOrientationRight: | ||||||
|  |     case UIImageOrientationRightMirrored: | ||||||
|  |       return @"landscape-right"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
| @@ -160,6 +160,7 @@ | |||||||
| 		B887518425E0102000DB86D6 /* CameraView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = "<group>"; }; | 		B887518425E0102000DB86D6 /* CameraView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = "<group>"; }; | ||||||
| 		B88873E5263D46C7008B1D0E /* FrameProcessorPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FrameProcessorPlugin.h; sourceTree = "<group>"; }; | 		B88873E5263D46C7008B1D0E /* FrameProcessorPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FrameProcessorPlugin.h; sourceTree = "<group>"; }; | ||||||
| 		B8994E6B263F03E100069589 /* JSINSObjectConversion.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = JSINSObjectConversion.mm; sourceTree = "<group>"; }; | 		B8994E6B263F03E100069589 /* JSINSObjectConversion.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = JSINSObjectConversion.mm; sourceTree = "<group>"; }; | ||||||
|  | 		B89A79692B3EF60F005E0357 /* UIImageOrientation+descriptor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIImageOrientation+descriptor.h"; sourceTree = "<group>"; }; | ||||||
| 		B8A1AEC32AD7EDE800169C0D /* AVCaptureVideoDataOutput+pixelFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureVideoDataOutput+pixelFormat.swift"; sourceTree = "<group>"; }; | 		B8A1AEC32AD7EDE800169C0D /* AVCaptureVideoDataOutput+pixelFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureVideoDataOutput+pixelFormat.swift"; sourceTree = "<group>"; }; | ||||||
| 		B8A1AEC52AD7F08E00169C0D /* CameraView+Focus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Focus.swift"; sourceTree = "<group>"; }; | 		B8A1AEC52AD7F08E00169C0D /* CameraView+Focus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Focus.swift"; sourceTree = "<group>"; }; | ||||||
| 		B8A1AEC72AD8005400169C0D /* CameraSession+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraSession+Configuration.swift"; sourceTree = "<group>"; }; | 		B8A1AEC72AD8005400169C0D /* CameraSession+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraSession+Configuration.swift"; sourceTree = "<group>"; }; | ||||||
| @@ -334,6 +335,7 @@ | |||||||
| 				B81D41EF263C86F900B041FD /* JSINSObjectConversion.h */, | 				B81D41EF263C86F900B041FD /* JSINSObjectConversion.h */, | ||||||
| 				B8994E6B263F03E100069589 /* JSINSObjectConversion.mm */, | 				B8994E6B263F03E100069589 /* JSINSObjectConversion.mm */, | ||||||
| 				B85F7AE82A77BB680089C539 /* FrameProcessorPlugin.m */, | 				B85F7AE82A77BB680089C539 /* FrameProcessorPlugin.m */, | ||||||
|  | 				B89A79692B3EF60F005E0357 /* UIImageOrientation+descriptor.h */, | ||||||
| 			); | 			); | ||||||
| 			path = "Frame Processor"; | 			path = "Frame Processor"; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user