Files
mediamtx/internal/forward/srt/dest.go
T
98ab3009ea support forwarding streams natively (#5558)
It is now possible to define forward destinations for each path configuration. For each destination, the server will create a client that will forward the stream to the intended destination. Supported protocols are RTSP, RTMP, SRT. API and metrics have also been improved to allow monitoring the new forwarding system.

---------

Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com>
2026-08-04 21:57:15 +02:00

136 lines
2.6 KiB
Go

// Package srt contains the SRT forward destination.
package srt
import (
"bufio"
"context"
"fmt"
"sync"
"time"
srtlib "github.com/datarhei/gosrt"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/protocols/mpegts"
"github.com/bluenviron/mediamtx/internal/stream"
)
func maxPayloadSize(v int) int {
return ((v - 16) / 188) * 188
}
// Dest is a SRT forward destination.
type Dest struct {
Stream *stream.Stream
Dest string
WriteTimeout conf.Duration
UDPMaxPayloadSize int
Parent logger.Writer
mutex sync.RWMutex
outboundBytesFunc func() uint64
}
// Log implements logger.Writer.
func (d *Dest) Log(level logger.Level, format string, args ...any) {
d.Parent.Log(level, format, args...)
}
// OutboundBytes returns the number of bytes sent by the destination.
func (d *Dest) OutboundBytes() uint64 {
d.mutex.RLock()
defer d.mutex.RUnlock()
if d.outboundBytesFunc == nil {
return 0
}
return d.outboundBytesFunc()
}
// Run runs the destination.
func (d *Dest) Run(ctx context.Context) error {
srtConf := srtlib.DefaultConfig()
address, err := srtConf.UnmarshalURL(d.Dest)
if err != nil {
return err
}
udpMaxPayloadSize := d.UDPMaxPayloadSize
if udpMaxPayloadSize == 0 {
udpMaxPayloadSize = 1472
}
srtConf.PayloadSize = uint32(maxPayloadSize(udpMaxPayloadSize))
err = srtConf.Validate()
if err != nil {
return err
}
terminate := make(chan struct{})
type runResult struct {
err error
}
errChan := make(chan runResult, 1)
go func() {
errChan <- runResult{err: d.runInner(ctx, address, srtConf, terminate)}
}()
select {
case res := <-errChan:
return res.err
case <-ctx.Done():
close(terminate)
return fmt.Errorf("terminated")
}
}
func (d *Dest) runInner(ctx context.Context, address string, srtConf srtlib.Config, terminate <-chan struct{}) error {
conn, err := srtlib.Dial("srt", address, srtConf)
if err != nil {
select {
case <-ctx.Done():
return nil
default:
}
return err
}
defer conn.Close()
select {
case <-ctx.Done():
return nil
default:
}
d.mutex.Lock()
d.outboundBytesFunc = func() uint64 {
var stats srtlib.Statistics
conn.Stats(&stats)
return stats.Accumulated.ByteSent
}
d.mutex.Unlock()
r := &stream.Reader{Parent: d}
bw := bufio.NewWriterSize(conn, int(srtConf.PayloadSize))
err = mpegts.FromStream(d.Stream.OrigDesc, r, bw, conn, time.Duration(d.WriteTimeout))
if err != nil {
return err
}
d.Stream.AddReader(r)
defer d.Stream.RemoveReader(r)
select {
case readErr := <-r.Error():
return readErr
case <-terminate:
return nil
}
}