depay.bend source
depay.bend on the hub · documented module
# From RTP packets to frames: the video's NAL units gathered into one# access unit per picture, the audio's frames as they come.## A picture's packets share an RTP timestamp, and the last has the# marker bit: either a new timestamp or the marker ends the access unit.import Baseimport ./bytes.bend as Bimport ./rtp.bend as Rimport ./audio.bend as Aimport ./frame.bend as F# dep: the units being rebuilt. au: the open access unit's NAL units,# the last first; at: its RTP time. base: the first time seen (based:# one was). aud: the audio.type Asm is Data: Asm{h265: Bool, dep: R.Dep, au: List<&2, List<&2, U32>>, at: U32, open: Bool, base: U32, based: Bool, aud: A.Aud}# What a packet gave: the frames it completed, and the state after.type Got is Data: Got{frames: List<&2, F.Frame>, st: Asm}def Asm.new(h265: Bool, aud: A.Aud) -> Asm: Asm{h265, R.Dep.new(), Nil{}, 0, False{}, 0, False{}, aud}# A unit a decoder can start at: an IDR slice (H.264 type 5), or an# IRAP picture (H.265 types 16 to 21).def Asm.idr(h265: Bool, +b: U32) -> Bool: match h265: case True{}: (U32.and(U32.shrn(b, 1n), 63) >= 16 && U32.and(U32.shrn(b, 1n), 63) <= 21 : U32) case False{}: U32.is_eq(U32.and(b, 31), 5)def Asm.key(+h265: Bool, au: List<&2, List<&2, U32>>) -> Bool: match au: case Nil{}: False{} case Con{Con{b, _}, t}: Asm.idr(h265, b) || Asm.key(h265, t) case Con{Nil{}, t}: Asm.key(h265, t)# The open access unit out, if it is to end.def Asm.flush.if(yes: Bool, +h265: Bool, dep: R.Dep, +au: List<&2, List<&2, U32>>, +at: U32, open: Bool, +base: U32, based: Bool, aud: A.Aud) -> Got: match yes: case False{}: Got{Nil{}, Asm{h265, dep, au, at, open, base, based, aud}} case True{}: Got{[F.Video{U32.sub(at, base), Asm.key(h265, au), R.Nal.annexb(R.Nal.rev(au, Nil{}))}], Asm{h265, dep, Nil{}, at, False{}, base, based, aud}}# End the open access unit when told to, or when the time moved on.def Asm.flush(m: Asm, rts: U32, force: Bool) -> Got: Asm{h265, dep, au, +at, +open, base, based, aud} = m Asm.flush.if(open && (force || U32.is_ne(rts, at)), h265, dep, au, at, open, base, based, aud)def Asm.any(nals: List<&2, List<&2, U32>>) -> Bool: match nals: case Nil{}: False{} case Con{_, _}: True{}# Units into the open access unit, which is of this time.def Asm.add(m: Asm, +nals: List<&2, List<&2, U32>>, +rts: U32) -> Asm: Asm{h265, dep, au, at, open, base, +based, aud} = m +has = Asm.any(nals) Asm{h265, dep, R.Nal.rev(nals, au), Bool.pick(U32, has, rts, at), open || has, Bool.pick(U32, based, base, rts), based || has, aud}def Asm.join.end(g: Got, f1: List<&2, F.Frame>) -> Got: Got{f2, m} = g Got{List.append(&2, F.Frame, f1, f2), m}def Asm.join(g: Got, nals: List<&2, List<&2, U32>>, marker: Bool, +rts: U32) -> Got: Got{f1, m} = g Asm.join.end(Asm.flush(Asm.add(m, nals, rts), rts, marker), f1)def Asm.out(o: R.Out, h265: Bool, au: List<&2, List<&2, U32>>, at: U32, open: Bool, base: U32, based: Bool, aud: A.Aud, marker: Bool, +rts: U32) -> Got: match o: case R.Out{nals, dep}: Asm.join(Asm.flush(Asm{h265, dep, au, at, open, base, based, aud}, rts, False{}), nals, marker, rts)def Asm.dep(h265: Bool, dep: R.Dep, payload: List<&2, U32>) -> R.Out: match h265: case True{}: R.H265.push(dep, payload) case False{}: R.H264.push(dep, payload)# A video payload into the state.def Asm.video(m: Asm, marker: Bool, rts: U32, payload: List<&2, U32>) -> Got: Asm{+h265, dep, au, at, open, base, based, aud} = m Asm.out(Asm.dep(h265, dep, payload), h265, au, at, open, base, based, aud, marker, rts)def Asm.sound.end(o: A.Sound, h265: Bool, dep: R.Dep, au: List<&2, List<&2, U32>>, at: U32, open: Bool, base: U32, based: Bool) -> Got: A.Sound{frames, aud} = o Got{frames, Asm{h265, dep, au, at, open, base, based, aud}}# An audio payload into the state.def Asm.sound(m: Asm, rts: U32, payload: List<&2, U32>) -> Got: Asm{h265, dep, au, at, open, base, based, aud} = m Asm.sound.end(A.Aud.push(aud, rts, payload), h265, dep, au, at, open, base, based)def Asm.audio.pt(m: Asm) -> Asm & U32: Asm{h265, dep, au, at, open, base, based, +aud} = m (Asm{h265, dep, au, at, open, base, based, aud}, A.Aud.pt(aud))def Asm.mine(video: Bool, audio: Bool, m: Asm, marker: Bool, rts: U32, payload: List<&2, U32>) -> Got: match video audio: case True{} _: Asm.video(m, marker, rts, payload) case False{} True{}: Asm.sound(m, rts, payload) case False{} False{}: Got{Nil{}, m}def Asm.typed(ma: Asm & U32, +p: U32, pt: U32, +sound: Bool, marker: Bool, rts: U32, payload: List<&2, U32>) -> Got: (m, apt) = ma Asm.mine(U32.is_eq(p, pt) && Bool.not(sound), U32.is_eq(p, apt) && sound, m, marker, rts, payload)# An RTP packet into the state: the video's, of payload type pt, or# (sound) one from the audio's channel, of the audio's type; the two# streams may well use the same number. Another type changes nothing,# as does something that is no RTP.def Asm.packet(rtp: R.Rtp, pt: U32, sound: Bool, m: Asm) -> Got: match rtp: case R.NoRtp{}: Got{Nil{}, m} case R.Rtp{marker, p, _, ts, _, payload}: Asm.typed(Asm.audio.pt(m), p, pt, sound, marker, ts, payload)