WIP - implement ChunkedRecorder
- configure AVAssetWriter for fragmented mp4 output - implement ChunkedRecorder to received chunk data via AVAssetWriterDelegate
This commit is contained in:
parent
23459b2635
commit
d9a1287b68
70
package/ios/Core/ChunkedRecorder.swift
Normal file
70
package/ios/Core/ChunkedRecorder.swift
Normal file
@ -0,0 +1,70 @@
|
||||
//
|
||||
// ChunkedRecorder.swift
|
||||
// VisionCamera
|
||||
//
|
||||
// Created by Rafael Bastos on 12/07/2024.
|
||||
// Copyright © 2024 mrousavy. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
|
||||
class ChunkedRecorder: NSObject {
|
||||
|
||||
let outputURL: URL
|
||||
|
||||
private var initSegment: Data?
|
||||
private var index: Int = 0
|
||||
|
||||
init(url: URL) throws {
|
||||
outputURL = url
|
||||
|
||||
guard FileManager.default.fileExists(atPath: outputURL.path) else {
|
||||
throw CameraError.unknown(message: "output directory does not exist at: \(outputURL.path)", cause: nil)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ChunkedRecorder: AVAssetWriterDelegate {
|
||||
|
||||
func assetWriter(_ writer: AVAssetWriter,
|
||||
didOutputSegmentData segmentData: Data,
|
||||
segmentType: AVAssetSegmentType,
|
||||
segmentReport: AVAssetSegmentReport?) {
|
||||
|
||||
switch segmentType {
|
||||
case .initialization:
|
||||
saveInitSegment(segmentData)
|
||||
case .separable:
|
||||
saveSegment(segmentData)
|
||||
@unknown default:
|
||||
fatalError("Unknown AVAssetSegmentType!")
|
||||
}
|
||||
}
|
||||
|
||||
private func saveInitSegment(_ data: Data) {
|
||||
initSegment = data
|
||||
}
|
||||
|
||||
private func saveSegment(_ data: Data) {
|
||||
guard let initSegment else {
|
||||
print("missing init segment")
|
||||
return
|
||||
}
|
||||
|
||||
let file = String(format: "%06d.mp4", index)
|
||||
index += 1
|
||||
let url = outputURL.appendingPathComponent(file)
|
||||
|
||||
do {
|
||||
let outputData = initSegment + data
|
||||
try outputData.write(to: url)
|
||||
print("writing", data.count, "to", url)
|
||||
} catch {
|
||||
print("Error--->", error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -29,6 +29,7 @@ class RecordingSession {
|
||||
private let assetWriter: AVAssetWriter
|
||||
private var audioWriter: AVAssetWriterInput?
|
||||
private var videoWriter: AVAssetWriterInput?
|
||||
private let recorder: ChunkedRecorder
|
||||
private let completionHandler: (RecordingSession, AVAssetWriter.Status, Error?) -> Void
|
||||
|
||||
private var startTimestamp: CMTime?
|
||||
@ -49,7 +50,8 @@ class RecordingSession {
|
||||
Gets the file URL of the recorded video.
|
||||
*/
|
||||
var url: URL {
|
||||
return assetWriter.outputURL
|
||||
// FIXME:
|
||||
return recorder.outputURL
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,8 +78,24 @@ class RecordingSession {
|
||||
completionHandler = completion
|
||||
|
||||
do {
|
||||
assetWriter = try AVAssetWriter(outputURL: url, fileType: fileType)
|
||||
recorder = try ChunkedRecorder(url: url.deletingLastPathComponent())
|
||||
assetWriter = AVAssetWriter(contentType: UTType(fileType.rawValue)!)
|
||||
assetWriter.shouldOptimizeForNetworkUse = false
|
||||
assetWriter.outputFileTypeProfile = .mpeg4AppleHLS
|
||||
assetWriter.preferredOutputSegmentInterval = CMTime(seconds: 6, preferredTimescale: 1)
|
||||
|
||||
/*
|
||||
Apple HLS fMP4 does not have an Edit List Box ('elst') in an initialization segment to remove
|
||||
audio priming duration which advanced audio formats like AAC have, since the sample tables
|
||||
are empty. As a result, if the output PTS of the first non-fully trimmed audio sample buffer is
|
||||
kCMTimeZero, the audio samples’ presentation time in segment files may be pushed forward by the
|
||||
audio priming duration. This may cause audio and video to be out of sync. You should add a time
|
||||
offset to all samples to avoid this situation.
|
||||
*/
|
||||
let startTimeOffset = CMTime(value: 10, timescale: 1)
|
||||
assetWriter.initialSegmentStartTime = startTimeOffset
|
||||
|
||||
assetWriter.delegate = recorder
|
||||
} catch let error as NSError {
|
||||
throw CameraError.capture(.createRecorderError(message: error.description))
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class ViewController: UIViewController {
|
||||
|
||||
cameraView.photo = true
|
||||
cameraView.video = true
|
||||
cameraView.audio = true
|
||||
cameraView.audio = false
|
||||
cameraView.isActive = true
|
||||
cameraView.cameraId = getCameraDeviceId() as NSString?
|
||||
cameraView.didSetProps([])
|
||||
@ -90,6 +90,7 @@ class ViewController: UIViewController {
|
||||
} else {
|
||||
cameraView.startRecording(
|
||||
options: [
|
||||
"fileType": "mp4",
|
||||
"videoCodec": "h265",
|
||||
]) { callback in
|
||||
print("callback", callback)
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
B3AF8E862C410FB700CC198C /* ReactStubs.m in Sources */ = {isa = PBXBuildFile; fileRef = B3AF8E852C410FB700CC198C /* ReactStubs.m */; };
|
||||
B3AF8E882C41159300CC198C /* ChunkedRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3AF8E872C41159300CC198C /* ChunkedRecorder.swift */; };
|
||||
B3AF8E892C41159300CC198C /* ChunkedRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3AF8E872C41159300CC198C /* ChunkedRecorder.swift */; };
|
||||
B3EF9F0D2C3FBD8300832EE7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3EF9F0C2C3FBD8300832EE7 /* AppDelegate.swift */; };
|
||||
B3EF9F0F2C3FBD8300832EE7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3EF9F0E2C3FBD8300832EE7 /* SceneDelegate.swift */; };
|
||||
B3EF9F112C3FBD8300832EE7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3EF9F102C3FBD8300832EE7 /* ViewController.swift */; };
|
||||
@ -165,6 +167,7 @@
|
||||
B3AF8E832C410FB600CC198C /* TestRecorder-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TestRecorder-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
B3AF8E842C410FB700CC198C /* ReactStubs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactStubs.h; sourceTree = "<group>"; };
|
||||
B3AF8E852C410FB700CC198C /* ReactStubs.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReactStubs.m; sourceTree = "<group>"; };
|
||||
B3AF8E872C41159300CC198C /* ChunkedRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChunkedRecorder.swift; sourceTree = "<group>"; };
|
||||
B3EF9F0A2C3FBD8300832EE7 /* TestRecorder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestRecorder.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B3EF9F0C2C3FBD8300832EE7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
B3EF9F0E2C3FBD8300832EE7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
@ -364,6 +367,7 @@
|
||||
B88103E22AD7065C0087F063 /* CameraSessionDelegate.swift */,
|
||||
B83D5EE629377117000AFD2F /* PreviewView.swift */,
|
||||
B8DB3BC9263DC4D8004C18D7 /* RecordingSession.swift */,
|
||||
B3AF8E872C41159300CC198C /* ChunkedRecorder.swift */,
|
||||
B887515C25E0102000DB86D6 /* PhotoCaptureDelegate.swift */,
|
||||
B84760DE2608F57D004C3180 /* CameraQueues.swift */,
|
||||
B887518325E0102000DB86D6 /* CameraError.swift */,
|
||||
@ -636,6 +640,7 @@
|
||||
B88977BE2B556DBA0095C92C /* AVCaptureDevice+minFocusDistance.swift in Sources */,
|
||||
B80175EC2ABDEBD000E7DE90 /* ResizeMode.swift in Sources */,
|
||||
B887519F25E0102000DB86D6 /* AVCaptureDevice.DeviceType+physicalDeviceDescriptor.swift in Sources */,
|
||||
B3AF8E882C41159300CC198C /* ChunkedRecorder.swift in Sources */,
|
||||
B88685ED2AD6A5E600E93869 /* CameraSession+CodeScanner.swift in Sources */,
|
||||
B8207AAD2B0E5DD70002990F /* AVCaptureSession+synchronizeBuffer.swift in Sources */,
|
||||
B8D22CDC2642DB4D00234472 /* AVAssetWriterInputPixelBufferAdaptor+initWithVideoSettings.swift in Sources */,
|
||||
@ -672,6 +677,7 @@
|
||||
B3EF9F6A2C3FC46900832EE7 /* Promise.swift in Sources */,
|
||||
B3EF9F4B2C3FC31E00832EE7 /* AVAssetWriterInputPixelBufferAdaptor+initWithVideoSettings.swift in Sources */,
|
||||
B3EF9F5E2C3FC43000832EE7 /* AVCapturePhotoOutput.QualityPrioritization+descriptor.swift in Sources */,
|
||||
B3AF8E892C41159300CC198C /* ChunkedRecorder.swift in Sources */,
|
||||
B3EF9F5F2C3FC43000832EE7 /* AVAuthorizationStatus+descriptor.swift in Sources */,
|
||||
B3EF9F602C3FC43000832EE7 /* AVCaptureDevice.FlashMode+descriptor.swift in Sources */,
|
||||
B3EF9F612C3FC43000832EE7 /* AVFileType+descriptor.swift in Sources */,
|
||||
|
Loading…
Reference in New Issue
Block a user