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,480 @@
|
|
|
1
|
+
#include "VideoCompositionItemDecoder.h"
|
|
2
|
+
|
|
3
|
+
#import "AVAssetTrackUtils.h"
|
|
4
|
+
#import "NativeResourceStats.h"
|
|
5
|
+
#include "RNSVMemoryBudget.h"
|
|
6
|
+
#import "RNSVColorSpace.h"
|
|
7
|
+
#import <AVFoundation/AVFoundation.h>
|
|
8
|
+
#import <Foundation/Foundation.h>
|
|
9
|
+
#include <unordered_map>
|
|
10
|
+
#include <mutex>
|
|
11
|
+
|
|
12
|
+
namespace RNSkiaVideo {
|
|
13
|
+
|
|
14
|
+
// Accounting follows each owned CMSampleBuffer through queue moves and swaps.
|
|
15
|
+
// The codec's internal allocations remain outside these counters.
|
|
16
|
+
static std::mutex decodedAccountingMutex;
|
|
17
|
+
struct DecodedAllocation { size_t bytes; uint64_t token; };
|
|
18
|
+
static std::unordered_map<CMSampleBufferRef, DecodedAllocation> decodedAccounting;
|
|
19
|
+
static void trackDecodedSample(CMSampleBufferRef sample) {
|
|
20
|
+
size_t bytes = pixelBufferBytes(CMSampleBufferGetImageBuffer(sample));
|
|
21
|
+
std::lock_guard<std::mutex> guard(decodedAccountingMutex);
|
|
22
|
+
auto token = MemoryBudget::instance().reserve(bytes, "iOS decoder queue");
|
|
23
|
+
try { decodedAccounting.emplace(sample, DecodedAllocation{bytes, token}); }
|
|
24
|
+
catch (...) { MemoryBudget::instance().release(token); throw; }
|
|
25
|
+
NativeResourceStats::decoderBuffers.add(1);
|
|
26
|
+
NativeResourceStats::decoderBytes.add(bytes);
|
|
27
|
+
}
|
|
28
|
+
static uint64_t takeDecodedAllocation(CMSampleBufferRef sample) {
|
|
29
|
+
std::lock_guard<std::mutex> guard(decodedAccountingMutex);
|
|
30
|
+
auto found = decodedAccounting.find(sample);
|
|
31
|
+
if (found == decodedAccounting.end()) return 0;
|
|
32
|
+
uint64_t token = found->second.token;
|
|
33
|
+
found->second.token = 0;
|
|
34
|
+
return token;
|
|
35
|
+
}
|
|
36
|
+
static void releaseDecodedSample(CMSampleBufferRef sample) {
|
|
37
|
+
{
|
|
38
|
+
std::lock_guard<std::mutex> guard(decodedAccountingMutex);
|
|
39
|
+
auto found = decodedAccounting.find(sample);
|
|
40
|
+
if (found != decodedAccounting.end()) {
|
|
41
|
+
NativeResourceStats::decoderBuffers.remove(1);
|
|
42
|
+
NativeResourceStats::decoderBytes.remove(found->second.bytes);
|
|
43
|
+
MemoryBudget::instance().release(found->second.token);
|
|
44
|
+
decodedAccounting.erase(found);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
CFRelease(sample);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
VideoCompositionItemDecoder::VideoCompositionItemDecoder(
|
|
51
|
+
std::shared_ptr<VideoCompositionItem> item, bool realTime,
|
|
52
|
+
AVURLAsset* sharedAsset, CMTime initialTime)
|
|
53
|
+
: frameRing(2) {
|
|
54
|
+
this->item = item;
|
|
55
|
+
this->realTime = realTime;
|
|
56
|
+
lock = [[NSObject alloc] init];
|
|
57
|
+
NSString* path =
|
|
58
|
+
[NSString stringWithCString:item->path.c_str()
|
|
59
|
+
encoding:[NSString defaultCStringEncoding]];
|
|
60
|
+
asset = sharedAsset
|
|
61
|
+
?: [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:path]
|
|
62
|
+
options:nil];
|
|
63
|
+
videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
|
|
64
|
+
if (!videoTrack) {
|
|
65
|
+
throw [NSError
|
|
66
|
+
errorWithDomain:@"com.azzapp.rnskv"
|
|
67
|
+
code:0
|
|
68
|
+
userInfo:@{
|
|
69
|
+
NSLocalizedDescriptionKey : [NSString
|
|
70
|
+
stringWithFormat:@"No video track for path: %@", path]
|
|
71
|
+
}];
|
|
72
|
+
}
|
|
73
|
+
width = videoTrack.naturalSize.width;
|
|
74
|
+
height = videoTrack.naturalSize.height;
|
|
75
|
+
rotation = AVAssetTrackUtils::GetTrackRotationInDegree(videoTrack);
|
|
76
|
+
currentFrame = nullptr;
|
|
77
|
+
this->setupReader(initialTime);
|
|
78
|
+
NativeResourceStats::decoders.add(1);
|
|
79
|
+
counted = true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
void VideoCompositionItemDecoder::setupReader(CMTime initialTime) {
|
|
83
|
+
readerGeneration++;
|
|
84
|
+
NSError* error = nil;
|
|
85
|
+
assetReader = [AVAssetReader assetReaderWithAsset:asset error:&error];
|
|
86
|
+
if (error) {
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
auto startTime = CMTimeMakeWithSeconds(item->startTime, NSEC_PER_SEC);
|
|
91
|
+
// Capped at the item's end: a seek past it would otherwise ask the reader
|
|
92
|
+
// for a negative duration.
|
|
93
|
+
auto position = CMTimeMakeWithSeconds(
|
|
94
|
+
MIN(MAX((CMTimeGetSeconds(initialTime) - item->compositionStartTime), 0),
|
|
95
|
+
item->duration),
|
|
96
|
+
NSEC_PER_SEC);
|
|
97
|
+
// The start in the track's own timescale, rounded down. Given in
|
|
98
|
+
// nanoseconds, a start on a frame's exact time (1.2 s, frame 36 at 30 fps)
|
|
99
|
+
// can land a hair before that frame once AVAssetReader applies the track's
|
|
100
|
+
// edit offset, which is not a whole number of nanoseconds (1/30 s for a
|
|
101
|
+
// file with reordered frames): the reader then hands over the previous
|
|
102
|
+
// frame stamped with the requested time and never the one asked for.
|
|
103
|
+
CMTime endTime = CMTimeAdd(
|
|
104
|
+
startTime, CMTimeMakeWithSeconds(item->duration, NSEC_PER_SEC));
|
|
105
|
+
CMTime readStart = CMTimeAdd(startTime, position);
|
|
106
|
+
if (videoTrack.naturalTimeScale > 0) {
|
|
107
|
+
readStart = CMTimeMinimum(
|
|
108
|
+
CMTimeConvertScale(readStart, videoTrack.naturalTimeScale,
|
|
109
|
+
kCMTimeRoundingMethod_RoundTowardNegativeInfinity),
|
|
110
|
+
endTime);
|
|
111
|
+
}
|
|
112
|
+
CMTime trackEnd = CMTimeRangeGetEnd(videoTrack.timeRange);
|
|
113
|
+
if (CMTIME_IS_NUMERIC(trackEnd)) endTime = CMTimeMinimum(endTime, trackEnd);
|
|
114
|
+
if (CMTimeCompare(readStart, endTime) >= 0) {
|
|
115
|
+
// An empty item/range is valid, including a seek past the clip. Do not
|
|
116
|
+
// start AVAssetReader with an empty/negative range and treat that as a
|
|
117
|
+
// decoder initialization error. A later seek can create a new reader.
|
|
118
|
+
assetReader = nil;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
assetReader.timeRange = CMTimeRangeFromTimeToTime(readStart, endTime);
|
|
122
|
+
|
|
123
|
+
// AVAssetReaderTrackOutput takes pixel buffer attributes and video settings
|
|
124
|
+
// in the same dictionary, so the color properties travel with them: an HDR
|
|
125
|
+
// track is tone-mapped to Rec.709 by AVFoundation before we ever see it
|
|
126
|
+
// (see RNSVColorSpace.h).
|
|
127
|
+
NSDictionary* pixBuffAttributes = @{
|
|
128
|
+
(id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA),
|
|
129
|
+
(id)kCVPixelBufferIOSurfacePropertiesKey : @{},
|
|
130
|
+
(id)kCVPixelBufferMetalCompatibilityKey : @YES,
|
|
131
|
+
AVVideoColorPropertiesKey : SDRColorProperties(),
|
|
132
|
+
};
|
|
133
|
+
CGSize resolution = item->resolution;
|
|
134
|
+
/*
|
|
135
|
+
* No explicit size asked for, but a cap on the longest side: scale the
|
|
136
|
+
* track's own dimensions to fit it.
|
|
137
|
+
*
|
|
138
|
+
* This is the size the frames are *decoded* at, so it is what the preview's
|
|
139
|
+
* four-deep frame ring costs. Left uncapped, a 4K clip is 33 MB a frame in
|
|
140
|
+
* BGRA — 133 MB of ring to fill a stage a few hundred points wide, which the
|
|
141
|
+
* OS watchdog terminates the app over.
|
|
142
|
+
*
|
|
143
|
+
* `naturalSize` is the encoded size, before the display matrix, which is why
|
|
144
|
+
* the cap is resolved here: a caller holding only display dimensions cannot
|
|
145
|
+
* tell a portrait clip from a rotated landscape one, and asking for the
|
|
146
|
+
* transpose would hand back a squashed picture. Even dimensions because
|
|
147
|
+
* hardware scalers dislike odd ones.
|
|
148
|
+
*/
|
|
149
|
+
if (!(resolution.width > 0 && resolution.height > 0) && item->maxLongSide > 0) {
|
|
150
|
+
CGSize natural = videoTrack.naturalSize;
|
|
151
|
+
CGFloat longest = MAX(natural.width, natural.height);
|
|
152
|
+
if (longest > item->maxLongSide) {
|
|
153
|
+
CGFloat scale = item->maxLongSide / longest;
|
|
154
|
+
resolution = CGSizeMake(MAX(2, round(natural.width * scale / 2) * 2),
|
|
155
|
+
MAX(2, round(natural.height * scale / 2) * 2));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (resolution.width > 0 && resolution.height > 0) {
|
|
159
|
+
pixBuffAttributes =
|
|
160
|
+
[NSMutableDictionary dictionaryWithDictionary:pixBuffAttributes];
|
|
161
|
+
[pixBuffAttributes setValue:@(resolution.width)
|
|
162
|
+
forKey:(id)kCVPixelBufferWidthKey];
|
|
163
|
+
[pixBuffAttributes setValue:@(resolution.height)
|
|
164
|
+
forKey:(id)kCVPixelBufferHeightKey];
|
|
165
|
+
width = resolution.width;
|
|
166
|
+
height = resolution.height;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
AVAssetReaderOutput* assetReaderOutput =
|
|
170
|
+
[[AVAssetReaderTrackOutput alloc] initWithTrack:videoTrack
|
|
171
|
+
outputSettings:pixBuffAttributes];
|
|
172
|
+
// Frames are only ever read (GPU import, read-only locks): skip the copy
|
|
173
|
+
// AVFoundation otherwise makes of every decoded buffer before vending it.
|
|
174
|
+
assetReaderOutput.alwaysCopiesSampleData = NO;
|
|
175
|
+
if (!assetReaderOutput || ![assetReader canAddOutput:assetReaderOutput]) {
|
|
176
|
+
throw [NSError errorWithDomain:@"com.azzapp.rnskv" code:0
|
|
177
|
+
userInfo:@{NSLocalizedDescriptionKey : @"Video reader cannot add its decoded output"}];
|
|
178
|
+
}
|
|
179
|
+
[assetReader addOutput:assetReaderOutput];
|
|
180
|
+
if (![assetReader startReading]) {
|
|
181
|
+
throw assetReader.error ?: [NSError errorWithDomain:@"com.azzapp.rnskv" code:0
|
|
182
|
+
userInfo:@{NSLocalizedDescriptionKey : @"Video reader cannot start decoding"}];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#define DECODER_INPUT_TIME_ADVANCE 0.1
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
using FrameQueue = std::list<std::pair<double, CMSampleBufferRef>>;
|
|
190
|
+
|
|
191
|
+
static constexpr size_t kMaxQueuedSamples = 8;
|
|
192
|
+
static constexpr size_t kMaxQueuedSampleBytes = 64ULL * 1024 * 1024;
|
|
193
|
+
static size_t queueBytes(const FrameQueue& queue) {
|
|
194
|
+
size_t bytes = 0;
|
|
195
|
+
for (const auto& frame : queue)
|
|
196
|
+
bytes += pixelBufferBytes(CMSampleBufferGetImageBuffer(frame.second));
|
|
197
|
+
return bytes;
|
|
198
|
+
}
|
|
199
|
+
// Keep the latest candidate at/before the requested position, plus bounded
|
|
200
|
+
// future samples. A large seek never stores all intermediate decoded frames.
|
|
201
|
+
static void prunePastSamples(FrameQueue& queue, CMTime position) {
|
|
202
|
+
while (queue.size() > 1) {
|
|
203
|
+
auto following = std::next(queue.begin());
|
|
204
|
+
if (CMTimeCompare(CMTimeMakeWithSeconds(following->first, NSEC_PER_SEC),
|
|
205
|
+
position) > 0) break;
|
|
206
|
+
releaseDecodedSample(queue.front().second);
|
|
207
|
+
queue.pop_front();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
static void readSamples(AVAssetReader* reader, CMTime latest, CMTime until,
|
|
211
|
+
CMTime endTime, FrameQueue& out, CMTime position,
|
|
212
|
+
size_t maximumSamples, size_t maximumBytes,
|
|
213
|
+
const std::atomic<bool>& closed) {
|
|
214
|
+
if (!maximumSamples || !maximumBytes) return;
|
|
215
|
+
CMTime latestSampleTime = latest;
|
|
216
|
+
while (!CMTIME_IS_VALID(latestSampleTime) ||
|
|
217
|
+
(CMTimeCompare(latestSampleTime, until) < 0 &&
|
|
218
|
+
CMTimeCompare(endTime, until) >= 0)) {
|
|
219
|
+
bool futureQueued = !out.empty() &&
|
|
220
|
+
CMTimeCompare(CMTimeMakeWithSeconds(out.back().first, NSEC_PER_SEC),
|
|
221
|
+
position) > 0;
|
|
222
|
+
if (futureQueued && (out.size() >= maximumSamples ||
|
|
223
|
+
queueBytes(out) >= maximumBytes)) break;
|
|
224
|
+
if (closed.load() || !reader || reader.status != AVAssetReaderStatusReading) break;
|
|
225
|
+
AVAssetReaderOutput* output = [reader.outputs firstObject];
|
|
226
|
+
CMSampleBufferRef sample = [output copyNextSampleBuffer];
|
|
227
|
+
if (!sample) break;
|
|
228
|
+
if (CMSampleBufferGetNumSamples(sample) == 0 ||
|
|
229
|
+
!CMSampleBufferGetImageBuffer(sample)) {
|
|
230
|
+
CFRelease(sample);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
// Already in presentation time: do not reapply track edits/slow motion.
|
|
234
|
+
auto timestamp = CMSampleBufferGetPresentationTimeStamp(sample);
|
|
235
|
+
double seconds = CMTimeGetSeconds(timestamp);
|
|
236
|
+
try { trackDecodedSample(sample); }
|
|
237
|
+
catch (...) { CFRelease(sample); throw; }
|
|
238
|
+
try { out.emplace_back(seconds, sample); }
|
|
239
|
+
catch (...) { releaseDecodedSample(sample); throw; }
|
|
240
|
+
prunePastSamples(out, position);
|
|
241
|
+
latestSampleTime = CMTimeMakeWithSeconds(seconds, NSEC_PER_SEC);
|
|
242
|
+
// At most one transient newly decoded sample can exceed the queue byte
|
|
243
|
+
// target while replacing the previous candidate; no live pixels recycled.
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
static CMTime lastSampleTime(const FrameQueue& queue) {
|
|
248
|
+
return queue.empty() ? kCMTimeInvalid
|
|
249
|
+
: CMTimeMakeWithSeconds(queue.back().first, NSEC_PER_SEC);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
void VideoCompositionItemDecoder::advanceDecoder(CMTime currentTime) {
|
|
253
|
+
std::lock_guard<std::mutex> readerGuard(readerMutex);
|
|
254
|
+
AVAssetReader* reader = nil;
|
|
255
|
+
CMTime latest = kCMTimeInvalid;
|
|
256
|
+
CMTime inputPosition;
|
|
257
|
+
CMTime position;
|
|
258
|
+
size_t maximumSamples = kMaxQueuedSamples;
|
|
259
|
+
size_t maximumBytes = kMaxQueuedSampleBytes;
|
|
260
|
+
CMTime endTime;
|
|
261
|
+
bool intoNextLoop = false;
|
|
262
|
+
uint64_t batch = 0;
|
|
263
|
+
@synchronized(lock) {
|
|
264
|
+
if (released) return;
|
|
265
|
+
CMTime startTime = CMTimeMakeWithSeconds(item->startTime, NSEC_PER_SEC);
|
|
266
|
+
CMTime compositionStartTime =
|
|
267
|
+
CMTimeMakeWithSeconds(item->compositionStartTime, NSEC_PER_SEC);
|
|
268
|
+
position =
|
|
269
|
+
CMTimeAdd(startTime, CMTimeSubtract(currentTime, compositionStartTime));
|
|
270
|
+
inputPosition =
|
|
271
|
+
realTime
|
|
272
|
+
? CMTimeAdd(position, CMTimeMakeWithSeconds(
|
|
273
|
+
DECODER_INPUT_TIME_ADVANCE, NSEC_PER_SEC))
|
|
274
|
+
: position;
|
|
275
|
+
CMTime duration = CMTimeMakeWithSeconds(item->duration, NSEC_PER_SEC);
|
|
276
|
+
endTime = CMTimeAdd(startTime, duration);
|
|
277
|
+
|
|
278
|
+
if (realTime && CMTimeCompare(endTime, inputPosition) < 0 && !hasLooped) {
|
|
279
|
+
// This pass is read to the item's end before the reader restarts for
|
|
280
|
+
// the next loop: the frames between the last position decoded and the
|
|
281
|
+
// end are still to be shown, and a seek into the last tenth of a second
|
|
282
|
+
// of the item lands on one of them. Dropping them left the frame from
|
|
283
|
+
// before such a seek on screen. Under the lock: it replaces the reader,
|
|
284
|
+
// once per loop.
|
|
285
|
+
prunePastSamples(decodedFrames, position);
|
|
286
|
+
readSamples(assetReader, lastSampleTime(decodedFrames), endTime, endTime,
|
|
287
|
+
decodedFrames, position, kMaxQueuedSamples,
|
|
288
|
+
kMaxQueuedSampleBytes, released);
|
|
289
|
+
setupReader(kCMTimeZero);
|
|
290
|
+
hasLooped = true;
|
|
291
|
+
}
|
|
292
|
+
// Once looped, the first frames of the next loop.
|
|
293
|
+
intoNextLoop = hasLooped;
|
|
294
|
+
reader = assetReader;
|
|
295
|
+
latest = lastSampleTime(intoNextLoop ? nextLoopFrames : decodedFrames);
|
|
296
|
+
batch = readerGeneration;
|
|
297
|
+
prunePastSamples(decodedFrames, position);
|
|
298
|
+
prunePastSamples(nextLoopFrames, position);
|
|
299
|
+
size_t queued = decodedFrames.size() + nextLoopFrames.size();
|
|
300
|
+
size_t bytes = queueBytes(decodedFrames) + queueBytes(nextLoopFrames);
|
|
301
|
+
maximumSamples = queued < kMaxQueuedSamples ? kMaxQueuedSamples - queued : 0;
|
|
302
|
+
maximumBytes = bytes < kMaxQueuedSampleBytes ? kMaxQueuedSampleBytes - bytes : 0;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Decoded outside the lock: acquireFrameForTime takes it on the UI thread at
|
|
306
|
+
// every vsync, and a batch held it for the whole read (longer since frames
|
|
307
|
+
// are tone-mapped to Rec.709).
|
|
308
|
+
FrameQueue fresh;
|
|
309
|
+
try {
|
|
310
|
+
readSamples(reader, latest, inputPosition, endTime, fresh, position,
|
|
311
|
+
maximumSamples, maximumBytes, released);
|
|
312
|
+
} catch (...) {
|
|
313
|
+
for (const auto& frame : fresh) releaseDecodedSample(frame.second);
|
|
314
|
+
throw;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
@synchronized(lock) {
|
|
318
|
+
if (released.load() || batch != readerGeneration || reader != assetReader) {
|
|
319
|
+
// A seek or a release replaced what this batch was read for.
|
|
320
|
+
for (const auto& frame : fresh) {
|
|
321
|
+
releaseDecodedSample(frame.second);
|
|
322
|
+
}
|
|
323
|
+
} else if (intoNextLoop && hasLooped) {
|
|
324
|
+
nextLoopFrames.splice(nextLoopFrames.end(), fresh);
|
|
325
|
+
} else {
|
|
326
|
+
// Or the loop wrapped meanwhile and the next loop's frames are current.
|
|
327
|
+
decodedFrames.splice(decodedFrames.end(), fresh);
|
|
328
|
+
}
|
|
329
|
+
prunePastSamples(decodedFrames, position);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
std::shared_ptr<VideoFrame>
|
|
334
|
+
VideoCompositionItemDecoder::acquireFrameForTime(CMTime currentTime,
|
|
335
|
+
bool force) {
|
|
336
|
+
if (!frameRing.canAcquire()) return nullptr;
|
|
337
|
+
CMSampleBufferRef nextFrame = nil;
|
|
338
|
+
// advanceDecoder appends to the frame lists from a decoding thread while
|
|
339
|
+
// this runs on the UI thread: every access to the lists goes through the
|
|
340
|
+
// decoder lock. The texture upload below happens outside of it so the
|
|
341
|
+
// decoding thread is not held back by the GPU.
|
|
342
|
+
@synchronized(lock) {
|
|
343
|
+
if (released) return nullptr;
|
|
344
|
+
if (hasLooped && CMTIME_IS_VALID(lastRequestedTime) &&
|
|
345
|
+
CMTimeCompare(currentTime, lastRequestedTime) < 0) {
|
|
346
|
+
hasLooped = false;
|
|
347
|
+
for (const auto& frame : decodedFrames) {
|
|
348
|
+
releaseDecodedSample(frame.second);
|
|
349
|
+
}
|
|
350
|
+
decodedFrames = nextLoopFrames;
|
|
351
|
+
nextLoopFrames.clear();
|
|
352
|
+
}
|
|
353
|
+
lastRequestedTime = currentTime;
|
|
354
|
+
|
|
355
|
+
// Plus a microsecond: a time asked for at exactly a frame's timestamp
|
|
356
|
+
// (the export asks for i / fps) lands a fraction of a nanosecond before
|
|
357
|
+
// it once rounded to nanoseconds, and was given the previous frame — an
|
|
358
|
+
// export at the file's frame rate duplicated and skipped frames.
|
|
359
|
+
double offset =
|
|
360
|
+
MAX(CMTimeGetSeconds(currentTime) - item->compositionStartTime, 0);
|
|
361
|
+
CMTime start = CMTimeMakeWithSeconds(item->startTime, NSEC_PER_SEC);
|
|
362
|
+
CMTime position = CMTimeAdd(
|
|
363
|
+
CMTimeAdd(start, CMTimeMakeWithSeconds(offset, NSEC_PER_SEC)),
|
|
364
|
+
CMTimeMake(1, 1000000));
|
|
365
|
+
|
|
366
|
+
auto it = decodedFrames.begin();
|
|
367
|
+
while (it != decodedFrames.end()) {
|
|
368
|
+
auto timestamp = CMTimeMakeWithSeconds(it->first, NSEC_PER_SEC);
|
|
369
|
+
if (CMTimeCompare(timestamp, position) <= 0 ||
|
|
370
|
+
(force && nextFrame == nullptr)) {
|
|
371
|
+
if (nextFrame != nullptr) {
|
|
372
|
+
releaseDecodedSample(nextFrame);
|
|
373
|
+
}
|
|
374
|
+
nextFrame = it->second;
|
|
375
|
+
it = decodedFrames.erase(it);
|
|
376
|
+
} else {
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (nextFrame) {
|
|
382
|
+
CVPixelBufferRef buffer = CMSampleBufferGetImageBuffer(nextFrame);
|
|
383
|
+
uint64_t memoryToken = takeDecodedAllocation(nextFrame);
|
|
384
|
+
try {
|
|
385
|
+
@synchronized(lock) {
|
|
386
|
+
if (released) {
|
|
387
|
+
MemoryBudget::instance().release(memoryToken);
|
|
388
|
+
releaseDecodedSample(nextFrame);
|
|
389
|
+
return nullptr;
|
|
390
|
+
}
|
|
391
|
+
auto frame = makeFrame(buffer, memoryToken);
|
|
392
|
+
releaseDecodedSample(nextFrame);
|
|
393
|
+
return frame;
|
|
394
|
+
}
|
|
395
|
+
} catch (...) {
|
|
396
|
+
MemoryBudget::instance().release(memoryToken);
|
|
397
|
+
releaseDecodedSample(nextFrame);
|
|
398
|
+
throw;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return nullptr;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
std::shared_ptr<VideoFrame>
|
|
405
|
+
VideoCompositionItemDecoder::makeFrame(CVPixelBufferRef buffer,
|
|
406
|
+
uint64_t memoryToken) {
|
|
407
|
+
// Both texture modes now use immutable, scoped pixel buffers. The helper
|
|
408
|
+
// controls the copy/import and explicitly closes this lease after its barrier.
|
|
409
|
+
auto frame = std::make_shared<VideoFrame>(buffer, width, height, rotation,
|
|
410
|
+
producerId, memoryToken);
|
|
411
|
+
frameRing.push(frame);
|
|
412
|
+
return frame;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
void VideoCompositionItemDecoder::seekTo(CMTime currentTime) {
|
|
416
|
+
std::lock_guard<std::mutex> readerGuard(readerMutex);
|
|
417
|
+
@synchronized(lock) {
|
|
418
|
+
if (released) return;
|
|
419
|
+
// A seek the reader can simply read up to: a scrub is a run of small
|
|
420
|
+
// forward steps, and rebuilding an AVAssetReader for each one is what
|
|
421
|
+
// makes dragging the playhead stutter. Reading on costs the frames in
|
|
422
|
+
// between, which is cheaper than a new reader for anything this short.
|
|
423
|
+
if (!hasLooped && seekReadsOn(assetReader &&
|
|
424
|
+
assetReader.status == AVAssetReaderStatusReading,
|
|
425
|
+
CMTIME_IS_VALID(lastRequestedTime),
|
|
426
|
+
CMTimeGetSeconds(lastRequestedTime),
|
|
427
|
+
CMTimeGetSeconds(currentTime))) {
|
|
428
|
+
// The reader is already ahead of the displayed frame. Keep its queued
|
|
429
|
+
// samples: throwing them away loses a nearby seek's target permanently.
|
|
430
|
+
// Keep its generation too, so an in-flight batch from this same reader
|
|
431
|
+
// can still publish the samples needed by the forward seek.
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
// Not release(): that drops the frame ring too, and in direct mode the
|
|
435
|
+
// frame on screen loses its texture — the preview goes black for as long
|
|
436
|
+
// as the new reader takes to decode. The picture a seek replaces stays
|
|
437
|
+
// alive until its replacement arrives.
|
|
438
|
+
discardReader();
|
|
439
|
+
setupReader(currentTime);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
void VideoCompositionItemDecoder::discardReader() {
|
|
444
|
+
@synchronized(lock) {
|
|
445
|
+
if (assetReader) {
|
|
446
|
+
[assetReader cancelReading];
|
|
447
|
+
assetReader = nullptr;
|
|
448
|
+
}
|
|
449
|
+
for (const auto& frame : decodedFrames) {
|
|
450
|
+
releaseDecodedSample(frame.second);
|
|
451
|
+
}
|
|
452
|
+
decodedFrames.clear();
|
|
453
|
+
for (const auto& frame : nextLoopFrames) {
|
|
454
|
+
releaseDecodedSample(frame.second);
|
|
455
|
+
}
|
|
456
|
+
nextLoopFrames.clear();
|
|
457
|
+
hasLooped = false;
|
|
458
|
+
lastRequestedTime = kCMTimeInvalid;
|
|
459
|
+
readerGeneration++;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
void VideoCompositionItemDecoder::release() {
|
|
464
|
+
// Stop a long read-on batch between samples before waiting for its current
|
|
465
|
+
// copy to return. cancelReading only runs after that copy has completed.
|
|
466
|
+
if (released.exchange(true)) return;
|
|
467
|
+
std::lock_guard<std::mutex> readerGuard(readerMutex);
|
|
468
|
+
@synchronized(lock) {
|
|
469
|
+
discardReader();
|
|
470
|
+
frameRing.releaseAll();
|
|
471
|
+
currentFrame = nullptr;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
VideoCompositionItemDecoder::~VideoCompositionItemDecoder() {
|
|
476
|
+
release();
|
|
477
|
+
if (counted) NativeResourceStats::decoders.remove(1);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
} // namespace RNSkiaVideo
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#import "RNSVHostObject.h"
|
|
4
|
+
#import "VideoComposition.h"
|
|
5
|
+
#import <AVFoundation/AVFoundation.h>
|
|
6
|
+
#import <jsi/jsi.h>
|
|
7
|
+
#import <map>
|
|
8
|
+
#include <atomic>
|
|
9
|
+
#include <mutex>
|
|
10
|
+
|
|
11
|
+
namespace RNSkiaVideo {
|
|
12
|
+
using namespace facebook;
|
|
13
|
+
|
|
14
|
+
class JSI_EXPORT VideoEncoderHostObject : public RNSVHostObject {
|
|
15
|
+
public:
|
|
16
|
+
VideoEncoderHostObject(std::string outPath, int width, int height,
|
|
17
|
+
int frameRate, int bitRate, std::string codec,
|
|
18
|
+
int audioBitRate, int audioSampleRate,
|
|
19
|
+
int audioChannelCount,
|
|
20
|
+
std::shared_ptr<VideoComposition> composition,
|
|
21
|
+
bool directEncoder);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether the device has an encoder for the given codec ("h264" or "hevc").
|
|
25
|
+
* H.264 is guaranteed; HEVC needs an A10 or later.
|
|
26
|
+
*/
|
|
27
|
+
static bool isCodecSupported(const std::string& codec);
|
|
28
|
+
~VideoEncoderHostObject() override;
|
|
29
|
+
jsi::Value get(jsi::Runtime&, const jsi::PropNameID& name) override;
|
|
30
|
+
std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime& rt) override;
|
|
31
|
+
|
|
32
|
+
private:
|
|
33
|
+
#if defined(RNSV_NATIVE_TESTS)
|
|
34
|
+
friend class NativeEncoderTestDriver;
|
|
35
|
+
#endif
|
|
36
|
+
std::string outPath;
|
|
37
|
+
int width;
|
|
38
|
+
int height;
|
|
39
|
+
int bitRate;
|
|
40
|
+
int frameRate;
|
|
41
|
+
std::string codec;
|
|
42
|
+
int audioBitRate;
|
|
43
|
+
int audioSampleRate;
|
|
44
|
+
int audioChannelCount;
|
|
45
|
+
std::shared_ptr<VideoComposition> composition;
|
|
46
|
+
// Direct native-buffer append avoids an additional CPU copy. The reference
|
|
47
|
+
// rgba-pixels path always fills our bounded pool, regardless of this option.
|
|
48
|
+
bool directEncoder = false;
|
|
49
|
+
std::atomic<bool> disposed{false};
|
|
50
|
+
std::recursive_mutex stateMutex;
|
|
51
|
+
bool prepared = false;
|
|
52
|
+
uint64_t poolMemoryToken = 0;
|
|
53
|
+
AVAssetWriter* assetWriter;
|
|
54
|
+
AVAssetWriterInput* assetWriterInput;
|
|
55
|
+
CVPixelBufferPoolRef pixelBufferPool = NULL;
|
|
56
|
+
// The pool buffer lent to JS for the GPU export path: drawn into through
|
|
57
|
+
// WebGPU, then appended as is. One at a time; owned until encoded or released.
|
|
58
|
+
CVPixelBufferRef vendedBuffer = NULL;
|
|
59
|
+
|
|
60
|
+
AVAssetWriterInput* audioWriterInput;
|
|
61
|
+
AVAssetReader* audioReader;
|
|
62
|
+
AVAssetReaderAudioMixOutput* audioMixOutput;
|
|
63
|
+
dispatch_queue_t audioQueue;
|
|
64
|
+
dispatch_semaphore_t audioCompletionSemaphore;
|
|
65
|
+
NSMutableArray<NSError*>* audioErrorHolder;
|
|
66
|
+
|
|
67
|
+
void prepare();
|
|
68
|
+
void prepareImpl();
|
|
69
|
+
void encodeFrame(CVPixelBufferRef source, CMTime time);
|
|
70
|
+
void encodePixels(const uint8_t* pixels, size_t rowBytes, CMTime time,
|
|
71
|
+
bool rgba = false);
|
|
72
|
+
CVPixelBufferRef acquireOutputBuffer();
|
|
73
|
+
uintptr_t acquireFrameBuffer();
|
|
74
|
+
void releaseFrameBuffer();
|
|
75
|
+
void appendBuffer(CVPixelBufferRef pixelBuffer, CMTime time);
|
|
76
|
+
void waitUntilReady();
|
|
77
|
+
void setupAudio();
|
|
78
|
+
void startWritingAudio();
|
|
79
|
+
void finish();
|
|
80
|
+
void release();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
} // namespace RNSkiaVideo
|