731 lines
22 KiB
Swift
731 lines
22 KiB
Swift
import Foundation
|
|
import AVFoundation
|
|
|
|
class MP4Box {
|
|
static func create(_ data:Data) throws -> MP4Box {
|
|
let buffer:ByteArray = ByteArray(data: data)
|
|
let size:UInt32 = try buffer.readUInt32()
|
|
let type:String = try buffer.readUTF8Bytes(4)
|
|
|
|
buffer.clear()
|
|
switch type {
|
|
case "moov", "trak", "mdia", "minf", "stbl", "edts":
|
|
return MP4ContainerBox(size: size, type: type)
|
|
case "mp4v", "s263", "avc1":
|
|
return MP4VisualSampleEntryBox(size: size, type: type)
|
|
case "mvhd", "mdhd":
|
|
return MP4MediaHeaderBox(size: size, type: type)
|
|
case "mp4a":
|
|
return MP4AudioSampleEntryBox(size: size, type: type)
|
|
case "esds":
|
|
return MP4ElementaryStreamDescriptorBox(size: size, type: type)
|
|
case "stts":
|
|
return MP4TimeToSampleBox(size: size, type: type)
|
|
case "stss":
|
|
return MP4SyncSampleBox(size: size, type: type)
|
|
case "stsd":
|
|
return MP4SampleDescriptionBox(size: size, type: type)
|
|
case "stco":
|
|
return MP4ChunkOffsetBox(size: size, type: type)
|
|
case "stsc":
|
|
return MP4SampleToChunkBox(size: size, type: type)
|
|
case "stsz":
|
|
return MP4SampleSizeBox(size: size, type: type)
|
|
case "elst":
|
|
return MP4EditListBox(size: size, type: type)
|
|
default:
|
|
return MP4Box(size: size, type: type)
|
|
}
|
|
}
|
|
|
|
var leafNode:Bool {
|
|
return false
|
|
}
|
|
|
|
fileprivate(set) var type:String = "undf"
|
|
fileprivate(set) var size:UInt32 = 0
|
|
fileprivate(set) var offset:UInt64 = 0
|
|
fileprivate(set) var parent:MP4Box? = nil
|
|
|
|
init() {
|
|
}
|
|
|
|
init(size: UInt32, type:String) {
|
|
self.size = size
|
|
self.type = type
|
|
}
|
|
|
|
func load(_ fileHandle:FileHandle) throws -> UInt32 {
|
|
if (size == 0) {
|
|
size = UInt32(fileHandle.seekToEndOfFile() - offset)
|
|
return size
|
|
}
|
|
fileHandle.seek(toFileOffset: offset + UInt64(size))
|
|
return size
|
|
}
|
|
|
|
func getBoxes(byName:String) -> [MP4Box] {
|
|
return []
|
|
}
|
|
|
|
func clear() {
|
|
parent = nil
|
|
}
|
|
|
|
func create(_ data:Data, offset:UInt32) throws -> MP4Box {
|
|
let box:MP4Box = try MP4Box.create(data)
|
|
box.parent = self
|
|
box.offset = self.offset + UInt64(offset)
|
|
return box
|
|
}
|
|
}
|
|
|
|
extension MP4Box: CustomStringConvertible {
|
|
// MARK: CustomStringConvertible
|
|
var description:String {
|
|
return Mirror(reflecting: self).description
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
class MP4ContainerBox: MP4Box {
|
|
|
|
fileprivate var children:[MP4Box] = []
|
|
|
|
override var leafNode: Bool {
|
|
return false
|
|
}
|
|
|
|
override func load(_ file:FileHandle) throws -> UInt32 {
|
|
children.removeAll(keepingCapacity: false)
|
|
|
|
var offset:UInt32 = parent == nil ? 0 : 8
|
|
file.seek(toFileOffset: self.offset + UInt64(offset))
|
|
|
|
while (size != offset) {
|
|
let child:MP4Box = try create(file.readData(ofLength: 8), offset: offset)
|
|
offset += try child.load(file)
|
|
children.append(child)
|
|
}
|
|
|
|
return offset
|
|
}
|
|
|
|
override func getBoxes(byName:String) -> [MP4Box] {
|
|
var list:[MP4Box] = []
|
|
for child in children {
|
|
if (byName == child.type || byName == "*" ) {
|
|
list.append(child)
|
|
}
|
|
if (!child.leafNode) {
|
|
list += child.getBoxes(byName: byName)
|
|
}
|
|
}
|
|
return list
|
|
}
|
|
|
|
override func clear() {
|
|
for child in children {
|
|
child.clear()
|
|
}
|
|
children.removeAll(keepingCapacity: false)
|
|
parent = nil
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
final class MP4MediaHeaderBox: MP4Box {
|
|
var version:UInt8 = 0
|
|
var creationTime:UInt32 = 0
|
|
var modificationTime:UInt32 = 0
|
|
var timeScale:UInt32 = 0
|
|
var duration:UInt32 = 0
|
|
var language:UInt16 = 0
|
|
var quality:UInt16 = 0
|
|
|
|
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
|
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
|
|
version = try buffer.readUInt8()
|
|
buffer.position += 3
|
|
creationTime = try buffer.readUInt32()
|
|
modificationTime = try buffer.readUInt32()
|
|
timeScale = try buffer.readUInt32()
|
|
duration = try buffer.readUInt32()
|
|
language = try buffer.readUInt16()
|
|
quality = try buffer.readUInt16()
|
|
buffer.clear()
|
|
return size
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
final class MP4ChunkOffsetBox: MP4Box {
|
|
var entries:[UInt32] = []
|
|
|
|
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
|
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
|
|
buffer.position += 4
|
|
|
|
let numberOfEntries:UInt32 = try buffer.readUInt32()
|
|
for _ in 0..<numberOfEntries {
|
|
entries.append(try buffer.readUInt32())
|
|
}
|
|
buffer.clear()
|
|
|
|
return size
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
final class MP4SyncSampleBox: MP4Box {
|
|
var entries:[UInt32] = []
|
|
|
|
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
|
entries.removeAll(keepingCapacity: false)
|
|
|
|
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
|
|
buffer.position += 4
|
|
|
|
let numberOfEntries:UInt32 = try buffer.readUInt32()
|
|
for _ in 0..<numberOfEntries {
|
|
entries.append(try buffer.readUInt32())
|
|
}
|
|
|
|
return size
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
final class MP4TimeToSampleBox: MP4Box {
|
|
struct Entry: CustomStringConvertible {
|
|
var sampleCount:UInt32 = 0
|
|
var sampleDuration:UInt32 = 0
|
|
|
|
var description:String {
|
|
return Mirror(reflecting: self).description
|
|
}
|
|
|
|
init(sampleCount:UInt32, sampleDuration:UInt32) {
|
|
self.sampleCount = sampleCount
|
|
self.sampleDuration = sampleDuration
|
|
}
|
|
}
|
|
|
|
var entries:[Entry] = []
|
|
|
|
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
|
entries.removeAll(keepingCapacity: false)
|
|
|
|
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
|
|
buffer.position += 4
|
|
|
|
let numberOfEntries:UInt32 = try buffer.readUInt32()
|
|
for _ in 0..<numberOfEntries {
|
|
entries.append(Entry(
|
|
sampleCount: try buffer.readUInt32(),
|
|
sampleDuration: try buffer.readUInt32()
|
|
))
|
|
}
|
|
|
|
return size
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
final class MP4SampleSizeBox: MP4Box {
|
|
var entries:[UInt32] = []
|
|
|
|
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
|
entries.removeAll(keepingCapacity: false)
|
|
|
|
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(self.size) - 8))
|
|
buffer.position += 4
|
|
|
|
let sampleSize:UInt32 = try buffer.readUInt32()
|
|
|
|
if (sampleSize != 0) {
|
|
entries.append(sampleSize)
|
|
return size
|
|
}
|
|
|
|
let numberOfEntries:UInt32 = try buffer.readUInt32()
|
|
for _ in 0..<numberOfEntries {
|
|
entries.append(try buffer.readUInt32())
|
|
}
|
|
buffer.clear()
|
|
|
|
return size
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
final class MP4ElementaryStreamDescriptorBox: MP4ContainerBox {
|
|
var audioDecorderSpecificConfig:Data = Data()
|
|
|
|
var tag:UInt8 = 0
|
|
var tagSize:UInt8 = 0
|
|
var id:UInt16 = 0
|
|
var streamDependenceFlag:UInt8 = 0
|
|
var urlFlag:UInt8 = 0
|
|
var ocrStreamFlag:UInt8 = 0
|
|
var streamPriority:UInt8 = 0
|
|
|
|
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
|
var tagSize:UInt8 = 0
|
|
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(self.size) - 8))
|
|
buffer.position += 4
|
|
|
|
tag = try buffer.readUInt8()
|
|
self.tagSize = try buffer.readUInt8()
|
|
if (self.tagSize == 0x80) {
|
|
buffer.position += 2
|
|
self.tagSize = try buffer.readUInt8()
|
|
}
|
|
|
|
id = try buffer.readUInt16()
|
|
|
|
let data:UInt8 = try buffer.readUInt8()
|
|
streamDependenceFlag = data >> 7
|
|
urlFlag = (data >> 6) & 0x1
|
|
ocrStreamFlag = (data >> 5) & 0x1
|
|
streamPriority = data & 0x1f
|
|
|
|
if (streamDependenceFlag == 1) {
|
|
let _:UInt16 = try buffer.readUInt16()
|
|
}
|
|
|
|
// Decorder Config Descriptor
|
|
let _:UInt8 = try buffer.readUInt8()
|
|
tagSize = try buffer.readUInt8()
|
|
if (tagSize == 0x80) {
|
|
buffer.position += 2
|
|
tagSize = try buffer.readUInt8()
|
|
}
|
|
buffer.position += 13
|
|
|
|
// Audio Decorder Spec Info
|
|
let _:UInt8 = try buffer.readUInt8()
|
|
tagSize = try buffer.readUInt8()
|
|
if (tagSize == 0x80) {
|
|
buffer.position += 2
|
|
tagSize = try buffer.readUInt8()
|
|
}
|
|
|
|
audioDecorderSpecificConfig = try buffer.readBytes(Int(tagSize))
|
|
|
|
return size
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
final class MP4AudioSampleEntryBox: MP4ContainerBox {
|
|
var version:UInt16 = 0
|
|
|
|
var channelCount:UInt16 = 0
|
|
var sampleSize:UInt16 = 0
|
|
var compressionId:UInt16 = 0
|
|
var packetSize:UInt16 = 0
|
|
var sampleRate:UInt32 = 0
|
|
var samplesPerPacket:UInt32 = 0
|
|
var bytesPerPacket:UInt32 = 0
|
|
var bytesPerFrame:UInt32 = 0
|
|
var bytesPerSample:UInt32 = 0
|
|
|
|
var soundVersion2Data:[UInt8] = []
|
|
|
|
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
|
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
|
|
buffer.position += 8
|
|
|
|
version = try buffer.readUInt16()
|
|
buffer.position += 6
|
|
|
|
channelCount = try buffer.readUInt16()
|
|
sampleSize = try buffer.readUInt16()
|
|
compressionId = try buffer.readUInt16()
|
|
packetSize = try buffer.readUInt16()
|
|
sampleRate = try buffer.readUInt32()
|
|
|
|
if (type != "mlpa") {
|
|
sampleRate = sampleRate >> 16
|
|
}
|
|
|
|
if (0 < version) {
|
|
samplesPerPacket = try buffer.readUInt32()
|
|
bytesPerPacket = try buffer.readUInt32()
|
|
bytesPerFrame = try buffer.readUInt32()
|
|
bytesPerSample = try buffer.readUInt32()
|
|
}
|
|
|
|
if (version == 2) {
|
|
soundVersion2Data += try buffer.readBytes(20)
|
|
}
|
|
|
|
var offset:UInt32 = UInt32(buffer.position) + 8
|
|
fileHandle.seek(toFileOffset: self.offset + UInt64(offset))
|
|
|
|
let esds:MP4Box = try create(fileHandle.readData(ofLength: 8), offset: offset)
|
|
offset += try esds.load(fileHandle)
|
|
children.append(esds)
|
|
|
|
// skip
|
|
fileHandle.seek(toFileOffset: self.offset + UInt64(size))
|
|
|
|
return size
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
final class MP4VisualSampleEntryBox: MP4ContainerBox {
|
|
static var dataSize:Int = 78
|
|
|
|
var width:UInt16 = 0
|
|
var height:UInt16 = 0
|
|
var hSolution:UInt32 = 0
|
|
var vSolution:UInt32 = 0
|
|
var frameCount:UInt16 = 1
|
|
var compressorname:String = ""
|
|
var depth:UInt16 = 16
|
|
|
|
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
|
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: MP4VisualSampleEntryBox.dataSize))
|
|
|
|
buffer.position += 24
|
|
width = try buffer.readUInt16()
|
|
height = try buffer.readUInt16()
|
|
hSolution = try buffer.readUInt32()
|
|
vSolution = try buffer.readUInt32()
|
|
buffer.position += 4
|
|
frameCount = try buffer.readUInt16()
|
|
compressorname = try buffer.readUTF8Bytes(32)
|
|
depth = try buffer.readUInt16()
|
|
let _:UInt16 = try buffer.readUInt16()
|
|
buffer.clear()
|
|
|
|
var offset:UInt32 = UInt32(MP4VisualSampleEntryBox.dataSize)
|
|
let child:MP4Box = try MP4Box.create(fileHandle.readData(ofLength: 8))
|
|
child.parent = self
|
|
child.offset = self.offset + UInt64(offset) + 8
|
|
offset += try child.load(fileHandle)
|
|
children.append(child)
|
|
|
|
// skip
|
|
fileHandle.seek(toFileOffset: self.offset + UInt64(size))
|
|
|
|
return size
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
final class MP4SampleDescriptionBox: MP4ContainerBox {
|
|
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
|
children.removeAll(keepingCapacity: false)
|
|
|
|
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: 8))
|
|
buffer.position = 4
|
|
|
|
var offset:UInt32 = 16
|
|
let numberOfEntries:UInt32 = try buffer.readUInt32()
|
|
for _ in 0..<numberOfEntries {
|
|
let child:MP4Box = try create(fileHandle.readData(ofLength: 8), offset: offset)
|
|
offset += try child.load(fileHandle)
|
|
children.append(child)
|
|
}
|
|
|
|
return offset
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
final class MP4SampleToChunkBox: MP4Box {
|
|
struct Entry:CustomStringConvertible {
|
|
var firstChunk:UInt32 = 0
|
|
var samplesPerChunk:UInt32 = 0
|
|
var sampleDescriptionIndex:UInt32 = 0
|
|
|
|
var description:String {
|
|
return Mirror(reflecting: self).description
|
|
}
|
|
|
|
init(firstChunk:UInt32, samplesPerChunk:UInt32, sampleDescriptionIndex:UInt32) {
|
|
self.firstChunk = firstChunk
|
|
self.samplesPerChunk = samplesPerChunk
|
|
self.sampleDescriptionIndex = sampleDescriptionIndex
|
|
}
|
|
}
|
|
|
|
var entries:[Entry] = []
|
|
|
|
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
|
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
|
|
buffer.position += 4
|
|
|
|
let numberOfEntries:UInt32 = try buffer.readUInt32()
|
|
for _ in 0..<numberOfEntries {
|
|
entries.append(Entry(
|
|
firstChunk: try buffer.readUInt32(),
|
|
samplesPerChunk: try buffer.readUInt32(),
|
|
sampleDescriptionIndex: try buffer.readUInt32()
|
|
))
|
|
}
|
|
buffer.clear()
|
|
|
|
return size
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
final class MP4EditListBox: MP4Box {
|
|
struct Entry: CustomStringConvertible {
|
|
var segmentDuration:UInt32 = 0
|
|
var mediaTime:UInt32 = 0
|
|
var mediaRate:UInt32 = 0
|
|
|
|
var description:String {
|
|
return Mirror(reflecting: self).description
|
|
}
|
|
|
|
init(segmentDuration:UInt32, mediaTime:UInt32, mediaRate:UInt32) {
|
|
self.segmentDuration = segmentDuration
|
|
self.mediaTime = mediaTime
|
|
self.mediaRate = mediaRate
|
|
}
|
|
}
|
|
|
|
var version:UInt32 = 0
|
|
var entries:[Entry] = []
|
|
|
|
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
|
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
|
|
|
|
version = try buffer.readUInt32()
|
|
entries.removeAll(keepingCapacity: false)
|
|
|
|
let numberOfEntries:UInt32 = try buffer.readUInt32()
|
|
for _ in 0..<numberOfEntries {
|
|
entries.append(Entry(
|
|
segmentDuration: try buffer.readUInt32(),
|
|
mediaTime: try buffer.readUInt32(),
|
|
mediaRate: try buffer.readUInt32()
|
|
))
|
|
}
|
|
|
|
return size
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
final class MP4Reader: MP4ContainerBox {
|
|
private(set) var url:URL
|
|
|
|
var isEmpty:Bool {
|
|
return getBoxes(byName: "mdhd").isEmpty
|
|
}
|
|
|
|
private var fileHandle:FileHandle? = nil
|
|
|
|
init(url:URL) {
|
|
do {
|
|
self.url = url
|
|
super.init()
|
|
fileHandle = try FileHandle(forReadingFrom: url)
|
|
} catch let error as NSError {
|
|
logger.error("\(error)")
|
|
}
|
|
}
|
|
|
|
func seek(toFileOffset: UInt64) {
|
|
return fileHandle!.seek(toFileOffset: toFileOffset)
|
|
}
|
|
|
|
func readData(ofLength: Int) -> Data {
|
|
return fileHandle!.readData(ofLength: ofLength)
|
|
}
|
|
|
|
func readData(ofBox:MP4Box) -> Data {
|
|
guard let fileHandle:FileHandle = fileHandle else {
|
|
return Data()
|
|
}
|
|
let currentOffsetInFile:UInt64 = fileHandle.offsetInFile
|
|
fileHandle.seek(toFileOffset: ofBox.offset + 8)
|
|
let data:Data = fileHandle.readData(ofLength: Int(ofBox.size) - 8)
|
|
fileHandle.seek(toFileOffset: currentOffsetInFile)
|
|
return data
|
|
}
|
|
|
|
func load() throws -> UInt32 {
|
|
guard let fileHandle:FileHandle = self.fileHandle else {
|
|
return 0
|
|
}
|
|
return try load(fileHandle)
|
|
}
|
|
|
|
func close() {
|
|
fileHandle?.closeFile()
|
|
}
|
|
|
|
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
|
let size:UInt64 = fileHandle.seekToEndOfFile()
|
|
fileHandle.seek(toFileOffset: 0)
|
|
self.size = UInt32(size)
|
|
return try super.load(fileHandle)
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
final class MP4TrakReader {
|
|
static let defaultBufferTime:Double = 500
|
|
|
|
var trak:MP4Box
|
|
var bufferTime:Double = MP4TrakReader.defaultBufferTime
|
|
weak var delegate:MP4SamplerDelegate?
|
|
|
|
private var id:Int = 0
|
|
private var handle:FileHandle?
|
|
private lazy var timerDriver:TimerDriver = {
|
|
var timerDriver:TimerDriver = TimerDriver()
|
|
timerDriver.delegate = self
|
|
return timerDriver
|
|
}()
|
|
private var currentOffset:UInt64 {
|
|
return UInt64(offset[cursor])
|
|
}
|
|
private var currentIsKeyframe:Bool {
|
|
return keyframe[cursor] != nil
|
|
}
|
|
private var currentDuration:Double {
|
|
return Double(totalTimeToSample) * 1000 / Double(timeScale)
|
|
}
|
|
private var currentTimeToSample:Double {
|
|
return Double(timeToSample[cursor]) * 1000 / Double(timeScale)
|
|
}
|
|
private var currentSampleSize:Int {
|
|
return Int((sampleSize.count == 1) ? sampleSize[0] : sampleSize[cursor])
|
|
}
|
|
private var cursor:Int = 0
|
|
private var offset:[UInt32] = []
|
|
private var keyframe:[Int:Bool] = [:]
|
|
private var timeScale:UInt32 = 0
|
|
private var sampleSize:[UInt32] = []
|
|
private var timeToSample:[UInt32] = []
|
|
private var totalTimeToSample:UInt32 = 0
|
|
|
|
init(id:Int, trak:MP4Box) {
|
|
self.id = id
|
|
self.trak = trak
|
|
|
|
let mdhd:MP4Box? = trak.getBoxes(byName: "mdhd").first
|
|
if let mdhd:MP4MediaHeaderBox = mdhd as? MP4MediaHeaderBox {
|
|
timeScale = mdhd.timeScale
|
|
}
|
|
|
|
let stss:MP4Box? = trak.getBoxes(byName: "stss").first
|
|
if let stss:MP4SyncSampleBox = stss as? MP4SyncSampleBox {
|
|
var keyframes:[UInt32] = stss.entries
|
|
for i in 0..<keyframes.count {
|
|
keyframe[Int(keyframes[i]) - 1] = true
|
|
}
|
|
}
|
|
|
|
let stts:MP4Box? = trak.getBoxes(byName: "stts").first
|
|
if let stts:MP4TimeToSampleBox = stts as? MP4TimeToSampleBox {
|
|
var timeToSample:[MP4TimeToSampleBox.Entry] = stts.entries
|
|
for i in 0..<timeToSample.count {
|
|
let entry:MP4TimeToSampleBox.Entry = timeToSample[i]
|
|
for _ in 0..<entry.sampleCount {
|
|
self.timeToSample.append(entry.sampleDuration)
|
|
}
|
|
}
|
|
}
|
|
|
|
let stsz:MP4Box? = trak.getBoxes(byName: "stsz").first
|
|
if let stsz:MP4SampleSizeBox = stsz as? MP4SampleSizeBox {
|
|
sampleSize = stsz.entries
|
|
}
|
|
|
|
let stco:MP4Box = trak.getBoxes(byName: "stco").first!
|
|
let stsc:MP4Box = trak.getBoxes(byName: "stsc").first!
|
|
var offsets:[UInt32] = (stco as! MP4ChunkOffsetBox).entries
|
|
var sampleToChunk:[MP4SampleToChunkBox.Entry] = (stsc as! MP4SampleToChunkBox).entries
|
|
|
|
var index:Int = 0
|
|
let count:Int = sampleToChunk.count
|
|
for i in 0..<count {
|
|
let m:Int = (i + 1 < count) ? Int(sampleToChunk[i + 1].firstChunk) - 1 : offsets.count
|
|
for j in (Int(sampleToChunk[i].firstChunk) - 1)..<m {
|
|
var offset:UInt32 = offsets[j]
|
|
for _ in 0..<sampleToChunk[i].samplesPerChunk {
|
|
self.offset.append(offset)
|
|
offset += sampleSize[index]
|
|
index += 1
|
|
}
|
|
}
|
|
}
|
|
totalTimeToSample = timeToSample[cursor]
|
|
}
|
|
|
|
func execute(_ reader:MP4Reader) {
|
|
do {
|
|
handle = try FileHandle(forReadingFrom: reader.url)
|
|
|
|
if let avcC:MP4Box = trak.getBoxes(byName: "avcC").first {
|
|
delegate?.didSet(config: reader.readData(ofBox: avcC), withID: id, type: .video)
|
|
}
|
|
if let esds:MP4ElementaryStreamDescriptorBox = trak.getBoxes(byName: "esds").first as? MP4ElementaryStreamDescriptorBox {
|
|
delegate?.didSet(config: Data(esds.audioDecorderSpecificConfig), withID: id, type: .audio)
|
|
}
|
|
|
|
timerDriver.interval = MachUtil.nanosToAbs(UInt64(currentTimeToSample * 1000 * 1000))
|
|
while (currentDuration <= bufferTime) {
|
|
tick(timerDriver)
|
|
}
|
|
timerDriver.startRunning()
|
|
} catch {
|
|
logger.warn("file open error :\(reader.url)")
|
|
}
|
|
}
|
|
|
|
private func hasNext() -> Bool {
|
|
return cursor + 1 < offset.count
|
|
}
|
|
|
|
private func next() {
|
|
defer {
|
|
cursor += 1
|
|
}
|
|
totalTimeToSample += timeToSample[cursor]
|
|
}
|
|
}
|
|
|
|
extension MP4TrakReader: TimerDriverDelegate {
|
|
// MARK: TimerDriverDelegate
|
|
func tick(_ driver:TimerDriver) {
|
|
guard let handle:FileHandle = handle else {
|
|
driver.stopRunning()
|
|
return
|
|
}
|
|
driver.interval = MachUtil.nanosToAbs(UInt64(currentTimeToSample * 1000 * 1000))
|
|
handle.seek(toFileOffset: currentOffset)
|
|
autoreleasepool {
|
|
delegate?.output(
|
|
data: handle.readData(ofLength: currentSampleSize),
|
|
withID: id,
|
|
currentTime: currentTimeToSample,
|
|
keyframe: currentIsKeyframe
|
|
)
|
|
}
|
|
if (hasNext()) {
|
|
next()
|
|
} else {
|
|
driver.stopRunning()
|
|
}
|
|
}
|
|
}
|
|
|
|
extension MP4TrakReader: CustomStringConvertible {
|
|
// MARK: CustomStringConvertible
|
|
var description:String {
|
|
return Mirror(reflecting: self).description
|
|
}
|
|
}
|