react-native-skia-webgpu-video 0.1.0-beta.2 → 0.1.0-beta.3
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 +0 -1
- package/README.md +28 -7
- package/android/cpp/VideoComposition.cpp +12 -0
- package/android/cpp/VideoCompositionFramesExtractor.cpp +6 -0
- package/android/cpp/VideoCompositionFramesExtractor.h +1 -0
- package/android/cpp/VideoCompositionFramesExtractorHostObject.cpp +1 -1
- package/android/src/main/java/com/azzapp/rnskv/PreviewFrameRateVote.java +104 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoComposition.java +4 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionDecoder.java +4 -2
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractor.java +32 -0
- package/docs/FRAME_PIXEL_FORMAT.md +124 -0
- package/docs/VERIFICATION_0.1.0-beta.3.md +147 -0
- package/ios/NativeResourceStats.h +9 -0
- package/ios/RNSVColorSpace.h +35 -4
- package/ios/ReactNativeSkiaVideo.mm +4 -0
- package/ios/VideoComposition.h +20 -0
- package/ios/VideoCompositionFramesExtractorHostObject.h +5 -0
- package/ios/VideoCompositionFramesExtractorHostObject.mm +31 -2
- package/ios/VideoCompositionItemDecoder.mm +21 -8
- package/ios/VideoFrame.mm +6 -0
- package/lib/module/exportFrames.js +25 -18
- package/lib/module/exportFrames.js.map +1 -1
- package/lib/module/frameInterop.js +7 -1
- package/lib/module/frameInterop.js.map +1 -1
- package/lib/module/videoCompositionPlayer.js +13 -5
- package/lib/module/videoCompositionPlayer.js.map +1 -1
- package/lib/module/yuvBlit.js +70 -0
- package/lib/module/yuvBlit.js.map +1 -1
- package/lib/typescript/src/exportFrames.d.ts +8 -8
- package/lib/typescript/src/exportFrames.d.ts.map +1 -1
- package/lib/typescript/src/frameInterop.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +26 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/videoCompositionPlayer.d.ts.map +1 -1
- package/lib/typescript/src/yuvBlit.d.ts +10 -0
- package/lib/typescript/src/yuvBlit.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/exportFrames.ts +52 -34
- package/src/frameInterop.ts +18 -1
- package/src/types.ts +27 -0
- package/src/videoCompositionPlayer.ts +14 -4
- package/src/yuvBlit.ts +66 -0
package/src/types.ts
CHANGED
|
@@ -149,6 +149,26 @@ export type VideoPlayer = {
|
|
|
149
149
|
on(name: "error", listener: (error: any) => void): () => void;
|
|
150
150
|
};
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* The pixel format decoded frames are handed over in, before they are drawn.
|
|
154
|
+
*
|
|
155
|
+
* - `yuv`: the decoder's own YUV 4:2:0 buffers, converted to RGB on the GPU
|
|
156
|
+
* when a frame is drawn. A frame takes 1.5 bytes a pixel rather than 4
|
|
157
|
+
* (12.4 MB rather than 33.2 MB in 4K), so the decoder holds more frames
|
|
158
|
+
* ahead for the same memory. On Android only 8-bit SDR tracks decoded at
|
|
159
|
+
* their own size; other tracks stay `rgba`.
|
|
160
|
+
* - `rgba`: frames converted to RGB as they are decoded (iOS: by VideoToolbox,
|
|
161
|
+
* Android: by an OpenGL pass), which a GPU export samples in place.
|
|
162
|
+
* - `auto`: `yuv`, on both platforms.
|
|
163
|
+
*
|
|
164
|
+
* Camera footage (Rec.709, or HLG tone mapped to Rec.709) renders the same
|
|
165
|
+
* either way: both convert with the frame's YCbCr matrix and range. On iOS,
|
|
166
|
+
* `rgba` also converts a file tagged with other primaries or another SDR
|
|
167
|
+
* transfer function (Display P3, sRGB) to Rec.709; `yuv` takes its samples as
|
|
168
|
+
* Rec.709, as Android does in both formats.
|
|
169
|
+
*/
|
|
170
|
+
export type VideoFramePixelFormat = "auto" | "yuv" | "rgba";
|
|
171
|
+
|
|
152
172
|
/**
|
|
153
173
|
* Represents a video composition.
|
|
154
174
|
*/
|
|
@@ -171,6 +191,13 @@ export type VideoComposition = {
|
|
|
171
191
|
* until its decoder has opened. The export always waits for it.
|
|
172
192
|
*/
|
|
173
193
|
lazyDecoders?: boolean;
|
|
194
|
+
/**
|
|
195
|
+
* The pixel format the video items' frames are decoded into, for the
|
|
196
|
+
* preview and the export, see {@link VideoFramePixelFormat}.
|
|
197
|
+
*
|
|
198
|
+
* @default 'auto'
|
|
199
|
+
*/
|
|
200
|
+
framePixelFormat?: VideoFramePixelFormat;
|
|
174
201
|
};
|
|
175
202
|
|
|
176
203
|
type VideoCompositionItemBase = {
|
|
@@ -319,6 +319,8 @@ export const useVideoComposition = <T = undefined>({
|
|
|
319
319
|
const lastImportedVersion = useSharedValue(-1);
|
|
320
320
|
// Whether the previous vsync imported and left its draw to this one.
|
|
321
321
|
const drawDeferred = useSharedValue(false);
|
|
322
|
+
// Whether the previous vsync brought new frames.
|
|
323
|
+
const importedLastVsync = useSharedValue(false);
|
|
322
324
|
// A new drawFrame can draw something else from the same time and frames.
|
|
323
325
|
useEffect(() => {
|
|
324
326
|
lastDrawnTime.value = -1;
|
|
@@ -384,14 +386,22 @@ export const useVideoComposition = <T = undefined>({
|
|
|
384
386
|
sourcesGeneration.value = generation;
|
|
385
387
|
const currentTime = framesExtractor.currentTime;
|
|
386
388
|
const framesVersion = framesExtractor.framesVersion;
|
|
387
|
-
|
|
389
|
+
const imported =
|
|
390
|
+
splitImportAndDraw && framesVersion !== lastImportedVersion.value;
|
|
391
|
+
const importedBefore = importedLastVsync.value;
|
|
392
|
+
// Version 0: no frame decoded yet.
|
|
393
|
+
importedLastVsync.value = imported && framesVersion > 0;
|
|
394
|
+
if (imported) {
|
|
388
395
|
// ownVideoFrames above has just imported the new frames: leave the
|
|
389
|
-
// draw to the next vsync, which finds them in the import cache.
|
|
390
|
-
//
|
|
391
|
-
//
|
|
396
|
+
// draw to the next vsync, which finds them in the import cache. Not
|
|
397
|
+
// when the previous vsync brought new frames too: frames arriving at
|
|
398
|
+
// every vsync (60 fps, or a display gone down to 30 Hz) leave no vsync
|
|
399
|
+
// free for the draw, and the next frame would replace this one before
|
|
400
|
+
// it is shown. Never twice running either, for the same reason.
|
|
392
401
|
lastImportedVersion.value = framesVersion;
|
|
393
402
|
if (
|
|
394
403
|
!drawDeferred.value &&
|
|
404
|
+
!importedBefore &&
|
|
395
405
|
currentFrame.value !== null &&
|
|
396
406
|
frameGeneration.value === generation
|
|
397
407
|
) {
|
package/src/yuvBlit.ts
CHANGED
|
@@ -30,6 +30,8 @@ type YuvBlit = {
|
|
|
30
30
|
sampler: GPUSampler;
|
|
31
31
|
module: GPUShaderModule;
|
|
32
32
|
pipelines: Map<GPUTextureFormat, GPURenderPipeline>;
|
|
33
|
+
/** The whole frame, as is: the uniforms of blitNv12Frame, written once. */
|
|
34
|
+
wholeFrame?: GPUBuffer;
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
// An ImageReader holds four buffers (GLFrameExtractor.READER_IMAGES); the
|
|
@@ -329,3 +331,67 @@ export const blitYuvFrame = (
|
|
|
329
331
|
entry.wrapper.endAccess()?.fence.destroy();
|
|
330
332
|
}
|
|
331
333
|
};
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* iOS: draws a whole NV12 decoder buffer into `target`, converting YUV to RGB
|
|
337
|
+
* with the buffer's YCbCr matrix and range, in its decoded orientation (the
|
|
338
|
+
* frame's rotation applies when it is drawn, as for BGRA frames). Submits the
|
|
339
|
+
* draw. AVAssetReader hands out buffers from a pool rather than a fixed few,
|
|
340
|
+
* so each frame is imported for this draw alone, unlike Android's: the import
|
|
341
|
+
* ends right after the submit (Dawn keeps the surface for the in-flight
|
|
342
|
+
* draw), while the caller holds the frame's lease until the draw completed.
|
|
343
|
+
*/
|
|
344
|
+
export const blitNv12Frame = (
|
|
345
|
+
device: GPUDevice,
|
|
346
|
+
source: NativeVideoFrame,
|
|
347
|
+
target: GPUTexture,
|
|
348
|
+
): void => {
|
|
349
|
+
"worklet";
|
|
350
|
+
const blit = getYuvBlit(device);
|
|
351
|
+
const pipeline = getPipeline(device, blit, target.format);
|
|
352
|
+
if (!blit.wholeFrame) {
|
|
353
|
+
blit.wholeFrame = device.createBuffer({
|
|
354
|
+
label: "video NV12 blit uniforms",
|
|
355
|
+
size: 16,
|
|
356
|
+
usage: videoUniformBufferUsage,
|
|
357
|
+
});
|
|
358
|
+
device.queue.writeBuffer(blit.wholeFrame, 0, new Float32Array([1, 1, 0, 0]));
|
|
359
|
+
}
|
|
360
|
+
const external = device.importExternalTexture({
|
|
361
|
+
label: "video decoder buffer",
|
|
362
|
+
source,
|
|
363
|
+
sourceColorSpace,
|
|
364
|
+
});
|
|
365
|
+
try {
|
|
366
|
+
const encoder = device.createCommandEncoder({ label: "video NV12 blit" });
|
|
367
|
+
const pass = encoder.beginRenderPass({
|
|
368
|
+
label: "video NV12 blit",
|
|
369
|
+
colorAttachments: [
|
|
370
|
+
{
|
|
371
|
+
view: target.createView(),
|
|
372
|
+
loadOp: "clear",
|
|
373
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
374
|
+
storeOp: "store",
|
|
375
|
+
},
|
|
376
|
+
],
|
|
377
|
+
});
|
|
378
|
+
pass.setPipeline(pipeline);
|
|
379
|
+
pass.setBindGroup(
|
|
380
|
+
0,
|
|
381
|
+
device.createBindGroup({
|
|
382
|
+
label: "video NV12 blit",
|
|
383
|
+
layout: blit.layout,
|
|
384
|
+
entries: [
|
|
385
|
+
{ binding: 0, resource: external },
|
|
386
|
+
{ binding: 1, resource: blit.sampler },
|
|
387
|
+
{ binding: 2, resource: { buffer: blit.wholeFrame } },
|
|
388
|
+
],
|
|
389
|
+
}),
|
|
390
|
+
);
|
|
391
|
+
pass.draw(3);
|
|
392
|
+
pass.end();
|
|
393
|
+
device.queue.submit([encoder.finish()]);
|
|
394
|
+
} finally {
|
|
395
|
+
(external as GPUExternalTexture & { destroy?: () => void }).destroy?.();
|
|
396
|
+
}
|
|
397
|
+
};
|