diff --git a/Moblin.xcodeproj/project.pbxproj b/Moblin.xcodeproj/project.pbxproj index dca1d7a81..fd172254a 100644 --- a/Moblin.xcodeproj/project.pbxproj +++ b/Moblin.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 0325D0B72BB29754007EE85B /* ChatTextToSpeechSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0325D0B62BB29754007EE85B /* ChatTextToSpeechSettingsView.swift */; }; 0325D0BB2BB3D7DD007EE85B /* KickViewers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0325D0BA2BB3D7DD007EE85B /* KickViewers.swift */; }; 0325D0BE2BB3DDD6007EE85B /* KickChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0325D0BD2BB3DDD6007EE85B /* KickChannel.swift */; }; + 032756A32BD81921002DC17F /* Bool+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032756A22BD81921002DC17F /* Bool+Extension.swift */; }; 03279CE72B4CD245006CEA5A /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03279CE62B4CD245006CEA5A /* Location.swift */; }; 03279CF22B4D2AEC006CEA5A /* RealtimeIrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03279CF12B4D2AEC006CEA5A /* RealtimeIrl.swift */; }; 032815D52AC855A5001F8A85 /* LicenseList in Frameworks */ = {isa = PBXBuildFile; productRef = 032815D42AC855A5001F8A85 /* LicenseList */; }; @@ -130,11 +131,11 @@ 0359F8DC2BD3DA61005BFDA8 /* ESSpecificData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F8422BD3DA5F005BFDA8 /* ESSpecificData.swift */; }; 0359F8DD2BD3DA61005BFDA8 /* HEVCDecoderConfigurationRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F8432BD3DA5F005BFDA8 /* HEVCDecoderConfigurationRecord.swift */; }; 0359F8DE2BD3DA61005BFDA8 /* HEVCNALUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F8442BD3DA5F005BFDA8 /* HEVCNALUnit.swift */; }; - 0359F8DF2BD3DA61005BFDA8 /* PacketizedElementaryStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F8452BD3DA5F005BFDA8 /* PacketizedElementaryStream.swift */; }; - 0359F8E02BD3DA61005BFDA8 /* TSField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F8462BD3DA5F005BFDA8 /* TSField.swift */; }; - 0359F8E12BD3DA61005BFDA8 /* TSPacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F8472BD3DA5F005BFDA8 /* TSPacket.swift */; }; - 0359F8E22BD3DA61005BFDA8 /* TSProgram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F8482BD3DA5F005BFDA8 /* TSProgram.swift */; }; - 0359F8E42BD3DA61005BFDA8 /* TSWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F84A2BD3DA5F005BFDA8 /* TSWriter.swift */; }; + 0359F8DF2BD3DA61005BFDA8 /* MpegTsPacketizedElementaryStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F8452BD3DA5F005BFDA8 /* MpegTsPacketizedElementaryStream.swift */; }; + 0359F8E02BD3DA61005BFDA8 /* MpegTsAdaptationField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F8462BD3DA5F005BFDA8 /* MpegTsAdaptationField.swift */; }; + 0359F8E12BD3DA61005BFDA8 /* MpegTsPacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F8472BD3DA5F005BFDA8 /* MpegTsPacket.swift */; }; + 0359F8E22BD3DA61005BFDA8 /* MpegTsProgram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F8482BD3DA5F005BFDA8 /* MpegTsProgram.swift */; }; + 0359F8E42BD3DA61005BFDA8 /* MpegTsWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F84A2BD3DA5F005BFDA8 /* MpegTsWriter.swift */; }; 0359F8E52BD3DA61005BFDA8 /* NetStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F84C2BD3DA5F005BFDA8 /* NetStream.swift */; }; 0359F8E72BD3DA61005BFDA8 /* AMF0Serializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F84F2BD3DA5F005BFDA8 /* AMF0Serializer.swift */; }; 0359F8E92BD3DA61005BFDA8 /* AMFFoundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359F8512BD3DA5F005BFDA8 /* AMFFoundation.swift */; }; @@ -400,6 +401,7 @@ 0325D0B62BB29754007EE85B /* ChatTextToSpeechSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTextToSpeechSettingsView.swift; sourceTree = ""; }; 0325D0BA2BB3D7DD007EE85B /* KickViewers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KickViewers.swift; sourceTree = ""; }; 0325D0BD2BB3DDD6007EE85B /* KickChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KickChannel.swift; sourceTree = ""; }; + 032756A22BD81921002DC17F /* Bool+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bool+Extension.swift"; sourceTree = ""; }; 03279CE62B4CD245006CEA5A /* Location.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = ""; }; 03279CF12B4D2AEC006CEA5A /* RealtimeIrl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealtimeIrl.swift; sourceTree = ""; }; 032815DB2AC915AE001F8A85 /* RandomEffect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomEffect.swift; sourceTree = ""; }; @@ -483,11 +485,11 @@ 0359F8422BD3DA5F005BFDA8 /* ESSpecificData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ESSpecificData.swift; sourceTree = ""; }; 0359F8432BD3DA5F005BFDA8 /* HEVCDecoderConfigurationRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HEVCDecoderConfigurationRecord.swift; sourceTree = ""; }; 0359F8442BD3DA5F005BFDA8 /* HEVCNALUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HEVCNALUnit.swift; sourceTree = ""; }; - 0359F8452BD3DA5F005BFDA8 /* PacketizedElementaryStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketizedElementaryStream.swift; sourceTree = ""; }; - 0359F8462BD3DA5F005BFDA8 /* TSField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TSField.swift; sourceTree = ""; }; - 0359F8472BD3DA5F005BFDA8 /* TSPacket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TSPacket.swift; sourceTree = ""; }; - 0359F8482BD3DA5F005BFDA8 /* TSProgram.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TSProgram.swift; sourceTree = ""; }; - 0359F84A2BD3DA5F005BFDA8 /* TSWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TSWriter.swift; sourceTree = ""; }; + 0359F8452BD3DA5F005BFDA8 /* MpegTsPacketizedElementaryStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MpegTsPacketizedElementaryStream.swift; sourceTree = ""; }; + 0359F8462BD3DA5F005BFDA8 /* MpegTsAdaptationField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MpegTsAdaptationField.swift; sourceTree = ""; }; + 0359F8472BD3DA5F005BFDA8 /* MpegTsPacket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MpegTsPacket.swift; sourceTree = ""; }; + 0359F8482BD3DA5F005BFDA8 /* MpegTsProgram.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MpegTsProgram.swift; sourceTree = ""; }; + 0359F84A2BD3DA5F005BFDA8 /* MpegTsWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MpegTsWriter.swift; sourceTree = ""; }; 0359F84C2BD3DA5F005BFDA8 /* NetStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetStream.swift; sourceTree = ""; }; 0359F84F2BD3DA5F005BFDA8 /* AMF0Serializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AMF0Serializer.swift; sourceTree = ""; }; 0359F8512BD3DA5F005BFDA8 /* AMFFoundation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AMFFoundation.swift; sourceTree = ""; }; @@ -1111,6 +1113,7 @@ 0359F8252BD3DA5F005BFDA8 /* URL+Extension.swift */, 0359F8262BD3DA5F005BFDA8 /* VTCompressionSession+Extension.swift */, 0359F8272BD3DA5F005BFDA8 /* VTDecompressionSession+Extension.swift */, + 032756A22BD81921002DC17F /* Bool+Extension.swift */, ); path = Extension; sourceTree = ""; @@ -1157,11 +1160,11 @@ 0359F8422BD3DA5F005BFDA8 /* ESSpecificData.swift */, 0359F8432BD3DA5F005BFDA8 /* HEVCDecoderConfigurationRecord.swift */, 0359F8442BD3DA5F005BFDA8 /* HEVCNALUnit.swift */, - 0359F8452BD3DA5F005BFDA8 /* PacketizedElementaryStream.swift */, - 0359F8462BD3DA5F005BFDA8 /* TSField.swift */, - 0359F8472BD3DA5F005BFDA8 /* TSPacket.swift */, - 0359F8482BD3DA5F005BFDA8 /* TSProgram.swift */, - 0359F84A2BD3DA5F005BFDA8 /* TSWriter.swift */, + 0359F8452BD3DA5F005BFDA8 /* MpegTsPacketizedElementaryStream.swift */, + 0359F8462BD3DA5F005BFDA8 /* MpegTsAdaptationField.swift */, + 0359F8472BD3DA5F005BFDA8 /* MpegTsPacket.swift */, + 0359F8482BD3DA5F005BFDA8 /* MpegTsProgram.swift */, + 0359F84A2BD3DA5F005BFDA8 /* MpegTsWriter.swift */, ); path = MPEG; sourceTree = ""; @@ -2211,12 +2214,13 @@ 03DBD0FF2B30091B004B0BE6 /* StreamWizardNetworkSetupSettingsView.swift in Sources */, 03A707C02B8F0FAE003BB7BE /* Utils.swift in Sources */, 0359F8B72BD3DA61005BFDA8 /* AVCaptureDevice+Extension.swift in Sources */, + 032756A32BD81921002DC17F /* Bool+Extension.swift in Sources */, 036BE2CC2ABBF0BA0048F9C5 /* StreamUrlSettingsView.swift in Sources */, 03EBEA632ACB4A17000DF427 /* BrowserEffect.swift in Sources */, 0379BB882AA836B500B718B6 /* StreamVideoSettingsView.swift in Sources */, 0359F8C92BD3DA61005BFDA8 /* FLVSoundRate.swift in Sources */, 03A983642A9A05D7008FF0DB /* StreamView.swift in Sources */, - 0359F8E12BD3DA61005BFDA8 /* TSPacket.swift in Sources */, + 0359F8E12BD3DA61005BFDA8 /* MpegTsPacket.swift in Sources */, 03DBD10A2B300DA8004B0BE6 /* StreamWizardNetworkSetupDirectSettingsView.swift in Sources */, 0379BB9C2AAD819800B718B6 /* StreamOverlayLeftView.swift in Sources */, 0315A3672AA33C6A00295443 /* WidgetImageSettingsView.swift in Sources */, @@ -2322,11 +2326,11 @@ 032A49BC2AC5DF3F00ED3916 /* ImportExportSettingsView.swift in Sources */, 03EF34BA2B1B028900A19221 /* StreamAfreecaTvSettingsView.swift in Sources */, 03A9836E2A9A05F9008FF0DB /* Model.swift in Sources */, - 0359F8E42BD3DA61005BFDA8 /* TSWriter.swift in Sources */, + 0359F8E42BD3DA61005BFDA8 /* MpegTsWriter.swift in Sources */, 03BC11642AE4BEC400C38FC4 /* Emotes.swift in Sources */, 03A983722A9A0659008FF0DB /* TwitchChatMoblin.swift in Sources */, 03FBD1812B3B0DE500757CA9 /* RtmpServerClient.swift in Sources */, - 0359F8E22BD3DA61005BFDA8 /* TSProgram.swift in Sources */, + 0359F8E22BD3DA61005BFDA8 /* MpegTsProgram.swift in Sources */, 034CE2EC2B4D2E7200E2C1BD /* StreamRealtimeIrlSettingsView.swift in Sources */, 0359F8DE2BD3DA61005BFDA8 /* HEVCNALUnit.swift in Sources */, 0379BBA52AAD945D00B718B6 /* LocalOverlaysSettingsView.swift in Sources */, @@ -2360,11 +2364,11 @@ 0359F8FE2BD3DA61005BFDA8 /* SRTStream.swift in Sources */, 0315A33D2AA2FA7D00295443 /* RemoteConnection.swift in Sources */, 03B373FF2A9FCA790028DDD5 /* ThermalStateView.swift in Sources */, - 0359F8DF2BD3DA61005BFDA8 /* PacketizedElementaryStream.swift in Sources */, + 0359F8DF2BD3DA61005BFDA8 /* MpegTsPacketizedElementaryStream.swift in Sources */, 0359F8EE2BD3DA61005BFDA8 /* RTMPMessage.swift in Sources */, 0359F8CA2BD3DA61005BFDA8 /* FLVSoundSize.swift in Sources */, 032A49B82AC54DD000ED3916 /* ExportSettingsView.swift in Sources */, - 0359F8E02BD3DA61005BFDA8 /* TSField.swift in Sources */, + 0359F8E02BD3DA61005BFDA8 /* MpegTsAdaptationField.swift in Sources */, 03085C0F2B487D950009DAB6 /* PixellateEffect.swift in Sources */, 03831D522AD90AB5008D26CC /* MoblinSettingsUrl.swift in Sources */, 032A49BA2AC54E6500ED3916 /* ImportSettingsView.swift in Sources */, diff --git a/Moblin/HaishinKit/Sources/Extension/Bool+Extension.swift b/Moblin/HaishinKit/Sources/Extension/Bool+Extension.swift new file mode 100644 index 000000000..fe9c2e1f7 --- /dev/null +++ b/Moblin/HaishinKit/Sources/Extension/Bool+Extension.swift @@ -0,0 +1,7 @@ +import Foundation + +extension Bool { + var uint8: UInt8 { + return self ? 1 : 0 + } +} diff --git a/Moblin/HaishinKit/Sources/MPEG/TSField.swift b/Moblin/HaishinKit/Sources/MPEG/MpegTsAdaptationField.swift similarity index 91% rename from Moblin/HaishinKit/Sources/MPEG/TSField.swift rename to Moblin/HaishinKit/Sources/MPEG/MpegTsAdaptationField.swift index ec9406fe8..545d577c2 100644 --- a/Moblin/HaishinKit/Sources/MPEG/TSField.swift +++ b/Moblin/HaishinKit/Sources/MPEG/MpegTsAdaptationField.swift @@ -1,6 +1,6 @@ import Foundation -class TSAdaptationField { +class MpegTsAdaptationField { static let fixedSectionSize: UInt8 = 2 var randomAccessIndicator = false var pcr: Data? @@ -9,7 +9,7 @@ class TSAdaptationField { init() {} func calcLength() -> UInt8 { - var length = TSAdaptationField.fixedSectionSize + var length = MpegTsAdaptationField.fixedSectionSize if let pcr { length += UInt8(truncatingIfNeeded: pcr.count) } diff --git a/Moblin/HaishinKit/Sources/MPEG/TSPacket.swift b/Moblin/HaishinKit/Sources/MPEG/MpegTsPacket.swift similarity index 94% rename from Moblin/HaishinKit/Sources/MPEG/TSPacket.swift rename to Moblin/HaishinKit/Sources/MPEG/MpegTsPacket.swift index 6c81a1013..74504adf8 100644 --- a/Moblin/HaishinKit/Sources/MPEG/TSPacket.swift +++ b/Moblin/HaishinKit/Sources/MPEG/MpegTsPacket.swift @@ -3,18 +3,18 @@ import AVFoundation /** - see: https://en.wikipedia.org/wiki/MPEG_transport_stream#Packet */ -struct TSPacket { +struct MpegTsPacket { static let size = 188 static let fixedHeaderSize = 4 var payloadUnitStartIndicator = false var pid: UInt16 = 0 var continuityCounter: UInt8 = 0 - var adaptationField: TSAdaptationField? + var adaptationField: MpegTsAdaptationField? var payload = Data() private func unusedSize() -> Int { let adaptationFieldSize = Int(adaptationField?.calcLength() ?? 0) - return TSPacket.size - TSPacket.fixedHeaderSize - adaptationFieldSize - payload.count + return MpegTsPacket.size - MpegTsPacket.fixedHeaderSize - adaptationFieldSize - payload.count } init(pid: UInt16) { diff --git a/Moblin/HaishinKit/Sources/MPEG/PacketizedElementaryStream.swift b/Moblin/HaishinKit/Sources/MPEG/MpegTsPacketizedElementaryStream.swift similarity index 52% rename from Moblin/HaishinKit/Sources/MPEG/PacketizedElementaryStream.swift rename to Moblin/HaishinKit/Sources/MPEG/MpegTsPacketizedElementaryStream.swift index 6ba63c8dc..9c0e392e5 100644 --- a/Moblin/HaishinKit/Sources/MPEG/PacketizedElementaryStream.swift +++ b/Moblin/HaishinKit/Sources/MPEG/MpegTsPacketizedElementaryStream.swift @@ -5,24 +5,14 @@ import CoreMedia - seealso: https://en.wikipedia.org/wiki/Packetized_elementary_stream */ -enum PESPTSDTSIndicator: UInt8 { - case none = 0 - case forbidden = 1 - case onlyPTS = 2 - case bothPresent = 3 -} - -struct PESOptionalHeader { - static let fixedSectionSize: Int = 3 - static let defaultMarkerBits: UInt8 = 2 - - var markerBits: UInt8 = PESOptionalHeader.defaultMarkerBits +private struct OptionalHeader { + var markerBits: UInt8 = 2 var scramblingControl: UInt8 = 0 var priority = false var dataAlignmentIndicator = false var copyright = false var originalOrCopy = false - var ptsDtsIndicator: UInt8 = PESPTSDTSIndicator.none.rawValue + var ptsDtsIndicator: UInt8 = 0 var esCRFlag = false var esRateFlag = false var dsmTrickModeFlag = false @@ -33,12 +23,6 @@ struct PESOptionalHeader { var optionalFields = Data() var stuffingBytes = Data() - init() {} - - init(data: Data) { - self.data = data - } - mutating func setTimestamp(_ timestamp: CMTime, presentationTimeStamp: CMTime, decodeTimeStamp: CMTime) { let base = Double(timestamp.seconds) if presentationTimeStamp != CMTime.invalid { @@ -58,109 +42,55 @@ struct PESOptionalHeader { pesHeaderLength = UInt8(optionalFields.count) } - var data: Data { - get { - var bytes = Data([0x00, 0x00]) - bytes[0] |= markerBits << 6 - bytes[0] |= scramblingControl << 4 - bytes[0] |= (priority ? 1 : 0) << 3 - bytes[0] |= (dataAlignmentIndicator ? 1 : 0) << 2 - bytes[0] |= (copyright ? 1 : 0) << 1 - bytes[0] |= (originalOrCopy ? 1 : 0) - bytes[1] |= ptsDtsIndicator << 6 - bytes[1] |= (esCRFlag ? 1 : 0) << 5 - bytes[1] |= (esRateFlag ? 1 : 0) << 4 - bytes[1] |= (dsmTrickModeFlag ? 1 : 0) << 3 - bytes[1] |= (additionalCopyInfoFlag ? 1 : 0) << 2 - bytes[1] |= (crcFlag ? 1 : 0) << 1 - bytes[1] |= extentionFlag ? 1 : 0 - return ByteArray() - .writeBytes(bytes) - .writeUInt8(pesHeaderLength) - .writeBytes(optionalFields) - .writeBytes(stuffingBytes) - .data - } - set { - let buffer = ByteArray(data: newValue) - do { - let bytes: Data = try buffer.readBytes(PESOptionalHeader.fixedSectionSize) - markerBits = (bytes[0] & 0b1100_0000) >> 6 - scramblingControl = bytes[0] & 0b0011_0000 >> 4 - priority = (bytes[0] & 0b0000_1000) == 0b0000_1000 - dataAlignmentIndicator = (bytes[0] & 0b0000_0100) == 0b0000_0100 - copyright = (bytes[0] & 0b0000_0010) == 0b0000_0010 - originalOrCopy = (bytes[0] & 0b0000_0001) == 0b0000_0001 - ptsDtsIndicator = (bytes[1] & 0b1100_0000) >> 6 - esCRFlag = (bytes[1] & 0b0010_0000) == 0b0010_0000 - esRateFlag = (bytes[1] & 0b0001_0000) == 0b0001_0000 - dsmTrickModeFlag = (bytes[1] & 0b0000_1000) == 0b0000_1000 - additionalCopyInfoFlag = (bytes[1] & 0b0000_0100) == 0b0000_0100 - crcFlag = (bytes[1] & 0b0000_0010) == 0b0000_0010 - extentionFlag = (bytes[1] & 0b0000_0001) == 0b0000_0001 - pesHeaderLength = bytes[2] - optionalFields = try buffer.readBytes(Int(pesHeaderLength)) - } catch { - logger.error("\(buffer)") - } - } + func encode() -> Data { + var bytes = Data([0x00, 0x00]) + bytes[0] |= markerBits << 6 + bytes[0] |= scramblingControl << 4 + bytes[0] |= priority.uint8 << 3 + bytes[0] |= dataAlignmentIndicator.uint8 << 2 + bytes[0] |= copyright.uint8 << 1 + bytes[0] |= originalOrCopy.uint8 + bytes[1] |= ptsDtsIndicator << 6 + bytes[1] |= esCRFlag.uint8 << 5 + bytes[1] |= esRateFlag.uint8 << 4 + bytes[1] |= dsmTrickModeFlag.uint8 << 3 + bytes[1] |= additionalCopyInfoFlag.uint8 << 2 + bytes[1] |= crcFlag.uint8 << 1 + bytes[1] |= extentionFlag.uint8 + return ByteArray() + .writeBytes(bytes) + .writeUInt8(pesHeaderLength) + .writeBytes(optionalFields) + .writeBytes(stuffingBytes) + .data } } -struct PacketizedElementaryStream { - static let untilPacketLengthSize: Int = 6 +struct MpegTsPacketizedElementaryStream { static let startCode = Data([0x00, 0x00, 0x01]) - - var startCode: Data = PacketizedElementaryStream.startCode - var streamID: UInt8 = 0 - var packetLength: UInt16 = 0 - var optionalPESHeader: PESOptionalHeader = .init() - var data = Data() - - var payload: Data { - get { - ByteArray() - .writeBytes(startCode) - .writeUInt8(streamID) - .writeUInt16(packetLength) - .writeBytes(optionalPESHeader.data) - .writeBytes(data) - .data - } - set { - let buffer = ByteArray(data: newValue) - do { - startCode = try buffer.readBytes(3) - streamID = try buffer.readUInt8() - packetLength = try buffer.readUInt16() - optionalPESHeader = try PESOptionalHeader(data: buffer.readBytes(buffer.bytesAvailable)) - buffer.position = PacketizedElementaryStream - .untilPacketLengthSize + 3 + Int(optionalPESHeader.pesHeaderLength) - data = try buffer.readBytes(buffer.bytesAvailable) - } catch { - logger.error("\(buffer)") - } - } - } + private var startCode = MpegTsPacketizedElementaryStream.startCode + private var streamID: UInt8 = 0 + private var packetLength: UInt16 = 0 + private var optionalHeader = OptionalHeader() + private var data = Data() init?( bytes: UnsafePointer, count: UInt32, presentationTimeStamp: CMTime, - decodeTimeStamp _: CMTime, timestamp: CMTime, config: AudioSpecificConfig, streamID: UInt8 ) { data.append(contentsOf: config.makeHeader(Int(count))) data.append(bytes, count: Int(count)) - optionalPESHeader.dataAlignmentIndicator = true - optionalPESHeader.setTimestamp( + optionalHeader.dataAlignmentIndicator = true + optionalHeader.setTimestamp( timestamp, presentationTimeStamp: presentationTimeStamp, decodeTimeStamp: CMTime.invalid ) - let length = data.count + optionalPESHeader.data.count + let length = data.count + optionalHeader.encode().count if length < Int(UInt16.max) { packetLength = UInt16(length) } else { @@ -191,13 +121,13 @@ struct PacketizedElementaryStream { if let stream = AVCFormatStream(bytes: bytes, count: count) { data.append(stream.toByteStream()) } - optionalPESHeader.dataAlignmentIndicator = true - optionalPESHeader.setTimestamp( + optionalHeader.dataAlignmentIndicator = true + optionalHeader.setTimestamp( timestamp, presentationTimeStamp: presentationTimeStamp, decodeTimeStamp: decodeTimeStamp ) - let length = data.count + optionalPESHeader.data.count + let length = data.count + optionalHeader.encode().count if length < Int(UInt16.max) { packetLength = UInt16(length) } @@ -230,33 +160,43 @@ struct PacketizedElementaryStream { if let stream = AVCFormatStream(bytes: bytes, count: count) { data.append(stream.toByteStream()) } - optionalPESHeader.dataAlignmentIndicator = true - optionalPESHeader.setTimestamp( + optionalHeader.dataAlignmentIndicator = true + optionalHeader.setTimestamp( timestamp, presentationTimeStamp: presentationTimeStamp, decodeTimeStamp: decodeTimeStamp ) - let length = data.count + optionalPESHeader.data.count + let length = data.count + optionalHeader.encode().count if length < Int(UInt16.max) { packetLength = UInt16(length) } self.streamID = streamID } - func arrayOfPackets(_ PID: UInt16, PCR: UInt64?) -> [TSPacket] { - let payload = self.payload - var packets: [TSPacket] = [] + private func encode() -> Data { + ByteArray() + .writeBytes(startCode) + .writeUInt8(streamID) + .writeUInt16(packetLength) + .writeBytes(optionalHeader.encode()) + .writeBytes(data) + .data + } + + func arrayOfPackets(_ PID: UInt16, PCR: UInt64?) -> [MpegTsPacket] { + let payload = encode() + var packets: [MpegTsPacket] = [] // start - var packet = TSPacket(pid: PID) + var packet = MpegTsPacket(pid: PID) packet.payloadUnitStartIndicator = true - packet.adaptationField = TSAdaptationField() + packet.adaptationField = MpegTsAdaptationField() if let PCR { packet.adaptationField!.pcr = TSProgramClockReference.encode(PCR, 0) } var payloadOffset = packet.setPayload(payload) packets.append(packet) // middle - packet = TSPacket(pid: PID) + packet = MpegTsPacket(pid: PID) while payloadOffset <= payload.count - 184 { packet.payload = payload[payloadOffset ..< payloadOffset + 184] packets.append(packet) @@ -268,18 +208,18 @@ struct PacketizedElementaryStream { break case 183: let remain = payload.subdata(in: payload.endIndex - rest ..< payload.endIndex - 1) - var packet = TSPacket(pid: PID) - packet.adaptationField = TSAdaptationField() + var packet = MpegTsPacket(pid: PID) + packet.adaptationField = MpegTsAdaptationField() _ = packet.setPayload(remain) packets.append(packet) - packet = TSPacket(pid: PID) - packet.adaptationField = TSAdaptationField() + packet = MpegTsPacket(pid: PID) + packet.adaptationField = MpegTsAdaptationField() _ = packet.setPayload(Data([payload[payload.count - 1]])) packets.append(packet) default: let remain = payload.subdata(in: payload.count - rest ..< payload.count) - var packet = TSPacket(pid: PID) - packet.adaptationField = TSAdaptationField() + var packet = MpegTsPacket(pid: PID) + packet.adaptationField = MpegTsAdaptationField() _ = packet.setPayload(remain) packets.append(packet) } diff --git a/Moblin/HaishinKit/Sources/MPEG/TSProgram.swift b/Moblin/HaishinKit/Sources/MPEG/MpegTsProgram.swift similarity index 92% rename from Moblin/HaishinKit/Sources/MPEG/TSProgram.swift rename to Moblin/HaishinKit/Sources/MPEG/MpegTsProgram.swift index b0f3295b1..2bbbe310b 100644 --- a/Moblin/HaishinKit/Sources/MPEG/TSProgram.swift +++ b/Moblin/HaishinKit/Sources/MPEG/MpegTsProgram.swift @@ -3,7 +3,7 @@ import Foundation /** - see: https://en.wikipedia.org/wiki/Program-specific_information */ -class TSProgram { +class MpegTsProgram { static let reservedBits: UInt8 = 0x03 static let defaultTableIDExtension: UInt16 = 1 var pointerField: UInt8 = 0 @@ -12,7 +12,7 @@ class TSProgram { var sectionSyntaxIndicator = false var privateBit = false var sectionLength: UInt16 = 0 - var tableIdExtension: UInt16 = TSProgram.defaultTableIDExtension + var tableIdExtension: UInt16 = MpegTsProgram.defaultTableIDExtension var versionNumber: UInt8 = 0 var currentNextIndicator = true var sectionNumber: UInt8 = 0 @@ -26,8 +26,8 @@ class TSProgram { self.data = data } - func packet(_ PID: UInt16) -> TSPacket { - var packet = TSPacket(pid: PID) + func packet(_ PID: UInt16) -> MpegTsPacket { + var packet = MpegTsPacket(pid: PID) packet.payloadUnitStartIndicator = true packet.setPayloadNoAdaptation(data) return packet @@ -42,12 +42,12 @@ class TSProgram { .writeUInt16( (sectionSyntaxIndicator ? 0x8000 : 0) | (privateBit ? 0x4000 : 0) | - UInt16(TSProgram.reservedBits) << 12 | + UInt16(MpegTsProgram.reservedBits) << 12 | sectionLength ) .writeUInt16(tableIdExtension) .writeUInt8( - TSProgram.reservedBits << 6 | + MpegTsProgram.reservedBits << 6 | versionNumber << 1 | (currentNextIndicator ? 1 : 0) ) @@ -82,7 +82,7 @@ class TSProgram { } } -final class TSProgramAssociation: TSProgram { +final class TSProgramAssociation: MpegTsProgram { var programs: [UInt16: UInt16] = [:] override var tableData: Data { @@ -106,7 +106,7 @@ final class TSProgramAssociation: TSProgram { } } -final class TSProgramMap: TSProgram { +final class TSProgramMap: MpegTsProgram { static let tableID: UInt8 = 2 var PCRPID: UInt16 = 0 diff --git a/Moblin/HaishinKit/Sources/MPEG/TSWriter.swift b/Moblin/HaishinKit/Sources/MPEG/MpegTsWriter.swift similarity index 85% rename from Moblin/HaishinKit/Sources/MPEG/TSWriter.swift rename to Moblin/HaishinKit/Sources/MPEG/MpegTsWriter.swift index c6188cd36..3a3e8fe0f 100644 --- a/Moblin/HaishinKit/Sources/MPEG/TSWriter.swift +++ b/Moblin/HaishinKit/Sources/MPEG/MpegTsWriter.swift @@ -5,12 +5,12 @@ import Foundation var payloadSize: Int = 1316 protocol TSWriterDelegate: AnyObject { - func writer(_ writer: TSWriter, doOutput data: Data) - func writer(_ writer: TSWriter, doOutputPointer pointer: UnsafeRawBufferPointer, count: Int) + func writer(_ writer: MpegTsWriter, doOutput data: Data) + func writer(_ writer: MpegTsWriter, doOutputPointer pointer: UnsafeRawBufferPointer, count: Int) } /// The TSWriter class represents writes MPEG-2 transport stream data. -class TSWriter { +class MpegTsWriter { static let defaultPATPID: UInt16 = 0 static let defaultPMTPID: UInt16 = 4095 static let defaultVideoPID: UInt16 = 256 @@ -23,9 +23,9 @@ class TSWriter { var expectedMedias: Set = [] private var audioContinuityCounter: UInt8 = 0 private var videoContinuityCounter: UInt8 = 0 - private let PCRPID: UInt16 = TSWriter.defaultVideoPID + private let PCRPID: UInt16 = MpegTsWriter.defaultVideoPID private var rotatedTimestamp = CMTime.zero - private var segmentDuration: Double = TSWriter.defaultSegmentDuration + private var segmentDuration: Double = MpegTsWriter.defaultSegmentDuration private let outputLock: DispatchQueue = .init( label: "com.haishinkit.HaishinKit.TSWriter", qos: .userInitiated @@ -35,7 +35,7 @@ class TSWriter { private var PAT: TSProgramAssociation = { let PAT: TSProgramAssociation = .init() - PAT.programs = [1: TSWriter.defaultPMTPID] + PAT.programs = [1: MpegTsWriter.defaultPMTPID] return PAT }() @@ -60,7 +60,7 @@ class TSWriter { && (expectedMedias.contains(.video) == (videoConfig != nil)) } - init(segmentDuration: Double = TSWriter.defaultSegmentDuration) { + init(segmentDuration: Double = MpegTsWriter.defaultSegmentDuration) { self.segmentDuration = segmentDuration } @@ -78,7 +78,7 @@ class TSWriter { audioContinuityCounter = 0 videoContinuityCounter = 0 PAT.programs.removeAll() - PAT.programs = [1: TSWriter.defaultPMTPID] + PAT.programs = [1: MpegTsWriter.defaultPMTPID] PMT = TSProgramMap() audioConfig = nil videoConfig = nil @@ -88,16 +88,15 @@ class TSWriter { isRunning.mutate { $0 = false } } - // swiftlint:disable:next function_parameter_count - private func writeSampleBuffer(_ PID: UInt16, - presentationTimeStamp: CMTime, - decodeTimeStamp: CMTime, - randomAccessIndicator: Bool, - PES: PacketizedElementaryStream) -> Data + private func encode(_ PID: UInt16, + presentationTimeStamp: CMTime, + decodeTimeStamp: CMTime, + randomAccessIndicator: Bool, + PES: MpegTsPacketizedElementaryStream) -> Data { let timestamp = decodeTimeStamp == .invalid ? presentationTimeStamp : decodeTimeStamp let packets = split(PID, PES: PES, timestamp: timestamp) - packets[0].adaptationField?.randomAccessIndicator = randomAccessIndicator + packets[0].adaptationField!.randomAccessIndicator = randomAccessIndicator rotateFileHandle(timestamp) let count = packets.count * 188 var data = Data( @@ -129,13 +128,13 @@ class TSWriter { private func nextContinuityCounter(PID: UInt16) -> UInt8 { switch PID { - case TSWriter.defaultAudioPID: + case MpegTsWriter.defaultAudioPID: defer { audioContinuityCounter += 1 audioContinuityCounter &= 0x0F } return audioContinuityCounter - case TSWriter.defaultVideoPID: + case MpegTsWriter.defaultVideoPID: defer { videoContinuityCounter += 1 videoContinuityCounter &= 0x0F @@ -232,8 +231,8 @@ class TSWriter { private func writeProgram() { PMT.PCRPID = PCRPID - write(PAT.packet(TSWriter.defaultPATPID).encode() - + PMT.packet(TSWriter.defaultPMTPID).encode()) + write(PAT.packet(MpegTsWriter.defaultPATPID).encode() + + PMT.packet(MpegTsWriter.defaultPMTPID).encode()) } private func writeProgramIfNeeded() { @@ -246,13 +245,16 @@ class TSWriter { writeProgram() } - private func split(_ PID: UInt16, PES: PacketizedElementaryStream, timestamp: CMTime) -> [TSPacket] { + private func split(_ PID: UInt16, PES: MpegTsPacketizedElementaryStream, + timestamp: CMTime) -> [MpegTsPacket] + { var PCR: UInt64? let timeSinceLatestPcr = timestamp.seconds - PCRTimestamp.seconds if PCRPID == PID, timeSinceLatestPcr >= 0.02 { PCR = UInt64((timestamp - .seconds - (PID == TSWriter.defaultVideoPID ? baseVideoTimestamp : baseAudioTimestamp) + .seconds - + (PID == MpegTsWriter.defaultVideoPID ? baseVideoTimestamp : baseAudioTimestamp) .seconds) * TSTimestamp.resolution) PCRTimestamp = timestamp @@ -261,7 +263,7 @@ class TSWriter { } } -extension TSWriter: AudioCodecDelegate { +extension MpegTsWriter: AudioCodecDelegate { func audioCodec(didOutput outputFormat: AVAudioFormat) { logger.info("ts-writer: Audio setup \(outputFormat)") var data = ESSpecificData() @@ -274,7 +276,7 @@ extension TSWriter: AudioCodecDelegate { logger.info("ts-writer: Unsupported audio format.") return } - data.elementaryPID = TSWriter.defaultAudioPID + data.elementaryPID = MpegTsWriter.defaultAudioPID PMT.elementaryStreamSpecificData.append(data) audioContinuityCounter = 0 audioConfig = AudioSpecificConfig(formatDescription: outputFormat.formatDescription) @@ -291,26 +293,25 @@ extension TSWriter: AudioCodecDelegate { } if baseAudioTimestamp == .invalid { baseAudioTimestamp = presentationTimeStamp - if PCRPID == TSWriter.defaultAudioPID { + if PCRPID == MpegTsWriter.defaultAudioPID { PCRTimestamp = baseAudioTimestamp } } guard let audioConfig else { return } - guard let PES = PacketizedElementaryStream( + guard let PES = MpegTsPacketizedElementaryStream( bytes: audioBuffer.data.assumingMemoryBound(to: UInt8.self), count: audioBuffer.byteLength, presentationTimeStamp: presentationTimeStamp, - decodeTimeStamp: .invalid, timestamp: baseAudioTimestamp, config: audioConfig, - streamID: TSWriter.audioStreamId + streamID: MpegTsWriter.audioStreamId ) else { return } - writeAudio(data: writeSampleBuffer( - TSWriter.defaultAudioPID, + writeAudio(data: encode( + MpegTsWriter.defaultAudioPID, presentationTimeStamp: presentationTimeStamp, decodeTimeStamp: .invalid, randomAccessIndicator: true, @@ -319,13 +320,13 @@ extension TSWriter: AudioCodecDelegate { } } -extension TSWriter: VideoCodecDelegate { +extension MpegTsWriter: VideoCodecDelegate { func videoCodec(_: VideoCodec, didOutput formatDescription: CMFormatDescription?) { guard let formatDescription else { return } var data = ESSpecificData() - data.elementaryPID = TSWriter.defaultVideoPID + data.elementaryPID = MpegTsWriter.defaultVideoPID videoContinuityCounter = 0 if let avcC = AVCDecoderConfigurationRecord.getData(formatDescription) { data.streamType = .h264 @@ -362,7 +363,7 @@ extension TSWriter: VideoCodecDelegate { } if baseVideoTimestamp == .invalid { baseVideoTimestamp = sampleBuffer.presentationTimeStamp - if PCRPID == TSWriter.defaultVideoPID { + if PCRPID == MpegTsWriter.defaultVideoPID { PCRTimestamp = baseVideoTimestamp } } @@ -370,33 +371,33 @@ extension TSWriter: VideoCodecDelegate { return } let randomAccessIndicator = !sampleBuffer.isNotSync - let PES: PacketizedElementaryStream + let PES: MpegTsPacketizedElementaryStream let bytes = UnsafeRawPointer(buffer).bindMemory(to: UInt8.self, capacity: length) if let videoConfig = videoConfig as? AVCDecoderConfigurationRecord { - PES = PacketizedElementaryStream( + PES = MpegTsPacketizedElementaryStream( bytes: bytes, count: length, presentationTimeStamp: sampleBuffer.presentationTimeStamp, decodeTimeStamp: sampleBuffer.decodeTimeStamp, timestamp: baseVideoTimestamp, config: randomAccessIndicator ? videoConfig : nil, - streamID: TSWriter.videoStreamId + streamID: MpegTsWriter.videoStreamId ) } else if let videoConfig = videoConfig as? HEVCDecoderConfigurationRecord { - PES = PacketizedElementaryStream( + PES = MpegTsPacketizedElementaryStream( bytes: bytes, count: length, presentationTimeStamp: sampleBuffer.presentationTimeStamp, decodeTimeStamp: sampleBuffer.decodeTimeStamp, timestamp: baseVideoTimestamp, config: randomAccessIndicator ? videoConfig : nil, - streamID: TSWriter.videoStreamId + streamID: MpegTsWriter.videoStreamId ) } else { return } - writeVideo(data: writeSampleBuffer( - TSWriter.defaultVideoPID, + writeVideo(data: encode( + MpegTsWriter.defaultVideoPID, presentationTimeStamp: sampleBuffer.presentationTimeStamp, decodeTimeStamp: sampleBuffer.decodeTimeStamp, randomAccessIndicator: randomAccessIndicator, diff --git a/Moblin/HaishinKit/Sources/SRT/SRTStream.swift b/Moblin/HaishinKit/Sources/SRT/SRTStream.swift index 2c6acaa16..be20d3000 100644 --- a/Moblin/HaishinKit/Sources/SRT/SRTStream.swift +++ b/Moblin/HaishinKit/Sources/SRT/SRTStream.swift @@ -18,8 +18,8 @@ public class SRTStream: NetStream { private var keyValueObservations: [NSKeyValueObservation] = [] private weak var connection: SRTConnection? - private lazy var writer: TSWriter = { - var writer = TSWriter() + private lazy var writer: MpegTsWriter = { + var writer = MpegTsWriter() writer.delegate = self return writer }() @@ -139,14 +139,14 @@ public class SRTStream: NetStream { } extension SRTStream: TSWriterDelegate { - func writer(_: TSWriter, doOutput data: Data) { + func writer(_: MpegTsWriter, doOutput data: Data) { guard readyState == .publishing else { return } connection?.socket?.doOutput(data: data) } - func writer(_: TSWriter, doOutputPointer pointer: UnsafeRawBufferPointer, count: Int) { + func writer(_: MpegTsWriter, doOutputPointer pointer: UnsafeRawBufferPointer, count: Int) { guard readyState == .publishing else { return }