ci / rust (push) Canceled after 0s
ci / web (push) Canceled after 0s
ci / package-preview (push) Canceled after 0s
ci / package-installer (push) Canceled after 0s
ci / linux-agent (push) Canceled after 0s
ci / edge-service (push) Canceled after 0s
ci / coturn-pop (push) Canceled after 0s
ci / package-windows-host (push) Canceled after 0s
106 lines
3.2 KiB
Go
106 lines
3.2 KiB
Go
//go:build !windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
const (
|
|
ringMagic = "RDMRING1"
|
|
ringVersion = uint32(1)
|
|
ringHeaderBytes = 40
|
|
slotHeaderBytes = 24
|
|
maxRingSlots = uint32(256)
|
|
maxRingSlotBytes = uint32(16 * 1024 * 1024)
|
|
)
|
|
|
|
type mappedEncodedRing struct {
|
|
file *os.File
|
|
path string
|
|
data []byte
|
|
slotCount uint32
|
|
slotBytes uint32
|
|
}
|
|
|
|
func openEncodedRing(path string) (EncodedRing, error) {
|
|
file, err := os.OpenFile(path, os.O_RDWR, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open encoded ring: %w", err)
|
|
}
|
|
stat, err := file.Stat()
|
|
if err != nil {
|
|
_ = file.Close()
|
|
return nil, err
|
|
}
|
|
if stat.Size() < ringHeaderBytes {
|
|
_ = file.Close()
|
|
return nil, errors.New("encoded ring is shorter than its header")
|
|
}
|
|
data, err := syscall.Mmap(int(file.Fd()), 0, int(stat.Size()), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
|
|
if err != nil {
|
|
_ = file.Close()
|
|
return nil, fmt.Errorf("map encoded ring: %w", err)
|
|
}
|
|
if string(data[:8]) != ringMagic || binary.LittleEndian.Uint32(data[8:12]) != ringVersion {
|
|
_ = syscall.Munmap(data)
|
|
_ = file.Close()
|
|
return nil, errors.New("encoded ring header is invalid")
|
|
}
|
|
slots, slotBytes := binary.LittleEndian.Uint32(data[12:16]), binary.LittleEndian.Uint32(data[16:20])
|
|
expected := uint64(ringHeaderBytes) + uint64(slots)*uint64(slotHeaderBytes+slotBytes)
|
|
if slots < 2 || slots > maxRingSlots || slots&(slots-1) != 0 || slotBytes == 0 || slotBytes > maxRingSlotBytes || expected != uint64(len(data)) {
|
|
_ = syscall.Munmap(data)
|
|
_ = file.Close()
|
|
return nil, errors.New("encoded ring geometry is invalid")
|
|
}
|
|
return &mappedEncodedRing{file: file, path: path, data: data, slotCount: slots, slotBytes: slotBytes}, nil
|
|
}
|
|
|
|
func openAudioRing(path string) (AudioRing, error) {
|
|
ring, err := openEncodedRing(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ring, nil
|
|
}
|
|
|
|
func (r *mappedEncodedRing) Pop() (*EncodedRingRecord, error) {
|
|
read := atomic.LoadUint64((*uint64)(unsafe.Pointer(&r.data[32])))
|
|
write := atomic.LoadUint64((*uint64)(unsafe.Pointer(&r.data[24])))
|
|
if read == write {
|
|
return nil, nil
|
|
}
|
|
start := ringHeaderBytes + int(read%uint64(r.slotCount))*int(slotHeaderBytes+r.slotBytes)
|
|
length := binary.LittleEndian.Uint32(r.data[start+20 : start+24])
|
|
if length == 0 || length > r.slotBytes {
|
|
return nil, errors.New("encoded ring record length is invalid")
|
|
}
|
|
record := &EncodedRingRecord{Sequence: binary.LittleEndian.Uint64(r.data[start : start+8]), PTS: binary.LittleEndian.Uint64(r.data[start+8 : start+16]), Kind: binary.LittleEndian.Uint32(r.data[start+16 : start+20]), Payload: append([]byte(nil), r.data[start+slotHeaderBytes:start+slotHeaderBytes+int(length)]...)}
|
|
record.KeyFrame = record.Kind == 2
|
|
if record.Sequence == 0 {
|
|
return nil, errors.New("encoded ring record sequence is invalid")
|
|
}
|
|
atomic.StoreUint64((*uint64)(unsafe.Pointer(&r.data[32])), read+1)
|
|
return record, nil
|
|
}
|
|
|
|
func (r *mappedEncodedRing) Close() error {
|
|
err := syscall.Munmap(r.data)
|
|
if r.file != nil {
|
|
if closeErr := r.file.Close(); err == nil {
|
|
err = closeErr
|
|
}
|
|
}
|
|
if removeErr := os.Remove(r.path); err == nil && removeErr != nil && !errors.Is(removeErr, os.ErrNotExist) {
|
|
err = removeErr
|
|
}
|
|
return err
|
|
}
|