125 lines
5.3 KiB
Swift
125 lines
5.3 KiB
Swift
//
|
||
// SpatialVideoWriter.swift
|
||
// tdvideo
|
||
//
|
||
// Created by mac on 2024/2/22.
|
||
//。两个普通视频合成空间视频
|
||
|
||
import UIKit
|
||
import AVFoundation
|
||
import VideoToolbox
|
||
import Photos
|
||
|
||
class SpatialVideoWriter {
|
||
|
||
private func removeExistingFile(at outputVideoURL: URL) throws {
|
||
do {
|
||
try FileManager.default.removeItem(atPath: outputVideoURL.path)
|
||
print("视频文件删除成功")
|
||
} catch {
|
||
print("删除视频文件出错:\(error)")
|
||
}
|
||
}
|
||
|
||
func writeSpatialVideo(leftEyeVideoURL: URL, rightEyeVideoURL: URL, outputVideoURL: URL, completion: @escaping (Bool, Error?) -> Void) {
|
||
|
||
do {
|
||
|
||
try removeExistingFile(at: outputVideoURL)
|
||
|
||
let leftEyeAsset = AVURLAsset(url: leftEyeVideoURL)
|
||
let rightEyeAsset = AVURLAsset(url: rightEyeVideoURL)
|
||
|
||
let assetWriter = try AVAssetWriter(outputURL: outputVideoURL, fileType: .mov)
|
||
|
||
let leftVideoTrack = leftEyeAsset.tracks(withMediaType: .video).first!
|
||
let videoSettings: [String: Any] = [
|
||
AVVideoWidthKey: leftVideoTrack.naturalSize.width,
|
||
AVVideoHeightKey: leftVideoTrack.naturalSize.height,
|
||
AVVideoCodecKey: AVVideoCodecType.hevc,
|
||
AVVideoCompressionPropertiesKey: [
|
||
kVTCompressionPropertyKey_MVHEVCVideoLayerIDs: [0, 1] as CFArray,
|
||
kCMFormatDescriptionExtension_HorizontalFieldOfView: 90_000, // asset-specific, in thousandths of a degree
|
||
kVTCompressionPropertyKey_HorizontalDisparityAdjustment: 200, // asset-specific
|
||
]
|
||
]
|
||
let input = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
|
||
assetWriter.add(input)
|
||
|
||
let adaptor = AVAssetWriterInputTaggedPixelBufferGroupAdaptor(assetWriterInput: input)
|
||
assetWriter.startWriting()
|
||
assetWriter.startSession(atSourceTime: .zero)
|
||
|
||
let leftEyeReader = try AVAssetReader(asset: leftEyeAsset)
|
||
let rightEyeReader = try AVAssetReader(asset: rightEyeAsset)
|
||
|
||
let readerOutputSettings: [String:Any] = [
|
||
kCVPixelBufferPixelFormatTypeKey as String: NSNumber(value: kCVPixelFormatType_32BGRA),
|
||
kCVPixelBufferWidthKey as String: leftVideoTrack.naturalSize.width,
|
||
kCVPixelBufferHeightKey as String: leftVideoTrack.naturalSize.height
|
||
]
|
||
|
||
let leftEyeOutput = AVAssetReaderTrackOutput(track: leftVideoTrack, outputSettings: readerOutputSettings)
|
||
let rightEyeOutput = AVAssetReaderTrackOutput(track: rightEyeAsset.tracks(withMediaType: .video).first!, outputSettings: readerOutputSettings)
|
||
|
||
leftEyeReader.add(leftEyeOutput)
|
||
rightEyeReader.add(rightEyeOutput)
|
||
|
||
leftEyeReader.startReading()
|
||
rightEyeReader.startReading()
|
||
|
||
while let leftBuffer = leftEyeOutput.copyNextSampleBuffer(),
|
||
let rightBuffer = rightEyeOutput.copyNextSampleBuffer() {
|
||
|
||
let time = Date().timeIntervalSince1970
|
||
print("获取了一帧" + String(time))
|
||
// 获取左右眼视频帧的像素缓冲区
|
||
guard let leftFrameBuffer = CMSampleBufferGetImageBuffer(leftBuffer),
|
||
let rightFrameBuffer = CMSampleBufferGetImageBuffer(rightBuffer) else {
|
||
print("获取左右眼像素缓冲区失败")
|
||
return
|
||
}
|
||
|
||
// 创建 CMTaggedBuffer 数组,包含左右眼视图的像素缓冲区
|
||
let leftCVPixelBuffer = leftFrameBuffer as CVPixelBuffer
|
||
let rightCVPixelBuffer = rightFrameBuffer as CVPixelBuffer
|
||
|
||
let left = CMTaggedBuffer(tags: [.stereoView(.leftEye), .videoLayerID(0)], pixelBuffer: leftCVPixelBuffer)
|
||
let right = CMTaggedBuffer(tags: [.stereoView(.rightEye), .videoLayerID(1)], pixelBuffer: rightCVPixelBuffer)
|
||
|
||
while !adaptor.assetWriterInput.isReadyForMoreMediaData {
|
||
// 等待直到 writerInput 准备好接收下一个视频帧
|
||
Thread.sleep(forTimeInterval: 0.1) // 暂停一段时间,避免过度占用资源
|
||
}
|
||
adaptor.appendTaggedBuffers([left, right], withPresentationTime: leftBuffer.presentationTimeStamp)
|
||
}
|
||
|
||
// 完成写入
|
||
print("完成写入")
|
||
input.markAsFinished()
|
||
outputVideoURL.stopAccessingSecurityScopedResource()
|
||
assetWriter.finishWriting { [self] in
|
||
print("可以保存")
|
||
completion(true, nil)
|
||
|
||
self.saveVideoToLibrary(videoURL: outputVideoURL, completion: completion)
|
||
}
|
||
} catch {
|
||
print("生成失败")
|
||
completion(false, error)
|
||
}
|
||
}
|
||
|
||
private func saveVideoToLibrary(videoURL: URL, completion: @escaping (Bool, Error?) -> Void) {
|
||
PHPhotoLibrary.shared().performChanges({
|
||
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
|
||
}) { success, error in
|
||
if success {
|
||
print("保存成功")
|
||
} else if let error = error {
|
||
print("保存失败")
|
||
}
|
||
}
|
||
}
|
||
}
|