You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 16:43:17