Как прочитать дорожку временного кода с AVAsset и Swift 3?

Я хотел бы прочитать значение времени дорожки временного кода. Существует отличная документация от Apple (см. Техническое примечание 2310), но она написана в Цели C.

Я перевел основную логику в Swift 3. Он работает точно так же, как версия ObjC, а это означает, что CMSampleBuffer из дорожки временного кода читается и преобразуется в CMBlockBuffer, Это не удается, когда я создаю указатель данных CMBlockBufferGetDataPointertimecodeFrame() 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)]
}   

1 ответ

Решение

Решение состоит в том, чтобы не конвертировать Int8 данные как UInt32(rawData.pointee) но чтобы получить доступ к UnsafeMutablePointer<Int8> память указателя как другой тип (временно). Это будет выглядеть так:

if let frames = rawData?.withMemoryRebound(to: UInt32.self, capacity: 1, { CFSwapInt32BigToHost($0.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($0.pointee) }) {
            return frames
        }
    }
    if type == kCMTimeCodeFormatType_TimeCode64 {
        if let frames = rawData?.withMemoryRebound(to: UInt64.self, capacity: 1, { CFSwapInt64BigToHost($0.pointee) }) {
            return UInt32(frames)
        }
    }
    return nil
}

Я надеюсь, что это полезно для тех, кто хочет прочитать начальный временной код дорожки временного кода видео.

Другие вопросы по тегам