我的视频只有 (4.0, 3.0) 像素的自然大小,这也是提取的帧大小

问题描述

上下文

我正在处理 1280x920 的视频文件,这是它们在 QuickTime 中显示或什至在我的 AVPlayer 中播放时的实际像素大小。

我在一个文件夹中有一堆视频,我需要将它们放在一个 AVMutableComposition 上并播放。

我还需要为每个视频提取最后一帧。

到目前为止,我所做的是在我的个人 AVAssetimageGenerator 上对每个人使用 AVAsset,并且无论我使用的是 generateCGImagesAsynchronously 还是 copyCGImage,它都有效。

但我认为在我的乐曲资产上运行 generateCGImagesAsynchronously 会更有效率,所以我只有一个调用,而不是循环播放每个原始曲目。

代替:

                   v-Get Frame
AVAsset1 |---------|
AVAsset2 |---------|
AVAsset3 |---------|

我想做:

                                v----------v----------v- Get Frames
AVMutableComposition: |---------||---------||---------|

问题

实际问题如下:

import AVKit

var video1URL = URL(fileReferenceLiteralResourceName: "video_bad.mp4") // One of my video file
let asset1 = AVAsset(url: video1URL)
let track1 = asset1.tracks(withMediaType: .video).first!

_ = track1.naturalSize // {w 4 h 3}

var video2URL = URL(fileReferenceLiteralResourceName: "video_ok.mp4") // Some mp4 I got from internet
let asset2 = AVAsset(url: video2URL)
let track2 = asset2.tracks(withMediaType: .video).first!

_ = track2.naturalSize // {w 1920 h 1080}

这是 Playground 的实际屏幕截图(您可以下载 here):

enter image description here

还有一点:

查看 QuickTime 检查器中的“当前比例”信息。视频显示正常,但显示为真正放大(请注意,没有像素模糊或任何其他内容,这与某些元数据有关)

我在 QuickTime 中处理的视频文件

enter image description here

来自互联网的视频文件

enter image description here

问题

  • 该信息是什么元数据以及如何处理它?
  • 为什么在原始曲目中与在不同的作品中不同?
  • 如何在此类视频中提取帧?

解决方法

因此,如果您偶然发现了这篇文章,那可能是您正在尝试了解 Tesla 编写视频的方式。

该问题没有简单的解决方案,这是由 Tesla 软件错误地设置 .mov 视频文件中的元数据引起的。我向 Apple 发起了一个事件,他们能够确认这一点。

因此,我编写了一些代码,通过重写指示视频轨道大小的字节来实际修复视频文件。

我们开始吧,它很丑,但为了完整起见,我想在这里发布一个解决方案,如果不是最好的。

import Foundation

struct VideoFixer {
    var url: URL
    
    private var fh: FileHandle?
    
    static func fix(_ url: URL) {
        var fixer = VideoFixer(url)
        fixer.fix()
    }
    
    init(_ url: URL) {
        self.url = url
    }
    
    mutating func fix() {
        guard let fh = try? FileHandle(forUpdating: url) else {
            return
        }
        
        var atom = Atom(fh)
        atom.seekTo(AtomType.moov)
        atom.enter()
        if atom.atom_type != AtomType.trak {
            atom.seekTo(AtomType.trak)
        }
        atom.enter()
        if atom.atom_type != AtomType.tkhd {
            atom.seekTo(AtomType.tkhd)
        }
        atom.seekTo(AtomType.tkhd)
        
        let data = atom.data()
        
        let width = data?.withUnsafeBytes { $0.load(fromByteOffset: 76,as: UInt16.self).bigEndian }
        let height = data?.withUnsafeBytes { $0.load(fromByteOffset: 80,as: UInt16.self).bigEndian }
        
        if width==4 && height==3 {
            guard let offset = try? fh.offset() else {
                return
            }
            try? fh.seek(toOffset: offset+76)
            //1280x960
            var newWidth = UInt16(1280).byteSwapped
            var newHeight = UInt16(960).byteSwapped
            let dataWidth = Data(bytes: &newWidth,count: 2)
            let dataHeight = Data(bytes: &newHeight,count: 2)
            fh.write(dataWidth)
            try? fh.seek(toOffset: offset+80)
            fh.write(dataHeight)
        }
        try? fh.close()

        
    }
}

typealias AtomType = UInt32

extension UInt32 {
    static var ftyp = UInt32(1718909296)
    static var mdat = UInt32(1835295092)
    static var free = UInt32(1718773093)
    static var moov = UInt32(1836019574)
    static var trak = UInt32(1953653099)
    static var tkhd = UInt32(1953196132)
}

struct Atom {
    var fh: FileHandle
    
    var atom_size: UInt32 = 0
    var atom_type: UInt32 = 0
    
    init(_ fh: FileHandle) {
        self.fh = fh
        
        self.read()
    }
    mutating func seekTo(_ type:AtomType) {
        while self.atom_type != type {
            self.next()
        }
    }
    mutating func next() {
        guard var offset = try? fh.offset() else {
            return
        }
        
        offset = offset-8+UInt64(atom_size)
        
        if (try? self.fh.seek(toOffset: UInt64(offset))) == nil {
            return
        }
        self.read()
    }
    mutating func read() {
        self.atom_size = fh.nextUInt32().bigEndian
        self.atom_type = fh.nextUInt32().bigEndian
    }
    mutating func enter() {
        self.atom_size = fh.nextUInt32().bigEndian
        self.atom_type = fh.nextUInt32().bigEndian
    }
    func data() -> Data? {
        guard let offset = try? fh.offset() else {
            return nil
        }
        let data = fh.readData(ofLength: Int(self.atom_size))
        try? fh.seek(toOffset: offset)
        return data
    }
}
extension FileHandle {
    func nextUInt32() -> UInt32 {
        let data = self.readData(ofLength: 4)
        let i32array = data.withUnsafeBytes { $0.load(as: UInt32.self) }
        //print(i32array)
        return i32array
    }
}