HaishinKit.swift/Sources/RTMP/RTMPMessage.swift

809 lines
24 KiB
Swift

import AVFoundation
enum RTMPMessageType: UInt8 {
case chunkSize = 0x01
case abort = 0x02
case ack = 0x03
case user = 0x04
case windowAck = 0x05
case bandwidth = 0x06
case audio = 0x08
case video = 0x09
case amf3Data = 0x0F
case amf3Shared = 0x10
case amf3Command = 0x11
case amf0Data = 0x12
case amf0Shared = 0x13
case amf0Command = 0x14
case aggregate = 0x16
func makeMessage() -> RTMPMessage {
switch self {
case .chunkSize:
return RTMPSetChunkSizeMessage()
case .abort:
return RTMPAbortMessge()
case .ack:
return RTMPAcknowledgementMessage()
case .user:
return RTMPUserControlMessage()
case .windowAck:
return RTMPWindowAcknowledgementSizeMessage()
case .bandwidth:
return RTMPSetPeerBandwidthMessage()
case .audio:
return RTMPAudioMessage()
case .video:
return RTMPVideoMessage()
case .amf3Data:
return RTMPDataMessage(objectEncoding: .amf3)
case .amf3Shared:
return RTMPSharedObjectMessage(objectEncoding: .amf3)
case .amf3Command:
return RTMPCommandMessage(objectEncoding: .amf3)
case .amf0Data:
return RTMPDataMessage(objectEncoding: .amf0)
case .amf0Shared:
return RTMPSharedObjectMessage(objectEncoding: .amf0)
case .amf0Command:
return RTMPCommandMessage(objectEncoding: .amf0)
case .aggregate:
return RTMPAggregateMessage()
}
}
}
class RTMPMessage {
let type: RTMPMessageType
var length: Int = 0
var streamId: UInt32 = 0
var timestamp: UInt32 = 0
var payload = Data()
init(type: RTMPMessageType) {
self.type = type
}
func execute(_ connection: RTMPConnection, type: RTMPChunkType) {
}
}
extension RTMPMessage: CustomDebugStringConvertible {
// MARK: CustomDebugStringConvertible
var debugDescription: String {
Mirror(reflecting: self).debugDescription
}
}
// MARK: -
/**
5.4.1. Set Chunk Size (1)
*/
final class RTMPSetChunkSizeMessage: RTMPMessage {
var size: UInt32 = 0
override var payload: Data {
get {
guard super.payload.isEmpty else {
return super.payload
}
super.payload = size.bigEndian.data
return super.payload
}
set {
if super.payload == newValue {
return
}
size = UInt32(data: newValue).bigEndian
super.payload = newValue
}
}
init() {
super.init(type: .chunkSize)
}
init(_ size: UInt32) {
super.init(type: .chunkSize)
self.size = size
}
override func execute(_ connection: RTMPConnection, type: RTMPChunkType) {
connection.socket.chunkSizeC = Int(size)
}
}
// MARK: -
/**
5.4.2. Abort Message (2)
*/
final class RTMPAbortMessge: RTMPMessage {
var chunkStreamId: UInt32 = 0
override var payload: Data {
get {
guard super.payload.isEmpty else {
return super.payload
}
super.payload = chunkStreamId.bigEndian.data
return super.payload
}
set {
if super.payload == newValue {
return
}
chunkStreamId = UInt32(data: newValue).bigEndian
super.payload = newValue
}
}
init() {
super.init(type: .abort)
}
}
// MARK: -
/**
5.4.3. Acknowledgement (3)
*/
final class RTMPAcknowledgementMessage: RTMPMessage {
var sequence: UInt32 = 0
override var payload: Data {
get {
guard super.payload.isEmpty else {
return super.payload
}
super.payload = sequence.bigEndian.data
return super.payload
}
set {
if super.payload == newValue {
return
}
sequence = UInt32(data: newValue).bigEndian
super.payload = newValue
}
}
init() {
super.init(type: .ack)
}
init(_ sequence: UInt32) {
super.init(type: .ack)
self.sequence = sequence
}
}
// MARK: -
/**
5.4.4. Window Acknowledgement Size (5)
*/
final class RTMPWindowAcknowledgementSizeMessage: RTMPMessage {
var size: UInt32 = 0
init() {
super.init(type: .windowAck)
}
init(_ size: UInt32) {
super.init(type: .windowAck)
self.size = size
}
override var payload: Data {
get {
guard super.payload.isEmpty else {
return super.payload
}
super.payload = size.bigEndian.data
return super.payload
}
set {
if super.payload == newValue {
return
}
size = UInt32(data: newValue).bigEndian
super.payload = newValue
}
}
override func execute(_ connection: RTMPConnection, type: RTMPChunkType) {
connection.windowSizeC = Int64(size)
connection.windowSizeS = Int64(size)
}
}
// MARK: -
/**
5.4.5. Set Peer Bandwidth (6)
*/
final class RTMPSetPeerBandwidthMessage: RTMPMessage {
enum Limit: UInt8 {
case hard = 0x00
case soft = 0x01
case dynamic = 0x02
case unknown = 0xFF
}
var size: UInt32 = 0
var limit: Limit = .hard
init() {
super.init(type: .bandwidth)
}
override var payload: Data {
get {
guard super.payload.isEmpty else {
return super.payload
}
var payload = Data()
payload.append(size.bigEndian.data)
payload.append(limit.rawValue)
super.payload = payload
return super.payload
}
set {
if super.payload == newValue {
return
}
size = UInt32(data: newValue[0..<4]).bigEndian
limit = Limit(rawValue: newValue[4]) ?? .unknown
super.payload = newValue
}
}
override func execute(_ connection: RTMPConnection, type: RTMPChunkType) {
connection.bandWidth = size
}
}
// MARK: -
/**
7.1.1. Command Message (20, 17)
*/
final class RTMPCommandMessage: RTMPMessage {
let objectEncoding: RTMPObjectEncoding
var commandName: String = ""
var transactionId: Int = 0
var commandObject: ASObject?
var arguments: [Any?] = []
override var payload: Data {
get {
guard super.payload.isEmpty else {
return super.payload
}
if type == .amf3Command {
serializer.writeUInt8(0)
}
serializer
.serialize(commandName)
.serialize(transactionId)
.serialize(commandObject)
for i in arguments {
serializer.serialize(i)
}
super.payload = serializer.data
serializer.clear()
return super.payload
}
set {
if length == newValue.count {
serializer.writeBytes(newValue)
serializer.position = 0
do {
if type == .amf3Command {
serializer.position = 1
}
commandName = try serializer.deserialize()
transactionId = try serializer.deserialize()
commandObject = try serializer.deserialize()
arguments.removeAll()
if 0 < serializer.bytesAvailable {
arguments.append(try serializer.deserialize())
}
} catch {
logger.error("\(self.serializer)")
}
serializer.clear()
}
super.payload = newValue
}
}
private var serializer: AMFSerializer = AMF0Serializer()
init(objectEncoding: RTMPObjectEncoding) {
self.objectEncoding = objectEncoding
super.init(type: objectEncoding.commandType)
}
init(streamId: UInt32, transactionId: Int, objectEncoding: RTMPObjectEncoding, commandName: String, commandObject: ASObject?, arguments: [Any?]) {
self.transactionId = transactionId
self.objectEncoding = objectEncoding
self.commandName = commandName
self.commandObject = commandObject
self.arguments = arguments
super.init(type: objectEncoding.commandType)
self.streamId = streamId
}
override func execute(_ connection: RTMPConnection, type: RTMPChunkType) {
guard let responder: Responder = connection.operations.removeValue(forKey: transactionId) else {
switch commandName {
case "close":
connection.close(isDisconnected: true)
default:
connection.dispatch(.rtmpStatus, bubbles: false, data: arguments.first)
}
return
}
switch commandName {
case "_result":
responder.on(result: arguments)
case "_error":
responder.on(status: arguments)
default:
break
}
}
}
// MARK: -
/**
7.1.2. Data Message (18, 15)
*/
final class RTMPDataMessage: RTMPMessage {
let objectEncoding: RTMPObjectEncoding
var handlerName: String = ""
var arguments: [Any?] = []
private var serializer: AMFSerializer = AMF0Serializer()
override var payload: Data {
get {
guard super.payload.isEmpty else {
return super.payload
}
if type == .amf3Data {
serializer.writeUInt8(0)
}
serializer.serialize(handlerName)
for arg in arguments {
serializer.serialize(arg)
}
super.payload = serializer.data
serializer.clear()
return super.payload
}
set {
guard super.payload != newValue else {
return
}
if length == newValue.count {
serializer.writeBytes(newValue)
serializer.position = 0
if type == .amf3Data {
serializer.position = 1
}
do {
handlerName = try serializer.deserialize()
while 0 < serializer.bytesAvailable {
arguments.append(try serializer.deserialize())
}
} catch {
logger.error("\(self.serializer)")
}
serializer.clear()
}
super.payload = newValue
}
}
init(objectEncoding: RTMPObjectEncoding) {
self.objectEncoding = objectEncoding
super.init(type: objectEncoding.dataType)
}
init(streamId: UInt32, objectEncoding: RTMPObjectEncoding, handlerName: String, arguments: [Any?] = []) {
self.objectEncoding = objectEncoding
self.handlerName = handlerName
self.arguments = arguments
super.init(type: objectEncoding.dataType)
self.streamId = streamId
}
override func execute(_ connection: RTMPConnection, type: RTMPChunkType) {
guard let stream: RTMPStream = connection.streams[streamId] else {
return
}
stream.info.byteCount.mutate { $0 += Int64(payload.count) }
}
}
// MARK: -
/**
7.1.3. Shared Object Message (19, 16)
*/
final class RTMPSharedObjectMessage: RTMPMessage {
let objectEncoding: RTMPObjectEncoding
var sharedObjectName: String = ""
var currentVersion: UInt32 = 0
var flags = Data(count: 8)
var events: [RTMPSharedObjectEvent] = []
override var payload: Data {
get {
guard super.payload.isEmpty else {
return super.payload
}
if type == .amf3Shared {
serializer.writeUInt8(0)
}
serializer
.writeUInt16(UInt16(sharedObjectName.utf8.count))
.writeUTF8Bytes(sharedObjectName)
.writeUInt32(currentVersion)
.writeBytes(flags)
for event in events {
event.serialize(&serializer)
}
super.payload = serializer.data
serializer.clear()
return super.payload
}
set {
if super.payload == newValue {
return
}
if length == newValue.count {
serializer.writeBytes(newValue)
serializer.position = 0
if type == .amf3Shared {
serializer.position = 1
}
do {
sharedObjectName = try serializer.readUTF8()
currentVersion = try serializer.readUInt32()
flags = try serializer.readBytes(8)
while 0 < serializer.bytesAvailable {
if let event: RTMPSharedObjectEvent = try RTMPSharedObjectEvent(serializer: &serializer) {
events.append(event)
}
}
} catch {
logger.error("\(self.serializer)")
}
serializer.clear()
}
super.payload = newValue
}
}
private var serializer: AMFSerializer = AMF0Serializer()
init(objectEncoding: RTMPObjectEncoding) {
self.objectEncoding = objectEncoding
super.init(type: objectEncoding.sharedObjectType)
}
init(timestamp: UInt32, objectEncoding: RTMPObjectEncoding, sharedObjectName: String, currentVersion: UInt32, flags: Data, events: [RTMPSharedObjectEvent]) {
self.objectEncoding = objectEncoding
self.sharedObjectName = sharedObjectName
self.currentVersion = currentVersion
self.flags = flags
self.events = events
super.init(type: objectEncoding.sharedObjectType)
self.timestamp = timestamp
}
override func execute(_ connection: RTMPConnection, type: RTMPChunkType) {
let persistence: Bool = (flags[3] & 2) != 0
RTMPSharedObject.getRemote(withName: sharedObjectName, remotePath: connection.uri!.absoluteWithoutQueryString, persistence: persistence).on(message: self)
}
}
// MARK: -
/**
7.1.5. Audio Message (9)
*/
final class RTMPAudioMessage: RTMPMessage {
private(set) var codec: FLVAudioCodec = .unknown
private(set) var soundRate: FLVSoundRate = .kHz44
private(set) var soundSize: FLVSoundSize = .snd8bit
private(set) var soundType: FLVSoundType = .stereo
override var payload: Data {
get {
super.payload
}
set {
if super.payload == newValue {
return
}
super.payload = newValue
if length == newValue.count && !newValue.isEmpty {
guard let codec = FLVAudioCodec(rawValue: newValue[0] >> 4),
let soundRate = FLVSoundRate(rawValue: (newValue[0] & 0b00001100) >> 2),
let soundSize = FLVSoundSize(rawValue: (newValue[0] & 0b00000010) >> 1),
let soundType = FLVSoundType(rawValue: (newValue[0] & 0b00000001)) else {
return
}
self.codec = codec
self.soundRate = soundRate
self.soundSize = soundSize
self.soundType = soundType
}
}
}
init() {
super.init(type: .audio)
}
init(streamId: UInt32, timestamp: UInt32, payload: Data) {
super.init(type: .audio)
self.streamId = streamId
self.timestamp = timestamp
self.payload = payload
}
override func execute(_ connection: RTMPConnection, type: RTMPChunkType) {
guard let stream: RTMPStream = connection.streams[streamId] else {
return
}
stream.info.byteCount.mutate { $0 += Int64(payload.count) }
guard codec.isSupported else {
return
}
switch type {
case .zero:
stream.audioTimestamp = Double(timestamp)
default:
stream.audioTimestamp += Double(timestamp)
}
switch FLVAACPacketType(rawValue: payload[1]) {
case .seq?:
let config = AudioSpecificConfig(bytes: [UInt8](payload[codec.headerSize..<payload.count]))
stream.mixer.audioIO.encoder.destination = .pcm
stream.mixer.audioIO.encoder.inSourceFormat = config?.audioStreamBasicDescription()
case .raw?:
enqueueSampleBuffer(stream, type: type)
case .none:
break
}
}
private func enqueueSampleBuffer(_ stream: RTMPStream, type: RTMPChunkType) {
if stream.mixer.audioIO.encoder.inSourceFormat == nil {
stream.mixer.audioIO.encoder.destination = .pcm
stream.mixer.audioIO.encoder.inSourceFormat = codec.audioStreamBasicDescription(soundRate, size: soundSize, type: soundType)
}
payload.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) -> Void in
stream.mixer.audioIO.encoder.encodeBytes(
buffer.baseAddress?.advanced(by: codec.headerSize),
count: payload.count - codec.headerSize,
presentationTimeStamp: CMTime(seconds: stream.audioTimestamp / 1000, preferredTimescale: 1000)
)
}
}
}
// MARK: -
/**
7.1.5. Video Message (9)
*/
final class RTMPVideoMessage: RTMPMessage {
private(set) var codec: FLVVideoCodec = .unknown
private(set) var status: OSStatus = noErr
init() {
super.init(type: .video)
}
init(streamId: UInt32, timestamp: UInt32, payload: Data) {
super.init(type: .video)
self.streamId = streamId
self.timestamp = timestamp
self.payload = payload
}
override func execute(_ connection: RTMPConnection, type: RTMPChunkType) {
guard let stream: RTMPStream = connection.streams[streamId] else {
return
}
stream.info.byteCount.mutate { $0 += Int64(payload.count) }
guard FLVTagType.video.headerSize <= payload.count else {
return
}
switch payload[1] {
case FLVAVCPacketType.seq.rawValue:
status = makeFormatDescription(stream)
stream.dispatch(.rtmpStatus, bubbles: false, data: RTMPStream.Code.videoDimensionChange.data(""))
case FLVAVCPacketType.nal.rawValue:
enqueueSampleBuffer(stream, type: type)
default:
break
}
}
private func enqueueSampleBuffer(_ stream: RTMPStream, type: RTMPChunkType) {
let isBaseline = stream.mixer.videoIO.decoder.isBaseline
// compositionTime -> SI24
var compositionTime = isBaseline ? 0 : Int32(data: [0] + payload[2..<5]).bigEndian
compositionTime <<= 8
compositionTime /= 256
switch type {
case .zero:
stream.videoTimestamp = Double(timestamp)
default:
stream.videoTimestamp += Double(timestamp)
}
var timing = CMSampleTimingInfo(
duration: CMTimeMake(value: Int64(timestamp), timescale: 1000),
presentationTimeStamp: CMTimeMake(value: Int64(stream.videoTimestamp) + Int64(compositionTime), timescale: 1000),
decodeTimeStamp: .invalid
)
payload.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in
var blockBuffer: CMBlockBuffer?
let length: Int = payload.count - FLVTagType.video.headerSize
guard CMBlockBufferCreateWithMemoryBlock(
allocator: kCFAllocatorDefault,
memoryBlock: nil,
blockLength: length,
blockAllocator: nil,
customBlockSource: nil,
offsetToData: 0,
dataLength: length,
flags: 0,
blockBufferOut: &blockBuffer) == noErr else {
stream.mixer.videoIO.decoder.needsSync.mutate { $0 = true }
return
}
guard CMBlockBufferReplaceDataBytes(
with: buffer.baseAddress!.advanced(by: FLVTagType.video.headerSize),
blockBuffer: blockBuffer!,
offsetIntoDestination: 0,
dataLength: length) == noErr else {
return
}
var sampleBuffer: CMSampleBuffer?
var sampleSizes: [Int] = [length]
guard CMSampleBufferCreate(
allocator: kCFAllocatorDefault,
dataBuffer: blockBuffer!,
dataReady: true,
makeDataReadyCallback: nil,
refcon: nil,
formatDescription: stream.mixer.videoIO.formatDescription,
sampleCount: 1,
sampleTimingEntryCount: 1,
sampleTimingArray: &timing,
sampleSizeEntryCount: 1,
sampleSizeArray: &sampleSizes,
sampleBufferOut: &sampleBuffer) == noErr else {
return
}
if let sampleBuffer = sampleBuffer {
sampleBuffer.isNotSync = !(payload[0] >> 4 == FLVFrameType.key.rawValue)
stream.mixer.videoIO.decodeSampleBuffer(sampleBuffer)
}
if stream.mixer.videoIO.queue.isPaused && stream.mixer.audioIO.encoder.formatDescription == nil {
stream.mixer.videoIO.queue.isPaused = false
}
}
}
private func makeFormatDescription(_ stream: RTMPStream) -> OSStatus {
var config = AVCConfigurationRecord()
config.data = payload.subdata(in: FLVTagType.video.headerSize..<payload.count)
return config.createFormatDescription(&stream.mixer.videoIO.formatDescription)
}
}
// MARK: -
/**
7.1.6. Aggregate Message (22)
*/
final class RTMPAggregateMessage: RTMPMessage {
init() {
super.init(type: .aggregate)
}
}
// MARK: -
/**
7.1.7. User Control Message Events
*/
final class RTMPUserControlMessage: RTMPMessage {
enum Event: UInt8 {
case streamBegin = 0x00
case streamEof = 0x01
case streamDry = 0x02
case setBuffer = 0x03
case recorded = 0x04
case ping = 0x06
case pong = 0x07
case bufferEmpty = 0x1F
case bufferFull = 0x20
case unknown = 0xFF
var bytes: [UInt8] {
[0x00, rawValue]
}
}
var event: Event = .unknown
var value: Int32 = 0
override var payload: Data {
get {
guard super.payload.isEmpty else {
return super.payload
}
super.payload.removeAll()
super.payload += event.bytes
super.payload += value.bigEndian.data
return super.payload
}
set {
if super.payload == newValue {
return
}
if length == newValue.count {
if let event = Event(rawValue: newValue[1]) {
self.event = event
}
value = Int32(data: newValue[2..<newValue.count]).bigEndian
}
super.payload = newValue
}
}
init() {
super.init(type: .user)
}
init(event: Event, value: Int32) {
super.init(type: .user)
self.event = event
self.value = value
}
override func execute(_ connection: RTMPConnection, type: RTMPChunkType) {
switch event {
case .ping:
connection.socket.doOutput(chunk: RTMPChunk(
type: .zero,
streamId: RTMPChunk.StreamID.control.rawValue,
message: RTMPUserControlMessage(event: .pong, value: value)
), locked: nil)
case .bufferEmpty:
connection.streams[UInt32(value)]?.dispatch(.rtmpStatus, bubbles: false, data: RTMPStream.Code.bufferEmpty.data(""))
case .bufferFull:
connection.streams[UInt32(value)]?.dispatch(.rtmpStatus, bubbles: false, data: RTMPStream.Code.bufferFull.data(""))
default:
break
}
}
}