Refactoring

This commit is contained in:
shogo4405 2016-11-21 22:30:35 +09:00
parent bda2fe9746
commit 3e8a41f46a
7 changed files with 176 additions and 150 deletions

View File

@ -34,7 +34,7 @@ public class RTMPBroadcaster: RTMPConnection {
completionHandler()
return
}
stream.append(url: url)
stream.appendFile(url)
completionHandler()
}

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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;