c43dd3ca4a
A native iOS receiver so an iPhone can act as the second receiver, speaking the existing signaling + RTP protocol (no C++ changes) and mirroring the Android receiver (Phase 8) source-to-source. - RTP core (header/packet, jitter buffer, H.264 depacketizer) ported from the Android receiver - BSD-socket signaling server (dual-stack, most-recent-peer, never-throwing sends) + NSBonjourServices - VideoToolbox H.264 decode (in-band SPS/PPS, real-time, rebuilds on size change) -> AVSampleBufferDisplayLayer - PLI keyframe recovery (500 ms) + pendingOffer for late surface attach - XcodeGen project + bootstrap.sh; XCTest port of the Android suite + new coverage - .gitignore for generated artifacts; CHANGELOG; PHASES + MEMORY updated Status: authored; on-device validation pending a Mac + Xcode 26 + iPhone 16.
74 lines
2.6 KiB
Swift
74 lines
2.6 KiB
Swift
import Foundation
|
|
|
|
/// Converts between Annex-B (start-code delimited) and AVCC (4-byte
|
|
/// big-endian length prefixed) H.264 representations.
|
|
///
|
|
/// VideoToolbox consumes AVCC: each NAL unit is preceded by a 32-bit length.
|
|
/// Our depacketizer emits Annex-B (the project's canonical 3-byte start codes),
|
|
/// so the decoder feeds AVCC derived here.
|
|
enum AvccConverter {
|
|
/// Splits an Annex-B access unit into its NAL units (start codes removed).
|
|
static func nalUnits(_ annexB: [UInt8]) -> [[UInt8]] {
|
|
guard annexB.count >= 4 else { return [] }
|
|
let n = annexB.count
|
|
|
|
// Locate every start code (3-byte `00 00 01` and 4-byte `00 00 00 01`).
|
|
var offsets: [Int] = []
|
|
var i = 0
|
|
while i + 2 < n {
|
|
if annexB[i] == 0 && annexB[i + 1] == 0 && annexB[i + 2] == 1 {
|
|
offsets.append(i)
|
|
i += 3
|
|
continue
|
|
}
|
|
if i + 3 < n, annexB[i + 2] == 0, annexB[i + 3] == 1 {
|
|
offsets.append(i)
|
|
i += 4
|
|
continue
|
|
}
|
|
i += 1
|
|
}
|
|
guard !offsets.isEmpty else { return [] }
|
|
|
|
var units: [[UInt8]] = []
|
|
for (idx, offset) in offsets.enumerated() {
|
|
let nalStart = offset + 3
|
|
let nalEnd = idx + 1 < offsets.count ? offsets[idx + 1] : n
|
|
let nal = Array(annexB[nalStart..<nalEnd])
|
|
if !nal.isEmpty { units.append(nal) }
|
|
}
|
|
return units
|
|
}
|
|
|
|
/// Returns the AVCC form of an Annex-B access unit, or nil if it has no NALs.
|
|
static func toAvcc(_ annexB: [UInt8]) -> [UInt8]? {
|
|
let units = nalUnits(annexB)
|
|
guard !units.isEmpty else { return nil }
|
|
var out: [UInt8] = []
|
|
out.reserveCapacity(annexB.count + units.count * 4)
|
|
for nal in units {
|
|
let len = nal.count
|
|
out.append(UInt8((len >> 24) & 0xFF))
|
|
out.append(UInt8((len >> 16) & 0xFF))
|
|
out.append(UInt8((len >> 8) & 0xFF))
|
|
out.append(UInt8(len & 0xFF))
|
|
out.append(contentsOf: nal)
|
|
}
|
|
return out
|
|
}
|
|
|
|
/// Splits an AVCC byte array (4-byte big-endian length prefixes) into NAL units.
|
|
static func fromAvcc(_ avcc: [UInt8]) -> [[UInt8]] {
|
|
var units: [[UInt8]] = []
|
|
var i = 0
|
|
let n = avcc.count
|
|
while i + 4 <= n {
|
|
let len = (Int(avcc[i]) << 24) | (Int(avcc[i + 1]) << 16) | (Int(avcc[i + 2]) << 8) | Int(avcc[i + 3])
|
|
guard len > 0, i + 4 + len <= n else { break }
|
|
units.append(Array(avcc[(i + 4)..<(i + 4 + len)]))
|
|
i += 4 + len
|
|
}
|
|
return units
|
|
}
|
|
}
|