Files
RemoteDesk/transport/hysteria2-agent/media_ring.go
T
曾志威 19a8e03a83
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
Document all-Rust migration and extend native media stack
2026-08-14 14:31:57 +08:00

157 lines
4.2 KiB
Go

package main
import (
"context"
"errors"
"fmt"
)
// EncodedRingRecord is the cross-process contract produced by the Rust
// Windows agent. PTS is the Media Foundation 100-ns media timeline.
type EncodedRingRecord struct {
Sequence uint64
PTS uint64
Kind uint32
KeyFrame bool
Payload []byte
}
// EncodedRing is intentionally a single-consumer interface. The producer may
// drop a video record when this queue is full; the consumer must never block
// input or audio while polling it.
type EncodedRing interface {
Pop() (*EncodedRingRecord, error)
Close() error
}
// AudioRing is a separate consumer contract even though its mmap slot layout
// matches the video ring. Keeping the interfaces distinct prevents audio from
// accidentally sharing video backpressure or drop policy.
type AudioRing interface {
Pop() (*EncodedRingRecord, error)
Close() error
}
func validateRingRecord(record *EncodedRingRecord) error {
if record == nil || record.Sequence == 0 || len(record.Payload) == 0 || len(record.Payload) > videoFrameMaxBytes {
return errors.New("encoded ring record is invalid")
}
return nil
}
// pumpEncodedRing drains the ring into RDV1 datagrams. The callback is the
// already-authenticated Hysteria2 UDP stream; this function never retries a
// failed video packet.
func pumpEncodedRing(ctx context.Context, ring EncodedRing, streamID uint32, generation uint64, send func([]byte) error) error {
if streamID == 0 || generation == 0 {
return errors.New("encoded ring stream identity is invalid")
}
if ring == nil || send == nil {
return errors.New("encoded ring pump arguments are invalid")
}
ticker := newMediaPollTicker()
defer ticker.Stop()
for {
drained := false
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
record, err := ring.Pop()
if err != nil {
return fmt.Errorf("read encoded media ring: %w", err)
}
if record == nil {
break
}
drained = true
if err := validateRingRecord(record); err != nil {
return err
}
flags := uint16(0)
if record.KeyFrame {
flags |= videoFlagKeyFrame
}
datagrams, err := fragmentVideoAccessUnit(VideoAccessUnit{
StreamID: streamID, Generation: generation, Sequence: record.Sequence,
FrameID: record.Sequence, PTS: record.PTS, DTS: record.PTS,
Flags: flags, Payload: record.Payload,
})
if err != nil {
return fmt.Errorf("fragment encoded media: %w", err)
}
for _, datagram := range datagrams {
if err := send(datagram); err != nil {
return fmt.Errorf("send encoded video datagram: %w", err)
}
}
}
if drained {
continue
}
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
}
}
// pumpAudioRing sends independent Opus packets as RDA1 datagrams. Audio does
// not wait for video and does not use video frame reassembly.
func pumpAudioRing(ctx context.Context, ring AudioRing, streamID uint32, generation uint64, send func([]byte) error) error {
if streamID == 0 || generation == 0 {
return errors.New("audio ring stream identity is invalid")
}
if ring == nil || send == nil {
return errors.New("audio ring pump arguments are invalid")
}
ticker := newMediaPollTicker()
defer ticker.Stop()
for {
drained := false
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
record, err := ring.Pop()
if err != nil {
return fmt.Errorf("read audio media ring: %w", err)
}
if record == nil {
break
}
drained = true
if record.Sequence == 0 || len(record.Payload) == 0 || len(record.Payload) > audioPacketMaxBytes {
return errors.New("audio ring record is invalid")
}
flags := uint16(0)
if record.Kind == 2 {
flags |= audioFlagDiscontinuity
}
datagram, err := encodeAudioDatagram(AudioPacket{
StreamID: streamID, Generation: generation, Sequence: record.Sequence,
PTS: record.PTS, DurationMS: 20, Flags: flags, Payload: record.Payload,
})
if err != nil {
return fmt.Errorf("encode audio datagram: %w", err)
}
if err := send(datagram); err != nil {
return fmt.Errorf("send audio datagram: %w", err)
}
}
if drained {
continue
}
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
}
}