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,492 @@
|
|
|
1
|
+
package com.azzapp.rnskv;
|
|
2
|
+
|
|
3
|
+
import android.media.MediaCodec;
|
|
4
|
+
import android.media.MediaCodecInfo.CodecProfileLevel;
|
|
5
|
+
import android.media.MediaCodecList;
|
|
6
|
+
import android.media.MediaExtractor;
|
|
7
|
+
import android.media.MediaFormat;
|
|
8
|
+
import android.os.Build;
|
|
9
|
+
import android.os.Handler;
|
|
10
|
+
import android.os.Looper;
|
|
11
|
+
import android.view.Surface;
|
|
12
|
+
|
|
13
|
+
import androidx.annotation.NonNull;
|
|
14
|
+
|
|
15
|
+
import java.io.IOException;
|
|
16
|
+
import java.nio.ByteBuffer;
|
|
17
|
+
import java.util.ArrayList;
|
|
18
|
+
import java.util.List;
|
|
19
|
+
import java.util.Stack;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A class that decodes a video item from a video composition asynchronously.
|
|
23
|
+
*/
|
|
24
|
+
public class VideoCompositionItemDecoder extends MediaCodec.Callback {
|
|
25
|
+
|
|
26
|
+
private final VideoComposition.Item item;
|
|
27
|
+
|
|
28
|
+
private MediaExtractor extractor;
|
|
29
|
+
|
|
30
|
+
private MediaCodec codec;
|
|
31
|
+
|
|
32
|
+
private MediaFormat format;
|
|
33
|
+
|
|
34
|
+
private boolean inputEOS = false;
|
|
35
|
+
|
|
36
|
+
private boolean hasRenderedFrame = false;
|
|
37
|
+
|
|
38
|
+
private boolean itemEndReached = false;
|
|
39
|
+
|
|
40
|
+
private int videoWidth;
|
|
41
|
+
|
|
42
|
+
private int videoHeight;
|
|
43
|
+
|
|
44
|
+
private int rotation;
|
|
45
|
+
|
|
46
|
+
private boolean prepared = false;
|
|
47
|
+
|
|
48
|
+
private boolean configured = false;
|
|
49
|
+
|
|
50
|
+
private boolean started = false;
|
|
51
|
+
|
|
52
|
+
private volatile boolean released = false;
|
|
53
|
+
|
|
54
|
+
private Surface surface;
|
|
55
|
+
|
|
56
|
+
private long initialPositionUs = 0;
|
|
57
|
+
|
|
58
|
+
private Handler callbackHandler;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Set by a seek until the codec's thread has run every callback queued
|
|
62
|
+
* before the flush: those name buffers the flush took back, and handling
|
|
63
|
+
* them after it hands out or drops the buffers of the new position.
|
|
64
|
+
*/
|
|
65
|
+
private boolean droppingStaleCallbacks = false;
|
|
66
|
+
|
|
67
|
+
private long flushGeneration = 0;
|
|
68
|
+
|
|
69
|
+
private final Stack<Frame> freeFrames = new Stack<>();
|
|
70
|
+
|
|
71
|
+
private final List<Frame> pendingFrames = new ArrayList<>();
|
|
72
|
+
|
|
73
|
+
private OnErrorListener onErrorListener;
|
|
74
|
+
|
|
75
|
+
private OnEndReachedListener onEndReachedListener;
|
|
76
|
+
|
|
77
|
+
private OnFrameAvailableListener onFrameAvailableListener;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Create a new VideoCompositionItemDecoder.
|
|
81
|
+
*
|
|
82
|
+
* @param item the video composition item to decode
|
|
83
|
+
*/
|
|
84
|
+
public VideoCompositionItemDecoder(VideoComposition.Item item) {
|
|
85
|
+
this.item = item;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Prepare the decoder.
|
|
90
|
+
*
|
|
91
|
+
* @throws IOException if the decoder cannot be prepared
|
|
92
|
+
*/
|
|
93
|
+
synchronized public void prepare() throws IOException {
|
|
94
|
+
if (prepared) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
extractor = new MediaExtractor();
|
|
98
|
+
extractor.setDataSource(item.getPath());
|
|
99
|
+
int trackIndex = selectTrack(extractor);
|
|
100
|
+
if (trackIndex == -1) {
|
|
101
|
+
throw new RuntimeException("No video track");
|
|
102
|
+
}
|
|
103
|
+
format = extractor.getTrackFormat(trackIndex);
|
|
104
|
+
String mime = format.getString(MediaFormat.KEY_MIME);
|
|
105
|
+
if (mime == null) {
|
|
106
|
+
throw new IOException("Could not determine file mime type");
|
|
107
|
+
}
|
|
108
|
+
mime = playableMime(format, mime);
|
|
109
|
+
codec = MediaCodec.createDecoderByType(mime);
|
|
110
|
+
extractor.selectTrack(trackIndex);
|
|
111
|
+
// Capped at the item's end, as on iOS.
|
|
112
|
+
long offsetUs = Math.min(
|
|
113
|
+
Math.max(initialPositionUs - TimeHelpers.secToUs(item.getCompositionStartTime()), 0),
|
|
114
|
+
TimeHelpers.secToUs(item.getDuration()));
|
|
115
|
+
if (item.getStartTime() != 0 || offsetUs != 0) {
|
|
116
|
+
extractor.seekTo(
|
|
117
|
+
TimeHelpers.secToUs(item.getStartTime()) + offsetUs,
|
|
118
|
+
MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
|
|
119
|
+
}
|
|
120
|
+
videoWidth = format.getInteger(MediaFormat.KEY_WIDTH);
|
|
121
|
+
videoHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
|
|
122
|
+
rotation = format.containsKey(MediaFormat.KEY_ROTATION) ? format.getInteger(MediaFormat.KEY_ROTATION) : 0;
|
|
123
|
+
prepared = true;
|
|
124
|
+
configure();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Start the decoder
|
|
129
|
+
*/
|
|
130
|
+
synchronized public void start() {
|
|
131
|
+
if (!prepared || !configured || started) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
codec.start();
|
|
135
|
+
started = true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Set the surface to render the video to.
|
|
140
|
+
*
|
|
141
|
+
* @param surface the surface to render the video to
|
|
142
|
+
*/
|
|
143
|
+
public void setSurface(Surface surface) {
|
|
144
|
+
this.surface = surface;
|
|
145
|
+
configure();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Where decoding starts, as a composition time, for a decoder opened after
|
|
151
|
+
* its item has begun. Must be called before {@link #prepare}.
|
|
152
|
+
*/
|
|
153
|
+
public void setInitialPosition(long compositionTimeUs) {
|
|
154
|
+
initialPositionUs = compositionTimeUs;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The thread the codec calls back on, instead of the one that configures it.
|
|
159
|
+
* Must be called before {@link #setSurface}.
|
|
160
|
+
*/
|
|
161
|
+
public void setCallbackHandler(Handler callbackHandler) {
|
|
162
|
+
this.callbackHandler = callbackHandler;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public void setOnErrorListener(OnErrorListener onErrorListener) {
|
|
166
|
+
this.onErrorListener = onErrorListener;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public void setOnFrameAvailableListener(OnFrameAvailableListener onFrameAvailableListener) {
|
|
170
|
+
this.onFrameAvailableListener = onFrameAvailableListener;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public void setOnEndReachedListener(OnEndReachedListener onEndReachedListener) {
|
|
174
|
+
this.onEndReachedListener = onEndReachedListener;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @return the video composition item
|
|
179
|
+
*/
|
|
180
|
+
public VideoComposition.Item getItem() {
|
|
181
|
+
return item;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @return the width of the decoded video in pixels after rotation
|
|
186
|
+
*/
|
|
187
|
+
/**
|
|
188
|
+
* Whether the track decodes to 8-bit SDR pictures: only those go through
|
|
189
|
+
* ImageReader's YUV 4:2:0 buffers; HDR and 10-bit keep the GL path, whose
|
|
190
|
+
* SurfaceTexture handles their formats.
|
|
191
|
+
*/
|
|
192
|
+
public synchronized boolean isEightBitSdr() {
|
|
193
|
+
if (format == null) return false;
|
|
194
|
+
if (format.containsKey(MediaFormat.KEY_COLOR_TRANSFER)) {
|
|
195
|
+
int transfer = format.getInteger(MediaFormat.KEY_COLOR_TRANSFER);
|
|
196
|
+
if (transfer == MediaFormat.COLOR_TRANSFER_HLG || transfer == MediaFormat.COLOR_TRANSFER_ST2084)
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
if (format.containsKey(MediaFormat.KEY_COLOR_STANDARD) &&
|
|
200
|
+
format.getInteger(MediaFormat.KEY_COLOR_STANDARD) == MediaFormat.COLOR_STANDARD_BT2020)
|
|
201
|
+
return false;
|
|
202
|
+
String mime = format.getString(MediaFormat.KEY_MIME);
|
|
203
|
+
if (!MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime) && !MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime))
|
|
204
|
+
return false;
|
|
205
|
+
if (format.containsKey(MediaFormat.KEY_PROFILE)) {
|
|
206
|
+
int profile = format.getInteger(MediaFormat.KEY_PROFILE);
|
|
207
|
+
if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime))
|
|
208
|
+
return profile == android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain;
|
|
209
|
+
return profile != android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileHigh10 &&
|
|
210
|
+
profile != android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileHigh422 &&
|
|
211
|
+
profile != android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileHigh444;
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public int getVideoWidth() {
|
|
217
|
+
return videoWidth;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @return the height of the decoded video in pixels after rotation
|
|
222
|
+
*/
|
|
223
|
+
public int getVideoHeight() {
|
|
224
|
+
return videoHeight;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @return the rotation of the decoded video in degrees
|
|
229
|
+
*/
|
|
230
|
+
public int getRotation() {
|
|
231
|
+
return rotation;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@Override
|
|
235
|
+
synchronized public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
|
|
236
|
+
if (!prepared || !configured || released || droppingStaleCallbacks) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (inputEOS || itemEndReached) {
|
|
241
|
+
try {
|
|
242
|
+
this.codec.queueInputBuffer(index, 0, 0, 0,
|
|
243
|
+
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
|
244
|
+
} catch (Throwable e) {}
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
ByteBuffer inputBuffer;
|
|
249
|
+
try {
|
|
250
|
+
inputBuffer = this.codec.getInputBuffer(index);
|
|
251
|
+
} catch (Throwable e) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (inputBuffer == null) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
int sampleSize = extractor.readSampleData(inputBuffer, 0);
|
|
259
|
+
if (sampleSize <= 0) {
|
|
260
|
+
this.codec.queueInputBuffer(index, 0, 0, 0,
|
|
261
|
+
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
this.codec.queueInputBuffer(
|
|
266
|
+
index,
|
|
267
|
+
0,
|
|
268
|
+
sampleSize,
|
|
269
|
+
extractor.getSampleTime(),
|
|
270
|
+
extractor.getSampleFlags()
|
|
271
|
+
);
|
|
272
|
+
} catch (Throwable e) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
extractor.advance();
|
|
276
|
+
inputEOS = extractor.getSampleTime() == -1;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@Override
|
|
280
|
+
synchronized public void onOutputBufferAvailable(
|
|
281
|
+
@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
|
|
282
|
+
if (released || droppingStaleCallbacks) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
boolean outputEOS = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
|
|
286
|
+
boolean sampleOutOfBounds =
|
|
287
|
+
info.presentationTimeUs > TimeHelpers.secToUs(item.getStartTime() + item.getDuration());
|
|
288
|
+
boolean sampleBeforeStartTime =
|
|
289
|
+
info.presentationTimeUs < TimeHelpers.secToUs(item.getStartTime());
|
|
290
|
+
|
|
291
|
+
// Surface output has no accessible ByteBuffer: getOutputBuffer returns
|
|
292
|
+
// null by contract. Its callback index and timestamp name the frame to
|
|
293
|
+
// render; probing the buffer would strand it and stall the decoder.
|
|
294
|
+
if (!itemEndReached && SurfaceOutputPolicy.hasFrame(info.size, info.flags)
|
|
295
|
+
&& !sampleOutOfBounds && !sampleBeforeStartTime) {
|
|
296
|
+
Frame frame = getFreeFrame();
|
|
297
|
+
frame.outputBufferIndex = index;
|
|
298
|
+
frame.presentationTimeUs = info.presentationTimeUs;
|
|
299
|
+
pendingFrames.add(frame);
|
|
300
|
+
if (onFrameAvailableListener != null) {
|
|
301
|
+
onFrameAvailableListener.onFrameAvailable(frame.presentationTimeUs);
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
try {
|
|
305
|
+
codec.releaseOutputBuffer(index, false);
|
|
306
|
+
} catch (Throwable e) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
itemEndReached = outputEOS || sampleOutOfBounds;
|
|
312
|
+
if (itemEndReached) {
|
|
313
|
+
if (onEndReachedListener != null) {
|
|
314
|
+
onEndReachedListener.onEndReached();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
@Override
|
|
320
|
+
public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
|
|
321
|
+
if (released) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (onErrorListener != null) {
|
|
325
|
+
onErrorListener.onError(e);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
@Override
|
|
330
|
+
public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
|
|
331
|
+
// Do nothing
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
synchronized public Long render(long compositionTimeUs) {
|
|
335
|
+
if (pendingFrames.isEmpty()) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
long compositionStartTimeUs = TimeHelpers.secToUs(item.getCompositionStartTime());
|
|
339
|
+
long startTimeUs = TimeHelpers.secToUs(item.getStartTime());
|
|
340
|
+
|
|
341
|
+
List<Frame> framesToRenders = new ArrayList<>();
|
|
342
|
+
for (Frame frame : pendingFrames) {
|
|
343
|
+
if (frame.presentationTimeUs - startTimeUs <= compositionTimeUs - compositionStartTimeUs || !hasRenderedFrame) {
|
|
344
|
+
framesToRenders.add(frame);
|
|
345
|
+
hasRenderedFrame = true;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (framesToRenders.isEmpty()) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
// Only the latest is drawn: after a seek the batch runs from the previous
|
|
352
|
+
// key frame, and drawing each one into the surface is GPU work nobody sees.
|
|
353
|
+
int lastIndex = framesToRenders.size() - 1;
|
|
354
|
+
for (int i = 0; i <= lastIndex; i++) {
|
|
355
|
+
try {
|
|
356
|
+
codec.releaseOutputBuffer(framesToRenders.get(i).outputBufferIndex, i == lastIndex);
|
|
357
|
+
} catch (Throwable e) {
|
|
358
|
+
// A codec flushed or released meanwhile: the buffer is gone with it.
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
freeFrames.addAll(framesToRenders);
|
|
362
|
+
pendingFrames.removeAll(framesToRenders);
|
|
363
|
+
|
|
364
|
+
return framesToRenders.get(framesToRenders.size() - 1).presentationTimeUs;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Seek to a specific time in the video.
|
|
369
|
+
*
|
|
370
|
+
* @param time the position in the composition timeline, in microseconds
|
|
371
|
+
*/
|
|
372
|
+
synchronized public void seekTo(long time) {
|
|
373
|
+
freeFrames.addAll(pendingFrames);
|
|
374
|
+
pendingFrames.clear();
|
|
375
|
+
codec.flush();
|
|
376
|
+
// Queued behind the callbacks from before the flush, and ahead of those
|
|
377
|
+
// start() brings: they run on this handler's thread in order.
|
|
378
|
+
if (callbackHandler != null) {
|
|
379
|
+
droppingStaleCallbacks = true;
|
|
380
|
+
long generation = ++flushGeneration;
|
|
381
|
+
callbackHandler.post(() -> {
|
|
382
|
+
synchronized (this) {
|
|
383
|
+
if (flushGeneration == generation) {
|
|
384
|
+
droppingStaleCallbacks = false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
long itemTime = time - TimeHelpers.secToUs(item.getCompositionStartTime());
|
|
390
|
+
long seekTime = TimeHelpers.secToUs(item.getStartTime()) + Math.max(itemTime, 0);
|
|
391
|
+
extractor.seekTo(seekTime, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
|
|
392
|
+
itemEndReached = false;
|
|
393
|
+
hasRenderedFrame = false;
|
|
394
|
+
inputEOS = false;
|
|
395
|
+
if (started) {
|
|
396
|
+
codec.start();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Release the decoder.
|
|
402
|
+
*/
|
|
403
|
+
synchronized public void release() {
|
|
404
|
+
if (!released) {
|
|
405
|
+
released = true;
|
|
406
|
+
if (extractor != null) {
|
|
407
|
+
extractor.release();
|
|
408
|
+
}
|
|
409
|
+
if (codec != null) {
|
|
410
|
+
codec.release();
|
|
411
|
+
codec = null;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private synchronized void configure() {
|
|
417
|
+
if (prepared && surface != null && !configured) {
|
|
418
|
+
if (callbackHandler == null) {
|
|
419
|
+
// Where MediaCodec would call back without a handler; seekTo posts to it.
|
|
420
|
+
Looper looper = Looper.myLooper();
|
|
421
|
+
callbackHandler = new Handler(looper != null ? looper : Looper.getMainLooper());
|
|
422
|
+
}
|
|
423
|
+
codec.setCallback(this, callbackHandler);
|
|
424
|
+
codec.configure(format, surface, null, 0);
|
|
425
|
+
configured = true;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* The mime type to decode the track as. A Dolby Vision track on a device
|
|
431
|
+
* without a Dolby Vision decoder is decoded as its base layer: HDR clips
|
|
432
|
+
* from an iPhone are profile 8.4, Dolby Vision metadata over an HLG HEVC
|
|
433
|
+
* stream any HEVC decoder plays; profile 9 is over AVC. Sets the format to
|
|
434
|
+
* match.
|
|
435
|
+
*/
|
|
436
|
+
private static String playableMime(MediaFormat format, String mime) {
|
|
437
|
+
if (!MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION.equals(mime)
|
|
438
|
+
|| new MediaCodecList(MediaCodecList.REGULAR_CODECS).findDecoderForFormat(format) != null) {
|
|
439
|
+
return mime;
|
|
440
|
+
}
|
|
441
|
+
int profile = format.containsKey(MediaFormat.KEY_PROFILE)
|
|
442
|
+
? format.getInteger(MediaFormat.KEY_PROFILE) : -1;
|
|
443
|
+
String base = profile == CodecProfileLevel.DolbyVisionProfileDvavSe
|
|
444
|
+
|| profile == CodecProfileLevel.DolbyVisionProfileDvavPer
|
|
445
|
+
|| profile == CodecProfileLevel.DolbyVisionProfileDvavPen
|
|
446
|
+
? MediaFormat.MIMETYPE_VIDEO_AVC
|
|
447
|
+
: MediaFormat.MIMETYPE_VIDEO_HEVC;
|
|
448
|
+
format.setString(MediaFormat.KEY_MIME, base);
|
|
449
|
+
// The Dolby Vision profile and level mean nothing to the base decoder.
|
|
450
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
451
|
+
format.removeKey(MediaFormat.KEY_PROFILE);
|
|
452
|
+
format.removeKey(MediaFormat.KEY_LEVEL);
|
|
453
|
+
}
|
|
454
|
+
return base;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private static int selectTrack(MediaExtractor extractor) {
|
|
458
|
+
int numTracks = extractor.getTrackCount();
|
|
459
|
+
for (int i = 0; i < numTracks; i++) {
|
|
460
|
+
MediaFormat format = extractor.getTrackFormat(i);
|
|
461
|
+
String mime = format.getString(MediaFormat.KEY_MIME);
|
|
462
|
+
if (mime != null && mime.startsWith("video/")) {
|
|
463
|
+
return i;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return -1;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private Frame getFreeFrame() {
|
|
470
|
+
return freeFrames.empty() ? new Frame() : freeFrames.pop();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
public interface OnErrorListener {
|
|
474
|
+
void onError(Exception error);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
public interface OnFrameAvailableListener {
|
|
478
|
+
void onFrameAvailable(long presentationTimeUs);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
public interface OnEndReachedListener {
|
|
482
|
+
void onEndReached();
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* A frame representing a decoded output buffer sample.
|
|
487
|
+
*/
|
|
488
|
+
private static class Frame {
|
|
489
|
+
private int outputBufferIndex;
|
|
490
|
+
private long presentationTimeUs;
|
|
491
|
+
}
|
|
492
|
+
}
|