Swift中如何存储AVDepthData以便后续处理?
解决方案:AVDepthData的高效存储与后续处理
方案1:将深度数据编码为独立的灰度视频流
这是你考虑的方向,利用AVAssetWriter将深度数据编码为单独的视频文件(或同一文件的辅助轨道),后续处理时通过时间戳匹配主视频帧和深度帧。
关键步骤:
- 为深度数据创建独立的
AVAssetWriterInput轨道,选择HEVC编码(压缩率高,适配灰度流) - 将
AVDepthData转换为符合编码要求的CMSampleBuffer,同步主视频帧的时间戳 - 开启非实时写入模式,无需严格满足实时性要求
核心代码示例:
初始化深度轨道
// 假设已初始化assetWriter实例(指向深度视频文件路径) let depthOutputSettings: [String: Any] = [ AVVideoCodecKey: AVVideoCodecType.hevc, AVVideoWidthKey: depthData.depthDataMap.width, AVVideoHeightKey: depthData.depthDataMap.height, AVVideoCompressionPropertiesKey: [ AVVideoAverageBitRateKey: 2_000_000, // 可根据深度精度需求调整 AVVideoProfileLevelKey: AVVideoProfileLevelHEVCMainAutoLevel ] ] guard let depthInput = assetWriter.add(AVAssetWriterInput(mediaType: .video, outputSettings: depthOutputSettings)) else { fatalError("Failed to create depth asset writer input") } depthInput.expectsMediaDataInRealTime = false // 非实时写入,放宽时序要求
转换AVDepthData到CMSampleBuffer
func convertDepthDataToSampleBuffer(_ depthData: AVDepthData, timestamp: CMTime) -> CMSampleBuffer? { // 将深度数据转换为16位灰度格式(适配HEVC编码) let convertedDepth = depthData.converting(toDepthDataType: kCVPixelFormatType_16Gray) let pixelBuffer = convertedDepth.depthDataMap // 创建格式描述 var formatDesc: CMFormatDescription? CMVideoFormatDescriptionCreateForImageBuffer(allocator: kCFAllocatorDefault, imageBuffer: pixelBuffer, formatDescriptionOut: &formatDesc) guard let desc = formatDesc else { return nil } // 创建CMSampleBuffer并设置时间戳 var sampleBuffer: CMSampleBuffer? CMSampleBufferCreateForImageBuffer(allocator: kCFAllocatorDefault, imageBuffer: pixelBuffer, dataReady: true, makeDataReadyCallback: nil, refcon: nil, formatDescription: desc, sampleTimingInfo: nil, sampleBufferOut: &sampleBuffer) if let buffer = sampleBuffer { CMSampleBufferSetOutputPresentationTimeStamp(buffer, timestamp) } return sampleBuffer }
写入流程
在处理主视频帧的同时,调用上述转换函数得到深度样本缓冲,通过AVAssetWriterInput.append(_:)写入,确保每帧主视频和深度帧的时间戳完全一致。
方案2:二进制压缩存储+索引文件
如果追求极致的存储效率和写入速度,可以将每帧深度数据压缩后写入二进制文件,同时维护一个索引文件记录每帧的时间戳、数据偏移量和格式信息,后续处理时通过索引快速定位并还原深度数据。
核心代码示例:
写入阶段
let depthBinURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("depth_data.bin") let indexURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("depth_index.json") var fileHandle: FileHandle? var indexEntries: [[String: Any]] = [] // 初始化文件句柄 do { try Data().write(to: depthBinURL) // 创建空文件 fileHandle = try FileHandle(forWritingTo: depthBinURL) } catch { print("Failed to initialize depth file: \(error)") } // 写入单帧深度数据 func writeDepthData(_ depthData: AVDepthData, timestamp: CMTime) { guard let handle = fileHandle else { return } let pixelBuffer = depthData.depthDataMap // 锁定像素缓冲并读取原始数据 CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly) defer { CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly) } let baseAddr = CVPixelBufferGetBaseAddress(pixelBuffer)! let dataSize = CVPixelBufferGetDataSize(pixelBuffer) let rawData = Data(bytes: baseAddr, count: dataSize) // ZLIB压缩(压缩率高,速度快) guard let compressedData = try? rawData.compressed(using: .zlib) else { return } // 记录偏移量并写入数据 let offset = handle.offsetInFile handle.write(compressedData) // 添加索引条目 indexEntries.append([ "timestamp": timestamp.seconds, "offset": offset, "length": compressedData.count, "width": CVPixelBufferGetWidth(pixelBuffer), "height": CVPixelBufferGetHeight(pixelBuffer), "format": CVPixelBufferGetPixelFormatType(pixelBuffer) ]) } // 完成写入后保存索引文件 func finalizeDepthStorage() { do { let indexData = try JSONSerialization.data(withJSONObject: indexEntries) try indexData.write(to: indexURL) fileHandle?.closeFile() } catch { print("Failed to save index file: \(error)") } }
读取阶段
func retrieveDepthData(forTimestamp targetTimestamp: CMTime) -> AVDepthData? { // 加载索引文件 guard let indexData = try? Data(contentsOf: indexURL), let entries = try? JSONSerialization.jsonObject(with: indexData) as? [[String: Any]], let handle = try? FileHandle(forReadingFrom: depthBinURL) else { return nil } // 找到最匹配的时间戳条目 guard let targetEntry = entries.min(by: { abs($0["timestamp"] as! Double - targetTimestamp.seconds) < abs($1["timestamp"] as! Double - targetTimestamp.seconds) }) else { return nil } // 读取并解压数据 let offset = targetEntry["offset"] as! UInt64 let length = targetEntry["length"] as! Int handle.seek(toFileOffset: offset) let compressedData = handle.readData(ofLength: length) guard let rawData = try? compressedData.decompressed(using: .zlib) else { return nil } // 重建CVPixelBuffer let width = targetEntry["width"] as! Int let height = targetEntry["height"] as! Int let format = targetEntry["format"] as! OSType var pixelBuffer: CVPixelBuffer? let attrs: [String: Any] = [ kCVPixelBufferWidthKey as String: width, kCVPixelBufferHeightKey as String: height, kCVPixelBufferPixelFormatTypeKey as String: format, kCVPixelBufferBytesPerRowAlignmentKey as String: CVPixelBufferGetBytesPerRow(nil, width, format, 0) ] CVPixelBufferCreateWithBytes(kCFAllocatorDefault, width, height, format, rawData.withUnsafeBytes { $0.baseAddress }, CVPixelBufferGetBytesPerRow(nil, width, format, 0), nil, nil, attrs as CFDictionary, &pixelBuffer) // 转换为AVDepthData guard let pb = pixelBuffer else { return nil } return AVDepthData(pixelBuffer: pb, depthDataFormat: .init(rawValue: format)) }
方案对比与选择
- 深度视频轨道方案:适配AVFoundation生态,后续处理可直接用
AVAssetReader读取,同步逻辑简单,适合需要和主视频统一管理的场景。 - 二进制压缩方案:写入/读取速度最快,存储占用最小,适合对性能要求极高的场景,但需要自行维护索引和数据解析逻辑。
- HEIC单帧存储:因编码开销大,30fps场景下速度跟不上,不推荐。
内容的提问来源于stack exchange,提问作者Bereketab Tessema
相关产品推荐
相关产品推荐

