react-native-skia-webgpu-video 0.0.0-stage → 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +303 -2
- package/android/BUILD_VALIDATION.md +62 -0
- package/android/CMakeLists.txt +99 -0
- package/android/build-validation.json +262 -0
- package/android/build.gradle +185 -0
- package/android/cpp/EncoderTargets.cpp +226 -0
- package/android/cpp/EncoderTargets.h +70 -0
- package/android/cpp/JNIHelpers.cpp +15 -0
- package/android/cpp/JNIHelpers.h +20 -0
- package/android/cpp/NativeEventDispatcher.cpp +59 -0
- package/android/cpp/NativeEventDispatcher.h +31 -0
- package/android/cpp/NativeHardwareBuffer.cpp +397 -0
- package/android/cpp/NativeHardwareBuffer.h +34 -0
- package/android/cpp/NativeRgbaBuffer.cpp +45 -0
- package/android/cpp/NativeRgbaBuffer.h +9 -0
- package/android/cpp/ReturnedVideoFrames.h +37 -0
- package/android/cpp/RgbaFrameLease.h +53 -0
- package/android/cpp/VideoCapabilities.cpp +87 -0
- package/android/cpp/VideoCapabilities.h +44 -0
- package/android/cpp/VideoComposition.cpp +170 -0
- package/android/cpp/VideoComposition.h +36 -0
- package/android/cpp/VideoCompositionFramesExtractor.cpp +82 -0
- package/android/cpp/VideoCompositionFramesExtractor.h +48 -0
- package/android/cpp/VideoCompositionFramesExtractorHostObject.cpp +230 -0
- package/android/cpp/VideoCompositionFramesExtractorHostObject.h +42 -0
- package/android/cpp/VideoCompositionFramesExtractorSyncHostObject.cpp +133 -0
- package/android/cpp/VideoCompositionFramesExtractorSyncHostObject.h +46 -0
- package/android/cpp/VideoEncoderHostObject.cpp +318 -0
- package/android/cpp/VideoEncoderHostObject.h +76 -0
- package/android/cpp/VideoFrame.cpp +293 -0
- package/android/cpp/VideoFrame.h +27 -0
- package/android/cpp/VideoPlayer.cpp +96 -0
- package/android/cpp/VideoPlayer.h +52 -0
- package/android/cpp/VideoPlayerHostObject.cpp +225 -0
- package/android/cpp/VideoPlayerHostObject.h +43 -0
- package/android/cpp/cpp-adapter.cpp +316 -0
- package/android/gradle.properties +5 -0
- package/android/instrumentation-inventory.json +178 -0
- package/android/proguard-rules.pro +4 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/azzapp/rnskv/AudioCompositionExporter.java +537 -0
- package/android/src/main/java/com/azzapp/rnskv/AudioCompositionPlayer.java +321 -0
- package/android/src/main/java/com/azzapp/rnskv/BufferLifetime.java +55 -0
- package/android/src/main/java/com/azzapp/rnskv/DecodeRequests.java +100 -0
- package/android/src/main/java/com/azzapp/rnskv/DecoderWindow.java +55 -0
- package/android/src/main/java/com/azzapp/rnskv/DisplayVsyncPeriod.java +72 -0
- package/android/src/main/java/com/azzapp/rnskv/EGLResourcesHolder.java +132 -0
- package/android/src/main/java/com/azzapp/rnskv/EGLUtils.java +225 -0
- package/android/src/main/java/com/azzapp/rnskv/FrameSize.java +50 -0
- package/android/src/main/java/com/azzapp/rnskv/GLFrameExtractor.java +510 -0
- package/android/src/main/java/com/azzapp/rnskv/JNIHelpers.java +12 -0
- package/android/src/main/java/com/azzapp/rnskv/NativeEventDispatcher.java +25 -0
- package/android/src/main/java/com/azzapp/rnskv/NativeHardwareBuffer.java +62 -0
- package/android/src/main/java/com/azzapp/rnskv/NativeRgbaBuffer.java +43 -0
- package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoModule.java +45 -0
- package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoPackage.java +43 -0
- package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoSpec.java +9 -0
- package/android/src/main/java/com/azzapp/rnskv/RgbaBufferAllocator.java +114 -0
- package/android/src/main/java/com/azzapp/rnskv/RgbaLayout.java +33 -0
- package/android/src/main/java/com/azzapp/rnskv/SurfaceOutputPolicy.java +13 -0
- package/android/src/main/java/com/azzapp/rnskv/TextureRenderer.java +150 -0
- package/android/src/main/java/com/azzapp/rnskv/TimeHelpers.java +11 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCapabilities.java +354 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoComposition.java +147 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionDecoder.java +779 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractor.java +463 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractorSync.java +241 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionItemDecoder.java +492 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoEncoder.java +639 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoFrame.java +74 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoPlayer.java +361 -0
- package/cpp/RNSVCheckedSizes.h +54 -0
- package/cpp/RNSVEventEmitter.cpp +199 -0
- package/cpp/RNSVEventEmitter.h +31 -0
- package/cpp/RNSVHostObject.cpp +79 -0
- package/cpp/RNSVHostObject.h +67 -0
- package/cpp/RNSVMemoryBudget.cpp +68 -0
- package/cpp/RNSVMemoryBudget.h +100 -0
- package/cpp/RNSVRuntimeLifecycleMonitor.cpp +88 -0
- package/cpp/RNSVRuntimeLifecycleMonitor.h +33 -0
- package/docs/ANDROID_DEVICE_306.md +95 -0
- package/docs/ANDROID_EXPORT_CACHE_306.md +48 -0
- package/docs/ANDROID_MEMORY_RETENTION_306.md +67 -0
- package/docs/BENCHMARKS.md +368 -0
- package/docs/IOS_DEVICE_TESTS.md +115 -0
- package/docs/IOS_SIMULATOR_TESTS.md +74 -0
- package/docs/MIGRATION.md +82 -0
- package/docs/NATIVE_MEMORY_INVESTIGATION_306.md +88 -0
- package/docs/PERFORMANCE_AB_IPHONE_303.md +196 -0
- package/docs/PERFORMANCE_AB_SIMULATOR.md +135 -0
- package/docs/PERFORMANCE_AB_SOAK_SIMULATOR.md +84 -0
- package/docs/PERFORMANCE_IPHONE_306_OPTIMIZATION.md +76 -0
- package/docs/PERFORMANCE_SIMULATOR.md +89 -0
- package/docs/PERFORMANCE_SIMULATOR_INITIAL.md +96 -0
- package/docs/PERFORMANCE_SOAK_IPHONE_303.md +41 -0
- package/docs/UPSTREAM_NOTES.md +188 -0
- package/docs/assets/banner.svg +26 -0
- package/examples/webgpu-overlay.ts +147 -0
- package/ios/AVAssetTrackUtils.h +24 -0
- package/ios/AudioCompositionUtils.h +27 -0
- package/ios/AudioCompositionUtils.mm +113 -0
- package/ios/DecoderWindow.h +52 -0
- package/ios/NativeResourceStats.h +32 -0
- package/ios/RNSVColorSpace.h +50 -0
- package/ios/RNSVJSIUtils.h +46 -0
- package/ios/RNSVScopedPixelBuffer.h +17 -0
- package/ios/RNSVVideoPlayer.h +46 -0
- package/ios/RNSVVideoPlayer.mm +410 -0
- package/ios/ReactNativeSkiaVideo.h +8 -0
- package/ios/ReactNativeSkiaVideo.mm +310 -0
- package/ios/SeekPolicy.h +27 -0
- package/ios/VideoComposition.h +148 -0
- package/ios/VideoCompositionFramesExtractorHostObject.h +105 -0
- package/ios/VideoCompositionFramesExtractorHostObject.mm +686 -0
- package/ios/VideoCompositionFramesExtractorSyncHostObject.h +37 -0
- package/ios/VideoCompositionFramesExtractorSyncHostObject.mm +168 -0
- package/ios/VideoCompositionItemDecoder.h +60 -0
- package/ios/VideoCompositionItemDecoder.mm +480 -0
- package/ios/VideoEncoderHostObject.h +83 -0
- package/ios/VideoEncoderHostObject.mm +706 -0
- package/ios/VideoFrame.h +66 -0
- package/ios/VideoFrame.mm +108 -0
- package/ios/VideoPlayerHostObject.h +52 -0
- package/ios/VideoPlayerHostObject.mm +332 -0
- package/lib/module/NativeReactNativeSkiaVideo.js +5 -0
- package/lib/module/NativeReactNativeSkiaVideo.js.map +1 -0
- package/lib/module/RNSkiaVideoModule.js +20 -0
- package/lib/module/RNSkiaVideoModule.js.map +1 -0
- package/lib/module/canvas.js +18 -0
- package/lib/module/canvas.js.map +1 -0
- package/lib/module/exportCache.js +22 -0
- package/lib/module/exportCache.js.map +1 -0
- package/lib/module/exportFrames.js +338 -0
- package/lib/module/exportFrames.js.map +1 -0
- package/lib/module/exportQueue.js +52 -0
- package/lib/module/exportQueue.js.map +1 -0
- package/lib/module/exportTarget.js +137 -0
- package/lib/module/exportTarget.js.map +1 -0
- package/lib/module/exportVideoComposition.js +492 -0
- package/lib/module/exportVideoComposition.js.map +1 -0
- package/lib/module/frameInterop.js +532 -0
- package/lib/module/frameInterop.js.map +1 -0
- package/lib/module/gpu.js +222 -0
- package/lib/module/gpu.js.map +1 -0
- package/lib/module/gpuDevice.js +54 -0
- package/lib/module/gpuDevice.js.map +1 -0
- package/lib/module/index.js +36 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/memory.js +38 -0
- package/lib/module/memory.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/previewLifecycle.js +52 -0
- package/lib/module/previewLifecycle.js.map +1 -0
- package/lib/module/runtimeYield.js +10 -0
- package/lib/module/runtimeYield.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/useEventListener.js +18 -0
- package/lib/module/utils/useEventListener.js.map +1 -0
- package/lib/module/videoCompositionPlayer.js +368 -0
- package/lib/module/videoCompositionPlayer.js.map +1 -0
- package/lib/module/videoFrameImage.js +185 -0
- package/lib/module/videoFrameImage.js.map +1 -0
- package/lib/module/videoPlayer.js +172 -0
- package/lib/module/videoPlayer.js.map +1 -0
- package/lib/module/yuvBlit.js +299 -0
- package/lib/module/yuvBlit.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts +7 -0
- package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts.map +1 -0
- package/lib/typescript/src/RNSkiaVideoModule.d.ts +4 -0
- package/lib/typescript/src/RNSkiaVideoModule.d.ts.map +1 -0
- package/lib/typescript/src/canvas.d.ts +4 -0
- package/lib/typescript/src/canvas.d.ts.map +1 -0
- package/lib/typescript/src/exportCache.d.ts +6 -0
- package/lib/typescript/src/exportCache.d.ts.map +1 -0
- package/lib/typescript/src/exportFrames.d.ts +57 -0
- package/lib/typescript/src/exportFrames.d.ts.map +1 -0
- package/lib/typescript/src/exportQueue.d.ts +5 -0
- package/lib/typescript/src/exportQueue.d.ts.map +1 -0
- package/lib/typescript/src/exportTarget.d.ts +28 -0
- package/lib/typescript/src/exportTarget.d.ts.map +1 -0
- package/lib/typescript/src/exportVideoComposition.d.ts +68 -0
- package/lib/typescript/src/exportVideoComposition.d.ts.map +1 -0
- package/lib/typescript/src/frameInterop.d.ts +17 -0
- package/lib/typescript/src/frameInterop.d.ts.map +1 -0
- package/lib/typescript/src/gpu.d.ts +39 -0
- package/lib/typescript/src/gpu.d.ts.map +1 -0
- package/lib/typescript/src/gpuDevice.d.ts +11 -0
- package/lib/typescript/src/gpuDevice.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +21 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/memory.d.ts +25 -0
- package/lib/typescript/src/memory.d.ts.map +1 -0
- package/lib/typescript/src/previewLifecycle.d.ts +22 -0
- package/lib/typescript/src/previewLifecycle.d.ts.map +1 -0
- package/lib/typescript/src/runtimeYield.d.ts +4 -0
- package/lib/typescript/src/runtimeYield.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +631 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/utils/useEventListener.d.ts +5 -0
- package/lib/typescript/src/utils/useEventListener.d.ts.map +1 -0
- package/lib/typescript/src/videoCompositionPlayer.d.ts +105 -0
- package/lib/typescript/src/videoCompositionPlayer.d.ts.map +1 -0
- package/lib/typescript/src/videoFrameImage.d.ts +60 -0
- package/lib/typescript/src/videoFrameImage.d.ts.map +1 -0
- package/lib/typescript/src/videoPlayer.d.ts +99 -0
- package/lib/typescript/src/videoPlayer.d.ts.map +1 -0
- package/lib/typescript/src/yuvBlit.d.ts +50 -0
- package/lib/typescript/src/yuvBlit.d.ts.map +1 -0
- package/package.json +155 -4
- package/patches/react-native-skia+3.7.0.patch +502 -0
- package/react-native-skia-webgpu-video.podspec +35 -0
- package/scripts/apply-skia-canvas-patch.mjs +48 -0
- package/scripts/skia-canvas-patch.json +291 -0
- package/src/NativeReactNativeSkiaVideo.ts +8 -0
- package/src/RNSkiaVideoModule.ts +27 -0
- package/src/canvas.ts +22 -0
- package/src/exportCache.ts +27 -0
- package/src/exportFrames.ts +421 -0
- package/src/exportQueue.ts +65 -0
- package/src/exportTarget.ts +179 -0
- package/src/exportVideoComposition.ts +718 -0
- package/src/frameInterop.ts +684 -0
- package/src/gpu.ts +290 -0
- package/src/gpuDevice.ts +80 -0
- package/src/index.tsx +45 -0
- package/src/memory.ts +48 -0
- package/src/previewLifecycle.ts +60 -0
- package/src/runtimeYield.ts +6 -0
- package/src/types.ts +661 -0
- package/src/utils/useEventListener.ts +22 -0
- package/src/videoCompositionPlayer.ts +568 -0
- package/src/videoFrameImage.ts +205 -0
- package/src/videoPlayer.ts +318 -0
- package/src/yuvBlit.ts +331 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
#include "NativeHardwareBuffer.h"
|
|
2
|
+
#include "RNSVMemoryBudget.h"
|
|
3
|
+
#include <fbjni/ByteBuffer.h>
|
|
4
|
+
#include <android/hardware_buffer_jni.h>
|
|
5
|
+
#include <android/log.h>
|
|
6
|
+
#include <EGL/egl.h>
|
|
7
|
+
#include <EGL/eglext.h>
|
|
8
|
+
#include <GLES2/gl2.h>
|
|
9
|
+
#include <GLES2/gl2ext.h>
|
|
10
|
+
#include <atomic>
|
|
11
|
+
#include <cerrno>
|
|
12
|
+
#include <cstring>
|
|
13
|
+
#include <limits>
|
|
14
|
+
#include <mutex>
|
|
15
|
+
#include <stdexcept>
|
|
16
|
+
#include <string>
|
|
17
|
+
#include <unordered_map>
|
|
18
|
+
|
|
19
|
+
namespace RNSkiaVideo {
|
|
20
|
+
using namespace facebook::jni;
|
|
21
|
+
namespace {
|
|
22
|
+
std::atomic<bool> interopEnabled{false};
|
|
23
|
+
std::atomic<uint64_t> nextHandle{1};
|
|
24
|
+
std::mutex registryMutex;
|
|
25
|
+
std::unordered_map<uint64_t, std::shared_ptr<HardwareFrameStorage>> frames;
|
|
26
|
+
|
|
27
|
+
struct Unsupported final : std::runtime_error {
|
|
28
|
+
explicit Unsupported(const std::string& detail)
|
|
29
|
+
: std::runtime_error("Android native buffer interop unavailable: " + detail) {}
|
|
30
|
+
};
|
|
31
|
+
bool extension(const char* values, const char* name) {
|
|
32
|
+
if (!values) return false;
|
|
33
|
+
const size_t length = std::strlen(name);
|
|
34
|
+
for (const char* p = values; (p = std::strstr(p, name)); p += length)
|
|
35
|
+
if ((p == values || p[-1] == ' ') && (p[length] == 0 || p[length] == ' ')) return true;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
void checkGl(const char* operation) {
|
|
39
|
+
const auto error = glGetError();
|
|
40
|
+
if (error != GL_NO_ERROR)
|
|
41
|
+
throw std::runtime_error(std::string(operation) + " failed: GL error " + std::to_string(error));
|
|
42
|
+
}
|
|
43
|
+
uint64_t newId() {
|
|
44
|
+
const auto id = nextHandle.fetch_add(1);
|
|
45
|
+
if (id == 0 || id > static_cast<uint64_t>(std::numeric_limits<jlong>::max()))
|
|
46
|
+
throw std::runtime_error("Hardware buffer lease identifiers exhausted");
|
|
47
|
+
return id;
|
|
48
|
+
}
|
|
49
|
+
uint64_t addFrame(const std::shared_ptr<HardwareFrameStorage>& owner) {
|
|
50
|
+
const auto id = newId();
|
|
51
|
+
std::lock_guard<std::mutex> lock(registryMutex);
|
|
52
|
+
frames.emplace(id, owner);
|
|
53
|
+
return id;
|
|
54
|
+
}
|
|
55
|
+
void releaseFrame(uint64_t handle) {
|
|
56
|
+
std::shared_ptr<HardwareFrameStorage> released;
|
|
57
|
+
{
|
|
58
|
+
std::lock_guard<std::mutex> lock(registryMutex);
|
|
59
|
+
auto found = frames.find(handle);
|
|
60
|
+
if (found == frames.end()) return;
|
|
61
|
+
released = std::move(found->second);
|
|
62
|
+
frames.erase(found);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
struct RenderTarget final {
|
|
67
|
+
std::shared_ptr<HardwareFrameStorage> owner;
|
|
68
|
+
EGLDisplay display = EGL_NO_DISPLAY;
|
|
69
|
+
EGLContext context = EGL_NO_CONTEXT;
|
|
70
|
+
EGLImageKHR image = EGL_NO_IMAGE_KHR;
|
|
71
|
+
GLuint texture = 0;
|
|
72
|
+
uint64_t id = 0;
|
|
73
|
+
RenderTarget* quarantineNext = nullptr;
|
|
74
|
+
PFNEGLDESTROYIMAGEKHRPROC destroyImage = nullptr;
|
|
75
|
+
~RenderTarget() {
|
|
76
|
+
if (texture) glDeleteTextures(1, &texture);
|
|
77
|
+
if (image != EGL_NO_IMAGE_KHR && destroyImage) destroyImage(display, image);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
std::unordered_map<uint64_t, std::unique_ptr<RenderTarget>> writers;
|
|
81
|
+
RenderTarget* quarantinedWriters = nullptr;
|
|
82
|
+
|
|
83
|
+
void quarantineWriter(std::unique_ptr<RenderTarget> writer) {
|
|
84
|
+
// Intrusive storage: uncertain GPU completion must not require a successful
|
|
85
|
+
// heap allocation to keep its backing and reservation alive.
|
|
86
|
+
std::lock_guard<std::mutex> lock(registryMutex);
|
|
87
|
+
writer->quarantineNext = quarantinedWriters;
|
|
88
|
+
quarantinedWriters = writer.release();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
std::unique_ptr<RenderTarget> makeTarget(int width, int height) {
|
|
92
|
+
if (width <= 0 || height <= 0 || width > 16384 || height > 16384)
|
|
93
|
+
throw std::invalid_argument("Invalid hardware video frame dimensions");
|
|
94
|
+
auto display = eglGetCurrentDisplay();
|
|
95
|
+
auto context = eglGetCurrentContext();
|
|
96
|
+
if (display == EGL_NO_DISPLAY || context == EGL_NO_CONTEXT)
|
|
97
|
+
throw std::runtime_error("Hardware frame producer has no current EGL context");
|
|
98
|
+
const auto* eglExtensions = eglQueryString(display, EGL_EXTENSIONS);
|
|
99
|
+
const auto* glExtensions = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
|
|
100
|
+
if (!extension(eglExtensions, "EGL_ANDROID_get_native_client_buffer") ||
|
|
101
|
+
!extension(eglExtensions, "EGL_ANDROID_image_native_buffer") ||
|
|
102
|
+
!extension(eglExtensions, "EGL_KHR_image_base") ||
|
|
103
|
+
!extension(glExtensions, "GL_OES_EGL_image"))
|
|
104
|
+
throw Unsupported("required EGLImage/AHardwareBuffer extensions are absent");
|
|
105
|
+
auto getClientBuffer = reinterpret_cast<PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC>(
|
|
106
|
+
eglGetProcAddress("eglGetNativeClientBufferANDROID"));
|
|
107
|
+
auto createImage = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
|
|
108
|
+
auto destroyImage = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
|
|
109
|
+
auto imageTarget = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(
|
|
110
|
+
eglGetProcAddress("glEGLImageTargetTexture2DOES"));
|
|
111
|
+
if (!getClientBuffer || !createImage || !destroyImage || !imageTarget)
|
|
112
|
+
throw Unsupported("required EGLImage entry points are absent");
|
|
113
|
+
|
|
114
|
+
auto target = std::make_unique<RenderTarget>();
|
|
115
|
+
target->owner = std::make_shared<HardwareFrameStorage>();
|
|
116
|
+
auto& owner = *target->owner;
|
|
117
|
+
owner.reservation = MemoryBudget::instance().reserve(
|
|
118
|
+
static_cast<uint64_t>(width) * height * 4, "android-frame-ahardwarebuffer");
|
|
119
|
+
AHardwareBuffer_Desc descriptor{};
|
|
120
|
+
descriptor.width = width; descriptor.height = height; descriptor.layers = 1;
|
|
121
|
+
descriptor.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
|
|
122
|
+
descriptor.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
|
|
123
|
+
AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT | AHARDWAREBUFFER_USAGE_CPU_READ_RARELY;
|
|
124
|
+
const int result = AHardwareBuffer_allocate(&descriptor, &owner.buffer);
|
|
125
|
+
if (result != 0) {
|
|
126
|
+
if (result == -EINVAL || result == EINVAL)
|
|
127
|
+
throw Unsupported("RGBA GPU color/sample buffer allocation is unsupported");
|
|
128
|
+
throw std::runtime_error("AHardwareBuffer allocation failed: " + std::to_string(result));
|
|
129
|
+
}
|
|
130
|
+
AHardwareBuffer_describe(owner.buffer, &owner.description);
|
|
131
|
+
if (owner.description.format != descriptor.format || owner.description.layers != 1 ||
|
|
132
|
+
owner.description.width != descriptor.width || owner.description.height != descriptor.height ||
|
|
133
|
+
owner.description.stride < descriptor.width ||
|
|
134
|
+
(owner.description.usage & descriptor.usage) != descriptor.usage)
|
|
135
|
+
throw std::runtime_error("Invalid allocated RGBA hardware buffer layout");
|
|
136
|
+
MemoryBudget::instance().resize(owner.reservation,
|
|
137
|
+
static_cast<uint64_t>(owner.description.stride) * height * 4,
|
|
138
|
+
"android-frame-ahardwarebuffer padded stride");
|
|
139
|
+
target->display = display; target->context = context; target->destroyImage = destroyImage;
|
|
140
|
+
const EGLint attributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
|
|
141
|
+
target->image = createImage(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
|
|
142
|
+
getClientBuffer(owner.buffer), attributes);
|
|
143
|
+
if (target->image == EGL_NO_IMAGE_KHR)
|
|
144
|
+
throw std::runtime_error("Cannot import hardware producer EGLImage: " + std::to_string(eglGetError()));
|
|
145
|
+
GLint previousTexture = 0;
|
|
146
|
+
glGetIntegerv(GL_TEXTURE_BINDING_2D, &previousTexture);
|
|
147
|
+
glGenTextures(1, &target->texture);
|
|
148
|
+
glBindTexture(GL_TEXTURE_2D, target->texture);
|
|
149
|
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
150
|
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
151
|
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
152
|
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
153
|
+
imageTarget(GL_TEXTURE_2D, target->image);
|
|
154
|
+
glBindTexture(GL_TEXTURE_2D, previousTexture);
|
|
155
|
+
checkGl("Hardware frame EGLImage binding");
|
|
156
|
+
return target;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
jlong create(alias_ref<jclass>, jint width, jint height) {
|
|
160
|
+
auto writer = makeTarget(width, height);
|
|
161
|
+
const auto id = newId();
|
|
162
|
+
writer->id = id;
|
|
163
|
+
std::lock_guard<std::mutex> lock(registryMutex);
|
|
164
|
+
writers.emplace(id, std::move(writer));
|
|
165
|
+
return static_cast<jlong>(id);
|
|
166
|
+
}
|
|
167
|
+
jint texture(alias_ref<jclass>, jlong id) {
|
|
168
|
+
std::lock_guard<std::mutex> lock(registryMutex);
|
|
169
|
+
auto found = writers.find(id);
|
|
170
|
+
if (found == writers.end()) throw std::runtime_error("Hardware render target is closed");
|
|
171
|
+
if (found->second->context != eglGetCurrentContext())
|
|
172
|
+
throw std::runtime_error("Hardware render target has a different EGL context");
|
|
173
|
+
return found->second->texture;
|
|
174
|
+
}
|
|
175
|
+
std::unique_ptr<RenderTarget> removeWriter(uint64_t id) {
|
|
176
|
+
std::lock_guard<std::mutex> lock(registryMutex);
|
|
177
|
+
auto found = writers.find(id);
|
|
178
|
+
if (found == writers.end()) {
|
|
179
|
+
for (auto** previous = &quarantinedWriters; *previous; previous = &(*previous)->quarantineNext) {
|
|
180
|
+
auto* writer = *previous;
|
|
181
|
+
if (writer->id != id) continue;
|
|
182
|
+
if (writer->context != eglGetCurrentContext())
|
|
183
|
+
throw std::runtime_error("Hardware render target has a different EGL context");
|
|
184
|
+
*previous = writer->quarantineNext;
|
|
185
|
+
writer->quarantineNext = nullptr;
|
|
186
|
+
return std::unique_ptr<RenderTarget>(writer);
|
|
187
|
+
}
|
|
188
|
+
return nullptr;
|
|
189
|
+
}
|
|
190
|
+
if (found->second->context != eglGetCurrentContext())
|
|
191
|
+
throw std::runtime_error("Hardware render target has a different EGL context");
|
|
192
|
+
auto writer = std::move(found->second);
|
|
193
|
+
writers.erase(found);
|
|
194
|
+
return writer;
|
|
195
|
+
}
|
|
196
|
+
jlong finish(alias_ref<jclass>, jlong id) {
|
|
197
|
+
auto writer = removeWriter(id);
|
|
198
|
+
if (!writer) throw std::runtime_error("Hardware render target is closed");
|
|
199
|
+
// RN WebGPU's native-buffer wrapper accepts no incoming producer fence.
|
|
200
|
+
// Finish and validate our isolated EGL work before making the pointer visible.
|
|
201
|
+
try {
|
|
202
|
+
glFinish();
|
|
203
|
+
checkGl("Hardware frame producer completion");
|
|
204
|
+
} catch (...) {
|
|
205
|
+
// Keep the allocation and budget live while producer completion is unknown.
|
|
206
|
+
quarantineWriter(std::move(writer));
|
|
207
|
+
throw;
|
|
208
|
+
}
|
|
209
|
+
return static_cast<jlong>(addFrame(writer->owner));
|
|
210
|
+
}
|
|
211
|
+
void abort(alias_ref<jclass>, jlong id) {
|
|
212
|
+
auto writer = removeWriter(id);
|
|
213
|
+
if (!writer) return;
|
|
214
|
+
try {
|
|
215
|
+
glFinish();
|
|
216
|
+
checkGl("Abandoned hardware frame producer completion");
|
|
217
|
+
} catch (...) {
|
|
218
|
+
quarantineWriter(std::move(writer));
|
|
219
|
+
throw;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/** Whether Dawn imports this format as OpaqueYCbCrAndroid: the public YUV
|
|
223
|
+
* formats and the driver-defined ones decoders use (Tensor: 0x123), unlike
|
|
224
|
+
* the color/depth formats with a WebGPU equivalent. Mirrors RN WebGPU's
|
|
225
|
+
* GPUExternalTexture::isOpaqueYCbCrFormat. */
|
|
226
|
+
bool isYuvFormat(uint32_t format) {
|
|
227
|
+
switch (format) {
|
|
228
|
+
case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
|
|
229
|
+
case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
|
|
230
|
+
case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
|
|
231
|
+
case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
|
|
232
|
+
case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
|
|
233
|
+
case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
|
|
234
|
+
case AHARDWAREBUFFER_FORMAT_BLOB:
|
|
235
|
+
case AHARDWAREBUFFER_FORMAT_D16_UNORM:
|
|
236
|
+
case AHARDWAREBUFFER_FORMAT_D24_UNORM:
|
|
237
|
+
case AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT:
|
|
238
|
+
case AHARDWAREBUFFER_FORMAT_D32_FLOAT:
|
|
239
|
+
case AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT:
|
|
240
|
+
case AHARDWAREBUFFER_FORMAT_S8_UINT:
|
|
241
|
+
case 0x38: case 0x39: case 0x3a: case 0x3b: // R8, R16, R16G16, R10G10B10A10
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Takes over an ImageReader Image and its YUV hardware buffer. On failure the
|
|
248
|
+
* caller still owns the Image; on success, the last lease closes it. */
|
|
249
|
+
jlong wrapImage(alias_ref<jclass>, alias_ref<jobject> hardwareBuffer, alias_ref<jobject> image,
|
|
250
|
+
jint cropX, jint cropY, jint cropWidth, jint cropHeight, jint rotation) {
|
|
251
|
+
auto* buffer = AHardwareBuffer_fromHardwareBuffer(Environment::current(), hardwareBuffer.get());
|
|
252
|
+
if (!buffer) throw std::runtime_error("Decoded image has no hardware buffer");
|
|
253
|
+
auto owner = std::make_shared<HardwareFrameStorage>();
|
|
254
|
+
AHardwareBuffer_acquire(buffer);
|
|
255
|
+
owner->buffer = buffer;
|
|
256
|
+
AHardwareBuffer_describe(buffer, &owner->description);
|
|
257
|
+
const auto& description = owner->description;
|
|
258
|
+
if (!isYuvFormat(description.format) || description.layers != 1 ||
|
|
259
|
+
(description.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE) == 0)
|
|
260
|
+
throw std::runtime_error("Decoded image is not a GPU-sampled YUV 4:2:0 buffer (format " +
|
|
261
|
+
std::to_string(description.format) + ", usage " + std::to_string(description.usage) +
|
|
262
|
+
", layers " + std::to_string(description.layers) + ")");
|
|
263
|
+
if (cropX < 0 || cropY < 0 || cropWidth <= 0 || cropHeight <= 0 ||
|
|
264
|
+
static_cast<uint64_t>(cropX) + cropWidth > description.width ||
|
|
265
|
+
static_cast<uint64_t>(cropY) + cropHeight > description.height ||
|
|
266
|
+
(rotation != 0 && rotation != 90 && rotation != 180 && rotation != 270))
|
|
267
|
+
throw std::runtime_error("Invalid decoded image crop or rotation");
|
|
268
|
+
// Luma plus half-size chroma at the buffer's stride; driver formats may
|
|
269
|
+
// compress, so this is an estimate.
|
|
270
|
+
owner->reservation = MemoryBudget::instance().reserve(
|
|
271
|
+
static_cast<uint64_t>(description.stride) * description.height * 3 / 2,
|
|
272
|
+
"android-frame-imagereader");
|
|
273
|
+
owner->cropX = cropX; owner->cropY = cropY;
|
|
274
|
+
owner->cropWidth = cropWidth; owner->cropHeight = cropHeight;
|
|
275
|
+
owner->rotation = rotation;
|
|
276
|
+
owner->image = make_global(image);
|
|
277
|
+
return static_cast<jlong>(addFrame(owner));
|
|
278
|
+
}
|
|
279
|
+
jlong retain(alias_ref<jclass>, jlong id) {
|
|
280
|
+
return static_cast<jlong>(addFrame(acquireHardwareFrame(id)));
|
|
281
|
+
}
|
|
282
|
+
void release(alias_ref<jclass>, jlong id) { releaseFrame(id); }
|
|
283
|
+
jboolean enabled(alias_ref<jclass>) { return interopEnabled.load(); }
|
|
284
|
+
void read(alias_ref<jclass>, jlong id, alias_ref<JByteBuffer> destination) {
|
|
285
|
+
auto owner = acquireHardwareFrame(id);
|
|
286
|
+
if (owner->description.format != AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM)
|
|
287
|
+
throw std::runtime_error("CPU readback of a YUV hardware frame is unsupported");
|
|
288
|
+
auto* env = Environment::current();
|
|
289
|
+
auto* bytes = static_cast<uint8_t*>(env->GetDirectBufferAddress(destination.get()));
|
|
290
|
+
const auto capacity = env->GetDirectBufferCapacity(destination.get());
|
|
291
|
+
const size_t row = static_cast<size_t>(owner->description.width) * 4;
|
|
292
|
+
if (!bytes || capacity < 0 || static_cast<uint64_t>(capacity) < row * owner->description.height)
|
|
293
|
+
throw std::invalid_argument("Hardware frame readback destination is too small");
|
|
294
|
+
void* source = nullptr;
|
|
295
|
+
const int result = AHardwareBuffer_lock(owner->buffer, AHARDWAREBUFFER_USAGE_CPU_READ_RARELY,
|
|
296
|
+
-1, nullptr, &source);
|
|
297
|
+
if (result != 0 || !source) throw std::runtime_error("Hardware frame diagnostic CPU lock failed");
|
|
298
|
+
for (size_t y = 0; y < owner->description.height; ++y)
|
|
299
|
+
std::memcpy(bytes + y * row, static_cast<uint8_t*>(source) +
|
|
300
|
+
y * owner->description.stride * 4, row);
|
|
301
|
+
if (AHardwareBuffer_unlock(owner->buffer, nullptr) != 0)
|
|
302
|
+
throw std::runtime_error("Hardware frame diagnostic CPU unlock failed");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
void probe() {
|
|
306
|
+
const auto previousDisplay = eglGetCurrentDisplay();
|
|
307
|
+
const auto previousContext = eglGetCurrentContext();
|
|
308
|
+
const auto previousDraw = eglGetCurrentSurface(EGL_DRAW);
|
|
309
|
+
const auto previousRead = eglGetCurrentSurface(EGL_READ);
|
|
310
|
+
auto display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
311
|
+
if (display == EGL_NO_DISPLAY || !eglInitialize(display, nullptr, nullptr))
|
|
312
|
+
throw std::runtime_error("Cannot initialize hardware-frame probe EGL display");
|
|
313
|
+
EGLContext context = EGL_NO_CONTEXT;
|
|
314
|
+
EGLSurface surface = EGL_NO_SURFACE;
|
|
315
|
+
const auto cleanup = [&] {
|
|
316
|
+
bool restored = false;
|
|
317
|
+
if (previousContext != EGL_NO_CONTEXT) {
|
|
318
|
+
restored = eglMakeCurrent(previousDisplay, previousDraw, previousRead, previousContext);
|
|
319
|
+
} else restored = eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
|
320
|
+
if (!restored) eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
|
321
|
+
if (surface != EGL_NO_SURFACE) eglDestroySurface(display, surface);
|
|
322
|
+
if (context != EGL_NO_CONTEXT) eglDestroyContext(display, context);
|
|
323
|
+
// The initialized default display is process-owned; never eglTerminate it.
|
|
324
|
+
if (!restored) throw std::runtime_error("Cannot restore caller EGL context after hardware-frame probe");
|
|
325
|
+
};
|
|
326
|
+
std::exception_ptr failure;
|
|
327
|
+
try {
|
|
328
|
+
const EGLint attributes[] = {EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
|
329
|
+
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8,
|
|
330
|
+
EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE};
|
|
331
|
+
EGLConfig config; EGLint count = 0;
|
|
332
|
+
if (!eglChooseConfig(display, attributes, &config, 1, &count) || count == 0)
|
|
333
|
+
throw Unsupported("no RGBA EGL producer configuration");
|
|
334
|
+
const EGLint contextAttributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
|
|
335
|
+
context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttributes);
|
|
336
|
+
const EGLint surfaceAttributes[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};
|
|
337
|
+
surface = eglCreatePbufferSurface(display, config, surfaceAttributes);
|
|
338
|
+
if (context == EGL_NO_CONTEXT || surface == EGL_NO_SURFACE ||
|
|
339
|
+
!eglMakeCurrent(display, surface, surface, context))
|
|
340
|
+
throw std::runtime_error("Cannot create hardware-frame probe EGL context");
|
|
341
|
+
{
|
|
342
|
+
auto target = makeTarget(4, 4);
|
|
343
|
+
GLuint framebuffer = 0;
|
|
344
|
+
glGenFramebuffers(1, &framebuffer);
|
|
345
|
+
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
|
346
|
+
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target->texture, 0);
|
|
347
|
+
const auto complete = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
348
|
+
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
|
349
|
+
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
350
|
+
glDeleteFramebuffers(1, &framebuffer);
|
|
351
|
+
if (complete != GL_FRAMEBUFFER_COMPLETE)
|
|
352
|
+
throw Unsupported("RGBA hardware buffer is not an EGL color render target");
|
|
353
|
+
checkGl("Hardware-frame probe");
|
|
354
|
+
}
|
|
355
|
+
} catch (...) { failure = std::current_exception(); }
|
|
356
|
+
cleanup();
|
|
357
|
+
if (failure) std::rethrow_exception(failure);
|
|
358
|
+
}
|
|
359
|
+
} // namespace
|
|
360
|
+
|
|
361
|
+
HardwareFrameStorage::~HardwareFrameStorage() {
|
|
362
|
+
if (image) {
|
|
363
|
+
// The last lease may go on any thread (JS, GC reaper): attach it.
|
|
364
|
+
ThreadScope attached;
|
|
365
|
+
try {
|
|
366
|
+
static const auto close =
|
|
367
|
+
findClassStatic("android/media/Image")->getMethod<void()>("close");
|
|
368
|
+
close(image);
|
|
369
|
+
} catch (...) {
|
|
370
|
+
// A closed reader already closed its images.
|
|
371
|
+
}
|
|
372
|
+
image.reset();
|
|
373
|
+
}
|
|
374
|
+
if (buffer) AHardwareBuffer_release(buffer);
|
|
375
|
+
if (reservation) MemoryBudget::instance().release(reservation);
|
|
376
|
+
}
|
|
377
|
+
std::shared_ptr<HardwareFrameStorage> acquireHardwareFrame(uint64_t handle) {
|
|
378
|
+
std::lock_guard<std::mutex> lock(registryMutex);
|
|
379
|
+
auto found = frames.find(handle);
|
|
380
|
+
if (found == frames.end()) throw std::runtime_error("Hardware video frame is disposed");
|
|
381
|
+
return found->second;
|
|
382
|
+
}
|
|
383
|
+
bool NativeHardwareBuffer::isEnabled() { return interopEnabled.load(); }
|
|
384
|
+
void NativeHardwareBuffer::configure(bool value) {
|
|
385
|
+
if (!value) { interopEnabled.store(false); return; }
|
|
386
|
+
if (!interopEnabled.load()) probe();
|
|
387
|
+
interopEnabled.store(true);
|
|
388
|
+
}
|
|
389
|
+
void NativeHardwareBuffer::registerNatives() {
|
|
390
|
+
javaClassStatic()->registerNatives({makeNativeMethod("nativeCreateRenderTarget", create),
|
|
391
|
+
makeNativeMethod("nativeRenderTargetTexture", texture), makeNativeMethod("nativeFinishRenderTarget", finish),
|
|
392
|
+
makeNativeMethod("nativeAbortRenderTarget", abort), makeNativeMethod("nativeRetain", retain),
|
|
393
|
+
makeNativeMethod("nativeRelease", release), makeNativeMethod("nativeReadPixels", read),
|
|
394
|
+
makeNativeMethod("nativeWrapImage", wrapImage),
|
|
395
|
+
makeNativeMethod("nativeIsEnabled", enabled)});
|
|
396
|
+
}
|
|
397
|
+
} // namespace RNSkiaVideo
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <android/hardware_buffer.h>
|
|
3
|
+
#include <fbjni/fbjni.h>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <memory>
|
|
6
|
+
|
|
7
|
+
namespace RNSkiaVideo {
|
|
8
|
+
|
|
9
|
+
struct HardwareFrameStorage final {
|
|
10
|
+
AHardwareBuffer* buffer = nullptr;
|
|
11
|
+
AHardwareBuffer_Desc description{};
|
|
12
|
+
uint64_t reservation = 0;
|
|
13
|
+
// ImageReader frames: the codec's YUV buffer, owned until the last lease
|
|
14
|
+
// closes its android.media.Image (returning the slot to the codec). The
|
|
15
|
+
// picture is the crop rectangle, turned clockwise by `rotation` for display.
|
|
16
|
+
facebook::jni::global_ref<jobject> image;
|
|
17
|
+
int cropX = 0;
|
|
18
|
+
int cropY = 0;
|
|
19
|
+
int cropWidth = 0;
|
|
20
|
+
int cropHeight = 0;
|
|
21
|
+
int rotation = 0;
|
|
22
|
+
~HardwareFrameStorage();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
std::shared_ptr<HardwareFrameStorage> acquireHardwareFrame(uint64_t handle);
|
|
26
|
+
|
|
27
|
+
struct NativeHardwareBuffer : facebook::jni::JavaClass<NativeHardwareBuffer> {
|
|
28
|
+
static constexpr auto kJavaDescriptor = "Lcom/azzapp/rnskv/NativeHardwareBuffer;";
|
|
29
|
+
static void registerNatives();
|
|
30
|
+
static bool isEnabled();
|
|
31
|
+
static void configure(bool enabled);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
} // namespace RNSkiaVideo
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#include "NativeRgbaBuffer.h"
|
|
2
|
+
#include "RNSVMemoryBudget.h"
|
|
3
|
+
#include <limits>
|
|
4
|
+
|
|
5
|
+
namespace RNSkiaVideo {
|
|
6
|
+
using namespace facebook::jni;
|
|
7
|
+
namespace {
|
|
8
|
+
jlong reserve(alias_ref<jclass>, jint size) {
|
|
9
|
+
if (size <= 0) throw std::invalid_argument("Invalid Android RGBA allocation size");
|
|
10
|
+
return static_cast<jlong>(MemoryBudget::instance().reserve(static_cast<uint64_t>(size) + 7, "android-frame-rgba"));
|
|
11
|
+
}
|
|
12
|
+
local_ref<JByteBuffer> alias(alias_ref<jclass>, alias_ref<JByteBuffer> backing) {
|
|
13
|
+
auto* env = Environment::current();
|
|
14
|
+
const auto size = env->GetDirectBufferCapacity(backing.get());
|
|
15
|
+
auto* bytes = env->GetDirectBufferAddress(backing.get());
|
|
16
|
+
if (!bytes || size <= 0 || size > std::numeric_limits<jint>::max())
|
|
17
|
+
throw std::invalid_argument("Invalid Android direct pixel buffer");
|
|
18
|
+
auto buffer = env->NewDirectByteBuffer(bytes, size);
|
|
19
|
+
if (env->ExceptionCheck()) throwPendingJniExceptionAsCppException();
|
|
20
|
+
if (!buffer) throw std::bad_alloc();
|
|
21
|
+
return adopt_local(static_cast<JByteBuffer::javaobject>(buffer));
|
|
22
|
+
}
|
|
23
|
+
void release(alias_ref<jclass>, jlong token) {
|
|
24
|
+
MemoryBudget::instance().release(static_cast<uint64_t>(token));
|
|
25
|
+
}
|
|
26
|
+
local_ref<JArrayLong> stats(alias_ref<jclass>) {
|
|
27
|
+
const auto snapshot = MemoryBudget::instance().snapshot();
|
|
28
|
+
auto result = JArrayLong::newArray(4);
|
|
29
|
+
const jlong values[] = {static_cast<jlong>(snapshot.currentBytes),
|
|
30
|
+
static_cast<jlong>(snapshot.peakBytes), static_cast<jlong>(snapshot.allocations),
|
|
31
|
+
static_cast<jlong>(snapshot.maxBytes)};
|
|
32
|
+
result->setRegion(0, 4, values);
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
void configure(alias_ref<jclass>, jlong bytes) {
|
|
36
|
+
if (bytes <= 0) throw std::invalid_argument("Invalid Android memory limit");
|
|
37
|
+
MemoryBudget::instance().configure(bytes);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
void NativeRgbaBuffer::registerNatives() {
|
|
41
|
+
javaClassStatic()->registerNatives({makeNativeMethod("nativeReserve", reserve),
|
|
42
|
+
makeNativeMethod("nativeAlias", alias), makeNativeMethod("nativeRelease", release),
|
|
43
|
+
makeNativeMethod("nativeBudgetStats", stats), makeNativeMethod("nativeConfigureBudget", configure)});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <fbjni/fbjni.h>
|
|
3
|
+
#include <fbjni/ByteBuffer.h>
|
|
4
|
+
namespace RNSkiaVideo {
|
|
5
|
+
struct NativeRgbaBuffer : facebook::jni::JavaClass<NativeRgbaBuffer> {
|
|
6
|
+
static constexpr auto kJavaDescriptor = "Lcom/azzapp/rnskv/NativeRgbaBuffer;";
|
|
7
|
+
static void registerNatives();
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "VideoFrame.h"
|
|
3
|
+
#include <android/log.h>
|
|
4
|
+
#include <exception>
|
|
5
|
+
|
|
6
|
+
namespace RNSkiaVideo {
|
|
7
|
+
|
|
8
|
+
// Every Java public map owns independent wrapper leases, including cache hits.
|
|
9
|
+
// Keep cleanup tied to the whole map, not only frames converted successfully.
|
|
10
|
+
class ReturnedVideoFrames final {
|
|
11
|
+
public:
|
|
12
|
+
explicit ReturnedVideoFrames(alias_ref<JMap<JString, VideoFrame>> frames) : frames_(frames) {}
|
|
13
|
+
~ReturnedVideoFrames() {
|
|
14
|
+
if (!closed_) {
|
|
15
|
+
try { close(); }
|
|
16
|
+
catch (const std::exception& error) {
|
|
17
|
+
__android_log_print(ANDROID_LOG_ERROR, "SkiaVideo", "Returned-frame cleanup failed: %s", error.what());
|
|
18
|
+
} catch (...) {
|
|
19
|
+
__android_log_print(ANDROID_LOG_ERROR, "SkiaVideo", "Returned-frame cleanup failed");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
void close() {
|
|
24
|
+
if (closed_) return;
|
|
25
|
+
std::exception_ptr failure;
|
|
26
|
+
for (auto& entry : *frames_) {
|
|
27
|
+
try { auto frame = entry.second; frame->close(); }
|
|
28
|
+
catch (...) { if (!failure) failure = std::current_exception(); }
|
|
29
|
+
}
|
|
30
|
+
closed_ = true;
|
|
31
|
+
if (failure) std::rethrow_exception(failure);
|
|
32
|
+
}
|
|
33
|
+
private:
|
|
34
|
+
alias_ref<JMap<JString, VideoFrame>> frames_;
|
|
35
|
+
bool closed_ = false;
|
|
36
|
+
};
|
|
37
|
+
} // namespace RNSkiaVideo
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstddef>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
#include <memory>
|
|
7
|
+
#include <mutex>
|
|
8
|
+
#include <stdexcept>
|
|
9
|
+
|
|
10
|
+
namespace RNSkiaVideo {
|
|
11
|
+
|
|
12
|
+
struct RgbaFrameStorage {
|
|
13
|
+
virtual ~RgbaFrameStorage() = default;
|
|
14
|
+
virtual size_t size() const = 0;
|
|
15
|
+
virtual uint8_t* data() = 0;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Closing a frame drops its own storage root. Explicit external aliases may
|
|
19
|
+
// still hold the same immutable allocation, and keep it alive independently.
|
|
20
|
+
class RgbaFrameLease final {
|
|
21
|
+
public:
|
|
22
|
+
explicit RgbaFrameLease(std::shared_ptr<RgbaFrameStorage> storage)
|
|
23
|
+
: storage_(std::move(storage)) {
|
|
24
|
+
if (!storage_ || !storage_->data() || storage_->size() == 0)
|
|
25
|
+
throw std::invalid_argument("Invalid RGBA frame storage");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
std::shared_ptr<RgbaFrameStorage> storage() const {
|
|
29
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
30
|
+
return storage_;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
void copyTo(uint8_t* destination, size_t capacity) const {
|
|
34
|
+
auto source = storage();
|
|
35
|
+
if (!source) throw std::runtime_error("RGBA video frame is disposed");
|
|
36
|
+
if (!destination || capacity < source->size())
|
|
37
|
+
throw std::invalid_argument("RGBA copy destination is too small");
|
|
38
|
+
// An explicit consumer may pass a retained source alias as destination.
|
|
39
|
+
// memmove also handles that overlap without undefined behavior.
|
|
40
|
+
std::memmove(destination, source->data(), source->size());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
void dispose() {
|
|
44
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
45
|
+
storage_.reset();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private:
|
|
49
|
+
mutable std::mutex mutex_;
|
|
50
|
+
std::shared_ptr<RgbaFrameStorage> storage_;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
} // namespace RNSkiaVideo
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Created by François de Campredon on 11/06/2024.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
#include "VideoCapabilities.h"
|
|
6
|
+
|
|
7
|
+
namespace RNSkiaVideo {
|
|
8
|
+
jint DecoderInfo::getMaxInstances() const {
|
|
9
|
+
static const auto getMaxInstancesMethod =
|
|
10
|
+
getClass()->getMethod<jint()>("getMaxInstances");
|
|
11
|
+
return getMaxInstancesMethod(self());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
jint DecoderInfo::getMaxWidth() const {
|
|
15
|
+
static const auto getMaxWidthMethod =
|
|
16
|
+
getClass()->getMethod<jint()>("getMaxWidth");
|
|
17
|
+
return getMaxWidthMethod(self());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
jint DecoderInfo::getMaxHeight() const {
|
|
21
|
+
static const auto getMaxHeightMethod =
|
|
22
|
+
getClass()->getMethod<jint()>("getMaxHeight");
|
|
23
|
+
return getMaxHeightMethod(self());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
std::string EncoderInfo::getEncoderName() const {
|
|
27
|
+
static const auto getEncoderNameMethod =
|
|
28
|
+
getClass()->getMethod<jstring()>("getEncoderName");
|
|
29
|
+
return getEncoderNameMethod(self())->toStdString();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
jboolean EncoderInfo::getHardwareAccelerated() const {
|
|
33
|
+
static const auto getHardwareAcceleratedMethod =
|
|
34
|
+
getClass()->getMethod<jboolean()>("getHardwareAccelerated");
|
|
35
|
+
return getHardwareAcceleratedMethod(self());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
jint EncoderInfo::getWidth() const {
|
|
39
|
+
static const auto getWidthMethod = getClass()->getMethod<jint()>("getWidth");
|
|
40
|
+
return getWidthMethod(self());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
jint EncoderInfo::getHeight() const {
|
|
44
|
+
static const auto getHeightMethod =
|
|
45
|
+
getClass()->getMethod<jint()>("getHeight");
|
|
46
|
+
return getHeightMethod(self());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
jint EncoderInfo::getFrameRate() const {
|
|
50
|
+
static const auto getFrameRateMethod =
|
|
51
|
+
getClass()->getMethod<jint()>("getFrameRate");
|
|
52
|
+
return getFrameRateMethod(self());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
jint EncoderInfo::getBitrate() const {
|
|
56
|
+
static const auto getBitrateMethod =
|
|
57
|
+
getClass()->getMethod<jint()>("getBitrate");
|
|
58
|
+
return getBitrateMethod(self());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
jni::local_ref<DecoderInfo>
|
|
62
|
+
VideoCapabilities::getDecodingCapabilitiesFor(std::string mimeType) {
|
|
63
|
+
static const auto cls = javaClassStatic();
|
|
64
|
+
static const auto getDecodingCapabilitiesForMethod =
|
|
65
|
+
cls->getStaticMethod<jni::local_ref<DecoderInfo>(
|
|
66
|
+
jni::alias_ref<JString>)>("getDecodingCapabilitiesFor");
|
|
67
|
+
return getDecodingCapabilitiesForMethod(cls, jni::make_jstring(mimeType));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
jni::local_ref<JList<EncoderInfo>>
|
|
71
|
+
VideoCapabilities::getValidEncoderConfigurations(
|
|
72
|
+
int width, int height, int framerate, int bitrate,
|
|
73
|
+
std::optional<std::string> codec) {
|
|
74
|
+
static const auto cls = javaClassStatic();
|
|
75
|
+
|
|
76
|
+
static const auto getValidEncoderConfigurationsMethod =
|
|
77
|
+
cls->getStaticMethod<jni::local_ref<JList<EncoderInfo>>(
|
|
78
|
+
jint, jint, jint, jint, jstring)>("getValidEncoderConfigurations");
|
|
79
|
+
// Nullable on the Java side, so built as a jstring ref: handing over a
|
|
80
|
+
// std::string built from nullptr would call strlen(nullptr).
|
|
81
|
+
auto codecRef = codec.has_value() ? make_jstring(codec.value())
|
|
82
|
+
: local_ref<JString>(nullptr);
|
|
83
|
+
return getValidEncoderConfigurationsMethod(cls, width, height, framerate,
|
|
84
|
+
bitrate, codecRef.get());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
} // namespace RNSkiaVideo
|