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.4 KiB
Swift

import CoreMedia
import Foundation
/// Builds a `CMVideoFormatDescription` for H.264 from in-band SPS + PPS.
///
/// VideoToolbox needs the parameter sets up front; the sender repeats them
/// before every keyframe, so any keyframe carries a complete set. This is the
/// Core Foundation recipe for an H.264 "config" format description.
enum H264FormatDescription {
static func create(sps: [UInt8], pps: [UInt8]) -> CMVideoFormatDescription? {
let pointersArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)
for ps in [sps, pps] {
guard let descriptor = makeParameterSetDescriptor(ps) else {
CFRelease(pointersArray)
return nil
}
CFArrayAppendValue(pointersArray, descriptor.takeUnretainedValue())
CFRelease(descriptor) // the array now owns it
}
let key = kCMFormatDescriptionExtension_SampleDescriptionPointers as CFString
let attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFDictionaryKeyCallBacks, &kCFDictionaryValueCallBacks)
CFDictionarySetValue(attrs, key, pointersArray)
var config: Unmanaged<CMVideoFormatDescription>?
let status = CMVideoFormatDescriptionCreate(
kCFAllocatorDefault,
kCMVideoCodecType_H264,
0, 0, 0,
attrs,
&config)
CFRelease(attrs)
CFRelease(pointersArray)
guard status == noErr, let c = config else { return nil }
return c.takeRetainedValue()
}
private static func makeParameterSetDescriptor(_ ps: [UInt8]) -> Unmanaged<CMVideoFormatDescription>? {
let cfData = Data(ps) as CFData
let oneElement = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)
CFArrayAppendValue(oneElement, cfData)
let key = kCMFormatDescriptionExtension_SampleDescriptionPointers as CFString
let attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFDictionaryKeyCallBacks, &kCFDictionaryValueCallBacks)
CFDictionarySetValue(attrs, key, oneElement)
var desc: Unmanaged<CMVideoFormatDescription>?
let status = CMVideoFormatDescriptionCreateForCodecType(
kCFAllocatorDefault,
kCMVideoCodecType_H264,
attrs,
&desc)
CFRelease(oneElement)
CFRelease(attrs)
guard status == noErr, let d = desc else { return nil }
return d
}
}