LNMP4Parser.swift 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963
  1. import Foundation
  2. public let kLNBoxSizeLengthInBytes = 4
  3. public let kLNBoxTypeLengthInBytes = 4
  4. public let kLNBoxLargeSizeLengthInBytes = 8
  5. public let kLNBoxLargeSizeFlagLengthInBytes = 1
  6. private func lnReadU32(_ bytes: UnsafePointer<UInt8>, _ offset: Int) -> UInt32 {
  7. (UInt32(bytes[offset]) << 24)
  8. | (UInt32(bytes[offset + 1]) << 16)
  9. | (UInt32(bytes[offset + 2]) << 8)
  10. | UInt32(bytes[offset + 3])
  11. }
  12. private extension Data {
  13. func lnU32(at offset: Int) -> UInt32 {
  14. guard count >= offset + 4 else { return 0 }
  15. return withUnsafeBytes { rawPtr in
  16. guard let base = rawPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return 0 }
  17. return lnReadU32(base, offset)
  18. }
  19. }
  20. func lnU64(at offset: Int) -> UInt64 {
  21. guard count >= offset + 8 else { return 0 }
  22. return withUnsafeBytes { rawPtr in
  23. guard let base = rawPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return 0 }
  24. return lnReadU64(base.advanced(by: offset))
  25. }
  26. }
  27. }
  28. private func lnReadU64(_ bytes: UnsafePointer<UInt8>) -> UInt64 {
  29. let b0 = UInt64(bytes[0]) << 56
  30. let b1 = UInt64(bytes[1]) << 48
  31. let b2 = UInt64(bytes[2]) << 40
  32. let b3 = UInt64(bytes[3]) << 32
  33. let b4 = UInt64(bytes[4]) << 24
  34. let b5 = UInt64(bytes[5]) << 16
  35. let b6 = UInt64(bytes[6]) << 8
  36. let b7 = UInt64(bytes[7])
  37. return b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7
  38. }
  39. @objc public enum LNMP4CodecType: Int {
  40. case unknown = 0
  41. case video
  42. case audio
  43. }
  44. @objc public enum LNMP4TrackType: UInt32 {
  45. case video = 0x76696465
  46. case audio = 0x736F756E
  47. case hint = 0x68696E74
  48. }
  49. @objc public enum LNMP4VideoStreamCodecID: Int {
  50. case unknown = 0
  51. case h264
  52. case h265
  53. }
  54. @objc public enum LNMP4BoxType: UInt32 {
  55. case unknown = 0x0
  56. case ftyp = 0x66747970
  57. case free = 0x66726565
  58. case mdat = 0x6D646174
  59. case moov = 0x6D6F6F76
  60. case mvhd = 0x6D766864
  61. case iods = 0x696F6473
  62. case trak = 0x7472616B
  63. case tkhd = 0x746B6864
  64. case edts = 0x65647473
  65. case elst = 0x656C7374
  66. case mdia = 0x6D646961
  67. case mdhd = 0x6D646864
  68. case hdlr = 0x68646C72
  69. case minf = 0x6D696E66
  70. case vmhd = 0x766D6864
  71. case dinf = 0x64696E66
  72. case dref = 0x64726566
  73. case url = 0x0075726C
  74. case stbl = 0x7374626C
  75. case stsd = 0x73747364
  76. case avc1 = 0x61766331
  77. case avcC = 0x61766343
  78. case stts = 0x73747473
  79. case stss = 0x73747373
  80. case stsc = 0x73747363
  81. case stsz = 0x7374737A
  82. case stco = 0x7374636F
  83. case ctts = 0x63747473
  84. case udta = 0x75647461
  85. case meta = 0x6D657461
  86. case ilst = 0x696C7374
  87. case data = 0x64617461
  88. case wide = 0x77696465
  89. case loci = 0x6C6F6369
  90. case smhd = 0x736D6864
  91. case vapc = 0x76617063
  92. case hvc1 = 0x68766331
  93. case hvcC = 0x68766343
  94. }
  95. public typealias LNMP4BoxDataFetcher = (LNMP4Box) -> Data?
  96. @objcMembers
  97. open class LNMP4Box: NSObject {
  98. public var type: LNMP4BoxType
  99. public var length: UInt64
  100. public var startIndexInBytes: UInt64
  101. public weak var superBox: LNMP4Box?
  102. public var subBoxes: [LNMP4Box] = []
  103. @objc(initWithType:startIndex:length:)
  104. public init(type: LNMP4BoxType, startIndex: UInt64, length: UInt64) {
  105. self.type = type
  106. self.startIndexInBytes = startIndex
  107. self.length = length
  108. }
  109. @objc(subBoxOfType:)
  110. public func subBox(ofType type: LNMP4BoxType) -> LNMP4Box? {
  111. for subBox in subBoxes {
  112. if subBox.type == type { return subBox }
  113. if let box = subBox.subBox(ofType: type) { return box }
  114. }
  115. return nil
  116. }
  117. @objc(superBoxOfType:)
  118. public func superBox(ofType type: LNMP4BoxType) -> LNMP4Box? {
  119. guard let superBox else { return nil }
  120. if superBox.type == type { return superBox }
  121. return superBox.superBox(ofType: type)
  122. }
  123. open func boxDidParsed(_ dataFetcher: LNMP4BoxDataFetcher) {}
  124. private func description(level: Int) -> String {
  125. var desc = "Box:\(typeString) offset:\(startIndexInBytes) size:\(length) "
  126. for _ in 0..<level { desc = "|--\(desc)" }
  127. desc = "\n\(desc)"
  128. for sub in subBoxes {
  129. desc += sub.description(level: level + 1)
  130. }
  131. return desc
  132. }
  133. public override var description: String {
  134. description(level: 0)
  135. }
  136. private var typeString: String {
  137. let value = type.rawValue
  138. if value == 0 { return "unknown" }
  139. let chars: [UInt8] = [
  140. UInt8((value >> 24) & 0xFF),
  141. UInt8((value >> 16) & 0xFF),
  142. UInt8((value >> 8) & 0xFF),
  143. UInt8(value & 0xFF)
  144. ]
  145. return String(bytes: chars.filter { $0 != 0 }, encoding: .ascii) ?? "unknown"
  146. }
  147. }
  148. @objcMembers public final class LNMP4MdatBox: LNMP4Box {}
  149. @objcMembers public final class LNMP4AvccBox: LNMP4Box {}
  150. @objcMembers public final class LNMP4HvccBox: LNMP4Box {}
  151. @objcMembers public final class LNMP4MvhdBox: LNMP4Box {}
  152. @objcMembers public final class LNMP4StsdBox: LNMP4Box {}
  153. @objcMembers public final class LNMP4TrackBox: LNMP4Box {}
  154. @objcMembers
  155. public final class LNStscEntry: NSObject {
  156. public var firstChunk: UInt32 = 0
  157. public var samplesPerChunk: UInt32 = 0
  158. public var sampleDescriptionIndex: UInt32 = 0
  159. }
  160. @objcMembers
  161. public final class LNMP4StscBox: LNMP4Box {
  162. public var entries: [LNStscEntry] = []
  163. public override func boxDidParsed(_ dataFetcher: LNMP4BoxDataFetcher) {
  164. guard let stscData = dataFetcher(self), stscData.count >= 16 else { return }
  165. let entryCount = Int(stscData.lnU32(at: 12))
  166. entries.removeAll(keepingCapacity: true)
  167. for i in 0..<entryCount {
  168. let base = 16 + i * 12
  169. guard stscData.count >= base + 12 else { break }
  170. let entry = LNStscEntry()
  171. entry.firstChunk = stscData.lnU32(at: base)
  172. entry.samplesPerChunk = stscData.lnU32(at: base + 4)
  173. entry.sampleDescriptionIndex = stscData.lnU32(at: base + 8)
  174. entries.append(entry)
  175. }
  176. }
  177. }
  178. @objcMembers
  179. public final class LNMP4StcoBox: LNMP4Box {
  180. public var chunkCount: UInt32 = 0
  181. public var chunkOffsets: [NSNumber] = []
  182. public override func boxDidParsed(_ dataFetcher: LNMP4BoxDataFetcher) {
  183. guard let stcoData = dataFetcher(self), stcoData.count >= 16 else { return }
  184. let entryCount = Int(stcoData.lnU32(at: 12))
  185. chunkCount = UInt32(entryCount)
  186. chunkOffsets.removeAll(keepingCapacity: true)
  187. for i in 0..<entryCount {
  188. let base = 16 + i * 4
  189. guard stcoData.count >= base + 4 else { break }
  190. chunkOffsets.append(NSNumber(value: stcoData.lnU32(at: base)))
  191. }
  192. }
  193. }
  194. @objcMembers
  195. public final class LNMP4StssBox: LNMP4Box {
  196. public var syncSamples: [NSNumber] = []
  197. public override func boxDidParsed(_ dataFetcher: LNMP4BoxDataFetcher) {
  198. guard let stssData = dataFetcher(self), stssData.count >= 16 else { return }
  199. let sampleCount = Int(stssData.lnU32(at: 12))
  200. syncSamples.removeAll(keepingCapacity: true)
  201. for i in 0..<sampleCount {
  202. let base = 16 + i * 4
  203. guard stssData.count >= base + 4 else { break }
  204. let index = Int(stssData.lnU32(at: base)) - 1
  205. syncSamples.append(NSNumber(value: max(index, 0)))
  206. }
  207. }
  208. }
  209. @objcMembers
  210. public final class LNMP4CttsBox: LNMP4Box {
  211. public var compositionOffsets: [NSNumber] = []
  212. public override func boxDidParsed(_ dataFetcher: LNMP4BoxDataFetcher) {
  213. guard let cttsData = dataFetcher(self), cttsData.count >= 16 else { return }
  214. let entryCount = Int(cttsData.lnU32(at: 12))
  215. compositionOffsets.removeAll(keepingCapacity: true)
  216. for i in 0..<entryCount {
  217. let base = 16 + i * 8
  218. guard cttsData.count >= base + 8 else { break }
  219. let sampleCount = Int(cttsData.lnU32(at: base))
  220. let compositionOffset = cttsData.lnU32(at: base + 4)
  221. for _ in 0..<sampleCount {
  222. compositionOffsets.append(NSNumber(value: compositionOffset))
  223. }
  224. }
  225. }
  226. }
  227. @objcMembers
  228. public final class LNSttsEntry: NSObject {
  229. public var sampleCount: UInt32 = 0
  230. public var sampleDelta: UInt32 = 0
  231. }
  232. @objcMembers
  233. public final class LNMP4SttsBox: LNMP4Box {
  234. public var entries: [LNSttsEntry] = []
  235. public override func boxDidParsed(_ dataFetcher: LNMP4BoxDataFetcher) {
  236. guard let sttsData = dataFetcher(self), sttsData.count >= 16 else { return }
  237. let entryCount = Int(sttsData.lnU32(at: 12))
  238. entries.removeAll(keepingCapacity: true)
  239. for i in 0..<entryCount {
  240. let base = 16 + i * 8
  241. guard sttsData.count >= base + 8 else { break }
  242. let entry = LNSttsEntry()
  243. entry.sampleCount = sttsData.lnU32(at: base)
  244. entry.sampleDelta = sttsData.lnU32(at: base + 4)
  245. entries.append(entry)
  246. }
  247. }
  248. }
  249. @objcMembers
  250. public final class LNMP4StszBox: LNMP4Box {
  251. public var sampleCount: UInt32 = 0
  252. public var sampleSizes: [NSNumber] = []
  253. public override func boxDidParsed(_ dataFetcher: LNMP4BoxDataFetcher) {
  254. guard let stszData = dataFetcher(self), stszData.count >= 20 else { return }
  255. let sampleSize = stszData.lnU32(at: 12)
  256. let sampleCount = Int(stszData.lnU32(at: 16))
  257. self.sampleCount = UInt32(sampleCount)
  258. sampleSizes.removeAll(keepingCapacity: true)
  259. for i in 0..<sampleCount {
  260. if sampleSize > 0 {
  261. sampleSizes.append(NSNumber(value: sampleSize))
  262. } else {
  263. let base = 20 + i * 4
  264. guard stszData.count >= base + 4 else { break }
  265. sampleSizes.append(NSNumber(value: stszData.lnU32(at: base)))
  266. }
  267. }
  268. }
  269. }
  270. @objcMembers
  271. public final class LNMP4HdlrBox: LNMP4Box {
  272. public var trackType: LNMP4TrackType = .video
  273. public override func boxDidParsed(_ dataFetcher: LNMP4BoxDataFetcher) {
  274. guard let hdlrData = dataFetcher(self), hdlrData.count >= 20 else { return }
  275. let trackTypeValue = hdlrData.lnU32(at: 16)
  276. trackType = LNMP4TrackType(rawValue: trackTypeValue) ?? .video
  277. }
  278. }
  279. @objcMembers
  280. public final class LNMP4Sample: NSObject {
  281. public var codecType: LNMP4CodecType = .unknown
  282. public var sampleDelta: UInt32 = 0
  283. public var sampleSize: UInt32 = 0
  284. public var sampleIndex: UInt32 = 0
  285. public var chunkIndex: UInt32 = 0
  286. public var streamOffset: UInt32 = 0
  287. public var pts: UInt64 = 0
  288. public var dts: UInt64 = 0
  289. public var isKeySample: Bool = false
  290. }
  291. @objcMembers
  292. public final class LNChunkOffsetEntry: NSObject {
  293. public var samplesPerChunk: UInt32 = 0
  294. public var offset: UInt32 = 0
  295. }
  296. @objcMembers
  297. public final class LNCttsEntry: NSObject {
  298. public var sampleCount: UInt32 = 0
  299. public var compositionOffset: UInt32 = 0
  300. }
  301. @objcMembers
  302. public final class LNMP4BoxFactory: NSObject {
  303. @objc(isTypeValueValid:)
  304. public static func isTypeValueValid(_ type: LNMP4BoxType) -> Bool {
  305. boxClass(for: type) != nil
  306. }
  307. @objc(boxClassForType:)
  308. public static func boxClass(for type: LNMP4BoxType) -> AnyClass? {
  309. switch type {
  310. case .stss:
  311. return LNMP4StssBox.self
  312. case .mdat:
  313. return LNMP4MdatBox.self
  314. case .avcC:
  315. return LNMP4AvccBox.self
  316. case .mdhd:
  317. return LNMP4MvhdBox.self
  318. case .stsd:
  319. return LNMP4StsdBox.self
  320. case .stsz:
  321. return LNMP4StszBox.self
  322. case .hdlr:
  323. return LNMP4HdlrBox.self
  324. case .stsc:
  325. return LNMP4StscBox.self
  326. case .stts:
  327. return LNMP4SttsBox.self
  328. case .stco:
  329. return LNMP4StcoBox.self
  330. case .hvcC:
  331. return LNMP4HvccBox.self
  332. case .ctts:
  333. return LNMP4CttsBox.self
  334. case .trak:
  335. return LNMP4TrackBox.self
  336. case .ftyp, .free, .moov, .mvhd, .tkhd, .edts, .elst, .mdia, .minf, .vmhd, .dinf, .dref, .url, .stbl, .avc1, .udta, .meta, .ilst, .data, .iods, .wide, .loci, .smhd, .vapc, .hvc1:
  337. return LNMP4Box.self
  338. default:
  339. return nil
  340. }
  341. }
  342. @objc(createBoxForType:startIndex:length:)
  343. public static func createBox(for type: LNMP4BoxType, startIndex: UInt64, length: UInt64) -> LNMP4Box {
  344. switch type {
  345. case .stss: return LNMP4StssBox(type: type, startIndex: startIndex, length: length)
  346. case .mdat: return LNMP4MdatBox(type: type, startIndex: startIndex, length: length)
  347. case .avcC: return LNMP4AvccBox(type: type, startIndex: startIndex, length: length)
  348. case .mdhd: return LNMP4MvhdBox(type: type, startIndex: startIndex, length: length)
  349. case .stsd: return LNMP4StsdBox(type: type, startIndex: startIndex, length: length)
  350. case .stsz: return LNMP4StszBox(type: type, startIndex: startIndex, length: length)
  351. case .hdlr: return LNMP4HdlrBox(type: type, startIndex: startIndex, length: length)
  352. case .stsc: return LNMP4StscBox(type: type, startIndex: startIndex, length: length)
  353. case .stts: return LNMP4SttsBox(type: type, startIndex: startIndex, length: length)
  354. case .stco: return LNMP4StcoBox(type: type, startIndex: startIndex, length: length)
  355. case .hvcC: return LNMP4HvccBox(type: type, startIndex: startIndex, length: length)
  356. case .ctts: return LNMP4CttsBox(type: type, startIndex: startIndex, length: length)
  357. case .trak: return LNMP4TrackBox(type: type, startIndex: startIndex, length: length)
  358. default: return LNMP4Box(type: type, startIndex: startIndex, length: length)
  359. }
  360. }
  361. }
  362. @objc
  363. public protocol LNMP4ParserDelegate: AnyObject {
  364. @objc optional func didParseMP4Box(_ box: LNMP4Box, parser: LNMP4Parser)
  365. @objc(MP4FileDidFinishParse:) optional func mp4FileDidFinishParse(_ parser: LNMP4Parser)
  366. }
  367. @objcMembers
  368. public final class LNMP4Parser: NSObject {
  369. public var rootBox: LNMP4Box?
  370. public var fileHandle: FileHandle?
  371. public weak var delegate: LNMP4ParserDelegate?
  372. private let filePath: String
  373. @objc(initWithFilePath:)
  374. public init(filePath: String) {
  375. self.filePath = filePath
  376. self.fileHandle = FileHandle(forReadingAtPath: filePath)
  377. super.init()
  378. }
  379. deinit {
  380. fileHandle?.closeFile()
  381. }
  382. public func parse() {
  383. guard !filePath.isEmpty, let fileHandle else { return }
  384. let fileSize = fileHandle.seekToEndOfFile()
  385. fileHandle.seek(toFileOffset: 0)
  386. rootBox = LNMP4BoxFactory.createBox(for: .unknown, startIndex: 0, length: UInt64(fileSize))
  387. guard let rootBox else { return }
  388. var bfsQueue: [LNMP4Box] = [rootBox]
  389. while !bfsQueue.isEmpty {
  390. let calBox = bfsQueue.removeFirst()
  391. if calBox.length <= UInt64(2 * (kLNBoxSizeLengthInBytes + kLNBoxTypeLengthInBytes)) {
  392. continue
  393. }
  394. var offset: UInt64 = calBox.superBox == nil ? 0 : (calBox.startIndexInBytes + UInt64(kLNBoxSizeLengthInBytes + kLNBoxTypeLengthInBytes))
  395. if shouldResetOffset(calBox.type) {
  396. calibrateOffset(&offset, boxType: calBox.type)
  397. }
  398. while true {
  399. if offset + UInt64(kLNBoxSizeLengthInBytes + kLNBoxTypeLengthInBytes) > calBox.startIndexInBytes + calBox.length {
  400. break
  401. }
  402. guard let parsed = readBoxTypeAndLength(offset: offset) else { break }
  403. let type = parsed.type
  404. let length = parsed.length
  405. if length == 0 {
  406. // Invalid box length; avoid infinite loop on malformed data.
  407. break
  408. }
  409. if offset + length > calBox.startIndexInBytes + calBox.length {
  410. break
  411. }
  412. if !LNMP4BoxFactory.isTypeValueValid(type), offset == calBox.startIndexInBytes + UInt64(kLNBoxSizeLengthInBytes + kLNBoxTypeLengthInBytes) {
  413. break
  414. }
  415. let subBox = LNMP4BoxFactory.createBox(for: type, startIndex: offset, length: length)
  416. subBox.superBox = calBox
  417. calBox.subBoxes.append(subBox)
  418. bfsQueue.append(subBox)
  419. didParseBox(subBox)
  420. offset += length
  421. }
  422. }
  423. didFinishParseFile()
  424. }
  425. @objc(readDataForBox:)
  426. public func readData(for box: LNMP4Box?) -> Data? {
  427. guard let box, let fileHandle else { return nil }
  428. fileHandle.seek(toFileOffset: box.startIndexInBytes)
  429. return fileHandle.readData(ofLength: Int(box.length))
  430. }
  431. @objc(readValue:length:)
  432. public func readValue(_ bytes: UnsafePointer<CChar>, length: Int) -> Int {
  433. var value = 0
  434. for i in 0..<length {
  435. value += (Int(bytes[i]) & 0xff) << ((length - i - 1) * 8)
  436. }
  437. return value
  438. }
  439. private func readValue(_ bytes: UnsafePointer<UInt8>, length: Int) -> UInt64 {
  440. var value: UInt64 = 0
  441. for i in 0..<length {
  442. value += UInt64(bytes[i] & 0xff) << UInt64((length - i - 1) * 8)
  443. }
  444. return value
  445. }
  446. private func readBoxTypeAndLength(offset: UInt64) -> (type: LNMP4BoxType, length: UInt64)? {
  447. guard let fileHandle else { return nil }
  448. fileHandle.seek(toFileOffset: offset)
  449. let headerData = fileHandle.readData(ofLength: kLNBoxSizeLengthInBytes + kLNBoxTypeLengthInBytes)
  450. guard headerData.count >= kLNBoxSizeLengthInBytes + kLNBoxTypeLengthInBytes else {
  451. return nil
  452. }
  453. let length: UInt64 = headerData.withUnsafeBytes { rawPtr in
  454. guard let bytes = rawPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return 0 }
  455. return readValue(bytes, length: kLNBoxSizeLengthInBytes)
  456. }
  457. let typeRaw: UInt32 = headerData.withUnsafeBytes { rawPtr in
  458. guard let bytes = rawPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return 0 }
  459. return UInt32(readValue(bytes.advanced(by: kLNBoxSizeLengthInBytes), length: kLNBoxTypeLengthInBytes))
  460. }
  461. var finalLength = length
  462. let type = LNMP4BoxType(rawValue: typeRaw) ?? .unknown
  463. if finalLength == UInt64(kLNBoxLargeSizeFlagLengthInBytes) {
  464. let extendedOffset = offset + UInt64(kLNBoxSizeLengthInBytes + kLNBoxTypeLengthInBytes)
  465. fileHandle.seek(toFileOffset: extendedOffset)
  466. let largeSizeData = fileHandle.readData(ofLength: kLNBoxLargeSizeLengthInBytes)
  467. guard largeSizeData.count >= kLNBoxLargeSizeLengthInBytes else {
  468. return nil
  469. }
  470. finalLength = largeSizeData.withUnsafeBytes { rawPtr in
  471. guard let bytes = rawPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return 0 }
  472. return readValue(bytes, length: kLNBoxLargeSizeLengthInBytes)
  473. }
  474. if finalLength == 0 { return nil }
  475. }
  476. if finalLength == 0 {
  477. return nil
  478. }
  479. return (type, finalLength)
  480. }
  481. private func shouldResetOffset(_ type: LNMP4BoxType) -> Bool {
  482. type == .stsd || type == .avc1 || type == .hvc1
  483. }
  484. private func calibrateOffset(_ offset: inout UInt64, boxType type: LNMP4BoxType) {
  485. switch type {
  486. case .stsd:
  487. offset += 8
  488. case .avc1, .hvc1:
  489. offset += 78
  490. default:
  491. break
  492. }
  493. }
  494. private func didParseBox(_ box: LNMP4Box) {
  495. box.boxDidParsed { [weak self] in
  496. self?.readData(for: $0)
  497. }
  498. delegate?.didParseMP4Box?(box, parser: self)
  499. }
  500. private func didFinishParseFile() {
  501. delegate?.mp4FileDidFinishParse?(self)
  502. }
  503. }
  504. @objcMembers
  505. public final class LNMP4ParserProxy: NSObject, LNMP4ParserDelegate {
  506. private var parser: LNMP4Parser
  507. private var _picWidth: Int = 0
  508. private var _picHeight: Int = 0
  509. private var _fps: Int = 0
  510. private var _duration: Double = 0
  511. public var spsData: Data?
  512. public var ppsData: Data?
  513. private var _videoSamples: [LNMP4Sample]?
  514. private var _videoSyncSampleIndexes: [NSNumber]?
  515. public var rootBox: LNMP4Box?
  516. public var videoTrackBox: LNMP4TrackBox?
  517. public var audioTrackBox: LNMP4TrackBox?
  518. public var vpsData: Data?
  519. public var videoCodecID: LNMP4VideoStreamCodecID = .unknown
  520. public var picWidth: Int {
  521. get {
  522. if _picWidth == 0 {
  523. _picWidth = readPicWidth()
  524. }
  525. return _picWidth
  526. }
  527. set {
  528. _picWidth = newValue
  529. }
  530. }
  531. public var picHeight: Int {
  532. get {
  533. if _picHeight == 0 {
  534. _picHeight = readPicHeight()
  535. }
  536. return _picHeight
  537. }
  538. set {
  539. _picHeight = newValue
  540. }
  541. }
  542. public var duration: Double {
  543. get {
  544. if _duration == 0 {
  545. _duration = readDuration()
  546. }
  547. return _duration
  548. }
  549. set {
  550. _duration = newValue
  551. }
  552. }
  553. public var videoSamples: [LNMP4Sample] {
  554. get {
  555. if _videoSamples == nil {
  556. _videoSamples = buildVideoSamples()
  557. }
  558. return _videoSamples ?? []
  559. }
  560. set {
  561. _videoSamples = newValue
  562. }
  563. }
  564. public var videoSyncSampleIndexes: [NSNumber] {
  565. get {
  566. if _videoSyncSampleIndexes == nil {
  567. if let stss = videoTrackBox?.subBox(ofType: .stss) as? LNMP4StssBox {
  568. _videoSyncSampleIndexes = stss.syncSamples
  569. } else {
  570. _videoSyncSampleIndexes = []
  571. }
  572. }
  573. return _videoSyncSampleIndexes ?? []
  574. }
  575. set {
  576. _videoSyncSampleIndexes = newValue
  577. }
  578. }
  579. public var fps: Int {
  580. get {
  581. if _fps == 0 {
  582. let samples = videoSamples
  583. let totalDuration = duration
  584. if samples.isEmpty || totalDuration <= 0 {
  585. return 0
  586. }
  587. _fps = Int(lround(Double(samples.count) / totalDuration))
  588. }
  589. return _fps
  590. }
  591. set {
  592. _fps = newValue
  593. }
  594. }
  595. @objc(initWithFilePath:)
  596. public init(filePath: String) {
  597. parser = LNMP4Parser(filePath: filePath)
  598. super.init()
  599. parser.delegate = self
  600. }
  601. public func parse() {
  602. _picWidth = 0
  603. _picHeight = 0
  604. _fps = 0
  605. _duration = 0
  606. _videoSamples = nil
  607. _videoSyncSampleIndexes = nil
  608. spsData = nil
  609. ppsData = nil
  610. vpsData = nil
  611. parser.parse()
  612. rootBox = parser.rootBox
  613. parseVideoDecoderConfigRecord()
  614. }
  615. @objc(readPacketOfSample:)
  616. public func readPacket(ofSample sampleIndex: Int) -> Data? {
  617. guard sampleIndex >= 0, sampleIndex < videoSamples.count else { return nil }
  618. guard let fileHandle = parser.fileHandle else { return nil }
  619. let sample = videoSamples[sampleIndex]
  620. fileHandle.seek(toFileOffset: UInt64(sample.streamOffset))
  621. return fileHandle.readData(ofLength: Int(sample.sampleSize))
  622. }
  623. @objc(readDataOfBox:length:offset:)
  624. public func readData(of box: LNMP4Box, length: Int, offset: Int) -> Data? {
  625. guard length > 0, offset >= 0 else { return nil }
  626. let end = UInt64(offset) + UInt64(length)
  627. guard end <= box.length else { return nil }
  628. guard let fileHandle = parser.fileHandle else { return nil }
  629. fileHandle.seek(toFileOffset: box.startIndexInBytes + UInt64(offset))
  630. return fileHandle.readData(ofLength: length)
  631. }
  632. private func parseVideoDecoderConfigRecord() {
  633. if videoCodecID == .h264 {
  634. spsData = parseAvccSPSData()
  635. ppsData = parseAvccPPSData()
  636. } else if videoCodecID == .h265 {
  637. parseHvccDecoderConfigRecord()
  638. }
  639. }
  640. private func buildVideoSamples() -> [LNMP4Sample] {
  641. guard let videoTrackBox,
  642. let stts = videoTrackBox.subBox(ofType: .stts) as? LNMP4SttsBox,
  643. let stsz = videoTrackBox.subBox(ofType: .stsz) as? LNMP4StszBox,
  644. let stsc = videoTrackBox.subBox(ofType: .stsc) as? LNMP4StscBox,
  645. let stco = videoTrackBox.subBox(ofType: .stco) as? LNMP4StcoBox else {
  646. return []
  647. }
  648. let ctts = videoTrackBox.subBox(ofType: .ctts) as? LNMP4CttsBox
  649. let keySampleIndexSet = Set(videoSyncSampleIndexes.map { $0.intValue })
  650. var samples: [LNMP4Sample] = []
  651. var ptsAccumulator: UInt64 = 0
  652. var stscEntryIndex: UInt32 = 0
  653. var stscEntrySampleIndex: UInt32 = 0
  654. var stscEntrySampleOffset: UInt32 = 0
  655. var sttsEntryIndex: UInt32 = 0
  656. var sttsEntrySampleIndex: UInt32 = 0
  657. var stcoChunkLogicIndex: UInt32 = 0
  658. for i in 0..<Int(stsz.sampleCount) {
  659. if Int(stscEntryIndex) >= stsc.entries.count
  660. || Int(sttsEntryIndex) >= stts.entries.count
  661. || Int(stcoChunkLogicIndex) >= stco.chunkOffsets.count {
  662. break
  663. }
  664. let stscEntry = stsc.entries[Int(stscEntryIndex)]
  665. let sttsEntry = stts.entries[Int(sttsEntryIndex)]
  666. let chunkOffset = stco.chunkOffsets[Int(stcoChunkLogicIndex)].uint32Value
  667. let sampleOffset = chunkOffset + stscEntrySampleOffset
  668. var cttsValue: UInt32 = 0
  669. if let ctts, i < ctts.compositionOffsets.count {
  670. cttsValue = ctts.compositionOffsets[i].uint32Value
  671. }
  672. let sample = LNMP4Sample()
  673. sample.codecType = .video
  674. sample.sampleIndex = UInt32(i)
  675. sample.chunkIndex = stcoChunkLogicIndex
  676. sample.sampleDelta = sttsEntry.sampleDelta
  677. sample.sampleSize = stsz.sampleSizes[i].uint32Value
  678. sample.pts = ptsAccumulator + UInt64(cttsValue)
  679. sample.dts = ptsAccumulator
  680. sample.streamOffset = sampleOffset
  681. sample.isKeySample = keySampleIndexSet.contains(i)
  682. samples.append(sample)
  683. stscEntrySampleOffset += sample.sampleSize
  684. ptsAccumulator += UInt64(sample.sampleDelta)
  685. stscEntrySampleIndex += 1
  686. if stscEntrySampleIndex >= stscEntry.samplesPerChunk {
  687. if stcoChunkLogicIndex + 1 < UInt32(stco.chunkOffsets.count) {
  688. stcoChunkLogicIndex += 1
  689. }
  690. stscEntrySampleIndex = 0
  691. stscEntrySampleOffset = 0
  692. }
  693. sttsEntrySampleIndex += 1
  694. if sttsEntrySampleIndex >= sttsEntry.sampleCount {
  695. sttsEntrySampleIndex = 0
  696. if sttsEntryIndex + 1 < UInt32(stts.entries.count) {
  697. sttsEntryIndex += 1
  698. }
  699. }
  700. if stscEntryIndex + 1 < UInt32(stsc.entries.count) {
  701. let nextFirstChunk = stsc.entries[Int(stscEntryIndex + 1)].firstChunk
  702. if stcoChunkLogicIndex >= nextFirstChunk - 1 {
  703. stscEntryIndex += 1
  704. }
  705. }
  706. }
  707. return samples
  708. }
  709. private func parseHvccDecoderConfigRecord() {
  710. guard let hvcc = videoTrackBox?.subBox(ofType: .hvcC),
  711. let extraData = parser.readData(for: hvcc),
  712. extraData.count > 8 else { return }
  713. let bytes = [UInt8](extraData)
  714. var index = 30
  715. guard index < bytes.count else { return }
  716. let arrayNum = Int(bytes[index])
  717. index += 1
  718. for _ in 0..<arrayNum {
  719. guard index + 2 <= bytes.count else { return }
  720. let value = Int(bytes[index])
  721. index += 1
  722. let naluType = value & 0x3F
  723. let naluNum = (Int(bytes[index]) << 8) + Int(bytes[index + 1])
  724. index += 2
  725. for _ in 0..<naluNum {
  726. guard index + 2 <= bytes.count else { return }
  727. let naluLength = (Int(bytes[index]) << 8) + Int(bytes[index + 1])
  728. index += 2
  729. guard index + naluLength <= bytes.count else { return }
  730. let paramData = Data(bytes[index..<(index + naluLength)])
  731. if naluType == 32 {
  732. vpsData = paramData
  733. } else if naluType == 33 {
  734. spsData = paramData
  735. } else if naluType == 34 {
  736. ppsData = paramData
  737. }
  738. index += naluLength
  739. }
  740. }
  741. }
  742. private func parseAvccSPSData() -> Data? {
  743. guard let avcc = videoTrackBox?.subBox(ofType: .avcC),
  744. let extraData = parser.readData(for: avcc),
  745. extraData.count > 16 else { return nil }
  746. let bytes = [UInt8](extraData)
  747. let spsLength = (Int(bytes[14]) << 8) + Int(bytes[15])
  748. let naluType = Int(bytes[16] & 0x1F)
  749. guard spsLength + 16 <= bytes.count, naluType == 7 else { return nil }
  750. return Data(bytes[16..<(16 + spsLength)])
  751. }
  752. private func parseAvccPPSData() -> Data? {
  753. guard let avcc = videoTrackBox?.subBox(ofType: .avcC),
  754. let extraData = parser.readData(for: avcc),
  755. extraData.count > 16 else { return nil }
  756. let bytes = [UInt8](extraData)
  757. var spsCount = Int(bytes[13] & 0x1F)
  758. let spsLength = (Int(bytes[14]) << 8) + Int(bytes[15])
  759. var prefixLength = 16 + spsLength
  760. while spsCount > 1 {
  761. guard prefixLength + 2 < bytes.count else { return nil }
  762. let nextSpsLength = (Int(bytes[prefixLength]) << 8) + Int(bytes[prefixLength + 1])
  763. prefixLength += nextSpsLength
  764. spsCount -= 1
  765. }
  766. guard prefixLength + 3 < bytes.count else { return nil }
  767. let ppsLength = (Int(bytes[prefixLength + 1]) << 8) + Int(bytes[prefixLength + 2])
  768. let naluType = Int(bytes[prefixLength + 3] & 0x1F)
  769. guard naluType == 8, ppsLength + prefixLength + 3 <= bytes.count else { return nil }
  770. return Data(bytes[(prefixLength + 3)..<(prefixLength + 3 + ppsLength)])
  771. }
  772. private func readPicWidth() -> Int {
  773. guard videoCodecID != .unknown,
  774. let box = videoTrackBox?.subBox(ofType: videoCodecID == .h264 ? .avc1 : .hvc1),
  775. let fileHandle = parser.fileHandle else { return 0 }
  776. fileHandle.seek(toFileOffset: box.startIndexInBytes + 32)
  777. let widthData = fileHandle.readData(ofLength: 2)
  778. guard widthData.count >= 2 else { return 0 }
  779. let bytes = [UInt8](widthData)
  780. return (Int(bytes[0]) << 8) + Int(bytes[1])
  781. }
  782. private func readPicHeight() -> Int {
  783. guard videoCodecID != .unknown,
  784. let box = videoTrackBox?.subBox(ofType: videoCodecID == .h264 ? .avc1 : .hvc1),
  785. let fileHandle = parser.fileHandle else { return 0 }
  786. fileHandle.seek(toFileOffset: box.startIndexInBytes + 34)
  787. let heightData = fileHandle.readData(ofLength: 2)
  788. guard heightData.count >= 2 else { return 0 }
  789. let bytes = [UInt8](heightData)
  790. return (Int(bytes[0]) << 8) + Int(bytes[1])
  791. }
  792. private func readDuration() -> Double {
  793. guard let mvhd = rootBox?.subBox(ofType: .mvhd),
  794. let mvhdData = parser.readData(for: mvhd),
  795. mvhdData.count > 24 else { return 0 }
  796. let version = Int(mvhdData.lnU32(at: 8))
  797. var timeScaleIndex = 20
  798. var durationIndex = 24
  799. var durationLength = 4
  800. if version == 1 {
  801. timeScaleIndex = 28
  802. durationIndex = 32
  803. durationLength = 8
  804. }
  805. guard mvhdData.count >= durationIndex + durationLength else { return 0 }
  806. let scale = Int(mvhdData.lnU32(at: timeScaleIndex))
  807. let durationValue: Int
  808. if durationLength == 4 {
  809. durationValue = Int(mvhdData.lnU32(at: durationIndex))
  810. } else {
  811. durationValue = Int(mvhdData.lnU64(at: durationIndex))
  812. }
  813. guard scale != 0 else { return 0 }
  814. return Double(durationValue) / Double(scale)
  815. }
  816. public func mp4FileDidFinishParse(_ parser: LNMP4Parser) {}
  817. public func didParseMP4Box(_ box: LNMP4Box, parser: LNMP4Parser) {
  818. switch box.type {
  819. case .hdlr:
  820. if let hdlr = box as? LNMP4HdlrBox,
  821. let trackBox = box.superBox(ofType: .trak) as? LNMP4TrackBox {
  822. switch hdlr.trackType {
  823. case .video:
  824. videoTrackBox = trackBox
  825. case .audio:
  826. audioTrackBox = trackBox
  827. default:
  828. break
  829. }
  830. }
  831. case .avc1:
  832. videoCodecID = .h264
  833. case .hvc1:
  834. videoCodecID = .h265
  835. default:
  836. break
  837. }
  838. }
  839. }