HaishinKit.swift/Sources/ISO/TransportStream.swift

390 lines
13 KiB
Swift

import AVFoundation
/**
- seealso: https://en.wikipedia.org/wiki/MPEG_transport_stream#Packet
*/
struct TSPacket {
static let size: Int = 188
static let headerSize: Int = 4
static let defaultSyncByte: UInt8 = 0x47
var syncByte: UInt8 = TSPacket.defaultSyncByte
var transportErrorIndicator: Bool = false
var payloadUnitStartIndicator: Bool = false
var transportPriority: Bool = false
var PID: UInt16 = 0
var scramblingControl: UInt8 = 0
var adaptationFieldFlag: Bool = false
var payloadFlag: Bool = false
var continuityCounter: UInt8 = 0
var adaptationField: TSAdaptationField?
var payload = Data()
private var remain: Int {
var adaptationFieldSize: Int = 0
if let adaptationField: TSAdaptationField = adaptationField, adaptationFieldFlag {
adaptationField.compute()
adaptationFieldSize = Int(adaptationField.length) + 1
}
return TSPacket.size - TSPacket.headerSize - adaptationFieldSize - payload.count
}
init() {
}
init?(data: Data) {
guard TSPacket.size == data.count else {
return nil
}
self.data = data
if syncByte != TSPacket.defaultSyncByte {
return nil
}
}
mutating func fill(_ data: Data?, useAdaptationField: Bool) -> Int {
guard let data: Data = data else {
payload.append(Data(repeating: 0xff, count: remain))
return 0
}
payloadFlag = true
let length: Int = min(data.count, remain, 182)
payload.append(data[0..<length])
if remain == 0 {
return length
}
if useAdaptationField {
adaptationFieldFlag = true
if adaptationField == nil {
adaptationField = TSAdaptationField()
}
adaptationField?.stuffing(remain)
adaptationField?.compute()
return length
}
payload.append(Data(repeating: 0xff, count: remain))
return length
}
}
extension TSPacket: DataConvertible {
// MARK: DataConvertible
var data: Data {
get {
var bytes: Data = Data([syncByte, 0x00, 0x00, 0x00])
bytes[1] |= transportErrorIndicator ? 0x80 : 0
bytes[1] |= payloadUnitStartIndicator ? 0x40 : 0
bytes[1] |= transportPriority ? 0x20 : 0
bytes[1] |= UInt8(PID >> 8)
bytes[2] |= UInt8(PID & 0x00FF)
bytes[3] |= scramblingControl << 6
bytes[3] |= adaptationFieldFlag ? 0x20 : 0
bytes[3] |= payloadFlag ? 0x10 : 0
bytes[3] |= continuityCounter
return ByteArray()
.writeBytes(bytes)
.writeBytes(adaptationFieldFlag ? adaptationField!.data : Data())
.writeBytes(payload)
.data
}
set {
let buffer: ByteArray = ByteArray(data: newValue)
do {
var data: Data = try buffer.readBytes(4)
syncByte = data[0]
transportErrorIndicator = data[1] & 0x80 == 0x80
payloadUnitStartIndicator = data[1] & 0x40 == 0x40
transportPriority = data[1] & 0x20 == 0x20
PID = UInt16(data[1] & 0x1f) << 8 | UInt16(data[2])
scramblingControl = UInt8(data[3] & 0xc0)
adaptationFieldFlag = data[3] & 0x20 == 0x20
payloadFlag = data[3] & 0x10 == 0x10
continuityCounter = UInt8(data[3] & 0xf)
if adaptationFieldFlag {
let length: Int = Int(try buffer.readUInt8())
buffer.position -= 1
adaptationField = TSAdaptationField(data: try buffer.readBytes(length + 1))
}
if payloadFlag {
payload = try buffer.readBytes(buffer.bytesAvailable)
}
} catch {
logger.error("\(buffer)")
}
}
}
}
// MARK: -
struct TSTimestamp {
static let resolution: Double = 90 * 1000 // 90kHz
static let PTSMask: UInt8 = 0x10
static let PTSDTSMask: UInt8 = 0x30
static func decode(_ data: Data) -> UInt64 {
var result: UInt64 = 0
result |= UInt64(data[0] & 0x0e) << 29
result |= UInt64(data[1]) << 22 | UInt64(data[2] & 0xfe) << 14
result |= UInt64(data[3]) << 7 | UInt64(data[3] & 0xfe) << 1
return result
}
static func encode(_ b: UInt64, _ m: UInt8) -> Data {
var data = Data(count: 5)
data[0] = UInt8(truncatingIfNeeded: b >> 29) | 0x01 | m
data[1] = UInt8(truncatingIfNeeded: b >> 22)
data[2] = UInt8(truncatingIfNeeded: b >> 14) | 0x01
data[3] = UInt8(truncatingIfNeeded: b >> 7)
data[4] = UInt8(truncatingIfNeeded: b << 1) | 0x01
return data
}
}
// MARK: -
struct TSProgramClockReference {
static let resolutionForBase: Int32 = 90 * 1000 // 90kHz
static let resolutionForExtension: Int32 = 27 * 1000 * 1000 // 27MHz
static func decode(_ data: Data) -> (UInt64, UInt16) {
var b: UInt64 = 0
var e: UInt16 = 0
b |= UInt64(data[0]) << 25
b |= UInt64(data[1]) << 17
b |= UInt64(data[2]) << 9
b |= UInt64(data[3]) << 1
b |= (data[4] & 0x80 == 0x80) ? 1 : 0
e |= UInt16(data[4] & 0x01) << 8
e |= UInt16(data[5])
return (b, e)
}
static func encode(_ b: UInt64, _ e: UInt16) -> Data {
var data: Data = Data(count: 6)
data[0] = UInt8(truncatingIfNeeded: b >> 25)
data[1] = UInt8(truncatingIfNeeded: b >> 17)
data[2] = UInt8(truncatingIfNeeded: b >> 9)
data[3] = UInt8(truncatingIfNeeded: b >> 1)
data[4] = 0xff
if b & 1 == 1 {
data[4] |= 0x80
} else {
data[4] &= 0x7f
}
if UInt16(data[4] & 0x01) >> 8 == 1 {
data[4] |= 1
} else {
data[4] &= 0xfe
}
data[5] = UInt8(truncatingIfNeeded: e)
return data
}
}
extension TSPacket: CustomStringConvertible {
// MARK: CustomStringConvertible
var description: String {
return Mirror(reflecting: self).description
}
}
// MARK: -
class TSAdaptationField {
static let PCRSize: Int = 6
static let fixedSectionSize: Int = 2
var length: UInt8 = 0
var discontinuityIndicator: Bool = false
var randomAccessIndicator: Bool = false
var elementaryStreamPriorityIndicator = false
var PCRFlag: Bool = false
var OPCRFlag: Bool = false
var splicingPointFlag: Bool = false
var transportPrivateDataFlag: Bool = false
var adaptationFieldExtensionFlag: Bool = false
var PCR: Data = Data()
var OPCR: Data = Data()
var spliceCountdown: UInt8 = 0
var transportPrivateDataLength: UInt8 = 0
var transportPrivateData: Data = Data()
var adaptationExtension: TSAdaptationExtensionField?
var stuffingBytes: Data = Data()
init() {
}
init?(data: Data) {
self.data = data
}
func compute() {
length = UInt8(truncatingIfNeeded: TSAdaptationField.fixedSectionSize)
length += UInt8(truncatingIfNeeded: PCR.count)
length += UInt8(truncatingIfNeeded: OPCR.count)
length += UInt8(truncatingIfNeeded: transportPrivateData.count)
if let adaptationExtension: TSAdaptationExtensionField = adaptationExtension {
length += adaptationExtension.length + 1
}
length += UInt8(truncatingIfNeeded: stuffingBytes.count)
length -= 1
}
func stuffing(_ size: Int) {
stuffingBytes = Data(repeating: 0xff, count: size)
length += UInt8(size)
}
}
extension TSAdaptationField: DataConvertible {
// MARK: DataConvertible
var data: Data {
get {
var byte: UInt8 = 0
byte |= discontinuityIndicator ? 0x80 : 0
byte |= randomAccessIndicator ? 0x40 : 0
byte |= elementaryStreamPriorityIndicator ? 0x20 : 0
byte |= PCRFlag ? 0x10 : 0
byte |= OPCRFlag ? 0x08 : 0
byte |= splicingPointFlag ? 0x04 : 0
byte |= transportPrivateDataFlag ? 0x02 : 0
byte |= adaptationFieldExtensionFlag ? 0x01 : 0
let buffer: ByteArray = ByteArray()
.writeUInt8(length)
.writeUInt8(byte)
if PCRFlag {
buffer.writeBytes(PCR)
}
if OPCRFlag {
buffer.writeBytes(OPCR)
}
if splicingPointFlag {
buffer.writeUInt8(spliceCountdown)
}
if transportPrivateDataFlag {
buffer.writeUInt8(transportPrivateDataLength).writeBytes(transportPrivateData)
}
if adaptationFieldExtensionFlag {
buffer.writeBytes(adaptationExtension!.data)
}
return buffer.writeBytes(stuffingBytes).data
}
set {
let buffer: ByteArray = ByteArray(data: newValue)
do {
length = try buffer.readUInt8()
let byte: UInt8 = try buffer.readUInt8()
discontinuityIndicator = byte & 0x80 == 0x80
randomAccessIndicator = byte & 0x40 == 0x40
elementaryStreamPriorityIndicator = byte & 0x20 == 0x20
PCRFlag = byte & 0x10 == 0x10
OPCRFlag = byte & 0x08 == 0x08
splicingPointFlag = byte & 0x04 == 0x04
transportPrivateDataFlag = byte & 0x02 == 0x02
adaptationFieldExtensionFlag = byte & 0x01 == 0x01
if PCRFlag {
PCR = try buffer.readBytes(TSAdaptationField.PCRSize)
}
if OPCRFlag {
OPCR = try buffer.readBytes(TSAdaptationField.PCRSize)
}
if splicingPointFlag {
spliceCountdown = try buffer.readUInt8()
}
if transportPrivateDataFlag {
transportPrivateDataLength = try buffer.readUInt8()
transportPrivateData = try buffer.readBytes(Int(transportPrivateDataLength))
}
if adaptationFieldExtensionFlag {
let length: Int = Int(try buffer.readUInt8())
buffer.position -= 1
adaptationExtension = TSAdaptationExtensionField(data: try buffer.readBytes(length + 1))
}
stuffingBytes = try buffer.readBytes(buffer.bytesAvailable)
} catch {
logger.error("\(buffer)")
}
}
}
}
extension TSAdaptationField: CustomStringConvertible {
// MARK: CustomStringConvertible
var description: String {
return Mirror(reflecting: self).description
}
}
// MARK: -
struct TSAdaptationExtensionField {
var length: UInt8 = 0
var legalTimeWindowFlag: Bool = false
var piecewiseRateFlag: Bool = false
var seamlessSpiceFlag: Bool = false
var legalTimeWindowOffset: UInt16 = 0
var piecewiseRate: UInt32 = 0
var spliceType: UInt8 = 0
var DTSNextAccessUnit: Data = Data(count: 5)
init?(data: Data) {
self.data = data
}
}
extension TSAdaptationExtensionField: DataConvertible {
// MARK: DataConvertible
var data: Data {
get {
let buffer: ByteArray = ByteArray()
.writeUInt8(length)
.writeUInt8(
(legalTimeWindowFlag ? 0x80 : 0) |
(piecewiseRateFlag ? 0x40 : 0) |
(seamlessSpiceFlag ? 0x1f : 0)
)
if legalTimeWindowFlag {
buffer.writeUInt16((legalTimeWindowFlag ? 0x8000 : 0) | legalTimeWindowOffset)
}
if piecewiseRateFlag {
buffer.writeUInt24(piecewiseRate)
}
if seamlessSpiceFlag {
buffer
.writeUInt8(spliceType)
.writeUInt8(spliceType << 4 | DTSNextAccessUnit[0])
.writeBytes(DTSNextAccessUnit.subdata(in: 1..<DTSNextAccessUnit.count))
}
return buffer.data
}
set {
let buffer: ByteArray = ByteArray(data: newValue)
do {
var byte: UInt8 = 0
length = try buffer.readUInt8()
byte = try buffer.readUInt8()
legalTimeWindowFlag = (byte & 0x80) == 0x80
piecewiseRateFlag = (byte & 0x40) == 0x40
seamlessSpiceFlag = (byte & 0x1f) == 0x1f
if legalTimeWindowFlag {
legalTimeWindowOffset = try buffer.readUInt16()
legalTimeWindowFlag = (legalTimeWindowOffset & 0x8000) == 0x8000
}
if piecewiseRateFlag {
piecewiseRate = try buffer.readUInt24()
}
if seamlessSpiceFlag {
DTSNextAccessUnit = try buffer.readBytes(DTSNextAccessUnit.count)
spliceType = DTSNextAccessUnit[0] & 0xf0 >> 4
DTSNextAccessUnit[0] = DTSNextAccessUnit[0] & 0x0f
}
} catch {
logger.error("\(buffer)")
}
}
}
}
extension TSAdaptationExtensionField: CustomStringConvertible {
// MARK: CustomStringConvertible
var description: String {
return Mirror(reflecting: self).description
}
}