Refactoring
This commit is contained in:
parent
fd1df9c02e
commit
b25f5235ae
|
@ -1,4 +1,5 @@
|
|||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
class MP4Box {
|
||||
static func create(_ data:Data) throws -> MP4Box {
|
||||
|
@ -515,16 +516,15 @@ final class MP4EditListBox: MP4Box {
|
|||
|
||||
// MARK: -
|
||||
final class MP4Reader: MP4ContainerBox {
|
||||
var url:URL? = nil {
|
||||
didSet {
|
||||
if (url == nil) {
|
||||
return
|
||||
}
|
||||
do {
|
||||
fileHandle = try FileHandle(forReadingFrom: url!)
|
||||
} catch let error as NSError {
|
||||
print(error)
|
||||
}
|
||||
private(set) var url:URL
|
||||
|
||||
init(url:URL) {
|
||||
do {
|
||||
self.url = url
|
||||
super.init()
|
||||
fileHandle = try FileHandle(forReadingFrom: url)
|
||||
} catch let error as NSError {
|
||||
logger.error("\(error)")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -581,7 +581,7 @@ final class MP4TrakReader {
|
|||
weak var delegate:MP4SamplerDelegate?
|
||||
|
||||
fileprivate var id:Int = 0
|
||||
fileprivate var reader:FileHandle?
|
||||
fileprivate var handle:FileHandle?
|
||||
private lazy var timerDriver:TimerDriver = {
|
||||
var timerDriver:TimerDriver = TimerDriver()
|
||||
timerDriver.delegate = self
|
||||
|
@ -664,16 +664,24 @@ final class MP4TrakReader {
|
|||
totalTimeToSample = timeToSample[cursor]
|
||||
}
|
||||
|
||||
func execute(url:URL) {
|
||||
func execute(_ reader:MP4Reader) {
|
||||
do {
|
||||
reader = try FileHandle(forReadingFrom: url)
|
||||
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: AVMediaTypeVideo)
|
||||
}
|
||||
if let esds:MP4ElementaryStreamDescriptorBox = trak.getBoxes(byName: "esds").first as? MP4ElementaryStreamDescriptorBox {
|
||||
delegate?.didSet(config: Data(esds.audioDecorderSpecificConfig), withID: id, type: AVMediaTypeAudio)
|
||||
}
|
||||
|
||||
timerDriver.interval = MachUtil.nanosToAbs(UInt64(currentTimeToSample * 1000 * 1000))
|
||||
while (currentDuration <= bufferTime) {
|
||||
tick(timerDriver)
|
||||
}
|
||||
timerDriver.startRunning()
|
||||
} catch {
|
||||
logger.warning("file open error :\(url)")
|
||||
logger.warning("file open error :\(reader.url)")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -692,16 +700,16 @@ final class MP4TrakReader {
|
|||
extension MP4TrakReader: TimerDriverDelegate {
|
||||
// MARK: TimerDriverDelegate
|
||||
func tick(_ driver:TimerDriver) {
|
||||
guard let reader:FileHandle = reader else {
|
||||
guard let handle:FileHandle = handle else {
|
||||
driver.stopRunning()
|
||||
return
|
||||
}
|
||||
driver.interval = MachUtil.nanosToAbs(UInt64(currentTimeToSample * 1000 * 1000))
|
||||
reader.seek(toFileOffset: currentOffset)
|
||||
handle.seek(toFileOffset: currentOffset)
|
||||
autoreleasepool {
|
||||
delegate?.output(
|
||||
data: reader.readData(ofLength: currentSampleSize),
|
||||
withType: id,
|
||||
data: handle.readData(ofLength: currentSampleSize),
|
||||
withID: id,
|
||||
currentTime: currentTimeToSample,
|
||||
keyframe: currentIsKeyframe
|
||||
)
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import Foundation
|
||||
|
||||
// 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 didOpen(_ reader:MP4Reader)
|
||||
func didSet(config:Data, withID:Int, type:String)
|
||||
func output(data:Data, withID:Int, currentTime:Double, keyframe:Bool)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
@ -13,26 +12,22 @@ class MP4Sampler {
|
|||
|
||||
weak var delegate:MP4SamplerDelegate?
|
||||
|
||||
fileprivate(set) var running:Bool = false
|
||||
fileprivate var files:[URL] = []
|
||||
fileprivate var handlers:[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 trakReaders:[MP4TrakReader] = []
|
||||
fileprivate(set) var running:Bool = false
|
||||
|
||||
func appendFile(_ file:URL, completionHandler: Handler? = nil) {
|
||||
lockQueue.async {
|
||||
self.files.append(file)
|
||||
self.handlers[file] = completionHandler
|
||||
self.files.append(file)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func execute(url:URL) {
|
||||
|
||||
reader.url = url
|
||||
let reader:MP4Reader = MP4Reader(url: url)
|
||||
|
||||
do {
|
||||
let _:UInt32 = try reader.load()
|
||||
|
@ -41,25 +36,13 @@ class MP4Sampler {
|
|||
return
|
||||
}
|
||||
|
||||
trakReaders.removeAll()
|
||||
delegate?.didOpen(reader)
|
||||
let traks:[MP4Box] = reader.getBoxes(byName: "trak")
|
||||
for i in 0..<traks.count {
|
||||
trakReaders.append(MP4TrakReader(id:i, trak:traks[i]))
|
||||
}
|
||||
|
||||
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 = trakReaders[i].trak.getBoxes(byName: "esds").first as? MP4ElementaryStreamDescriptorBox {
|
||||
delegate?.didSet(audioDecorderSpecificConfig: Data(esds.audioDecorderSpecificConfig), withType: i)
|
||||
}
|
||||
}
|
||||
|
||||
for reader in trakReaders {
|
||||
reader.delegate = delegate
|
||||
let trakReader:MP4TrakReader = MP4TrakReader(id:i, trak:traks[i])
|
||||
trakReader.delegate = delegate
|
||||
operations.addOperation {
|
||||
reader.execute(url: url)
|
||||
trakReader.execute(reader)
|
||||
}
|
||||
}
|
||||
operations.waitUntilAllOperationsAreFinished()
|
||||
|
|
|
@ -2,6 +2,7 @@ import Foundation
|
|||
import AVFoundation
|
||||
|
||||
protocol RTMPMuxerDelegate: class {
|
||||
func metadata(_ metadata:ASObject)
|
||||
func sampleOutput(audio buffer:Data, withTimestamp:Double, muxer:RTMPMuxer)
|
||||
func sampleOutput(video buffer:Data, withTimestamp:Double, muxer:RTMPMuxer)
|
||||
}
|
||||
|
@ -11,14 +12,12 @@ final class RTMPMuxer {
|
|||
static let aac:UInt8 = FLVAudioCodec.aac.rawValue << 4 | FLVSoundRate.kHz44.rawValue << 2 | FLVSoundSize.snd16bit.rawValue << 1 | FLVSoundType.stereo.rawValue
|
||||
|
||||
weak var delegate:RTMPMuxerDelegate? = nil
|
||||
fileprivate var avcC:Data?
|
||||
fileprivate var audioDecorderSpecificConfig:Data?
|
||||
fileprivate var configs:[Int:Data] = [:]
|
||||
fileprivate var audioTimestamp:CMTime = kCMTimeZero
|
||||
fileprivate var videoTimestamp:CMTime = kCMTimeZero
|
||||
|
||||
func dispose() {
|
||||
avcC = nil
|
||||
audioDecorderSpecificConfig = nil
|
||||
configs.removeAll()
|
||||
audioTimestamp = kCMTimeZero
|
||||
videoTimestamp = kCMTimeZero
|
||||
}
|
||||
|
@ -100,32 +99,44 @@ extension RTMPMuxer: VideoEncoderDelegate {
|
|||
}
|
||||
|
||||
extension RTMPMuxer: MP4SamplerDelegate {
|
||||
// MP4SampleDelegate
|
||||
func didSet(avcC: Data, withType:Int) {
|
||||
if (avcC == self.avcC) {
|
||||
return
|
||||
// MARK: MP4SampleDelegate
|
||||
func didOpen(_ reader: MP4Reader) {
|
||||
var metadata:ASObject = ASObject()
|
||||
if let avc1:MP4VisualSampleEntryBox = reader.getBoxes(byName: "avc1").first as? MP4VisualSampleEntryBox {
|
||||
metadata["width"] = avc1.width
|
||||
metadata["height"] = avc1.height
|
||||
metadata["videocodecid"] = FLVVideoCodec.avc.rawValue
|
||||
}
|
||||
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
|
||||
if let _:MP4AudioSampleEntryBox = reader.getBoxes(byName: "mp4a").first as? MP4AudioSampleEntryBox {
|
||||
metadata["audiocodecid"] = FLVAudioCodec.aac.rawValue
|
||||
}
|
||||
delegate?.metadata(metadata)
|
||||
}
|
||||
|
||||
func didSet(audioDecorderSpecificConfig:Data, withType:Int) {
|
||||
if (withType == 2) {
|
||||
func didSet(config:Data, withID:Int, type:String) {
|
||||
guard configs[withID] != config else {
|
||||
return
|
||||
}
|
||||
if (audioDecorderSpecificConfig == self.audioDecorderSpecificConfig) {
|
||||
return
|
||||
configs[withID] = config
|
||||
switch type {
|
||||
case AVMediaTypeVideo:
|
||||
var buffer:Data = Data([FLVFrameType.key.rawValue << 4 | FLVVideoCodec.avc.rawValue, FLVAVCPacketType.seq.rawValue, 0, 0, 0])
|
||||
buffer.append(config)
|
||||
delegate?.sampleOutput(video: buffer, withTimestamp: 0, muxer: self)
|
||||
case AVMediaTypeAudio:
|
||||
if (withID != 1) {
|
||||
break
|
||||
}
|
||||
var buffer:Data = Data([RTMPMuxer.aac, FLVAACPacketType.seq.rawValue])
|
||||
buffer.append(config)
|
||||
delegate?.sampleOutput(audio: buffer, withTimestamp: 0, muxer: self)
|
||||
default:
|
||||
break
|
||||
}
|
||||
var buffer:Data = Data([RTMPMuxer.aac, FLVAACPacketType.seq.rawValue])
|
||||
buffer.append(audioDecorderSpecificConfig)
|
||||
delegate?.sampleOutput(audio: buffer, withTimestamp: 0, muxer: self)
|
||||
self.audioDecorderSpecificConfig = audioDecorderSpecificConfig
|
||||
}
|
||||
|
||||
func output(data:Data, withType:Int, currentTime:Double, keyframe:Bool) {
|
||||
switch withType {
|
||||
func output(data:Data, withID:Int, currentTime:Double, keyframe:Bool) {
|
||||
switch withID {
|
||||
case 0:
|
||||
let compositionTime:Int32 = 0
|
||||
var buffer:Data = Data([((keyframe ? FLVFrameType.key.rawValue : FLVFrameType.inter.rawValue) << 4) | FLVVideoCodec.avc.rawValue, FLVAVCPacketType.nal.rawValue])
|
||||
|
|
|
@ -606,6 +606,10 @@ extension RTMPStream: IEventDispatcher {
|
|||
|
||||
extension RTMPStream: RTMPMuxerDelegate {
|
||||
// MARK: RTMPMuxerDelegate
|
||||
func metadata(_ metadata:ASObject) {
|
||||
send(handlerName: "@setDataFrame", arguments: "onMetaData", metadata)
|
||||
}
|
||||
|
||||
func sampleOutput(audio buffer:Data, withTimestamp:Double, muxer:RTMPMuxer) {
|
||||
guard readyState == .publishing else {
|
||||
return
|
||||
|
|
|
@ -17,7 +17,7 @@ class TimerDriver {
|
|||
weak var delegate:TimerDriverDelegate?
|
||||
|
||||
fileprivate var runloop:RunLoop?
|
||||
fileprivate var lastFired:UInt64 = 0
|
||||
fileprivate var nextFire:UInt64 = 0
|
||||
fileprivate weak var timer:Timer? {
|
||||
didSet {
|
||||
if let oldValue:Timer = oldValue {
|
||||
|
@ -30,12 +30,11 @@ class TimerDriver {
|
|||
}
|
||||
|
||||
@objc func on(timer:Timer) {
|
||||
let now:UInt64 = mach_absolute_time()
|
||||
guard interval <= now - lastFired else {
|
||||
guard nextFire <= mach_absolute_time() else {
|
||||
return
|
||||
}
|
||||
lastFired = now
|
||||
delegate?.tick(self)
|
||||
nextFire += interval
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,6 +58,8 @@ extension TimerDriver: Runnable {
|
|||
timer = Timer(
|
||||
timeInterval: 0.0001, target: self, selector: #selector(TimerDriver.on(timer:)), userInfo: nil, repeats: true
|
||||
)
|
||||
nextFire = mach_absolute_time() + interval
|
||||
delegate?.tick(self)
|
||||
runloop = .current
|
||||
runloop?.run()
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import XCTest
|
|||
@testable import lf
|
||||
|
||||
final class MP4SamplerTests: XCTestCase {
|
||||
func testMain() {
|
||||
func main() {
|
||||
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()
|
||||
|
|
|
@ -1092,7 +1092,7 @@
|
|||
attributes = {
|
||||
LastSwiftMigration = 0700;
|
||||
LastSwiftUpdateCheck = 0800;
|
||||
LastUpgradeCheck = 0810;
|
||||
LastUpgradeCheck = 0820;
|
||||
ORGANIZATIONNAME = "Shogo Endo";
|
||||
TargetAttributes = {
|
||||
2915EC511D85BDF100621092 = {
|
||||
|
@ -1753,7 +1753,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = SUEQ2SZ2L5;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
|
@ -1782,7 +1782,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = SUEQ2SZ2L5;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0810"
|
||||
LastUpgradeVersion = "0820"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0810"
|
||||
LastUpgradeVersion = "0820"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
Loading…
Reference in New Issue