161 lines
4.2 KiB
Go
161 lines
4.2 KiB
Go
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]...)
|
|
}
|