VPCamera/SwiftProject/SwiftProject/Project/Util/VideoWriter.swift
bluesea df1b9de863 完成:
1.视频的四种格式转换:FSBS、HSBS、HOU、FOU;

注:发现一个问题,根据之前那个ios哥们留下来的算法,转出来的视频的第一帧感觉有问题
2024-03-08 11:40:55 +08:00

118 lines
4.4 KiB
Swift

//
// VideoWriter.swift
// SpacialVideoConvertor
//
// Created by Andy Qua on 04/01/2024.
//
// Based on code from xaphod/VideoWriter.swift - https://gist.github.com/xaphod/de83379cc982108a5b38115957a247f9
//
import Foundation
import AVFoundation
import CoreImage
class VideoWriter {
fileprivate var writer: AVAssetWriter
fileprivate var writerInput: AVAssetWriterInput
fileprivate var pixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor
fileprivate let queue: DispatchQueue
static var ciContext = CIContext.init() // we reuse a single context for performance reasons
let pixelSize: CGSize
var lastPresentationTime: CMTime?
init?(url: URL, width: Int, height: Int, orientation: CGAffineTransform, sessionStartTime: CMTime, isRealTime: Bool, queue: DispatchQueue) {
print("VideoWriter init: width=\(width) height=\(height), url=\(url)")
self.queue = queue
let outputSettings: [String:Any] = [
AVVideoCodecKey : AVVideoCodecType.h264, // or .hevc if you like
AVVideoWidthKey : width,
AVVideoHeightKey: height,
]
self.pixelSize = CGSize.init(width: width, height: height)
let input = AVAssetWriterInput.init(mediaType: .video, outputSettings: outputSettings)
input.expectsMediaDataInRealTime = isRealTime
input.transform = orientation
guard
let writer = try? AVAssetWriter.init(url: url, fileType: .mp4),
writer.canAdd(input),
sessionStartTime != .invalid
else {
return nil
}
let sourceBufferAttributes: [String:Any] = [
String(kCVPixelBufferPixelFormatTypeKey) : kCVPixelFormatType_32ARGB, // yes, ARGB is right here for images...
String(kCVPixelBufferWidthKey) : width,
String(kCVPixelBufferHeightKey) : height,
]
let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor.init(assetWriterInput: input, sourcePixelBufferAttributes: sourceBufferAttributes)
self.pixelBufferAdaptor = pixelBufferAdaptor
writer.add(input)
writer.startWriting()
writer.startSession(atSourceTime: sessionStartTime)
if let error = writer.error {
NSLog("VideoWriter init: ERROR - \(error)")
return nil
}
self.writer = writer
self.writerInput = input
}
func add(image: CIImage, presentationTime: CMTime) -> Bool {
if self.writerInput.isReadyForMoreMediaData == false {
return false
}
if self.pixelBufferAdaptor.appendPixelBufferForImage(image, presentationTime: presentationTime) {
self.lastPresentationTime = presentationTime
return true
}
return false
}
func add(buffer: CVPixelBuffer, presentationTime: CMTime) -> Bool {
if self.writerInput.isReadyForMoreMediaData == false {
return false
}
if self.pixelBufferAdaptor.append(buffer, withPresentationTime: presentationTime) {
self.lastPresentationTime = presentationTime
return true
}
return false
}
func add(sampleBuffer: CMSampleBuffer) -> Bool {
if self.writerInput.isReadyForMoreMediaData == false {
print("VideoWriter: not ready for more data")
return false
}
if self.writerInput.append(sampleBuffer) {
self.lastPresentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
return true
}
return false
}
func finish() async throws -> AVAsset? {
writerInput.markAsFinished()
print("VideoWriter: calling writer.finishWriting()")
await writer.finishWriting()
if self.writer.status != .completed {
print("VideoWriter finish: error in finishWriting - \(self.writer.error?.localizedDescription ?? "Unknown")")
return nil
}
let asset = AVURLAsset.init(url: self.writer.outputURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey : true])
let duration = try await CMTimeGetSeconds( asset.load(.duration) )
// can check for minimum duration here (ie. consider a failure if too short)
print("VideoWriter: finishWriting() complete, duration=\(duration)")
return asset
}
}