react-native-skia-webgpu-video 0.0.0-stage → 0.1.0-beta.0

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.
Files changed (236) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +303 -2
  3. package/android/BUILD_VALIDATION.md +62 -0
  4. package/android/CMakeLists.txt +99 -0
  5. package/android/build-validation.json +262 -0
  6. package/android/build.gradle +185 -0
  7. package/android/cpp/EncoderTargets.cpp +226 -0
  8. package/android/cpp/EncoderTargets.h +70 -0
  9. package/android/cpp/JNIHelpers.cpp +15 -0
  10. package/android/cpp/JNIHelpers.h +20 -0
  11. package/android/cpp/NativeEventDispatcher.cpp +59 -0
  12. package/android/cpp/NativeEventDispatcher.h +31 -0
  13. package/android/cpp/NativeHardwareBuffer.cpp +397 -0
  14. package/android/cpp/NativeHardwareBuffer.h +34 -0
  15. package/android/cpp/NativeRgbaBuffer.cpp +45 -0
  16. package/android/cpp/NativeRgbaBuffer.h +9 -0
  17. package/android/cpp/ReturnedVideoFrames.h +37 -0
  18. package/android/cpp/RgbaFrameLease.h +53 -0
  19. package/android/cpp/VideoCapabilities.cpp +87 -0
  20. package/android/cpp/VideoCapabilities.h +44 -0
  21. package/android/cpp/VideoComposition.cpp +170 -0
  22. package/android/cpp/VideoComposition.h +36 -0
  23. package/android/cpp/VideoCompositionFramesExtractor.cpp +82 -0
  24. package/android/cpp/VideoCompositionFramesExtractor.h +48 -0
  25. package/android/cpp/VideoCompositionFramesExtractorHostObject.cpp +230 -0
  26. package/android/cpp/VideoCompositionFramesExtractorHostObject.h +42 -0
  27. package/android/cpp/VideoCompositionFramesExtractorSyncHostObject.cpp +133 -0
  28. package/android/cpp/VideoCompositionFramesExtractorSyncHostObject.h +46 -0
  29. package/android/cpp/VideoEncoderHostObject.cpp +318 -0
  30. package/android/cpp/VideoEncoderHostObject.h +76 -0
  31. package/android/cpp/VideoFrame.cpp +293 -0
  32. package/android/cpp/VideoFrame.h +27 -0
  33. package/android/cpp/VideoPlayer.cpp +96 -0
  34. package/android/cpp/VideoPlayer.h +52 -0
  35. package/android/cpp/VideoPlayerHostObject.cpp +225 -0
  36. package/android/cpp/VideoPlayerHostObject.h +43 -0
  37. package/android/cpp/cpp-adapter.cpp +316 -0
  38. package/android/gradle.properties +5 -0
  39. package/android/instrumentation-inventory.json +178 -0
  40. package/android/proguard-rules.pro +4 -0
  41. package/android/src/main/AndroidManifest.xml +2 -0
  42. package/android/src/main/java/com/azzapp/rnskv/AudioCompositionExporter.java +537 -0
  43. package/android/src/main/java/com/azzapp/rnskv/AudioCompositionPlayer.java +321 -0
  44. package/android/src/main/java/com/azzapp/rnskv/BufferLifetime.java +55 -0
  45. package/android/src/main/java/com/azzapp/rnskv/DecodeRequests.java +100 -0
  46. package/android/src/main/java/com/azzapp/rnskv/DecoderWindow.java +55 -0
  47. package/android/src/main/java/com/azzapp/rnskv/EGLResourcesHolder.java +132 -0
  48. package/android/src/main/java/com/azzapp/rnskv/EGLUtils.java +225 -0
  49. package/android/src/main/java/com/azzapp/rnskv/FrameSize.java +50 -0
  50. package/android/src/main/java/com/azzapp/rnskv/GLFrameExtractor.java +510 -0
  51. package/android/src/main/java/com/azzapp/rnskv/JNIHelpers.java +12 -0
  52. package/android/src/main/java/com/azzapp/rnskv/NativeEventDispatcher.java +25 -0
  53. package/android/src/main/java/com/azzapp/rnskv/NativeHardwareBuffer.java +62 -0
  54. package/android/src/main/java/com/azzapp/rnskv/NativeRgbaBuffer.java +35 -0
  55. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoModule.java +45 -0
  56. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoPackage.java +43 -0
  57. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoSpec.java +9 -0
  58. package/android/src/main/java/com/azzapp/rnskv/RgbaBufferAllocator.java +83 -0
  59. package/android/src/main/java/com/azzapp/rnskv/RgbaLayout.java +33 -0
  60. package/android/src/main/java/com/azzapp/rnskv/SurfaceOutputPolicy.java +13 -0
  61. package/android/src/main/java/com/azzapp/rnskv/TextureRenderer.java +150 -0
  62. package/android/src/main/java/com/azzapp/rnskv/TimeHelpers.java +11 -0
  63. package/android/src/main/java/com/azzapp/rnskv/VideoCapabilities.java +354 -0
  64. package/android/src/main/java/com/azzapp/rnskv/VideoComposition.java +147 -0
  65. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionDecoder.java +772 -0
  66. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractor.java +354 -0
  67. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractorSync.java +241 -0
  68. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionItemDecoder.java +492 -0
  69. package/android/src/main/java/com/azzapp/rnskv/VideoEncoder.java +639 -0
  70. package/android/src/main/java/com/azzapp/rnskv/VideoFrame.java +74 -0
  71. package/android/src/main/java/com/azzapp/rnskv/VideoPlayer.java +361 -0
  72. package/cpp/RNSVCheckedSizes.h +54 -0
  73. package/cpp/RNSVEventEmitter.cpp +199 -0
  74. package/cpp/RNSVEventEmitter.h +31 -0
  75. package/cpp/RNSVHostObject.cpp +79 -0
  76. package/cpp/RNSVHostObject.h +67 -0
  77. package/cpp/RNSVMemoryBudget.cpp +68 -0
  78. package/cpp/RNSVMemoryBudget.h +100 -0
  79. package/cpp/RNSVRuntimeLifecycleMonitor.cpp +88 -0
  80. package/cpp/RNSVRuntimeLifecycleMonitor.h +33 -0
  81. package/docs/ANDROID_DEVICE_306.md +95 -0
  82. package/docs/ANDROID_EXPORT_CACHE_306.md +48 -0
  83. package/docs/ANDROID_MEMORY_RETENTION_306.md +67 -0
  84. package/docs/BENCHMARKS.md +368 -0
  85. package/docs/IOS_DEVICE_TESTS.md +115 -0
  86. package/docs/IOS_SIMULATOR_TESTS.md +74 -0
  87. package/docs/MIGRATION.md +82 -0
  88. package/docs/NATIVE_MEMORY_INVESTIGATION_306.md +88 -0
  89. package/docs/PERFORMANCE_AB_IPHONE_303.md +196 -0
  90. package/docs/PERFORMANCE_AB_SIMULATOR.md +135 -0
  91. package/docs/PERFORMANCE_AB_SOAK_SIMULATOR.md +84 -0
  92. package/docs/PERFORMANCE_IPHONE_306_OPTIMIZATION.md +76 -0
  93. package/docs/PERFORMANCE_SIMULATOR.md +89 -0
  94. package/docs/PERFORMANCE_SIMULATOR_INITIAL.md +96 -0
  95. package/docs/PERFORMANCE_SOAK_IPHONE_303.md +41 -0
  96. package/docs/UPSTREAM_NOTES.md +188 -0
  97. package/docs/assets/banner.svg +26 -0
  98. package/examples/webgpu-overlay.ts +147 -0
  99. package/ios/AVAssetTrackUtils.h +24 -0
  100. package/ios/AudioCompositionUtils.h +27 -0
  101. package/ios/AudioCompositionUtils.mm +113 -0
  102. package/ios/DecoderWindow.h +52 -0
  103. package/ios/NativeResourceStats.h +32 -0
  104. package/ios/RNSVColorSpace.h +50 -0
  105. package/ios/RNSVJSIUtils.h +46 -0
  106. package/ios/RNSVScopedPixelBuffer.h +17 -0
  107. package/ios/RNSVVideoPlayer.h +46 -0
  108. package/ios/RNSVVideoPlayer.mm +410 -0
  109. package/ios/ReactNativeSkiaVideo.h +8 -0
  110. package/ios/ReactNativeSkiaVideo.mm +310 -0
  111. package/ios/SeekPolicy.h +27 -0
  112. package/ios/VideoComposition.h +148 -0
  113. package/ios/VideoCompositionFramesExtractorHostObject.h +105 -0
  114. package/ios/VideoCompositionFramesExtractorHostObject.mm +686 -0
  115. package/ios/VideoCompositionFramesExtractorSyncHostObject.h +37 -0
  116. package/ios/VideoCompositionFramesExtractorSyncHostObject.mm +168 -0
  117. package/ios/VideoCompositionItemDecoder.h +60 -0
  118. package/ios/VideoCompositionItemDecoder.mm +480 -0
  119. package/ios/VideoEncoderHostObject.h +83 -0
  120. package/ios/VideoEncoderHostObject.mm +706 -0
  121. package/ios/VideoFrame.h +66 -0
  122. package/ios/VideoFrame.mm +108 -0
  123. package/ios/VideoPlayerHostObject.h +52 -0
  124. package/ios/VideoPlayerHostObject.mm +332 -0
  125. package/lib/module/NativeReactNativeSkiaVideo.js +5 -0
  126. package/lib/module/NativeReactNativeSkiaVideo.js.map +1 -0
  127. package/lib/module/RNSkiaVideoModule.js +20 -0
  128. package/lib/module/RNSkiaVideoModule.js.map +1 -0
  129. package/lib/module/canvas.js +18 -0
  130. package/lib/module/canvas.js.map +1 -0
  131. package/lib/module/exportCache.js +22 -0
  132. package/lib/module/exportCache.js.map +1 -0
  133. package/lib/module/exportFrames.js +338 -0
  134. package/lib/module/exportFrames.js.map +1 -0
  135. package/lib/module/exportQueue.js +52 -0
  136. package/lib/module/exportQueue.js.map +1 -0
  137. package/lib/module/exportTarget.js +137 -0
  138. package/lib/module/exportTarget.js.map +1 -0
  139. package/lib/module/exportVideoComposition.js +492 -0
  140. package/lib/module/exportVideoComposition.js.map +1 -0
  141. package/lib/module/frameInterop.js +532 -0
  142. package/lib/module/frameInterop.js.map +1 -0
  143. package/lib/module/gpu.js +222 -0
  144. package/lib/module/gpu.js.map +1 -0
  145. package/lib/module/gpuDevice.js +54 -0
  146. package/lib/module/gpuDevice.js.map +1 -0
  147. package/lib/module/index.js +36 -0
  148. package/lib/module/index.js.map +1 -0
  149. package/lib/module/memory.js +38 -0
  150. package/lib/module/memory.js.map +1 -0
  151. package/lib/module/package.json +1 -0
  152. package/lib/module/previewLifecycle.js +52 -0
  153. package/lib/module/previewLifecycle.js.map +1 -0
  154. package/lib/module/runtimeYield.js +10 -0
  155. package/lib/module/runtimeYield.js.map +1 -0
  156. package/lib/module/types.js +4 -0
  157. package/lib/module/types.js.map +1 -0
  158. package/lib/module/utils/useEventListener.js +18 -0
  159. package/lib/module/utils/useEventListener.js.map +1 -0
  160. package/lib/module/videoCompositionPlayer.js +348 -0
  161. package/lib/module/videoCompositionPlayer.js.map +1 -0
  162. package/lib/module/videoFrameImage.js +185 -0
  163. package/lib/module/videoFrameImage.js.map +1 -0
  164. package/lib/module/videoPlayer.js +172 -0
  165. package/lib/module/videoPlayer.js.map +1 -0
  166. package/lib/module/yuvBlit.js +299 -0
  167. package/lib/module/yuvBlit.js.map +1 -0
  168. package/lib/typescript/package.json +1 -0
  169. package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts +7 -0
  170. package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts.map +1 -0
  171. package/lib/typescript/src/RNSkiaVideoModule.d.ts +4 -0
  172. package/lib/typescript/src/RNSkiaVideoModule.d.ts.map +1 -0
  173. package/lib/typescript/src/canvas.d.ts +4 -0
  174. package/lib/typescript/src/canvas.d.ts.map +1 -0
  175. package/lib/typescript/src/exportCache.d.ts +6 -0
  176. package/lib/typescript/src/exportCache.d.ts.map +1 -0
  177. package/lib/typescript/src/exportFrames.d.ts +57 -0
  178. package/lib/typescript/src/exportFrames.d.ts.map +1 -0
  179. package/lib/typescript/src/exportQueue.d.ts +5 -0
  180. package/lib/typescript/src/exportQueue.d.ts.map +1 -0
  181. package/lib/typescript/src/exportTarget.d.ts +28 -0
  182. package/lib/typescript/src/exportTarget.d.ts.map +1 -0
  183. package/lib/typescript/src/exportVideoComposition.d.ts +68 -0
  184. package/lib/typescript/src/exportVideoComposition.d.ts.map +1 -0
  185. package/lib/typescript/src/frameInterop.d.ts +17 -0
  186. package/lib/typescript/src/frameInterop.d.ts.map +1 -0
  187. package/lib/typescript/src/gpu.d.ts +39 -0
  188. package/lib/typescript/src/gpu.d.ts.map +1 -0
  189. package/lib/typescript/src/gpuDevice.d.ts +11 -0
  190. package/lib/typescript/src/gpuDevice.d.ts.map +1 -0
  191. package/lib/typescript/src/index.d.ts +21 -0
  192. package/lib/typescript/src/index.d.ts.map +1 -0
  193. package/lib/typescript/src/memory.d.ts +25 -0
  194. package/lib/typescript/src/memory.d.ts.map +1 -0
  195. package/lib/typescript/src/previewLifecycle.d.ts +22 -0
  196. package/lib/typescript/src/previewLifecycle.d.ts.map +1 -0
  197. package/lib/typescript/src/runtimeYield.d.ts +4 -0
  198. package/lib/typescript/src/runtimeYield.d.ts.map +1 -0
  199. package/lib/typescript/src/types.d.ts +631 -0
  200. package/lib/typescript/src/types.d.ts.map +1 -0
  201. package/lib/typescript/src/utils/useEventListener.d.ts +5 -0
  202. package/lib/typescript/src/utils/useEventListener.d.ts.map +1 -0
  203. package/lib/typescript/src/videoCompositionPlayer.d.ts +85 -0
  204. package/lib/typescript/src/videoCompositionPlayer.d.ts.map +1 -0
  205. package/lib/typescript/src/videoFrameImage.d.ts +60 -0
  206. package/lib/typescript/src/videoFrameImage.d.ts.map +1 -0
  207. package/lib/typescript/src/videoPlayer.d.ts +99 -0
  208. package/lib/typescript/src/videoPlayer.d.ts.map +1 -0
  209. package/lib/typescript/src/yuvBlit.d.ts +50 -0
  210. package/lib/typescript/src/yuvBlit.d.ts.map +1 -0
  211. package/package.json +154 -4
  212. package/patches/react-native-skia+3.7.0.patch +502 -0
  213. package/react-native-skia-webgpu-video.podspec +35 -0
  214. package/scripts/apply-skia-canvas-patch.mjs +48 -0
  215. package/scripts/skia-canvas-patch.json +291 -0
  216. package/src/NativeReactNativeSkiaVideo.ts +8 -0
  217. package/src/RNSkiaVideoModule.ts +27 -0
  218. package/src/canvas.ts +22 -0
  219. package/src/exportCache.ts +27 -0
  220. package/src/exportFrames.ts +421 -0
  221. package/src/exportQueue.ts +65 -0
  222. package/src/exportTarget.ts +179 -0
  223. package/src/exportVideoComposition.ts +718 -0
  224. package/src/frameInterop.ts +684 -0
  225. package/src/gpu.ts +290 -0
  226. package/src/gpuDevice.ts +80 -0
  227. package/src/index.tsx +45 -0
  228. package/src/memory.ts +48 -0
  229. package/src/previewLifecycle.ts +60 -0
  230. package/src/runtimeYield.ts +6 -0
  231. package/src/types.ts +661 -0
  232. package/src/utils/useEventListener.ts +22 -0
  233. package/src/videoCompositionPlayer.ts +520 -0
  234. package/src/videoFrameImage.ts +205 -0
  235. package/src/videoPlayer.ts +318 -0
  236. package/src/yuvBlit.ts +331 -0
@@ -0,0 +1,43 @@
1
+ package com.azzapp.rnskv;
2
+
3
+ import androidx.annotation.Nullable;
4
+
5
+ import com.facebook.react.bridge.NativeModule;
6
+ import com.facebook.react.bridge.ReactApplicationContext;
7
+ import com.facebook.react.module.model.ReactModuleInfo;
8
+ import com.facebook.react.module.model.ReactModuleInfoProvider;
9
+ import com.facebook.react.BaseReactPackage;
10
+
11
+ import java.util.HashMap;
12
+ import java.util.Map;
13
+
14
+ public class ReactNativeSkiaVideoPackage extends BaseReactPackage {
15
+
16
+ @Nullable
17
+ @Override
18
+ public NativeModule getModule(String name, ReactApplicationContext reactContext) {
19
+ if (name.equals(ReactNativeSkiaVideoModule.NAME)) {
20
+ return new ReactNativeSkiaVideoModule(reactContext);
21
+ } else {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ @Override
27
+ public ReactModuleInfoProvider getReactModuleInfoProvider() {
28
+ return () -> {
29
+ final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
30
+ moduleInfos.put(
31
+ ReactNativeSkiaVideoModule.NAME,
32
+ new ReactModuleInfo(
33
+ ReactNativeSkiaVideoModule.NAME,
34
+ ReactNativeSkiaVideoModule.NAME,
35
+ false, // canOverrideExistingModule
36
+ false, // needsEagerInit
37
+ false, // isCxxModule
38
+ true // isTurboModule
39
+ ));
40
+ return moduleInfos;
41
+ };
42
+ }
43
+ }
@@ -0,0 +1,9 @@
1
+ package com.azzapp.rnskv;
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext;
4
+
5
+ abstract class ReactNativeSkiaVideoSpec extends NativeReactNativeSkiaVideoSpec {
6
+ ReactNativeSkiaVideoSpec(ReactApplicationContext context) {
7
+ super(context);
8
+ }
9
+ }
@@ -0,0 +1,83 @@
1
+ package com.azzapp.rnskv;
2
+ import java.nio.ByteBuffer;
3
+ import java.util.function.Function;
4
+ import java.util.function.IntFunction;
5
+ import java.util.function.IntConsumer;
6
+ import java.util.function.IntToLongFunction;
7
+ import java.util.function.LongConsumer;
8
+
9
+ /** Allocation transaction registers its budget owner before allocating any pixel storage. */
10
+ final class RgbaBufferAllocator {
11
+ private final BufferLifetime lifetimes;
12
+ private final IntToLongFunction reserve;
13
+ private final LongConsumer release;
14
+ private final IntFunction<ByteBuffer> allocateBacking;
15
+ private final Function<ByteBuffer, ByteBuffer> alias;
16
+ private final IntConsumer checkHeap;
17
+ RgbaBufferAllocator(BufferLifetime lifetimes, IntToLongFunction reserve, LongConsumer release,
18
+ IntFunction<ByteBuffer> allocateBacking, Function<ByteBuffer, ByteBuffer> alias) {
19
+ this(lifetimes, reserve, release, allocateBacking, alias, size -> {});
20
+ }
21
+ RgbaBufferAllocator(BufferLifetime lifetimes, IntToLongFunction reserve, LongConsumer release,
22
+ IntFunction<ByteBuffer> allocateBacking, Function<ByteBuffer, ByteBuffer> alias, IntConsumer checkHeap) {
23
+ this.lifetimes = lifetimes; this.reserve = reserve; this.release = release;
24
+ this.allocateBacking = allocateBacking; this.alias = alias; this.checkHeap = checkHeap;
25
+ }
26
+
27
+ /** Refuse a single backing too large for ART's heap and cleanup headroom.
28
+ * Do not subtract currently occupied heap: allocateDirect must be allowed to
29
+ * trigger ART's natural GC and reclaim unreachable backing owners. A free-heap
30
+ * precheck would stop streaming before that GC, leaving reclaimable buffers live.
31
+ * The native quota and narrow allocation-OOM catch remain the actual limits. */
32
+ static void checkAllocationCapacity(int size, long maxHeap) {
33
+ if (size <= 0) throw new IllegalArgumentException("Invalid RGBA allocation size");
34
+ if (maxHeap <= 0) throw new IllegalStateException("Android heap capacity is unavailable");
35
+ long headroom = Math.min(16L * 1024 * 1024, maxHeap / 16);
36
+ long available = Math.max(0, maxHeap - headroom);
37
+ if ((long) size + 7 > available) {
38
+ throw new IllegalStateException("Android RGBA memory budget exceeds single-allocation Java heap capacity; "
39
+ + "reduce maxLongSide or use lazyDecoders (requested=" + ((long) size + 7)
40
+ + ", available=" + available + ", headroom=" + headroom + ")");
41
+ }
42
+ }
43
+
44
+ ByteBuffer allocate(int size) {
45
+ lifetimes.drain();
46
+ checkHeap.accept(size);
47
+ long token = reserve.applyAsLong(size);
48
+ Allocation owner;
49
+ try {
50
+ owner = new Allocation();
51
+ // Pre-register before the large allocation. Alias/registration failures
52
+ // remain accounted until this owner and its backing have been collected.
53
+ lifetimes.track(owner, null, token);
54
+ } catch (Throwable error) {
55
+ // No pixel backing has been allocated at this point.
56
+ release.accept(token);
57
+ throw error;
58
+ }
59
+ // Re-check any injected capacity policy after the reservation; concurrent heap
60
+ // use itself is handled by ART allocation/GC and the narrow OOM catch below.
61
+ checkHeap.accept(size);
62
+ try {
63
+ owner.backing = allocateBacking.apply(size);
64
+ } catch (OutOfMemoryError firstError) {
65
+ // ART may have queued dead JNI roots during its allocation GC. Drain them
66
+ // before one retry, so their backing owners can be reclaimed by that next
67
+ // natural allocation GC. Live aliases are not queued and remain untouched.
68
+ lifetimes.drain();
69
+ try {
70
+ owner.backing = allocateBacking.apply(size);
71
+ } catch (OutOfMemoryError finalError) {
72
+ // Retry only the backing allocation, once, with the same reserved owner.
73
+ // No alias escaped. Never return its token early while storage might live.
74
+ throw new IllegalStateException("Android RGBA memory budget exhausted during allocation; "
75
+ + "reduce maxLongSide or use lazyDecoders (requested=" + size + ", attempts=2)", finalError);
76
+ }
77
+ }
78
+ ByteBuffer exposed = alias.apply(owner.backing);
79
+ lifetimes.track(exposed, owner, 0);
80
+ return exposed;
81
+ }
82
+ private static final class Allocation { ByteBuffer backing; }
83
+ }
@@ -0,0 +1,33 @@
1
+ package com.azzapp.rnskv;
2
+
3
+ /** Checked byte arithmetic shared by readback and encoder upload. */
4
+ final class RgbaLayout {
5
+ private RgbaLayout() {}
6
+
7
+ static int bytesPerRow(int width) {
8
+ if (width <= 0 || width > Integer.MAX_VALUE / 4) {
9
+ throw new IllegalArgumentException("Invalid RGBA width: " + width);
10
+ }
11
+ return width * 4;
12
+ }
13
+
14
+ static int byteSize(int width, int height) {
15
+ int stride = bytesPerRow(width);
16
+ if (height <= 0 || height > Integer.MAX_VALUE / stride) {
17
+ throw new IllegalArgumentException("Invalid RGBA height: " + height);
18
+ }
19
+ return stride * height;
20
+ }
21
+
22
+ static int requiredBytes(int width, int height, int stride) {
23
+ int packedStride = bytesPerRow(width);
24
+ if (stride < packedStride || height <= 0) {
25
+ throw new IllegalArgumentException("Invalid RGBA stride or height");
26
+ }
27
+ long bytes = (long) stride * (height - 1) + packedStride;
28
+ if (bytes > Integer.MAX_VALUE) {
29
+ throw new IllegalArgumentException("RGBA upload exceeds Android ByteBuffer limits");
30
+ }
31
+ return (int) bytes;
32
+ }
33
+ }
@@ -0,0 +1,13 @@
1
+ package com.azzapp.rnskv;
2
+
3
+ import android.media.MediaCodec;
4
+
5
+ /** Surface frames can have no byte payload; an empty EOS is only a stream marker. */
6
+ final class SurfaceOutputPolicy {
7
+ private SurfaceOutputPolicy() { }
8
+
9
+ static boolean hasFrame(int size, int flags) {
10
+ return (flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0
11
+ && (size > 0 || (flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == 0);
12
+ }
13
+ }
@@ -0,0 +1,150 @@
1
+ package com.azzapp.rnskv;
2
+
3
+ import android.opengl.GLES11Ext;
4
+ import android.opengl.GLES20;
5
+
6
+ import java.nio.FloatBuffer;
7
+
8
+ /**
9
+ * A class that renders a texture.
10
+ */
11
+ public class TextureRenderer {
12
+
13
+ private static final String VERTEX_SHADER =
14
+ """
15
+ attribute vec4 aFramePosition;
16
+ attribute vec4 aTexCoords;
17
+ uniform mat4 uTexTransform;
18
+ varying vec2 vTexCoords;
19
+ void main() {
20
+ gl_Position = aFramePosition;
21
+ vTexCoords = (uTexTransform * aTexCoords).xy;
22
+ }
23
+ """;
24
+
25
+ private static final String FRAGMENT_SHADER =
26
+ """
27
+ precision highp float;
28
+ uniform sampler2D uTexSampler;
29
+ varying vec2 vTexCoords;
30
+ void main() {
31
+ gl_FragColor = texture2D(uTexSampler, vTexCoords);
32
+ }
33
+ """;
34
+
35
+ private static final String FRAGMENT_SHADER_EXTERNAL =
36
+ """
37
+ #extension GL_OES_EGL_image_external : require
38
+ precision highp float;
39
+ uniform samplerExternalOES uTexSampler;
40
+ varying vec2 vTexCoords;
41
+ void main() {
42
+ gl_FragColor = texture2D(uTexSampler, vTexCoords);
43
+ }
44
+ """;
45
+
46
+ private static final FloatBuffer POS_VERTICES = EGLUtils.createFloatBuffer(
47
+ -1f, -1f, 0f, 1f,
48
+ 1f, -1f, 0f, 1f,
49
+ -1f, 1f, 0f, 1f,
50
+ 1f, 1f, 0f, 1f
51
+ );
52
+
53
+ private static final FloatBuffer TEX_VERTICES = EGLUtils.createFloatBuffer(
54
+ 0f, 1f, 0f, 1f,
55
+ 1f, 1f, 0f, 1f,
56
+ 0f, 0f, 0f, 1f,
57
+ 1f, 0f, 0f, 1f
58
+ );
59
+
60
+ private final int program;
61
+
62
+ private final int aFramePositionLoc;
63
+
64
+ private final int aTexCoordsLoc;
65
+
66
+ private final int uTexTransformLoc;
67
+
68
+ private final int uTexSamplerLoc;
69
+
70
+ boolean external = false;
71
+
72
+
73
+ public TextureRenderer() {
74
+ this(false);
75
+ }
76
+
77
+ /**
78
+ * Create a new TextureRenderer.
79
+ */
80
+ public TextureRenderer(boolean external) {
81
+ this.external = external;
82
+ program = EGLUtils.createProgram(
83
+ VERTEX_SHADER,
84
+ external ? FRAGMENT_SHADER_EXTERNAL : FRAGMENT_SHADER
85
+ );
86
+
87
+ aFramePositionLoc = GLES20.glGetAttribLocation(
88
+ program,
89
+ "aFramePosition"
90
+ );
91
+ aTexCoordsLoc = GLES20.glGetAttribLocation(
92
+ program,
93
+ "aTexCoords"
94
+ );
95
+ uTexTransformLoc = GLES20.glGetUniformLocation(
96
+ program,
97
+ "uTexTransform"
98
+ );
99
+
100
+ uTexSamplerLoc = GLES20.glGetUniformLocation(
101
+ program,
102
+ "uTexSampler"
103
+ );
104
+ }
105
+
106
+ /**
107
+ * Draw the texture.
108
+ *
109
+ * @param textureId the texture to draw
110
+ * @param transformMatrix the texture transform matrix to apply
111
+ * (usually the texture matrix from a SurfaceTexture)
112
+ */
113
+ public void draw(
114
+ int textureId,
115
+ float[] transformMatrix
116
+ ) {
117
+ GLES20.glUseProgram(program);
118
+
119
+ GLES20.glEnableVertexAttribArray(aFramePositionLoc);
120
+ GLES20.glVertexAttribPointer(
121
+ aFramePositionLoc, 4, GLES20.GL_FLOAT, false, 0,
122
+ POS_VERTICES
123
+ );
124
+
125
+ GLES20.glEnableVertexAttribArray(aTexCoordsLoc);
126
+ GLES20.glVertexAttribPointer(
127
+ aTexCoordsLoc, 4, GLES20.GL_FLOAT, false, 0,
128
+ TEX_VERTICES
129
+ );
130
+
131
+ GLES20.glUniformMatrix4fv(uTexTransformLoc, 1, false, transformMatrix, 0);
132
+
133
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
134
+ GLES20.glBindTexture(
135
+ external ? GLES11Ext.GL_TEXTURE_EXTERNAL_OES : GLES20.GL_TEXTURE_2D, textureId);
136
+ GLES20.glUniform1i(uTexSamplerLoc, 0);
137
+
138
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
139
+
140
+ GLES20.glDisableVertexAttribArray(aFramePositionLoc);
141
+ GLES20.glDisableVertexAttribArray(aTexCoordsLoc);
142
+ }
143
+
144
+ /**
145
+ * Release the resources. (delete the program)
146
+ */
147
+ public void release() {
148
+ GLES20.glDeleteProgram(program);
149
+ }
150
+ }
@@ -0,0 +1,11 @@
1
+ package com.azzapp.rnskv;
2
+
3
+ public class TimeHelpers {
4
+ public static long secToUs(double sec) {
5
+ return Math.round(sec * 1000000);
6
+ }
7
+
8
+ public static long nsecToUs(long nsec) {
9
+ return Math.round(nsec / 1000);
10
+ }
11
+ }
@@ -0,0 +1,354 @@
1
+ package com.azzapp.rnskv;
2
+
3
+ import android.media.MediaCodec;
4
+ import android.media.MediaCodecInfo;
5
+ import android.media.MediaCodecList;
6
+ import android.media.MediaFormat;
7
+ import android.os.Build;
8
+ import android.util.Log;
9
+ import android.util.Range;
10
+
11
+ import java.util.ArrayList;
12
+ import java.util.Comparator;
13
+ import java.util.List;
14
+
15
+ public class VideoCapabilities {
16
+
17
+ public static class DecoderInfo {
18
+ private int maxInstances;
19
+ private int maxWidth;
20
+ private int maxHeight;
21
+
22
+ public DecoderInfo(int maxInstances, int maxWidth, int maxHeight) {
23
+ this.maxInstances = maxInstances;
24
+ this.maxWidth = maxWidth;
25
+ this.maxHeight = maxHeight;
26
+ }
27
+
28
+ public int getMaxInstances() {
29
+ return maxInstances;
30
+ }
31
+
32
+ public int getMaxWidth() {
33
+ return maxWidth;
34
+ }
35
+
36
+ public int getMaxHeight() {
37
+ return maxHeight;
38
+ }
39
+ }
40
+
41
+
42
+ public static DecoderInfo getDecodingCapabilitiesFor(String mimeType) {
43
+ MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
44
+ MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
45
+
46
+ for (MediaCodecInfo codecInfo : codecInfos) {
47
+ if (codecInfo.isEncoder()) {
48
+ // Decoding capabilities: only decoders are relevant here.
49
+ continue;
50
+ }
51
+ MediaCodecInfo.CodecCapabilities capabilities;
52
+ try {
53
+ capabilities = codecInfo.getCapabilitiesForType(mimeType);
54
+ } catch (IllegalArgumentException e) {
55
+ continue;
56
+ }
57
+ if (capabilities != null) {
58
+ MediaCodecInfo.VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities();
59
+ if (videoCapabilities != null) {
60
+ int maxInstances = capabilities.getMaxSupportedInstances();
61
+ int maxWidth = videoCapabilities.getSupportedWidths().getUpper();
62
+ int maxHeight = videoCapabilities.getSupportedHeights().getUpper();
63
+ return new DecoderInfo(maxInstances, maxWidth, maxHeight);
64
+ }
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+
70
+
71
+ public static class EncoderInfo {
72
+ private final String encoderName;
73
+
74
+ private final boolean hardwareAccelerated;
75
+
76
+ private final int width;
77
+
78
+ private final int height;
79
+
80
+ private final int frameRate;
81
+
82
+ private final int bitrate;
83
+
84
+ public EncoderInfo(
85
+ String encoderName,
86
+ boolean hardWareAccelerated,
87
+ int width,
88
+ int height,
89
+ int frameRate,
90
+ int bitrate
91
+ ) {
92
+ this.encoderName = encoderName;
93
+ this.hardwareAccelerated = hardWareAccelerated;
94
+ this.width = width;
95
+ this.height = height;
96
+ this.frameRate = frameRate;
97
+ this.bitrate = bitrate;
98
+ }
99
+
100
+ public String getEncoderName() {
101
+ return encoderName;
102
+ }
103
+
104
+ public boolean getHardwareAccelerated() {
105
+ return hardwareAccelerated;
106
+ }
107
+
108
+ public int getWidth() {
109
+ return width;
110
+ }
111
+
112
+ public int getHeight() {
113
+ return height;
114
+ }
115
+
116
+ public int getFrameRate() {
117
+ return frameRate;
118
+ }
119
+
120
+ public int getBitrate() {
121
+ return bitrate;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Returns the configurations the platform encoders will actually accept,
127
+ * closest to the requested one.
128
+ *
129
+ * @param width the requested width
130
+ * @param height the requested height
131
+ * @param frameRate the requested frame rate in frames per second
132
+ * @param bitRate the requested bit rate in bits per second
133
+ * @param codec the codec to probe encoders for, "h264" or "hevc"; null
134
+ * means H.264. A codec the device cannot encode falls back
135
+ * to H.264, so the answer always describes the codec an
136
+ * export with the same argument would really use.
137
+ * @return the valid configurations, empty if none were found
138
+ */
139
+ public static List<EncoderInfo> getValidEncoderConfigurations(
140
+ int width,
141
+ int height,
142
+ int frameRate,
143
+ int bitRate,
144
+ String codec
145
+ ) {
146
+ List<EncoderInfo> encoderInfos = new ArrayList<>();
147
+
148
+ // Probed with the mime type the export will encode with: sizes, frame rates
149
+ // and bitrates accepted by an H.264 encoder say nothing about what the HEVC
150
+ // one on the same chip will take.
151
+ String mimeType = VideoEncoder.resolveMimeType(codec);
152
+
153
+ boolean rotated = height > width;
154
+
155
+ List<MediaCodecInfoWithOverrides> mediaCodecInfoWithOverrides =
156
+ getPotentialEncoders(mimeType,
157
+ rotated ? height : width, rotated ? width : height,
158
+ frameRate, bitRate);
159
+
160
+ for (MediaCodecInfoWithOverrides codecInfoWithOverrides : mediaCodecInfoWithOverrides) {
161
+ MediaFormat format;
162
+ if (rotated) {
163
+ format = MediaFormat.createVideoFormat(mimeType,
164
+ codecInfoWithOverrides.height, codecInfoWithOverrides.width);
165
+ } else {
166
+ format = MediaFormat.createVideoFormat(mimeType,
167
+ codecInfoWithOverrides.width, codecInfoWithOverrides.height);
168
+ }
169
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
170
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
171
+ format.setInteger(MediaFormat.KEY_BIT_RATE, codecInfoWithOverrides.bitrate);
172
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, VideoEncoder.DEFAULT_I_FRAME_INTERVAL_SECONDS);
173
+
174
+ // While framerate can be marked as supported it can in fact not be the case
175
+ // so we need to create the codec to see if there is no error and progressively decrease the frame rate
176
+ int foundFrameRate = codecInfoWithOverrides.frameRate;
177
+ while (true) {
178
+ MediaCodec encoder = null;
179
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, foundFrameRate);
180
+ try {
181
+ encoder = MediaCodec.createByCodecName(codecInfoWithOverrides.codecInfo.getName());
182
+ encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
183
+ } catch (Exception e) {
184
+ if (encoder != null) {
185
+ encoder = null;
186
+ }
187
+ }
188
+ if (encoder != null) {
189
+ encoder.release();
190
+ encoderInfos.add(new EncoderInfo(
191
+ codecInfoWithOverrides.codecInfo.getName(),
192
+ codecInfoWithOverrides.hardwareAccelerated == 1,
193
+ rotated ? codecInfoWithOverrides.height : codecInfoWithOverrides.width,
194
+ rotated ? codecInfoWithOverrides.width : codecInfoWithOverrides.height,
195
+ foundFrameRate,
196
+ codecInfoWithOverrides.bitrate
197
+ ));
198
+ break;
199
+ } else if (foundFrameRate > 45) {
200
+ foundFrameRate = 45;
201
+ } else if (foundFrameRate > 30) {
202
+ foundFrameRate = 30;
203
+ } else {
204
+ break;
205
+ }
206
+ }
207
+ }
208
+ return encoderInfos;
209
+ }
210
+
211
+ private record MediaCodecInfoWithOverrides(
212
+ MediaCodecInfo codecInfo,
213
+ int width,
214
+ int height,
215
+ int bitrate,
216
+ int frameRate,
217
+
218
+ int hardwareAccelerated,
219
+ int aspectRatioOverride,
220
+ int resolutionOverride,
221
+ int frameRateOverride,
222
+ int bitrateOverride
223
+ ) {
224
+ }
225
+
226
+
227
+ private static List<MediaCodecInfoWithOverrides> getPotentialEncoders(
228
+ String mimeType,
229
+ int width,
230
+ int height,
231
+ int frameRate,
232
+ int bitrate
233
+ ) {
234
+
235
+ MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
236
+ MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
237
+
238
+ List<MediaCodecInfoWithOverrides> codecInfoWithOverrides = new ArrayList<>();
239
+
240
+ for (MediaCodecInfo codecInfo : codecInfos) {
241
+ if (!codecInfo.isEncoder()) {
242
+ continue;
243
+ }
244
+
245
+ MediaCodecInfo.CodecCapabilities capabilities;
246
+ try {
247
+ capabilities = codecInfo.getCapabilitiesForType(mimeType);
248
+ } catch (IllegalArgumentException e) {
249
+ continue;
250
+ }
251
+
252
+ MediaCodecInfo.VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities();
253
+
254
+ // Initialize with the original parameters
255
+ int currentWidth = width;
256
+ int currentHeight = height;
257
+ int currentBitrate = bitrate;
258
+ int currentFrameRate = frameRate;
259
+ int resolutionOverride = 0;
260
+ int aspectRatioOverride = 0;
261
+ int frameRateOverride = 0;
262
+ int bitrateOverride = 0;
263
+ int hardwareAccelerated = isHardwareAccelerated(codecInfo) ? 1 : 0;
264
+
265
+ float aspectRatio = (float) width / height;
266
+
267
+ if (!videoCapabilities.isSizeSupported(width, height)) {
268
+ Range<Integer> supportedWidths = videoCapabilities.getSupportedWidths();
269
+ int resizedWidth = supportedWidths.clamp(currentWidth);
270
+ int resizedWidthHeight = Math.round(resizedWidth / aspectRatio);
271
+ resizedWidthHeight = resizedWidthHeight - resizedWidthHeight % 2;
272
+ int potentialInvalidHeight = videoCapabilities.getSupportedHeightsFor(resizedWidth)
273
+ .clamp(resizedWidthHeight);
274
+ float potentialWidthBasedAspectRatioOverrides =
275
+ Math.abs(aspectRatio - (float) resizedWidth / potentialInvalidHeight);
276
+
277
+ Range<Integer> supportedHeights = videoCapabilities.getSupportedHeights();
278
+ int resizedHeight = supportedHeights.clamp(currentHeight);
279
+ int resizedHeightWidth = Math.round(resizedHeight * aspectRatio);
280
+ resizedHeightWidth = resizedHeightWidth - resizedHeightWidth % 2;
281
+ int potentialInvalidWidth = videoCapabilities.getSupportedWidthsFor(resizedHeight)
282
+ .clamp(resizedHeightWidth);
283
+ float potentialHeightBasedAspectRatioOverrides =
284
+ Math.abs(aspectRatio - (float) potentialInvalidWidth / resizedHeight);
285
+
286
+ if (videoCapabilities.isSizeSupported(resizedWidth, resizedWidthHeight)) {
287
+ currentWidth = resizedWidth;
288
+ currentHeight = resizedWidthHeight;
289
+ } else if (videoCapabilities.isSizeSupported(resizedHeightWidth, resizedHeight)) {
290
+ currentWidth = resizedHeightWidth;
291
+ currentHeight = resizedHeight;
292
+ } else if (potentialWidthBasedAspectRatioOverrides < 0.001 || potentialHeightBasedAspectRatioOverrides < 0.001) {
293
+ // Slight aspect ratio overrides might be acceptable in some cases
294
+ if (potentialWidthBasedAspectRatioOverrides < potentialHeightBasedAspectRatioOverrides) {
295
+ currentWidth = resizedWidth;
296
+ currentHeight = potentialInvalidHeight;
297
+ } else {
298
+ currentWidth = potentialInvalidWidth;
299
+ currentHeight = resizedHeight;
300
+ }
301
+ aspectRatioOverride = 1;
302
+ } else {
303
+ continue;
304
+ }
305
+ resolutionOverride = 1;
306
+ }
307
+
308
+ if (!videoCapabilities.areSizeAndRateSupported(currentWidth, currentHeight, currentFrameRate)) {
309
+ Range<Double> frameRateRange = videoCapabilities.getSupportedFrameRatesFor(currentWidth, currentHeight);
310
+ currentFrameRate = (int) Math.floor(frameRateRange.clamp((double) currentFrameRate));
311
+ frameRateOverride = 1;
312
+ }
313
+
314
+ if (!videoCapabilities.getBitrateRange().contains(currentBitrate)) {
315
+ Range<Integer> bitrateRange = videoCapabilities.getBitrateRange();
316
+ currentBitrate = bitrateRange.clamp(currentBitrate);
317
+ bitrateOverride = 1;
318
+ }
319
+
320
+ codecInfoWithOverrides.add(new MediaCodecInfoWithOverrides(
321
+ codecInfo,
322
+ currentWidth,
323
+ currentHeight,
324
+ currentBitrate,
325
+ currentFrameRate,
326
+ hardwareAccelerated,
327
+ aspectRatioOverride,
328
+ resolutionOverride,
329
+ frameRateOverride,
330
+ bitrateOverride
331
+ ));
332
+ }
333
+
334
+ // Sort the list based on resolution, frame rate, and bitrate overrides
335
+ codecInfoWithOverrides.sort(Comparator
336
+ .comparingInt(MediaCodecInfoWithOverrides::aspectRatioOverride)
337
+ .thenComparingInt(MediaCodecInfoWithOverrides::hardwareAccelerated).reversed()
338
+ .thenComparingInt(MediaCodecInfoWithOverrides::resolutionOverride)
339
+ .thenComparingInt(MediaCodecInfoWithOverrides::frameRateOverride)
340
+ .thenComparingInt(MediaCodecInfoWithOverrides::bitrateOverride));
341
+
342
+ return codecInfoWithOverrides;
343
+ }
344
+
345
+ private static boolean isHardwareAccelerated(MediaCodecInfo codecInfo) {
346
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
347
+ return codecInfo.isHardwareAccelerated();
348
+ } else {
349
+ // Use heuristics for older Android versions
350
+ String name = codecInfo.getName().toLowerCase();
351
+ return name.contains("qcom") || name.contains("intel") || name.contains("nvidia") || name.contains("mtk");
352
+ }
353
+ }
354
+ }