diff --git a/shared/AVLiveWire/Sources/AVLiveWire/StreamDemuxer.swift b/shared/AVLiveWire/Sources/AVLiveWire/StreamDemuxer.swift new file mode 100644 index 0000000..1ab7378 --- /dev/null +++ b/shared/AVLiveWire/Sources/AVLiveWire/StreamDemuxer.swift @@ -0,0 +1,49 @@ +import Foundation + +public struct StreamDemuxer { + public struct Frame: Equatable { + public let header: FrameHeader + public let payload: Data + } + + private var buffer = Data() + public init() {} + + /// Append bytes; return every complete frame now available. + public mutating func feed(_ chunk: Data) -> [Frame] { + buffer.append(chunk) + var out: [Frame] = [] + while true { + guard let start = findMagic() else { + // keep at most 3 trailing bytes (partial magic) + if buffer.count > 3 { + buffer = buffer.suffix(3) + } + break + } + if start > 0 { buffer.removeFirst(start) } + guard buffer.count >= FrameHeader.byteCount, + let h = FrameHeader(decoding: buffer) else { break } + let total = FrameHeader.byteCount + Int(h.length) + guard buffer.count >= total else { break } + let payloadStart = buffer.index( + buffer.startIndex, offsetBy: FrameHeader.byteCount) + let payloadEnd = buffer.index( + buffer.startIndex, offsetBy: total) + out.append(Frame(header: h, + payload: Data(buffer[payloadStart.. Int? { + let m = FrameHeader.magic + let bytes = [UInt8](buffer) + guard bytes.count >= m.count else { return nil } + for i in 0...(bytes.count - m.count) { + if Array(bytes[i.. Data { + let h = FrameHeader(tag: tag, pid: 0, timestamp: 1, + length: UInt32(payload.count)) + return h.encoded() + payload + } + + func testSingleFrame() { + var d = StreamDemuxer() + let out = d.feed(frame(.video, Data([1, 2, 3]))) + XCTAssertEqual(out.count, 1) + XCTAssertEqual(out[0].payload, Data([1, 2, 3])) + } + + func testSplitAcrossChunks() { + var d = StreamDemuxer() + let whole = frame(.video, Data([9, 9, 9, 9, 9])) + XCTAssertTrue(d.feed(whole.prefix(10)).isEmpty) + let out = d.feed(whole.dropFirst(10)) + XCTAssertEqual(out.count, 1) + XCTAssertEqual(out[0].payload, Data([9, 9, 9, 9, 9])) + } + + func testTwoFramesOneChunk() { + var d = StreamDemuxer() + let out = d.feed(frame(.meta, Data([1])) + + frame(.video, Data([2, 2]))) + XCTAssertEqual(out.count, 2) + } + + func testResyncAfterGarbage() { + var d = StreamDemuxer() + let out = d.feed(Data([0xDE, 0xAD]) + + frame(.video, Data([7]))) + XCTAssertEqual(out.count, 1) + XCTAssertEqual(out[0].payload, Data([7])) + } +}