import debug from "debug" ; import { range } from "lodash" ; import { BitWriter2 , bufferReader , bufferWriter , getBit , } from "../../../../common/src" ; import { RtcpHeader } from "../header" ; const log = debug ( "werift/rtp/rtcp/rtpfb/twcc" ) ; export class TransportWideCC { static count = 15 ; count = TransportWideCC . count ; length = 2 ; senderSsrc ! : number ; mediaSourceSsrc ! : number ; baseSequenceNumber ! : number ; packetStatusCount ! : number ; /** 24bit multiples of 64ms */ referenceTime ! : number ; fbPktCount ! : number ; packetChunks : ( RunLengthChunk | StatusVectorChunk ) [ ] = [ ] ; recvDeltas : RecvDelta [ ] = [ ] ; header ! : RtcpHeader ; constructor ( props = { } ) { Object . assign ( this , props ) ; if ( ! this . header ) { this . header = new RtcpHeader ( { type : 205 , count : this . count , version : 2 , } ) ; } } static deSerialize ( data , header ) { const [ senderSsrc , mediaSsrc , baseSequenceNumber , packetStatusCount , referenceTime , fbPktCount , ] = bufferReader ( data , [ 4 , 4 , 2 , 2 , 3 , 1 ] ) ; const packetChunks = [ ] ; const recvDeltas = [ ] ; let packetStatusPos = 16 ; for ( let processedPacketNum = 0 ; processedPacketNum < packetStatusCount ; ) { const type = getBit ( data . slice ( packetStatusPos , packetStatusPos + 1 ) [ 0 ] , 0 , 1 ) ; let iPacketStatus ; switch ( type ) { case PacketChunk . TypeTCCRunLengthChunk : { const packetStatus = RunLengthChunk . deSerialize ( data . slice ( packetStatusPos , packetStatusPos + 2 ) ) ; iPacketStatus = packetStatus ; const packetNumberToProcess = Math . min ( packetStatusCount - processedPacketNum , packetStatus . runLength ) ; if ( packetStatus . packetStatus === PacketStatus . TypeTCCPacketReceivedSmallDelta || packetStatus . packetStatus === PacketStatus . TypeTCCPacketReceivedLargeDelta ) { range ( packetNumberToProcess ) . forEach ( ( ) => { recvDeltas . push ( new RecvDelta ( { type : packetStatus . packetStatus as any } ) ) ; } ) ; } processedPacketNum += packetNumberToProcess ; } break ; case PacketChunk . TypeTCCStatusVectorChunk : { const packetStatus = StatusVectorChunk . deSerialize ( data . slice ( packetStatusPos , packetStatusPos + 2 ) ) ; iPacketStatus = packetStatus ; if ( packetStatus . symbolSize === 0 ) { packetStatus . symbolList . forEach ( ( v ) => { if ( v === PacketStatus . TypeTCCPacketReceivedSmallDelta ) { recvDeltas . push ( new RecvDelta ( { type : PacketStatus . TypeTCCPacketReceivedSmallDelta , } ) ) ; } } ) ; } if ( packetStatus . symbolSize === 1 ) { packetStatus . symbolList . forEach ( ( v ) => { if ( v === PacketStatus . TypeTCCPacketReceivedSmallDelta || v === PacketStatus . TypeTCCPacketReceivedLargeDelta ) { recvDeltas . push ( new RecvDelta ( { type : v , } ) ) ; } } ) ; } processedPacketNum += packetStatus . symbolList . length ; } break ; } if ( ! iPacketStatus ) throw new Error ( ) ; packetStatusPos += 2 ; packetChunks . push ( iPacketStatus ) ; } let recvDeltaPos = packetStatusPos ; recvDeltas . forEach ( ( delta ) => { if ( delta . type === PacketStatus . TypeTCCPacketReceivedSmallDelta ) { delta . deSerialize ( data . slice ( recvDeltaPos , recvDeltaPos + 1 ) ) ; recvDeltaPos ++ ; } if ( delta . type === PacketStatus . TypeTCCPacketReceivedLargeDelta ) { delta . deSerialize ( data . slice ( recvDeltaPos , recvDeltaPos + 2 ) ) ; recvDeltaPos += 2 ; } } ) ; return new TransportWideCC ( { senderSsrc , mediaSourceSsrc : mediaSsrc , baseSequenceNumber , packetStatusCount , referenceTime , fbPktCount , recvDeltas , packetChunks , header , } ) ; } serialize ( ) { const constBuf = bufferWriter ( [ 4 , 4 , 2 , 2 , 3 , 1 ] , [ this . senderSsrc , this . mediaSourceSsrc , this . baseSequenceNumber , this . packetStatusCount , this . referenceTime , this . fbPktCount , ] ) ; const chunks = Buffer . concat ( this . packetChunks . map ( ( chunk ) => chunk . serialize ( ) ) ) ; const deltas = Buffer . concat ( this . recvDeltas . map ( ( delta ) => { try { return delta . serialize ( ) ; } catch ( error ) { log ( error ?. message ) ; return undefined ; } } ) . filter ( ( v ) => v ) as Buffer [ ] ) ; const buf = Buffer . concat ( [ constBuf , chunks , deltas ] ) ; if ( this . header . padding && buf . length % 4 !== 0 ) { const rest = 4 - ( buf . length % 4 ) ; const padding = Buffer . alloc ( rest ) ; padding [ padding . length - 1 ] = padding . length ; this . header . length = Math . floor ( ( buf . length + padding . length ) / 4 ) ; return Buffer . concat ( [ this . header . serialize ( ) , buf , padding ] ) ; } this . header . length = Math . floor ( buf . length / 4 ) ; return Buffer . concat ( [ this . header . serialize ( ) , buf ] ) ; } get packetResults ( ) : PacketResult [ ] { const currentSequenceNumber = this . baseSequenceNumber - 1 ; const results = this . packetChunks . filter ( ( v ) => v instanceof RunLengthChunk ) . map ( ( chunk ) => ( chunk as RunLengthChunk ) . results ( currentSequenceNumber ) ) . flatMap ( ( v ) => v ) ; let deltaIdx = 0 ; const referenceTime = BigInt ( this . referenceTime ) * 64n ; let currentReceivedAtMs = referenceTime ; for ( const result of results ) { const recvDelta = this . recvDeltas [ deltaIdx ] ; if ( ! result . received || ! recvDelta ) { continue ; } currentReceivedAtMs += BigInt ( recvDelta . delta ) / 1000n ; result . delta = recvDelta . delta ; result . receivedAtMs = Number ( currentReceivedAtMs ) ; deltaIdx ++ ; } return results ; } } export class RunLengthChunk { type ! : PacketChunk . TypeTCCRunLengthChunk ; packetStatus ! : PacketStatus ; /** 13bit */ runLength ! : number ; constructor ( props = { } ) { Object . assign ( this , props ) ; this . type = PacketChunk . TypeTCCRunLengthChunk ; } static deSerialize ( data ) { const packetStatus = getBit ( data [ 0 ] , 1 , 2 ) ; const runLength = ( getBit ( data [ 0 ] , 3 , 5 ) << 8 ) + data [ 1 ] ; return new RunLengthChunk ( { type : 0 , packetStatus , runLength } ) ; } serialize ( ) { const buf = new BitWriter2 ( 16 ) . set ( 0 ) . set ( this . packetStatus , 2 ) . set ( this . runLength , 13 ) . buffer ; return buf ; } results ( currentSequenceNumber ) { const received = this . packetStatus === PacketStatus . TypeTCCPacketReceivedSmallDelta || this . packetStatus === PacketStatus . TypeTCCPacketReceivedLargeDelta ; const results = [ ] ; for ( let i = 0 ; i <= this . runLength ; ++ i ) { results . push ( new PacketResult ( { sequenceNumber : ++ currentSequenceNumber , received } ) ) ; } return results ; } } export class StatusVectorChunk { type ! : number ; symbolSize ! : number ; symbolList : number [ ] = [ ] ; constructor ( props = { } ) { Object . assign ( this , props ) ; } static deSerialize ( data ) { const type = PacketChunk . TypeTCCStatusVectorChunk ; let symbolSize = getBit ( data [ 0 ] , 1 , 1 ) ; const symbolList = [ ] ; switch ( symbolSize ) { case 0 : range ( 6 ) . forEach ( ( _ , i ) => symbolList . push ( getBit ( data [ 0 ] , 2 + i , 1 ) ) ) ; range ( 8 ) . forEach ( ( _ , i ) => symbolList . push ( getBit ( data [ 1 ] , i , 1 ) ) ) ; break ; case 1 : range ( 3 ) . forEach ( ( i ) => symbolList . push ( getBit ( data [ 0 ] , 2 + i * 2 , 2 ) ) ) ; range ( 4 ) . forEach ( ( i ) => symbolList . push ( getBit ( data [ 1 ] , i * 2 , 2 ) ) ) ; break ; default : symbolSize = ( getBit ( data [ 0 ] , 2 , 6 ) << 8 ) + data [ 1 ] ; } return new StatusVectorChunk ( { type , symbolSize , symbolList } ) ; } serialize ( ) { const buf = Buffer . alloc ( 2 ) ; const writer = new BitWriter2 ( 16 ) . set ( 1 ) . set ( this . symbolSize ) ; const bits = this . symbolSize === 0 ? 1 : 2 ; this . symbolList . forEach ( ( v ) => { writer . set ( v , bits ) ; } ) ; buf . writeUInt16BE ( writer . value ) ; return buf ; } } export class RecvDelta { /**optional (If undefined, it will be set automatically.)*/ type ? : PacketStatus . TypeTCCPacketReceivedSmallDelta | PacketStatus . TypeTCCPacketReceivedLargeDelta ; /**micro sec */ delta ! : number ; constructor ( props = { } ) { Object . assign ( this , props ) ; } static deSerialize ( data ) { let type ; let delta ; if ( data . length === 1 ) { type = PacketStatus . TypeTCCPacketReceivedSmallDelta ; delta = 250 * data [ 0 ] ; } else if ( data . length === 2 ) { type = PacketStatus . TypeTCCPacketReceivedLargeDelta ; delta = 250 * data . readInt16BE ( ) ; } if ( type === undefined || delta === undefined ) throw new Error ( ) ; return new RecvDelta ( { type , delta } ) ; } deSerialize ( data ) { const res = RecvDelta . deSerialize ( data ) ; this . delta = res . delta ; } parsed = false ; parseDelta ( ) { this . delta = Math . floor ( this . delta / 250 ) ; if ( this . delta < 0 || this . delta > 255 ) { if ( this . delta > 32767 ) this . delta = 32767 ; if ( this . delta < - 32768 ) this . delta = - 32768 ; if ( ! this . type ) this . type = PacketStatus . TypeTCCPacketReceivedLargeDelta ; } else { if ( ! this . type ) this . type = PacketStatus . TypeTCCPacketReceivedSmallDelta ; } this . parsed = true ; } serialize ( ) { if ( ! this . parsed ) this . parseDelta ( ) ; if ( this . type === PacketStatus . TypeTCCPacketReceivedSmallDelta ) { const buf = Buffer . alloc ( 1 ) ; buf . writeUInt8 ( this . delta ) ; return buf ; } else if ( this . type === PacketStatus . TypeTCCPacketReceivedLargeDelta ) { const buf = Buffer . alloc ( 2 ) ; buf . writeInt16BE ( this . delta ) ; return buf ; } throw new Error ( "errDeltaExceedLimit " + this . delta + " " + this . type ) ; } } export enum PacketChunk { TypeTCCRunLengthChunk , TypeTCCStatusVectorChunk , packetStatusChunkLength } export enum PacketStatus { TypeTCCPacketNotReceived , TypeTCCPacketReceivedSmallDelta , TypeTCCPacketReceivedLargeDelta , TypeTCCPacketReceivedWithoutDelta } export class PacketResult { sequenceNumber = 0 ; delta = 0 ; received = false ; receivedAtMs = 0 ; constructor ( props ) { Object . assign ( this , props ) ; } }