Refactoring
This commit is contained in:
parent
bda2fe9746
commit
3e8a41f46a
|
@ -34,7 +34,7 @@ public class RTMPBroadcaster: RTMPConnection {
|
|||
completionHandler()
|
||||
return
|
||||
}
|
||||
stream.append(url: url)
|
||||
stream.appendFile(url)
|
||||
completionHandler()
|
||||
}
|
||||
|
||||
|
|
|
@ -292,7 +292,7 @@ final class MP4ElementaryStreamDescriptorBox: MP4ContainerBox {
|
|||
if (streamDependenceFlag == 1) {
|
||||
let _:UInt16 = try buffer.readUInt16()
|
||||
}
|
||||
|
||||
|
||||
// Decorder Config Descriptor
|
||||
let _:UInt8 = try buffer.readUInt8()
|
||||
tagSize = try buffer.readUInt8()
|
||||
|
@ -508,7 +508,7 @@ final class MP4EditListBox: MP4Box {
|
|||
mediaRate: try buffer.readUInt32()
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
@ -571,3 +571,135 @@ final class MP4Reader: MP4ContainerBox {
|
|||
return try super.load(fileHandle)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
final class MP4TrakReader {
|
||||
var trak:MP4Box
|
||||
weak var delegate:MP4SamplerDelegate?
|
||||
|
||||
private var id:Int = 0
|
||||
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
|
||||
|
||||
private var duration:Double = 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])] = 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 j:Int = Int(sampleToChunk[i].firstChunk) - 1
|
||||
let m:Int = (i + 1 < count) ? Int(sampleToChunk[i + 1].firstChunk) - 1 : offsets.count
|
||||
for _ in j..<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(url:URL) {
|
||||
duration = 0
|
||||
do {
|
||||
let reader:FileHandle = try FileHandle(forReadingFrom: url)
|
||||
while (hasNext()) {
|
||||
duration += currentTimeToSample
|
||||
reader.seek(toFileOffset: currentOffset)
|
||||
autoreleasepool {
|
||||
delegate?.output(
|
||||
data: reader.readData(ofLength: currentSampleSize),
|
||||
withType: id,
|
||||
currentTime: currentTimeToSample,
|
||||
keyframe: currentIsKeyframe
|
||||
)
|
||||
}
|
||||
if (1000 <= duration) {
|
||||
usleep(UInt32(currentTimeToSample * 1000 * 0.5))
|
||||
}
|
||||
next()
|
||||
}
|
||||
} catch {
|
||||
logger.warning("")
|
||||
}
|
||||
}
|
||||
|
||||
private func hasNext() -> Bool {
|
||||
return cursor + 1 < offset.count
|
||||
}
|
||||
|
||||
private func next() {
|
||||
defer {
|
||||
cursor += 1
|
||||
}
|
||||
totalTimeToSample += timeToSample[cursor]
|
||||
}
|
||||
}
|
||||
|
||||
extension MP4TrakReader: CustomStringConvertible {
|
||||
// MARK: CustomStringConvertible
|
||||
var description:String {
|
||||
return Mirror(reflecting: self).description
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,110 +1,10 @@
|
|||
import Foundation
|
||||
|
||||
struct MP4SampleTable {
|
||||
var trak:MP4Box
|
||||
var currentOffset:UInt64 {
|
||||
return UInt64(offset[cursor])
|
||||
}
|
||||
var currentIsKeyframe:Bool {
|
||||
return keyframe[cursor] != nil
|
||||
}
|
||||
var currentDuration:Double {
|
||||
return Double(totalTimeToSample) * 1000 / Double(timeScale)
|
||||
}
|
||||
var currentTimeToSample:Double {
|
||||
return Double(timeToSample[cursor]) * 1000 / Double(timeScale)
|
||||
}
|
||||
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(trak:MP4Box) {
|
||||
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])] = 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 j:Int = Int(sampleToChunk[i].firstChunk) - 1
|
||||
let m:Int = (i + 1 < count) ? Int(sampleToChunk[i + 1].firstChunk) - 1 : offsets.count
|
||||
for _ in j..<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 hasNext() -> Bool {
|
||||
return cursor + 1 < offset.count
|
||||
}
|
||||
|
||||
mutating func next() {
|
||||
defer {
|
||||
cursor += 1
|
||||
}
|
||||
totalTimeToSample += timeToSample[cursor]
|
||||
}
|
||||
}
|
||||
|
||||
extension MP4SampleTable: CustomStringConvertible {
|
||||
// MARK: CustomStringConvertible
|
||||
var description:String {
|
||||
return Mirror(reflecting: self).description
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
protocol MP4SamplerDelegate: class {
|
||||
func didSet(avcC: Data, withType:Int)
|
||||
func didSet(audioDecorderSpecificConfig: Data, withType:Int)
|
||||
func output(data: Data, withType:Int, currentTime:Double, keyframe:Bool)
|
||||
func didSet(avcC:Data, withType:Int)
|
||||
func didSet(audioDecorderSpecificConfig:Data, withType:Int)
|
||||
func output(data:Data, withType:Int, currentTime:Double, keyframe:Bool)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
@ -117,11 +17,12 @@ class MP4Sampler {
|
|||
fileprivate var files:[URL:Handler?] = [:]
|
||||
fileprivate let lockQueue:DispatchQueue = DispatchQueue(label: "com.github.shogo4405.lf.MP4Sampler.lock")
|
||||
fileprivate let loopQueue:DispatchQueue = DispatchQueue(label: "com.github.shgoo4405.lf.MP4Sampler.loop")
|
||||
fileprivate let operations:OperationQueue = OperationQueue()
|
||||
|
||||
private var reader:MP4Reader = MP4Reader()
|
||||
private var sampleTables:[MP4SampleTable] = []
|
||||
private var trakReaders:[MP4TrakReader] = []
|
||||
|
||||
func append(file:URL, completionHandler: Handler? = nil) {
|
||||
func appendFile(_ file:URL, completionHandler: Handler? = nil) {
|
||||
lockQueue.async {
|
||||
self.files[file] = completionHandler
|
||||
}
|
||||
|
@ -138,39 +39,28 @@ class MP4Sampler {
|
|||
return
|
||||
}
|
||||
|
||||
sampleTables.removeAll()
|
||||
var traks:[MP4Box] = reader.getBoxes(byName: "trak")
|
||||
trakReaders.removeAll()
|
||||
let traks:[MP4Box] = reader.getBoxes(byName: "trak")
|
||||
for i in 0..<traks.count {
|
||||
sampleTables.append(MP4SampleTable(trak: traks[i]))
|
||||
trakReaders.append(MP4TrakReader(id:i, trak:traks[i]))
|
||||
}
|
||||
|
||||
for i in 0..<sampleTables.count {
|
||||
if let avcC:MP4Box = sampleTables[i].trak.getBoxes(byName: "avcC").first {
|
||||
for i in 0..<trakReaders.count {
|
||||
if let avcC:MP4Box = trakReaders[i].trak.getBoxes(byName: "avcC").first {
|
||||
delegate?.didSet(avcC: reader.readData(ofBox: avcC), withType: i)
|
||||
}
|
||||
if let esds:MP4ElementaryStreamDescriptorBox = sampleTables[i].trak.getBoxes(byName: "esds").first as? MP4ElementaryStreamDescriptorBox {
|
||||
if let esds:MP4ElementaryStreamDescriptorBox = trakReaders[i].trak.getBoxes(byName: "esds").first as? MP4ElementaryStreamDescriptorBox {
|
||||
delegate?.didSet(audioDecorderSpecificConfig: Data(esds.audioDecorderSpecificConfig), withType: i)
|
||||
}
|
||||
}
|
||||
|
||||
repeat {
|
||||
for i in 0..<sampleTables.count {
|
||||
autoreleasepool {
|
||||
reader.seek(toFileOffset: sampleTables[i].currentOffset)
|
||||
let length:Int = sampleTables[i].currentSampleSize
|
||||
delegate?.output(
|
||||
data: reader.readData(ofLength: length),
|
||||
withType: i,
|
||||
currentTime: sampleTables[i].currentTimeToSample,
|
||||
keyframe: sampleTables[i].currentIsKeyframe
|
||||
)
|
||||
}
|
||||
if (sampleTables[i].hasNext()) {
|
||||
sampleTables[i].next()
|
||||
}
|
||||
for i in 0..<trakReaders.count {
|
||||
operations.addOperation {
|
||||
self.trakReaders[i].delegate = self.delegate
|
||||
self.trakReaders[i].execute(url: url)
|
||||
}
|
||||
}
|
||||
while inLoop(sampleTables: sampleTables)
|
||||
operations.waitUntilAllOperationsAreFinished()
|
||||
|
||||
reader.close()
|
||||
}
|
||||
|
@ -183,15 +73,6 @@ class MP4Sampler {
|
|||
execute(url: url)
|
||||
handler?()
|
||||
}
|
||||
|
||||
private func inLoop(sampleTables:[MP4SampleTable]) -> Bool{
|
||||
for i in sampleTables {
|
||||
if (i.hasNext()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
extension MP4Sampler: Runnable {
|
||||
|
|
|
@ -105,13 +105,14 @@ extension RTMPMuxer: MP4SamplerDelegate {
|
|||
if (avcC == self.avcC) {
|
||||
return
|
||||
}
|
||||
logger.info("\(avcC)")
|
||||
var buffer:Data = Data([FLVFrameType.key.rawValue << 4 | FLVVideoCodec.avc.rawValue, FLVAVCPacketType.seq.rawValue, 0, 0, 0])
|
||||
buffer.append(avcC)
|
||||
delegate?.sampleOutput(video: buffer, withTimestamp: 0, muxer: self)
|
||||
self.avcC = avcC
|
||||
}
|
||||
|
||||
func didSet(audioDecorderSpecificConfig: Data, withType:Int) {
|
||||
func didSet(audioDecorderSpecificConfig:Data, withType:Int) {
|
||||
if (withType == 2) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -533,7 +533,7 @@ open class RTMPStream: NetStream {
|
|||
return metadata
|
||||
}
|
||||
|
||||
func append(url:URL, completionHandler: MP4Sampler.Handler? = nil) {
|
||||
func appendFile(_ file:URL, completionHandler: MP4Sampler.Handler? = nil) {
|
||||
lockQueue.async {
|
||||
if (self.sampler == nil) {
|
||||
self.sampler = MP4Sampler()
|
||||
|
@ -545,7 +545,7 @@ open class RTMPStream: NetStream {
|
|||
break
|
||||
}
|
||||
}
|
||||
self.sampler?.append(file: url, completionHandler: completionHandler)
|
||||
self.sampler?.appendFile(file, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@testable import lf
|
||||
|
||||
final class MP4SamplerTests: XCTestCase {
|
||||
func testMain() {
|
||||
let bundle:Bundle = Bundle(for: type(of: self))
|
||||
let url:URL = URL(fileURLWithPath: bundle.path(forResource: "SampleVideo_360x240_5mb", ofType: "mp4")!)
|
||||
let sampler:MP4Sampler = MP4Sampler()
|
||||
sampler.appendFile(url)
|
||||
sampler.startRunning()
|
||||
sleep(10)
|
||||
sampler.stopRunning()
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
2901A4EE1D437170002BBD23 /* ClockedQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2901A4ED1D437170002BBD23 /* ClockedQueue.swift */; };
|
||||
2901A4EF1D437662002BBD23 /* ClockedQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2901A4ED1D437170002BBD23 /* ClockedQueue.swift */; };
|
||||
2911F9901DE2DA19007FD1EC /* lf.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 29B8761B1CD701F900FC07DA /* lf.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
2911F9B31DE337BB007FD1EC /* MP4SamplerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2911F9B21DE337BB007FD1EC /* MP4SamplerTests.swift */; };
|
||||
2915EC4D1D85BB8C00621092 /* RTMPTSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294852551D84BFAD002DE492 /* RTMPTSocket.swift */; };
|
||||
2915EC541D85BDF100621092 /* ReplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2915EC531D85BDF100621092 /* ReplayKit.framework */; };
|
||||
2915EC601D85BDF100621092 /* ReplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2915EC531D85BDF100621092 /* ReplayKit.framework */; };
|
||||
|
@ -291,6 +292,7 @@
|
|||
|
||||
/* Begin PBXFileReference section */
|
||||
2901A4ED1D437170002BBD23 /* ClockedQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ClockedQueue.swift; path = Sources/Util/ClockedQueue.swift; sourceTree = SOURCE_ROOT; };
|
||||
2911F9B21DE337BB007FD1EC /* MP4SamplerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MP4SamplerTests.swift; sourceTree = "<group>"; };
|
||||
2915EC521D85BDF100621092 /* Screencast.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Screencast.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2915EC531D85BDF100621092 /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = System/Library/Frameworks/ReplayKit.framework; sourceTree = SDKROOT; };
|
||||
2915EC5F1D85BDF100621092 /* ScreencastUI.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ScreencastUI.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -561,6 +563,7 @@
|
|||
29B876D51CD70CE700FC07DA /* PacketizedElementaryStreamTests.swift */,
|
||||
29B876D61CD70CE700FC07DA /* ProgramSpecificTests.swift */,
|
||||
29B876DB1CD70CE700FC07DA /* TSTests.swift */,
|
||||
2911F9B21DE337BB007FD1EC /* MP4SamplerTests.swift */,
|
||||
);
|
||||
name = ISO;
|
||||
sourceTree = "<group>";
|
||||
|
@ -683,13 +686,6 @@
|
|||
name = HTTP;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
296543501D62FC0300734698 /* RTMP */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = RTMP;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
296543541D62FE3E00734698 /* macOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -835,7 +831,6 @@
|
|||
29BA5B8F1D50BEAD00A51EA8 /* Tests Objective-C */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
296543501D62FC0300734698 /* RTMP */,
|
||||
29BA5B921D50BEAD00A51EA8 /* Info.plist */,
|
||||
2997BDD11D50D2FC000AF900 /* README.md */,
|
||||
2965434E1D62FAED00734698 /* RTMPConnectionTests.m */,
|
||||
|
@ -1373,6 +1368,7 @@
|
|||
29798E701CE6110F00F5CBD0 /* ProgramSpecificTests.swift in Sources */,
|
||||
29798E711CE6110F00F5CBD0 /* SwiftCore+ExtensionTests.swift in Sources */,
|
||||
2942424F1CF4C02300D65DCB /* MD5Tests.swift in Sources */,
|
||||
2911F9B31DE337BB007FD1EC /* MP4SamplerTests.swift in Sources */,
|
||||
29798E721CE6110F00F5CBD0 /* TSTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
Loading…
Reference in New Issue