This commit is contained in:
Soph :3 2025-11-24 20:56:27 +02:00
parent 7eec0f88e4
commit 403dfc5092
5 changed files with 179 additions and 262 deletions

View file

@ -2,140 +2,121 @@ package rtsp
import (
"context"
"fmt"
"log"
"sync"
"strconv"
"github.com/bluenviron/gortsplib/v5"
"github.com/bluenviron/gortsplib/v5/pkg/base"
"github.com/bluenviron/gortsplib/v5/pkg/description"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/pion/rtp"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/jonas-koeritz/actioncam/libipcamera"
)
type Server struct {
type Handler struct {
server *gortsplib.Server
stream *gortsplib.ServerStream
mu sync.RWMutex
}
func (h *Handler) OnDescribe(_ *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
h.mu.RLock()
defer h.mu.RUnlock()
return &base.Response{StatusCode: base.StatusOK}, h.stream, nil
}
func (h *Handler) OnSetup(_ *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
h.mu.RLock()
defer h.mu.RUnlock()
return &base.Response{StatusCode: base.StatusOK}, h.stream, nil
}
func (h *Handler) OnPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
return &base.Response{StatusCode: base.StatusOK}, nil
}
type Server struct {
server *gortsplib.Server
stream *gortsplib.ServerStream
media *description.Media
gs *gortsplib.Server
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
}
func CreateServer(parentCtx context.Context, host string, port int, relay *libipcamera.RTPRelay) (*Server, error) {
ctx, cancel := context.WithCancel(parentCtx)
func CreateServer(ctx context.Context, host string, port int, relay *libipcamera.RTPRelay) *Server {
cctx, cancel := context.WithCancel(ctx)
// Create the RTSP server instance first
h := &handler{}
gs := &gortsplib.Server{
Handler: h,
UDPRTPAddress: ":8000",
UDPRTCPAddress: ":8001",
RTSPAddress: fmt.Sprintf("%s:%d", host, port),
}
h := &Handler{}
srv := &Server{cancel: cancel}
// Now create the H264 stream bound to this server
stream, media, err := newH264Stream(gs)
if err != nil {
cancel()
return nil, fmt.Errorf("create H264 stream: %w", err)
}
// Final server struct assembly
srv := &Server{
ctx: ctx,
cancel: cancel,
gs: gs,
stream: stream,
media: media,
}
h.srv = srv // link handler to server instance
// Start pumping RTP frames from relay into RTSP
srv.wg.Add(1)
go srv.relayPump(relay)
// Start the RTSP server
go func() {
log.Printf("RTSP server ready on rtsp://%s:%d/", host, port)
if err := gs.StartAndWait(); err != nil {
log.Printf("RTSP stopped: %v", err)
}
cancel()
}()
return srv, nil
}
func (s *Server) Close() {
s.cancel()
if s.gs != nil {
s.gs.Close()
h.server = &gortsplib.Server{
Handler: h,
RTSPAddress: host + ":" + strconv.Itoa(port),
UDPRTPAddress: ":8000",
UDPRTCPAddress: ":8001",
}
s.wg.Wait()
if s.stream != nil {
s.stream.Close()
}
}
// Relay pump: consumes RTP from channel
func (s *Server) relayPump(relay *libipcamera.RTPRelay) {
defer s.wg.Done()
for {
select {
case <-s.ctx.Done():
return
case frame, ok := <-relay.Output:
if !ok {
if err := h.server.Start(); err != nil {
panic(err)
}
h.mu.Lock()
desc := &description.Session{
Medias: []*description.Media{{
Type: description.MediaTypeVideo,
Formats: []format.Format{&format.H264{
PayloadTyp: 96,
PacketizationMode: 1,
}},
}},
}
h.stream = &gortsplib.ServerStream{Server: h.server, Desc: desc}
if err := h.stream.Initialize(); err != nil {
panic(err)
}
srv.server = h.server
srv.stream = h.stream
h.mu.Unlock()
// Pump frames -> RTP
srv.wg.Add(1)
go func() {
defer srv.wg.Done()
var seq uint16
var ts uint32
for {
select {
case <-cctx.Done():
return
case frame, ok := <-relay.Frames:
if !ok {
return
}
pkt := &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: seq,
Timestamp: ts,
},
Payload: frame.Data,
}
seq++
ts += 3600
h.stream.WritePacketRTP(h.stream.Desc.Medias[0], pkt)
}
// Build RTP packet
var pkt rtp.Packet
if err := pkt.Unmarshal(frame.Payload); err != nil {
continue
}
s.stream.WritePacketRTP(s.media, &pkt)
}
}
}()
log.Printf("RTSP server ready at rtsp://%s:%d/", host, port)
return srv
}
// Handler below…
type handler struct{ srv *Server }
func (h *handler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
return &base.Response{StatusCode: base.StatusOK}, h.srv.stream, nil
}
func (h *handler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
return &base.Response{StatusCode: base.StatusOK}, h.srv.stream, nil
}
func (h *handler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
return &base.Response{StatusCode: base.StatusOK}, nil
}
// Build simple baseline H264 SDP
func newH264Stream(gs *gortsplib.Server) (*gortsplib.ServerStream, *description.Media, error) {
h264 := &format.H264{
PayloadTyp: 96,
PacketizationMode: 1,
SPS: []byte{0x67, 0x42, 0xC0, 0x1F, 0x96, 0x54, 0x05, 0x01, 0xED, 0x00, 0xF0, 0x88, 0x45, 0x80},
PPS: []byte{0x68, 0xCE, 0x38, 0x80},
}
media := &description.Media{
Type: description.MediaTypeVideo,
Formats: []format.Format{h264},
}
desc := &description.Session{
Medias: []*description.Media{media},
}
// REQUIRED: link stream to the running server
stream := gortsplib.NewServerStream(gs, desc)
return stream, media, nil
func (s *Server) Stop() {
s.cancel()
s.server.Close()
s.stream.Close()
s.wg.Wait()
}