Files
fegger c43dd3ca4a feat(ios): native receiver app (Swift, min iOS 17)
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.
2026-09-10 21:31:53 +02:00

58 lines
2.5 KiB
Swift

import XCTest
@testable import Receiver
final class JitterBufferTests: XCTestCase {
private func packet(seq: Int, ts: Int = 100) -> RtpPacket {
RtpPacket(header: RtpHeader(sequenceNumber: seq, timestamp: ts), payload: [UInt8(seq)])
}
func testInOrderReleasesImmediately() {
let jitter = JitterBuffer()
XCTAssertEqual(jitter.push(packet(seq: 1)).map { $0.header.sequenceNumber }, [1])
XCTAssertEqual(jitter.push(packet(seq: 2)).map { $0.header.sequenceNumber }, [2])
XCTAssertEqual(jitter.push(packet(seq: 3)).map { $0.header.sequenceNumber }, [3])
}
func testReordersOutOfOrderPackets() {
let jitter = JitterBuffer()
XCTAssertEqual(jitter.push(packet(seq: 1)).map { $0.header.sequenceNumber }, [1])
XCTAssertTrue(jitter.push(packet(seq: 3)).isEmpty)
XCTAssertEqual(jitter.push(packet(seq: 2)).map { $0.header.sequenceNumber }, [2, 3])
}
func testOverflowReleasesInOrderAndAdvances() {
let jitter = JitterBuffer(maxDepth: 4)
XCTAssertEqual(jitter.push(packet(seq: 1)).map { $0.header.sequenceNumber }, [1])
// seq 2 is lost; 3..6 stay buffered (within the depth bound).
for seq in 3...6 {
XCTAssertTrue(jitter.push(packet(seq: seq)).isEmpty)
}
// seq 7 overflows the buffer: 3..7 flush in order.
XCTAssertEqual(jitter.push(packet(seq: 7)).map { $0.header.sequenceNumber }, [3, 4, 5, 6, 7])
// Delivery continues in order afterwards.
XCTAssertEqual(jitter.push(packet(seq: 8)).map { $0.header.sequenceNumber }, [8])
XCTAssertEqual(jitter.push(packet(seq: 9)).map { $0.header.sequenceNumber }, [9])
}
func testDiscardsStragglers() {
let jitter = JitterBuffer(maxDepth: 4)
_ = jitter.push(packet(seq: 1))
for seq in 3...8 {
_ = jitter.push(packet(seq: seq))
}
XCTAssertTrue(jitter.push(packet(seq: 9)).isNotEmpty)
// seq 4 is now far behind the expected sequence: discarded, not delivered.
XCTAssertTrue(jitter.push(packet(seq: 4)).isEmpty)
// In-order delivery continues from 10.
XCTAssertEqual(jitter.push(packet(seq: 10)).map { $0.header.sequenceNumber }, [10])
}
func testClearResetsState() {
let jitter = JitterBuffer()
_ = jitter.push(packet(seq: 5))
jitter.clear()
// A completely different sequence now starts fresh.
XCTAssertEqual(jitter.push(packet(seq: 100)).map { $0.header.sequenceNumber }, [100])
}
}