From 521d7c8ccf930404f1c61d0c3dee0c15942e0a2d Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Wed, 23 Aug 2023 14:23:31 +0200 Subject: [PATCH] feat: Use `ByteBuffer` for much faster `toArrayBuffer()` :zap: --- android/src/main/cpp/FrameHostObject.cpp | 17 ++++++----- android/src/main/cpp/java-bindings/JFrame.cpp | 7 +++-- android/src/main/cpp/java-bindings/JFrame.h | 2 +- .../mrousavy/camera/frameprocessor/Frame.java | 29 ++++++++----------- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/android/src/main/cpp/FrameHostObject.cpp b/android/src/main/cpp/FrameHostObject.cpp index 81de379..b0bf850 100644 --- a/android/src/main/cpp/FrameHostObject.cpp +++ b/android/src/main/cpp/FrameHostObject.cpp @@ -98,28 +98,29 @@ jsi::Value FrameHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& pr const jsi::Value& thisArg, const jsi::Value* args, size_t count) -> jsi::Value { - auto buffer = this->frame->toByteArray(); - auto arraySize = buffer->size(); + auto buffer = this->frame->toByteBuffer(); + if (!buffer->isDirect()) { + throw std::runtime_error("Failed to get byte content of Frame - array is not direct ByteBuffer!"); + } + auto size = buffer->getDirectSize(); static constexpr auto ARRAYBUFFER_CACHE_PROP_NAME = "__frameArrayBufferCache"; if (!runtime.global().hasProperty(runtime, ARRAYBUFFER_CACHE_PROP_NAME)) { - vision::TypedArray arrayBuffer(runtime, arraySize); + vision::TypedArray arrayBuffer(runtime, size); runtime.global().setProperty(runtime, ARRAYBUFFER_CACHE_PROP_NAME, arrayBuffer); } // Get from global JS cache auto arrayBufferCache = runtime.global().getPropertyAsObject(runtime, ARRAYBUFFER_CACHE_PROP_NAME); auto arrayBuffer = vision::getTypedArray(runtime, arrayBufferCache).get(runtime); - if (arrayBuffer.size(runtime) != arraySize) { - arrayBuffer = vision::TypedArray(runtime, arraySize); + if (arrayBuffer.size(runtime) != size) { + arrayBuffer = vision::TypedArray(runtime, size); runtime.global().setProperty(runtime, ARRAYBUFFER_CACHE_PROP_NAME, arrayBuffer); } // directly write to C++ JSI ArrayBuffer auto destinationBuffer = arrayBuffer.data(runtime); - buffer->getRegion(0, - static_cast(arraySize), - reinterpret_cast(destinationBuffer)); + memcpy(destinationBuffer, buffer->getDirectAddress(), sizeof(uint8_t) * size); return arrayBuffer; }; diff --git a/android/src/main/cpp/java-bindings/JFrame.cpp b/android/src/main/cpp/java-bindings/JFrame.cpp index 393a47a..b6d05c6 100644 --- a/android/src/main/cpp/java-bindings/JFrame.cpp +++ b/android/src/main/cpp/java-bindings/JFrame.cpp @@ -57,9 +57,10 @@ int JFrame::getBytesPerRow() const { return getBytesPerRowMethod(self()); } -local_ref JFrame::toByteArray() const { - static const auto toByteArrayMethod = getClass()->getMethod("toByteArray"); - return toByteArrayMethod(self()); +local_ref JFrame::toByteBuffer() const { + static const auto toByteBufferMethod = getClass()->getMethod("toByteBuffer"); + return toByteBufferMethod(self()); +} } void JFrame::incrementRefCount() { diff --git a/android/src/main/cpp/java-bindings/JFrame.h b/android/src/main/cpp/java-bindings/JFrame.h index b419fe3..27fa89e 100644 --- a/android/src/main/cpp/java-bindings/JFrame.h +++ b/android/src/main/cpp/java-bindings/JFrame.h @@ -25,7 +25,7 @@ struct JFrame : public JavaClass { jlong getTimestamp() const; local_ref getOrientation() const; local_ref getPixelFormat() const; - local_ref toByteArray() const; + local_ref toByteBuffer() const; void incrementRefCount(); void decrementRefCount(); void close(); diff --git a/android/src/main/java/com/mrousavy/camera/frameprocessor/Frame.java b/android/src/main/java/com/mrousavy/camera/frameprocessor/Frame.java index be21eb4..98c3236 100644 --- a/android/src/main/java/com/mrousavy/camera/frameprocessor/Frame.java +++ b/android/src/main/java/com/mrousavy/camera/frameprocessor/Frame.java @@ -89,36 +89,31 @@ public class Frame { return image.getPlanes()[0].getRowStride(); } - private static byte[] byteArrayCache; + private static ByteBuffer byteArrayCache; @SuppressWarnings("unused") @DoNotStrip - public byte[] toByteArray() { + public ByteBuffer toByteBuffer() { switch (image.getFormat()) { case ImageFormat.YUV_420_888: ByteBuffer yBuffer = image.getPlanes()[0].getBuffer(); - ByteBuffer vuBuffer = image.getPlanes()[2].getBuffer(); + ByteBuffer uBuffer = image.getPlanes()[1].getBuffer(); + ByteBuffer vBuffer = image.getPlanes()[2].getBuffer(); int ySize = yBuffer.remaining(); - int vuSize = vuBuffer.remaining(); + int uSize = uBuffer.remaining(); + int vSize = vBuffer.remaining(); + int totalSize = ySize + uSize + vSize; - if (byteArrayCache == null || byteArrayCache.length != ySize + vuSize) { - byteArrayCache = new byte[ySize + vuSize]; + if (byteArrayCache == null) { + byteArrayCache = ByteBuffer.allocate(totalSize); } - yBuffer.get(byteArrayCache, 0, ySize); - vuBuffer.get(byteArrayCache, ySize, vuSize); + byteArrayCache.rewind(); + byteArrayCache.put(yBuffer).put(uBuffer).put(vBuffer); return byteArrayCache; case ImageFormat.JPEG: - ByteBuffer rgbBuffer = image.getPlanes()[0].getBuffer(); - int size = rgbBuffer.remaining(); - - if (byteArrayCache == null || byteArrayCache.length != size) { - byteArrayCache = new byte[size]; - } - rgbBuffer.get(byteArrayCache); - - return byteArrayCache; + return image.getPlanes()[0].getBuffer(); default: throw new RuntimeException("Cannot convert Frame with Format " + image.getFormat() + " to byte array!"); }