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.
58 lines
2.4 KiB
Swift
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
|
|
}
|
|
}
|