five
This commit is contained in:
parent
7eec0f88e4
commit
403dfc5092
5 changed files with 179 additions and 262 deletions
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue