You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
136 lines
4.9 KiB
136 lines
4.9 KiB
import Foundation
|
|
|
|
/// Writes RTP packets for one outgoing stream (RFC 3550).
|
|
struct RTPStream {
|
|
let ssrc: UInt32
|
|
let payloadType: UInt8
|
|
private(set) var sequence: UInt16 = .random(in: 0...UInt16.max)
|
|
private(set) var packetCount: UInt32 = 0
|
|
private(set) var octetCount: UInt32 = 0
|
|
|
|
init(ssrc: UInt32, payloadType: UInt8) {
|
|
self.ssrc = ssrc
|
|
self.payloadType = payloadType
|
|
}
|
|
|
|
mutating func packet(timestamp: UInt32, marker: Bool, payload: [UInt8]) -> [UInt8] {
|
|
var w = ByteWriter(capacity: 12 + payload.count)
|
|
w.u8(0x80)
|
|
w.u8((marker ? 0x80 : 0) | (payloadType & 0x7F))
|
|
w.u16(sequence)
|
|
w.u32(timestamp)
|
|
w.u32(ssrc)
|
|
w.append(payload)
|
|
sequence &+= 1
|
|
packetCount &+= 1
|
|
octetCount &+= UInt32(payload.count)
|
|
return w.bytes
|
|
}
|
|
}
|
|
|
|
/// H.264 packetization (RFC 6184, packetization-mode 1): single NAL units, STAP-A and FU-A.
|
|
enum H264Packetizer {
|
|
/// Largest RTP payload; keeps IP packets well below 1500 bytes after SRTP and IPv6 headers.
|
|
static let maxPayload = 1100
|
|
|
|
/// Splits one access unit (NAL units without start codes or length prefixes) into RTP payloads.
|
|
/// Small leading parameter sets (SPS/PPS) are aggregated into one STAP-A.
|
|
static func payloads(for nalUnits: [[UInt8]]) -> [[UInt8]] {
|
|
var out: [[UInt8]] = []
|
|
var aggregate: [[UInt8]] = []
|
|
|
|
func flushAggregate() {
|
|
if aggregate.count == 1 {
|
|
out.append(aggregate[0])
|
|
} else if aggregate.count > 1 {
|
|
var w = ByteWriter()
|
|
w.u8((aggregate.map { $0[0] & 0x60 }.max() ?? 0) | 24) // STAP-A, highest NRI
|
|
for nal in aggregate { w.u16(UInt16(nal.count)); w.append(nal) }
|
|
out.append(w.bytes)
|
|
}
|
|
aggregate = []
|
|
}
|
|
|
|
for nal in nalUnits where !nal.isEmpty {
|
|
let type = nal[0] & 0x1F
|
|
if type == 7 || type == 8 {
|
|
let size = aggregate.reduce(1) { $0 + 2 + $1.count } + 2 + nal.count
|
|
if size > maxPayload { flushAggregate() }
|
|
aggregate.append(nal)
|
|
continue
|
|
}
|
|
flushAggregate()
|
|
if nal.count <= maxPayload {
|
|
out.append(nal)
|
|
continue
|
|
}
|
|
let indicator = (nal[0] & 0xE0) | 28 // FU-A
|
|
var offset = 1
|
|
while offset < nal.count {
|
|
let end = min(offset + maxPayload - 2, nal.count)
|
|
var header = type
|
|
if offset == 1 { header |= 0x80 }
|
|
if end == nal.count { header |= 0x40 }
|
|
out.append([indicator, header] + nal[offset..<end])
|
|
offset = end
|
|
}
|
|
}
|
|
flushAggregate()
|
|
return out
|
|
}
|
|
|
|
/// Splits an AVCC buffer (4-byte big-endian length prefixes, as from VideoToolbox) into NAL units.
|
|
static func nalUnits(avcc: [UInt8]) -> [[UInt8]] {
|
|
var out: [[UInt8]] = []
|
|
var i = 0
|
|
while i + 4 <= avcc.count {
|
|
let length = Int(avcc[i]) << 24 | Int(avcc[i + 1]) << 16 | Int(avcc[i + 2]) << 8 | Int(avcc[i + 3])
|
|
i += 4
|
|
guard length > 0, i + length <= avcc.count else { break }
|
|
out.append(Array(avcc[i..<i + length]))
|
|
i += length
|
|
}
|
|
return out
|
|
}
|
|
}
|
|
|
|
/// RTCP feedback the browser sends that the sender must act on.
|
|
enum RTCPFeedback {
|
|
/// True if a (compound) RTCP packet asks for a key frame (PLI or FIR).
|
|
static func requestsKeyFrame(_ packet: [UInt8]) -> Bool {
|
|
var i = 0
|
|
while i + 4 <= packet.count {
|
|
let fmt = packet[i] & 0x1F
|
|
let type = packet[i + 1]
|
|
let length = ((Int(packet[i + 2]) << 8 | Int(packet[i + 3])) + 1) * 4
|
|
if (type == 206 && fmt == 1) || (type == 206 && fmt == 4) { return true }
|
|
guard length > 0 else { return false }
|
|
i += length
|
|
}
|
|
return false
|
|
}
|
|
|
|
/// RFC 7983: RTP/RTCP start with version 2 (first byte 128…191); RTCP payload types are 192…223.
|
|
static func isRTCP(_ packet: [UInt8]) -> Bool {
|
|
packet.count >= 8 && packet[0] >= 128 && packet[0] <= 191 && packet[1] >= 192 && packet[1] <= 223
|
|
}
|
|
|
|
/// Sender report so the browser can map RTP timestamps to wall clock (RFC 3550 6.4.1).
|
|
static func senderReport(ssrc: UInt32, rtpTimestamp: UInt32, packets: UInt32, octets: UInt32, now: Date = Date()) -> [UInt8] {
|
|
let ntp = now.timeIntervalSince1970 + 2_208_988_800
|
|
let seconds = UInt32(ntp)
|
|
let fraction = UInt32((ntp - Double(seconds)) * 4_294_967_296)
|
|
var w = ByteWriter(capacity: 28)
|
|
w.u8(0x80)
|
|
w.u8(200)
|
|
w.u16(6)
|
|
w.u32(ssrc)
|
|
w.u32(seconds)
|
|
w.u32(fraction)
|
|
w.u32(rtpTimestamp)
|
|
w.u32(packets)
|
|
w.u32(octets)
|
|
return w.bytes
|
|
}
|
|
}
|