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.
- 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/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 +35 -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 +83 -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 +772 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractor.java +354 -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 +348 -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 +85 -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 +154 -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 +520 -0
- package/src/videoFrameImage.ts +205 -0
- package/src/videoPlayer.ts +318 -0
- 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,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,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
|
+
}
|