package gateway const ( apolloAudioDataShards = 4 apolloAudioParityShards = 2 apolloAudioTotalShards = apolloAudioDataShards + apolloAudioParityShards apolloAudioMaximumBlocks = 4 ) type apolloAudioFECBlock struct { base uint16 timestamp uint32 ssrc uint32 haveFEC bool size int shards [apolloAudioTotalShards][]byte received [apolloAudioTotalShards]bool count int } type apolloAudioAssembler struct { blocks map[uint16]*apolloAudioFECBlock } func (a *apolloAudioAssembler) Add(codec *apolloMediaCodec, shard apolloAudioShard) ([][]byte, bool, error) { if codec == nil || len(shard.payload) == 0 || len(shard.payload) > 1408 || len(shard.payload)%16 != 0 { return nil, false, errApolloMedia } if a.blocks == nil { a.blocks = make(map[uint16]*apolloAudioFECBlock) } base := shard.base if base&3 != 0 { return nil, false, errApolloMedia } index := 0 if shard.parity { if shard.parityIndex >= apolloAudioParityShards { return nil, false, errApolloMedia } index = apolloAudioDataShards + int(shard.parityIndex) } else { index = int(uint16(shard.sequence - base)) if index >= apolloAudioDataShards { return nil, false, errApolloMedia } } evicted := false block := a.blocks[base] if block == nil { if len(a.blocks) >= apolloAudioMaximumBlocks { var oldest uint16 var maximumAge uint16 for candidate := range a.blocks { age := base - candidate if age > maximumAge && age < 1<<15 { oldest, maximumAge = candidate, age } } if maximumAge == 0 { return nil, false, errApolloMedia } delete(a.blocks, oldest) evicted = true } block = &apolloAudioFECBlock{base: base} a.blocks[base] = block } if block.size == 0 { block.size = len(shard.payload) } else if block.size != len(shard.payload) { return nil, evicted, errApolloMedia } if shard.parity { if block.haveFEC && (block.timestamp != shard.timestamp || block.ssrc != shard.ssrc) { return nil, evicted, errApolloMedia } block.timestamp, block.ssrc, block.haveFEC = shard.timestamp, shard.ssrc, true } else { if block.haveFEC && (shard.timestamp != block.timestamp+uint32(index*5) || shard.ssrc != block.ssrc) { return nil, evicted, errApolloMedia } } if block.received[index] { return nil, evicted, errApolloMedia } block.shards[index] = append([]byte(nil), shard.payload...) block.received[index] = true block.count++ if block.count < apolloAudioDataShards { return nil, evicted, nil } if err := reconstructApolloAudioBlock(block); err != nil { return nil, evicted, err } output := make([][]byte, apolloAudioDataShards) for index := range output { payload, err := codec.openApolloAudioCipher(base+uint16(index), block.shards[index]) if err != nil { return nil, evicted, err } output[index] = payload } delete(a.blocks, base) return output, evicted, nil } func reconstructApolloAudioBlock(block *apolloAudioFECBlock) error { if block == nil || block.count < apolloAudioDataShards || block.size == 0 { return errApolloMedia } missing := false for index := 0; index < apolloAudioDataShards; index++ { if !block.received[index] { missing = true block.shards[index] = make([]byte, block.size) } } if !missing { return nil } if !block.haveFEC { return errApolloMedia } rows := make([][]byte, 0, apolloAudioDataShards) shards := make([][]byte, 0, apolloAudioDataShards) for index, received := range block.received { if !received { continue } rows = append(rows, apolloAudioFECRow(index)) shards = append(shards, block.shards[index]) if len(rows) == apolloAudioDataShards { break } } inverse, ok := apolloGFInvert(rows) if !ok { return errApolloMedia } for index := 0; index < apolloAudioDataShards; index++ { if block.received[index] { continue } for source, coefficient := range inverse[index] { apolloGFAXPY(block.shards[index], shards[source], coefficient) } } return nil } func apolloAudioFECRow(index int) []byte { if index < apolloAudioDataShards { row := make([]byte, apolloAudioDataShards) row[index] = 1 return row } parity := [8]byte{0x77, 0x40, 0x38, 0x0e, 0xc7, 0xa7, 0x0d, 0x6c} return append([]byte(nil), parity[(index-apolloAudioDataShards)*apolloAudioDataShards:(index-apolloAudioDataShards+1)*apolloAudioDataShards]...) }