WIP - implement ChunkedRecorder
- configure AVAssetWriter for fragmented mp4 output - implement ChunkedRecorder to received chunk data via AVAssetWriterDelegate
This commit is contained in:
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))
|
||||
}
|
||||
|
Reference in New Issue
Block a user