如何使用 AVAsset 和 Swift 3 读取时间码轨道?

How to read a timecode track with AVAsset and Swift 3?

我想读取时间码轨道的时间值。有一个 来自 Apple 的优秀文档(参见 Technical Note 2310) 但它写在 Objective C.

我已经将核心逻辑翻译成Swift 3.它的工作原理与 ObjC 版本,这意味着来自时间码的 CMSampleBuffer 曲目被读取并转换为 CMBlockBuffer。当我失败时 创建数据指针 CMBlockBufferGetDataPointer(在 timecodeFrame() func), 这意味着原始数据总是 给我0帧。所以它归结为问题,我如何 正确处理原始数据?

import Foundation
import AVFoundation
import CoreMedia

let movie = URL(fileURLWithPath: "videoWithTimecodeTrack.mov")
let asset = AVAsset(url: movie)

asset.loadValuesAsynchronously(forKeys: ["tracks"]) {

    var error: NSError?
    guard asset.statusOfValue(forKey: "tracks", error: &error) == AVKeyValueStatus.loaded
        else { if let error = error { return print(error) } }

    readStartTimecode(asset: asset)
}


func readStartTimecode(ofAsset asset: AVAsset) {

    let timecodeTracks = asset.tracks(withMediaType: AVMediaTypeTimecode)
    guard let timecodeTrack = timecodeTracks.first,
        let assetReader = try? AVAssetReader(asset: asset) else { return }

    let readerOutput = AVAssetReaderTrackOutput(track: timecodeTrack, outputSettings: nil)
    assetReader.add(readerOutput)
    guard assetReader.startReading() else { return }

    while let sampleBuffer = readerOutput.copyNextSampleBuffer() {
        if let frame = timecodeFrame(sampleBuffer: sampleBuffer) {
            print("timecodeFrame: \(frame)")
        }
    }
}

func timecodeFrame(sampleBuffer: CMSampleBuffer) -> UInt32? {

    guard let blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer),
        let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer)
        else { return nil }

    var rawData: UnsafeMutablePointer<Int8>? = nil
    var length: Int = 0
    var totalLength: Int = 0

    let status = CMBlockBufferGetDataPointer(blockBuffer, 0, &length, &totalLength, &rawData)
    guard status == kCMBlockBufferNoErr,
        let frameRead = rawData?.pointee
        else { return nil }

    let type = CMFormatDescriptionGetMediaSubType(formatDescription)

    if type == kCMTimeCodeFormatType_TimeCode32 {
        let frame = UInt32(frameRead)
        let bigFrame = CFSwapInt32BigToHost(frame)
        print("kCMTimeCodeFormatType_TimeCode32: \(bigFrame)")
    }
    if type == kCMTimeCodeFormatType_TimeCode64 {
        print("kCMTimeCodeFormatType_TimeCode64")
        // todo
    }
    return nil
}    

编辑: 数据指针检索的 Objective C 版本如下所示:

size_t length = 0;
size_t totalLength = 0;
char *rawData = NULL;

CMBlockBufferGetDataPointer(blockBuffer, 0, &length, &totalLength, &rawData);
if (status == kCMBlockBufferNoErr) {
    int32_t *frameNumberRead = (int32_t *)rawData;
    (int)Endian32_Swap(*frameNumberRead)]
}   

解决方案是不要像 UInt32(rawData.pointee) 那样转换 Int8 数据,而是将 UnsafeMutablePointer<Int8> 指针的内存作为不同类型(暂时)访问。这看起来像这样:

if let frames = rawData?.withMemoryRebound(to: UInt32.self, capacity: 1, { CFSwapInt32BigToHost([=10=].pointee) }) {
    return frames
}

完整的函数如下所示:

func timecodeFrame(sampleBuffer: CMSampleBuffer) -> UInt32? {

    guard let blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer),
        let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer)
        else { return nil }

    var rawData: UnsafeMutablePointer<Int8>? = nil
    var length: Int = 0
    var totalLength: Int = 0

    let status = CMBlockBufferGetDataPointer(blockBuffer, 0, &length, &totalLength, &rawData)
    guard status == kCMBlockBufferNoErr else { return nil }

    let type = CMFormatDescriptionGetMediaSubType(formatDescription)

    if type == kCMTimeCodeFormatType_TimeCode32 {
        if let frames = rawData?.withMemoryRebound(to: UInt32.self, capacity: 1, { CFSwapInt32BigToHost([=11=].pointee) }) {
            return frames
        }
    }
    if type == kCMTimeCodeFormatType_TimeCode64 {
        if let frames = rawData?.withMemoryRebound(to: UInt64.self, capacity: 1, { CFSwapInt64BigToHost([=11=].pointee) }) {
            return UInt32(frames)
        }
    }
    return nil
}

我希望这对想要阅读视频时间码轨道的开始时间码的其他人有用。