~/bend-docscommunity

flv.bend source

flv.bend on the hub · documented module

# The frames inside RTMP's tags (the forms FLV keeps them in):##   video  H.264: a codec byte (frame type and 7 for AVC), a packet#          type, a 24-bit time offset; then either the configuration#          (the parameter sets, and how many bytes a size takes), or a#          picture's NAL units, each after its size.#          H.265, as enhanced RTMP carries it: a byte with its top bit#          set (frame type and packet type), the four letters "hvc1",#          and the same two things: the configuration, or a picture,#          with the time offset (packet type 1) or without (type 3).#   audio  a format byte, then: for AAC (10), a packet type and either#          the configuration (profile, rate, channels) or one frame;#          for G.711 (7 the A-law, 8 the mu-law), the samples.## A picture comes out in Annex B form, the parameter sets in front of# each key picture; an AAC frame after its ADTS header; G.711 as it is.# Times are the tags' (ms, plus the video's offset), from the first one# seen, in 90 kHz ticks. Other codecs are passed over.import Baseimport ./bytes.bend as Bimport ./rtmp_core.bend as Kimport ./frame.bend as Fimport ./audio.bend as U# lens: the bytes of a NAL unit's size. sets: the parameter sets. obj,# freq, chan: the audio's configuration. sound: the audio's codec once# known ("AAC", "PCMA", "PCMU"). base: the first time seen (based: one# was). video: the video's codec ("H264" until the stream says "H265").type Flv is Data:  Flv{lens: U32, sets: List<&2, U32>, obj: U32, freq: U32, chan: U32, sound: String,    base: U32, based: Bool, video: String}# What a tag gave: its frames (none, or one), and the state after.type Out is Data:  Out{frames: List<&2, F.Frame>, st: Flv}def Flv.new() -> Flv:  Flv{4, Nil{}, 0, 0, 0, "", 0, False{}, "H264"}# What is known of the stream so far (the audio's configuration comes# before the first picture when there is audio).def Flv.info(st: Flv) -> F.Info:  Flv{_, _, _, _, chan, sound, _, _, video} = st  F.Info{video, Nil{}, sound, 0, chan}def Flv.sets.cut(c: B.Cut, acc: List<&2, U32>,  k: List<&2, U32> -> List<&2, U32> -> List<&2, U32>) -> List<&2, U32>:  match c:    case B.Short{}:      acc    case B.Cut{nal, rest}:      k(rest, B.Bytes.onto(nal, 1 <> 0 <> 0 <> 0 <> acc))# count parameter sets, each after a 16-bit size, onto acc in Annex B# form, the last first; then what follows them.def Flv.sets(n: Nat, bs: List<&2, U32>, acc: List<&2, U32>,  k: List<&2, U32> -> List<&2, U32> -> List<&2, U32>) -> List<&2, U32>:  match n bs:    case 1n+p Con{a, Con{b, t}}:      Flv.sets.cut(B.Bytes.cut(U32.or(U32.shln(a, 8n), b), t), acc, rest => acc2 =>        Flv.sets(p, rest, acc2, k))    case _ rest:      k(rest, acc)def Flv.pps(bs: List<&2, U32>, acc: List<&2, U32>) -> List<&2, U32>:  match bs:    case Con{n, t}:      Flv.sets(U32.to_nat(n), t, acc, _ => acc2 => acc2)    case Nil{}:      acc# The parameter sets of an AVCDecoderConfigurationRecord, after its# first five bytes: the SPS count in 5 bits, the SPSs, the PPS count,# the PPSs.def Flv.avcc(bs: List<&2, U32>) -> List<&2, U32>:  match bs:    case Con{n, t}:      B.Bytes.rev(Flv.sets(U32.to_nat(U32.and(n, 31)), t, Nil{}, rest => acc =>        Flv.pps(rest, acc)))    case Nil{}:      Nil{}def Flv.nals.cut(c: B.Cut, acc: List<&2, U32>,  k: List<&2, U32> -> List<&2, U32> -> List<&2, U32>) -> List<&2, U32>:  match c:    case B.Short{}:      B.Bytes.rev(acc)    case B.Cut{nal, rest}:      k(rest, B.Bytes.onto(nal, 1 <> 0 <> 0 <> 0 <> acc))def Flv.nals.at(+bs: List<&2, U32>, +lens: U32, acc: List<&2, U32>,  k: List<&2, U32> -> List<&2, U32> -> List<&2, U32>) -> List<&2, U32>:  Flv.nals.cut(B.Bytes.cut(B.Bytes.be(U32.to_nat(lens), bs, 0),    B.Bytes.drop(U32.to_nat(lens), bs)), acc, k)# A picture's NAL units, each after its size in lens bytes, onto acc in# Annex B form, the last first. Every unit takes at least a byte.def Flv.nals(fuel: Nat, +lens: U32, bs: List<&2, U32>, acc: List<&2, U32>) ->  List<&2, U32>:  match fuel bs:    case 1n+p Con{h, t}:      Flv.nals.at(h <> t, lens, acc, rest => acc2 => Flv.nals(p, lens, rest, acc2))    case _ _:      B.Bytes.rev(acc)# ms since the base, in 90 kHz ticks; a time before the base (audio and# video do not come in strict order) is the base.def Flv.ticks(+ms: U32, +base: U32) -> U32:  Bool.pick(U32, (ms >= base : U32), (U32.sub(ms, base) * 90 : U32), 0)# A 24-bit time offset as a U32 to add: a negative one wraps around.def Flv.cts(a: U32, b: U32, c: U32) -> U32:  +v = U32.or(U32.or(U32.shln(a, 16n), U32.shln(b, 8n)), c)  Bool.pick(U32, (v >= 8388608 : U32), U32.or(v, 4278190080), v)def Flv.picture(+key: Bool, +ts: U32, cts: U32, +body: List<&2, U32>, st: Flv) -> Out:  Flv{+lens, +sets, obj, freq, chan, sound, base, +based, video} = st  +b = Bool.pick(U32, based, base, ts)  Out{[F.Video{Flv.ticks(U32.add(ts, cts), b), key,      B.Bytes.cat(Bool.pick(List<&2, U32>, key, sets, Nil{}),        Flv.nals(U32.to_nat(B.Bytes.len(body)), lens, body, Nil{}))}],    Flv{lens, sets, obj, freq, chan, sound, b, True{}, video}}# H.264's configuration (an AVCDecoderConfigurationRecord).def Flv.config(body: List<&2, U32>, st: Flv) -> Out:  match body:    case Con{_, Con{_, Con{_, Con{_, Con{l, rest}}}}}:      Flv{_, _, obj, freq, chan, sound, base, based, _} = st      Out{Nil{}, Flv{U32.add(U32.and(l, 3), 1), Flv.avcc(rest), obj, freq, chan, sound,        base, based, "H264"}}    case _:      Out{Nil{}, st}# The arrays of an HEVCDecoderConfigurationRecord: each a type byte, a# 16-bit count, and that many parameter sets (VPS, SPS, PPS).def Flv.arrays(n: Nat, bs: List<&2, U32>, acc: List<&2, U32>) -> List<&2, U32>:  match n bs:    case 1n+p Con{_, Con{a, Con{b, t}}}:      Flv.sets(U32.to_nat(U32.or(U32.shln(a, 8n), b)), t, acc, rest => acc2 =>        Flv.arrays(p, rest, acc2))    case _ _:      accdef Flv.hvcc.at(bs: List<&2, U32>, st: Flv) -> Out:  match bs:    case Con{l, Con{n, rest}}:      Flv{_, _, obj, freq, chan, sound, base, based, _} = st      Out{Nil{}, Flv{U32.add(U32.and(l, 3), 1),        B.Bytes.rev(Flv.arrays(U32.to_nat(n), rest, Nil{})), obj, freq, chan, sound, base,        based, "H265"}}    case _:      Out{Nil{}, st}# H.265's configuration: after 21 bytes of profile and level, how many# bytes a size takes, the number of arrays, and the arrays.def Flv.hvcc(body: List<&2, U32>, st: Flv) -> Out:  Flv.hvcc.at(B.Bytes.drop(21n, body), st)def Flv.timed(body: List<&2, U32>, key: Bool, ts: U32, st: Flv) -> Out:  match body:    case Con{c0, Con{c1, Con{c2, rest}}}:      Flv.picture(key, ts, Flv.cts(c0, c1, c2), rest, st)    case _:      Out{Nil{}, st}def Flv.video.kind(avc: Bool, kind: Nat, key: Bool, ts: U32, body: List<&2, U32>,  st: Flv) -> Out:  match avc kind:    case True{} 0n:      Flv.config(B.Bytes.drop(3n, body), st)    case True{} 1n:      Flv.timed(body, key, ts, st)    case _ _:      Out{Nil{}, st}# An enhanced tag of H.265: packet type 0 is the configuration, 1 a# picture after its time offset, 3 a picture with none.def Flv.hevc(hvc1: Bool, kind: Nat, key: Bool, ts: U32, body: List<&2, U32>,  st: Flv) -> Out:  match hvc1 kind:    case True{} 0n:      Flv.hvcc(body, st)    case True{} 1n:      Flv.timed(body, key, ts, st)    case True{} 3n:      Flv.picture(key, ts, 0, body, st)    case _ _:      Out{Nil{}, st}def Flv.video.by(ex: Bool, +b0: U32, rest: List<&2, U32>, ts: U32, st: Flv) -> Out:  match ex rest:    case True{} Con{a, Con{b, Con{c, Con{d, body}}}}:      Flv.hevc(U32.is_eq(a, 104) && U32.is_eq(b, 118) && U32.is_eq(c, 99)        && U32.is_eq(d, 49), U32.to_nat(U32.and(b0, 15)),        U32.is_eq(U32.and(U32.shrn(b0, 4n), 7), 1), ts, body, st)    case False{} Con{kind, body}:      Flv.video.kind(U32.is_eq(U32.and(b0, 15), 7), U32.to_nat(kind),        U32.is_eq(U32.shrn(b0, 4n), 1), ts, body, st)    case _ _:      Out{Nil{}, st}# A video tag: the first byte's top bit says it is an enhanced one.def Flv.video(data: List<&2, U32>, ts: U32, st: Flv) -> Out:  match data:    case Con{+b0, rest}:      Flv.video.by((b0 >= 128 : U32), b0, rest, ts, st)    case Nil{}:      Out{Nil{}, st}def Flv.sound(+ts: U32, +body: List<&2, U32>, st: Flv) -> Out:  Flv{lens, sets, +obj, +freq, +chan, +sound, base, +based, video} = st  +b = Bool.pick(U32, based, base, ts)  Out{Bool.pick(List<&2, F.Frame>, String.eq(sound, "AAC"), [F.Audio{Flv.ticks(ts, b),      B.Bytes.cat(U.Aud.adts(B.Bytes.len(body), obj, freq, chan), body)}], Nil{}),    Flv{lens, sets, obj, freq, chan, sound, b, True{}, video}}# The audio's configuration: 5 bits of object type, 4 of rate index, 4# of channels.def Flv.asc(body: List<&2, U32>, st: Flv) -> Out:  match body:    case Con{+a, Con{+b, _}}:      Flv{lens, sets, _, _, _, _, base, based, video} = st      Out{Nil{}, Flv{lens, sets, U32.shrn(a, 3n),        U32.or(U32.shln(U32.and(a, 7), 1n), U32.shrn(b, 7n)), U32.and(U32.shrn(b, 3n), 15),        "AAC", base, based, video}}    case _:      Out{Nil{}, st}def Flv.aac(data: List<&2, U32>, ts: U32, st: Flv) -> Out:  match data:    case Con{0, body}:      Flv.asc(body, st)    case Con{1, body}:      Flv.sound(ts, body, st)    case _:      Out{Nil{}, st}# G.711: the samples as they are, one channel.def Flv.g711(name: String, +ts: U32, samples: List<&2, U32>, st: Flv) -> Out:  Flv{lens, sets, obj, freq, _, _, base, +based, video} = st  +b = Bool.pick(U32, based, base, ts)  Out{[F.Audio{Flv.ticks(ts, b), samples}],    Flv{lens, sets, obj, freq, 1, name, b, True{}, video}}def Flv.audio.by(format: Nat, rest: List<&2, U32>, ts: U32, st: Flv) -> Out:  match format:    case 10n:      Flv.aac(rest, ts, st)    case 7n:      Flv.g711("PCMA", ts, rest, st)    case 8n:      Flv.g711("PCMU", ts, rest, st)    case _:      Out{Nil{}, st}# An audio tag: the format is the first byte's top 4 bits.def Flv.audio(data: List<&2, U32>, ts: U32, st: Flv) -> Out:  match data:    case Con{b0, rest}:      Flv.audio.by(U32.to_nat(U32.shrn(b0, 4n)), rest, ts, st)    case Nil{}:      Out{Nil{}, st}def Flv.typed(video: Bool, audio: Bool, data: List<&2, U32>, ts: U32, st: Flv) -> Out:  match video audio:    case True{} _:      Flv.video(data, ts, st)    case False{} True{}:      Flv.audio(data, ts, st)    case False{} False{}:      Out{Nil{}, st}# A tag into the state: the frame it holds, if it holds one.def Flv.frames(st: Flv, t: K.Tag) -> Out:  K.Tag{+typ, ts, data} = t  Flv.typed(U32.is_eq(typ, 9), U32.is_eq(typ, 8), data, ts, st)