Files
VerseVDI-Data-Plane/gateway/apollo_video_fec.go

280 lines
7.6 KiB
Go

package gateway
import (
"bytes"
"encoding/binary"
)
const (
apolloVideoMaximumDataShards = 255
apolloVideoMaximumBlocks = 4
apolloVideoShardPayloadSize = apolloVideoRawPacketSize - apolloRTPHeaderSize - 4 - apolloVideoNVHeaderSize
)
type apolloVideoShard struct {
frame uint32
block uint8
lastBlock uint8
dataPackets int
parity int
index int
sequence uint16
streamIndex uint32
flags byte
payload []byte
}
type apolloVideoFECBlock struct {
dataPackets int
parity int
firstSeq uint16
streamBase uint32
haveBase bool
shards [][]byte
received []bool
count int
complete bool
}
type apolloVideoAssembler struct {
haveFrame bool
frame uint32
lastBlock uint8
blocks [apolloVideoMaximumBlocks]*apolloVideoFECBlock
}
func (a *apolloVideoAssembler) Add(shard apolloVideoShard) ([]byte, error) {
if len(shard.payload) != apolloVideoShardPayloadSize || shard.dataPackets < 1 || shard.dataPackets > apolloVideoMaximumDataShards || shard.parity < 0 || shard.dataPackets+shard.parity > 255 || shard.block > shard.lastBlock || shard.lastBlock >= apolloVideoMaximumBlocks || shard.index >= shard.dataPackets+shard.parity {
return nil, errApolloMedia
}
if !a.haveFrame || apolloFrameNewer(shard.frame, a.frame) {
*a = apolloVideoAssembler{haveFrame: true, frame: shard.frame, lastBlock: shard.lastBlock}
} else if shard.frame != a.frame {
return nil, nil
}
if shard.lastBlock != a.lastBlock {
return nil, errApolloMedia
}
block := a.blocks[shard.block]
if block == nil {
block = &apolloVideoFECBlock{
dataPackets: shard.dataPackets,
parity: shard.parity,
shards: make([][]byte, shard.dataPackets+shard.parity),
received: make([]bool, shard.dataPackets+shard.parity),
}
a.blocks[shard.block] = block
} else if block.dataPackets != shard.dataPackets || block.parity != shard.parity {
return nil, errApolloMedia
}
if shard.index < shard.dataPackets {
if shard.index == 0 && shard.flags&0x04 == 0 || shard.index == shard.dataPackets-1 && shard.flags&0x02 == 0 || shard.flags&^byte(0x07) != 0 {
return nil, errApolloMedia
}
base := shard.sequence - uint16(shard.index)
streamBase := shard.streamIndex - uint32(shard.index)
if !block.haveBase {
block.firstSeq, block.streamBase, block.haveBase = base, streamBase, true
} else if block.firstSeq != base || block.streamBase != streamBase {
return nil, errApolloMedia
}
}
if block.received[shard.index] {
if !bytes.Equal(block.shards[shard.index], shard.payload) {
return nil, errApolloMedia
}
return nil, nil
}
block.shards[shard.index] = append([]byte(nil), shard.payload...)
block.received[shard.index] = true
block.count++
if block.count < block.dataPackets {
return nil, nil
}
if !block.complete {
if err := reconstructApolloVideoBlock(block); err != nil {
return nil, err
}
block.complete = true
}
for index := uint8(0); index <= a.lastBlock; index++ {
if a.blocks[index] == nil || !a.blocks[index].complete {
return nil, nil
}
}
capacity := 0
for blockIndex := uint8(0); blockIndex <= a.lastBlock; blockIndex++ {
capacity += a.blocks[blockIndex].dataPackets * apolloVideoShardPayloadSize
}
frame := make([]byte, 0, capacity)
for blockIndex := uint8(0); blockIndex <= a.lastBlock; blockIndex++ {
for shardIndex := 0; shardIndex < a.blocks[blockIndex].dataPackets; shardIndex++ {
frame = append(frame, a.blocks[blockIndex].shards[shardIndex]...)
}
}
if len(frame) < 8 || frame[0] != 0x01 {
return nil, errApolloMedia
}
lastPayloadLength := int(binary.LittleEndian.Uint16(frame[4:6]))
end := len(frame) - apolloVideoShardPayloadSize + lastPayloadLength
if lastPayloadLength == 0 || lastPayloadLength > apolloVideoShardPayloadSize || end <= 8 || end > len(frame) {
return nil, errApolloMedia
}
*a = apolloVideoAssembler{}
return append([]byte(nil), frame[8:end]...), nil
}
func apolloFrameNewer(first, second uint32) bool {
return int32(first-second) > 0
}
func reconstructApolloVideoBlock(block *apolloVideoFECBlock) error {
if block == nil || block.count < block.dataPackets {
return errApolloMedia
}
missing := false
for index := 0; index < block.dataPackets; index++ {
if !block.received[index] {
missing = true
block.shards[index] = make([]byte, apolloVideoShardPayloadSize)
}
}
if !missing {
return nil
}
selectedRows := make([][]byte, 0, block.dataPackets)
selectedShards := make([][]byte, 0, block.dataPackets)
for index, received := range block.received {
if !received {
continue
}
selectedRows = append(selectedRows, apolloVideoFECRow(index, block.dataPackets, block.parity))
selectedShards = append(selectedShards, block.shards[index])
if len(selectedRows) == block.dataPackets {
break
}
}
if len(selectedRows) != block.dataPackets {
return errApolloMedia
}
inverse, ok := apolloGFInvert(selectedRows)
if !ok {
return errApolloMedia
}
for index := 0; index < block.dataPackets; index++ {
if block.received[index] {
continue
}
for source, coefficient := range inverse[index] {
apolloGFAXPY(block.shards[index], selectedShards[source], coefficient)
}
block.received[index] = true
}
return nil
}
func apolloVideoFECRow(index, dataPackets, parity int) []byte {
row := make([]byte, dataPackets)
if index < dataPackets {
row[index] = 1
return row
}
parityIndex := index - dataPackets
for dataIndex := range row {
row[dataIndex] = apolloGFInverse(byte((parity + dataIndex) ^ parityIndex))
}
return row
}
func apolloGFInvert(matrix [][]byte) ([][]byte, bool) {
size := len(matrix)
if size == 0 {
return nil, false
}
work := make([][]byte, size)
inverse := make([][]byte, size)
for row := range matrix {
if len(matrix[row]) != size {
return nil, false
}
work[row] = append([]byte(nil), matrix[row]...)
inverse[row] = make([]byte, size)
inverse[row][row] = 1
}
for column := 0; column < size; column++ {
pivot := column
for pivot < size && work[pivot][column] == 0 {
pivot++
}
if pivot == size {
return nil, false
}
work[column], work[pivot] = work[pivot], work[column]
inverse[column], inverse[pivot] = inverse[pivot], inverse[column]
factor := apolloGFInverse(work[column][column])
for index := column; index < size; index++ {
work[column][index] = apolloGFMultiply(work[column][index], factor)
}
for index := range inverse[column] {
inverse[column][index] = apolloGFMultiply(inverse[column][index], factor)
}
for row := 0; row < size; row++ {
if row == column || work[row][column] == 0 {
continue
}
factor = work[row][column]
for index := column; index < size; index++ {
work[row][index] ^= apolloGFMultiply(work[column][index], factor)
}
for index := range inverse[row] {
inverse[row][index] ^= apolloGFMultiply(inverse[column][index], factor)
}
}
}
return inverse, true
}
func apolloGFAXPY(destination, source []byte, coefficient byte) {
if coefficient == 0 {
return
}
for index := range destination {
destination[index] ^= apolloGFMultiply(source[index], coefficient)
}
}
func apolloGFInverse(value byte) byte {
if value == 0 {
return 0
}
return apolloGFPow(value, 254)
}
func apolloGFPow(value byte, exponent uint8) byte {
result := byte(1)
for exponent != 0 {
if exponent&1 != 0 {
result = apolloGFMultiply(result, value)
}
value = apolloGFMultiply(value, value)
exponent >>= 1
}
return result
}
func apolloGFMultiply(first, second byte) byte {
var product byte
for second != 0 {
if second&1 != 0 {
product ^= first
}
high := first & 0x80
first <<= 1
if high != 0 {
first ^= 0x1d
}
second >>= 1
}
return product
}