Files
VerseVDI-Data-Plane/gateway/apollo_audio_fec.go
T

145 lines
3.8 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, error) {
if codec == nil || len(shard.payload) == 0 || len(shard.payload) > 1408 || len(shard.payload)%16 != 0 {
return nil, errApolloMedia
}
if a.blocks == nil {
a.blocks = make(map[uint16]*apolloAudioFECBlock)
}
base := shard.base
if base&3 != 0 {
return nil, errApolloMedia
}
block := a.blocks[base]
if block == nil {
if len(a.blocks) >= apolloAudioMaximumBlocks {
return nil, errApolloMedia
}
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, errApolloMedia
}
index := 0
if shard.parity {
if shard.parityIndex >= apolloAudioParityShards {
return nil, errApolloMedia
}
index = apolloAudioDataShards + int(shard.parityIndex)
if block.haveFEC && (block.timestamp != shard.timestamp || block.ssrc != shard.ssrc) {
return nil, errApolloMedia
}
block.timestamp, block.ssrc, block.haveFEC = shard.timestamp, shard.ssrc, true
} else {
index = int(uint16(shard.sequence - base))
if index >= apolloAudioDataShards {
return nil, errApolloMedia
}
if block.haveFEC && (shard.timestamp != block.timestamp+uint32(index*5) || shard.ssrc != block.ssrc) {
return nil, errApolloMedia
}
}
if block.received[index] {
return nil, errApolloMedia
}
block.shards[index] = append([]byte(nil), shard.payload...)
block.received[index] = true
block.count++
if block.count < apolloAudioDataShards {
return nil, nil
}
if err := reconstructApolloAudioBlock(block); err != nil {
return nil, err
}
output := make([][]byte, apolloAudioDataShards)
for index := range output {
payload, err := codec.openApolloAudioCipher(base+uint16(index), block.shards[index])
if err != nil {
return nil, err
}
output[index] = payload
}
delete(a.blocks, base)
return output, 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]...)
}