Files
Piik/internal/client/mediaedge/relay_derivation.go
T
AAA c9a6642ac1 feat(media): share Browser encoding and complete local diagnostics
* fix: allow Linux interface discovery in embedded media service

* feat: expand local diagnostic reports

* research: validate independent Browser encoding producers

* feat: prototype Browser pool ownership and record remaining media gates

* feat: refine Browser shared encoders and native output reuse

* Refine carrier costs and simplify release entry points

* Close media phase and prepare piik design handoff
2026-09-09 20:59:44 +08:00

415 lines
11 KiB
Go

package mediaedge
import (
"context"
"errors"
"io"
"sync"
"time"
"github.com/TNTcraftHIM/Screener/internal/client/nativecapture"
"github.com/TNTcraftHIM/Screener/internal/media/encoded"
"github.com/TNTcraftHIM/Screener/internal/media/forwarding"
"github.com/livekit/mediatransportutil/pkg/utils"
"github.com/pion/rtp"
)
const relayPacketLimit = encoded.MaxPacketWindow
type RelayOptions struct {
CaptureProcess string
Capabilities nativecapture.Capabilities
}
func relayBackend(codec string, options *RelayOptions) (nativecapture.EncodedVideoOptions, bool) {
backend := nativecapture.EncodedVideoOptions{Codec: codec}
if options == nil || options.CaptureProcess == "" {
return backend, false
}
for _, adapter := range options.Capabilities.Adapters {
if codec == "vp8" && options.Capabilities.SoftwareVP8 {
backend.AdapterIndex = adapter.Index
return backend, true
}
if codec == "h264" && len(adapter.HardwareH264) > 0 {
backend.AdapterIndex, backend.EncoderIndex = adapter.Index, adapter.HardwareH264[0].Index
return backend, true
}
}
return backend, false
}
// relayDerivation owns only the optional missing lower representation. Raw RTP
// never waits for it. A retired process must exit before another one can start.
type relayDerivation struct {
source *Source
options RelayOptions
mu sync.Mutex
run *relayRun
}
type relayPlan struct {
profile *nativecapture.VideoProfile
output nativecapture.OutputProfile
format uint64
controls OutputPlan
wanted bool
}
type relayRun struct {
owner *relayDerivation
plan relayPlan
ctx context.Context
cancel context.CancelCauseFunc
done chan struct{}
packets chan *rtp.Packet
anchorRTP uint32
anchorTime time.Time
}
// Called with writeMu held. Declared ceilings are not measured input quality.
func (source *Source) configureRelayFormats(reset bool) error {
source.mu.Lock()
defer source.mu.Unlock()
if source.closed {
return io.ErrClosedPipe
}
if source.relayProfile == nil {
return nil
}
base := len(source.formats) - 1
format := source.formats[base].Load()
width, height := uint32(format>>32), uint32(format)
outputs := nativecapture.ScreenShareOutputs(*source.relayProfile)
lower := outputs[0]
if width > 0 && height > 0 {
lower.Width, lower.Height = min(width/4*2, lower.Width), min(height/4*2, lower.Height)
}
if len(source.outputBitrates) != 2+source.capacity {
source.outputBitrates = make([]uint32, 2+source.capacity)
reset = true
}
for slot := range source.outputBitrates {
if reset || source.outputBitrates[slot] != 0 {
source.outputBitrates[slot] = lower.Bitrate
}
}
source.outputBitrates[1] = source.relayProfile.Bitrate
formats := []forwarding.LayerFormat{
{Width: lower.Width, Height: lower.Height, Bitrate: lower.Bitrate},
{Width: width, Height: height, Bitrate: source.relayProfile.Bitrate},
}
if err := source.media.UpdateFormats(formats); err != nil {
return err
}
for slot, group := range source.groups {
if slot != 0 {
if err := group.media.UpdateFormats(formats); err != nil {
return err
}
}
}
return nil
}
func (source *Source) relayPlan() relayPlan {
source.writeMu.Lock()
defer source.writeMu.Unlock()
source.mu.Lock()
plan := relayPlan{}
if source.closed || source.relayProfile == nil || len(source.outputBitrates) == 0 {
source.mu.Unlock()
return plan
}
base := len(source.formats) - 1
plan.profile, plan.format = source.relayProfile, source.formats[base].Load()
edges := make([]*Edge, 0, len(source.edges))
for edge := range source.edges {
edges = append(edges, edge)
}
source.mu.Unlock()
width, height := uint32(plan.format>>32), uint32(plan.format)
plan.output = nativecapture.ScreenShareOutputs(*plan.profile)[0]
plan.output.Width = min(width/4*2, plan.output.Width)
plan.output.Height = min(height/4*2, plan.output.Height)
if !plan.output.Valid() || width > 2560 || height > 1440 {
return plan
}
demands := source.collectDemands(edges, nil)
for _, demand := range demands {
plan.wanted = plan.wanted || demand.active && demand.lower
}
plan.controls, _ = source.planGroups(demands)
return plan
}
func (relay *relayDerivation) push(packet *rtp.Packet) {
relay.mu.Lock()
requestOriginal := false
defer func() {
relay.mu.Unlock()
if requestOriginal {
relay.source.RequestRecoveryFrame()
}
}()
if relay.run != nil {
select {
case <-relay.run.done:
relay.run = nil
default:
}
}
if packet.Marker {
plan := relay.source.relayPlan()
if !plan.wanted && relay.source.groupRecovery.Load()&2 != 0 {
relay.source.groupRecovery.And(^uint32(2))
requestOriginal = true
}
if relay.run != nil && (!plan.wanted || plan.profile != relay.run.plan.profile || plan.format != relay.run.plan.format) {
relay.run.cancel(nil)
return
}
if relay.run == nil && plan.wanted {
at, available := relay.source.media.ReferenceTime(len(relay.source.formats)-1, packet.Timestamp)
if !available {
return
}
ctx, cancel := context.WithCancelCause(relay.source.engine.ctx)
relay.run = &relayRun{
owner: relay, plan: plan, ctx: ctx, cancel: cancel,
done: make(chan struct{}), packets: make(chan *rtp.Packet, relayPacketLimit),
anchorRTP: packet.Timestamp, anchorTime: at,
}
go relay.run.serve()
// Earlier packets of this frame used the raw path only. Begin assembly
// with the next complete frame, not this frame's trailing fragment.
return
}
}
if relay.run == nil {
return
}
select {
case relay.run.packets <- packet.Clone():
case <-relay.run.ctx.Done():
default:
// Discarding a compressed reference frame silently poisons the decoder.
// Retire this derived output; the existing edge owner handles its failure.
relay.run.cancel(errors.New("native relay decoder input is full"))
}
}
func (relay *relayDerivation) cancel() {
relay.mu.Lock()
if relay.run != nil {
relay.run.cancel(nil)
}
relay.mu.Unlock()
}
func (relay *relayDerivation) close() {
relay.mu.Lock()
run := relay.run
if run != nil {
run.cancel(nil)
}
relay.mu.Unlock()
if run != nil {
<-run.done
}
}
func (run *relayRun) serve() {
defer close(run.done)
defer func() {
if cause := context.Cause(run.ctx); cause != nil && !errors.Is(cause, context.Canceled) {
for slot := 0; slot < 2+run.owner.source.capacity; slot++ {
if slot == 1 {
continue
}
retire, _ := run.owner.source.markLayerUnavailable(slot, run)
if retire != nil {
retire()
}
}
}
}()
backend, _ := relayBackend(run.owner.source.codec, &run.owner.options)
backend.Preference = run.plan.profile.Preference
backend.Outputs = make([]nativecapture.OutputProfile, run.owner.source.capacity+1)
for slot := range backend.Outputs {
backend.Outputs[slot] = run.plan.output
}
stream, err := nativecapture.StartEncodedVideo(run.ctx, run.owner.options.CaptureProcess, backend)
if err != nil {
run.cancel(err)
return
}
run.owner.source.SetLowestLayerRateControlled(true)
defer run.owner.source.SetLowestLayerRateControlled(false)
run.owner.source.BeginGeneration()
readDone := make(chan struct{})
go func() {
defer close(readDone)
run.read(stream)
}()
run.owner.source.media.SendPLI(int32(len(run.owner.source.formats)-1), false)
if err = run.write(stream); err != nil {
run.cancel(err)
}
_ = stream.Close()
<-readDone
}
func (run *relayRun) write(stream *nativecapture.Stream) error {
waitingRecovery := true
input := relayVideoInput{codec: run.owner.source.codec, onPacketDropped: func() {
waitingRecovery = true
run.owner.source.media.SendPLI(int32(len(run.owner.source.formats)-1), false)
}}
clock := utils.NewWrapAround[uint32, uint64](utils.WrapAroundParams{})
clock.Update(run.anchorRTP)
for {
select {
case <-run.ctx.Done():
return context.Cause(run.ctx)
case packet := <-run.packets:
if err := input.push(packet); err != nil {
return err
}
for {
sample, timestamp := input.pop()
if sample == nil {
break
}
frame, err := input.frame(sample.Data)
if err != nil {
return err
}
if waitingRecovery && !frame.KeyFrame {
continue
}
if frame.Width == 0 || frame.Height == 0 {
continue
}
waitingRecovery = false
stamp := clock.Update(timestamp)
if stamp.IsUnhandled || stamp.ExtendedVal < clock.GetExtendedStart() {
continue
}
ticks := stamp.ExtendedVal - clock.GetExtendedStart()
frame.Timestamp = time.Duration(ticks/videoClockRate)*time.Second + time.Duration(ticks%videoClockRate)*time.Second/videoClockRate
frame.Duration = sample.Duration
if frame.Duration <= 0 {
frame.Duration = time.Second / time.Duration(run.plan.profile.Framerate)
}
plan := run.owner.source.relayPlan()
if !plan.wanted || plan.profile != run.plan.profile || plan.format != run.plan.format {
return context.Canceled
}
if err = stream.WriteFrame(frame); err != nil {
return err
}
}
}
}
}
func (run *relayRun) read(stream *nativecapture.Stream) {
for {
frame, err := stream.Read()
if err != nil {
run.cancel(err)
return
}
if run.ctx.Err() != nil {
return
}
source := run.owner.source
switch frame.Kind {
case nativecapture.FrameStatus:
continue
case nativecapture.FrameBegin:
var plan OutputPlan
plan, err = source.beginFrame(frame.Timestamp, run.anchorTime.Add(frame.Timestamp))
if err == nil {
for slot := range stream.Outputs() {
physical := slot
if slot > 0 {
physical++
}
if plan.Bitrates[physical] > 0 {
err = stream.SetOutputBitrate(slot, plan.Bitrates[physical])
}
if err == nil {
err = stream.SetOutputActive(slot, plan.Active[physical])
}
if err != nil {
break
}
}
}
for _, physical := range plan.RecoveryLayers {
if physical == 1 {
source.RequestRecoveryFrame()
continue
}
slot := physical
if slot > 1 {
slot--
}
if err == nil {
err = stream.RequestKeyFrame(slot)
}
}
case nativecapture.FrameH264, nativecapture.FrameVP8:
if frame.Layer > source.capacity || (frame.Kind == nativecapture.FrameH264) != (source.codec == "h264") {
err = errors.New("native relay output does not match its source")
break
}
physical := frame.Layer
if physical > 0 {
physical++
}
err = run.acceptFrame(physical, frame)
case nativecapture.FrameLayerUnavailable:
physical := frame.Layer
if physical > 0 {
physical++
}
retire, failure := source.markLayerUnavailable(physical, run)
err = failure
if retire != nil {
retire()
}
default:
err = errors.New("native relay output failed")
}
if err != nil {
run.cancel(err)
return
}
}
}
func (run *relayRun) acceptFrame(slot int, frame nativecapture.Frame) error {
run.owner.mu.Lock()
defer run.owner.mu.Unlock()
source := run.owner.source
source.writeMu.Lock()
defer source.writeMu.Unlock()
source.mu.Lock()
current := run.owner.run == run && run.ctx.Err() == nil && !source.closed &&
source.relayProfile == run.plan.profile && source.formats[len(source.formats)-1].Load() == run.plan.format
source.mu.Unlock()
if !current {
return context.Canceled
}
if err := source.setFormat(slot, frame.Width, frame.Height); err != nil {
return err
}
return source.writeVideo(slot, encoded.Frame{
Data: frame.Data, PTS: frame.Timestamp, Duration: frame.Duration, Recovery: frame.KeyFrame,
})
}