rtsp: support unwrapping MPEG-TS tracks (#5476)
this allows to use MPEG-TS tracks with other protocols and with the recording system. --------- Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com>
This commit is contained in:
@@ -717,6 +717,8 @@ components:
|
||||
deprecated: true
|
||||
srtPublishPassphrase:
|
||||
type: string
|
||||
rtspDemuxMpegts:
|
||||
type: boolean
|
||||
|
||||
# RTSP source
|
||||
rtspTransport:
|
||||
|
||||
@@ -8,7 +8,7 @@ srt://localhost:8890?streamid=publish:mystream&pkt_size=1316
|
||||
|
||||
Replace `mystream` with any name you want. The resulting stream will be available on path `/mystream`.
|
||||
|
||||
If you need to use the standard stream ID syntax instead of the custom one in use by this server, see [Standard stream ID syntax](../4-other/21-srt-specific-features.md#standard-stream-id-syntax).
|
||||
If you need to use the standard stream ID syntax instead of the custom one in use by this server, read [Standard stream ID syntax](../4-other/21-srt-specific-features.md#standard-stream-id-syntax).
|
||||
|
||||
If you want to publish a stream by using a client in listening mode (i.e. with `mode=listener` appended to the URL), read the next section.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# RTSP clients
|
||||
|
||||
RTSP is a protocol that allows to publish and read streams. It supports several underlying transport protocols and encryption (see [RTSP-specific features](../4-other/23-rtsp-specific-features.md)). In order to publish a stream to the server with the RTSP protocol, use this URL:
|
||||
RTSP is a protocol that allows to publish and read streams. It supports several underlying transport protocols and encryption. In order to publish a stream to the server with the RTSP protocol, use this URL:
|
||||
|
||||
```
|
||||
rtsp://localhost:8554/mystream
|
||||
@@ -9,3 +9,20 @@ rtsp://localhost:8554/mystream
|
||||
The resulting stream will be available on path `/mystream`.
|
||||
|
||||
Some clients that can publish with RTSP are [FFmpeg](15-ffmpeg.md), [GStreamer](16-gstreamer.md), [OBS Studio](17-obs-studio.md), [Python and OpenCV](18-python-opencv.md).
|
||||
|
||||
Advanced RTSP features and settings are described in [RTSP-specific features](../4-other/23-rtsp-specific-features.md).
|
||||
|
||||
## MPEG-TS inside RTSP
|
||||
|
||||
Some RTSP clients encode tracks with MPEG-TS before sending them to the server, causing the server to see a single "MPEG-TS" track, and preventing track conversion from a protocol to another.
|
||||
|
||||
It's possible to automatically demux these MPEG-TS-encoded streams, by toggling `rtspDemuxMpegts`:
|
||||
|
||||
```yml
|
||||
pathDefaults:
|
||||
# Demux MPEG-TS over RTSP into elementary streams.
|
||||
# When enabled, RTSP publishers sending MP2T/90000 will be demultiplexed
|
||||
# and their elementary streams (H.264, H.265, AAC, etc.) exposed as native tracks.
|
||||
# This allows HLS, WebRTC, and other outputs to work transparently with MPEG-TS sources.
|
||||
rtspDemuxMpegts: true
|
||||
```
|
||||
|
||||
@@ -44,4 +44,4 @@ paths:
|
||||
|
||||
All available parameters are listed in the [configuration file](../5-references/1-configuration-file.md).
|
||||
|
||||
Advanced RTSP features are described in [RTSP-specific features](../4-other/23-rtsp-specific-features.md).
|
||||
Advanced RTSP features and settings are described in [RTSP-specific features](../4-other/23-rtsp-specific-features.md).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# RTMP clients
|
||||
|
||||
RTMP is a protocol that allows to read and publish streams. It supports encryption, see [RTMP-specific features](../4-other/24-rtmp-specific-features.md). Streams can be published to the server by using the URL:
|
||||
RTMP is a protocol that allows to read and publish streams. It supports encryption, read [RTMP-specific features](../4-other/24-rtmp-specific-features.md). Streams can be published to the server by using the URL:
|
||||
|
||||
```
|
||||
rtmp://localhost/mystream
|
||||
|
||||
@@ -36,7 +36,7 @@ ffmpeg -re -f lavfi -i testsrc=size=1280x720:rate=30 \
|
||||
|
||||
## FFmpeg and RTP over UDP
|
||||
|
||||
In _MediaMTX_ configuration, add a path with `source: udp+rtp://238.0.0.1:1234` and a valid `rtpSDP` (see [RTP](12-rtp.md)). Then:
|
||||
In _MediaMTX_ configuration, add a path with `source: udp+rtp://238.0.0.1:1234` and a valid `rtpSDP` (read [RTP](12-rtp.md)). Then:
|
||||
|
||||
```sh
|
||||
ffmpeg -re -f lavfi -i testsrc=size=1280x720:rate=30 \
|
||||
|
||||
@@ -20,7 +20,7 @@ d.video_0 ! rtspclientsink location=rtsp://localhost:8554/mystream
|
||||
|
||||
The resulting stream will be available on path `/mystream`.
|
||||
|
||||
For advanced options, see [RTSP-specific features](../4-other/23-rtsp-specific-features.md).
|
||||
For advanced options, read [RTSP-specific features](../4-other/23-rtsp-specific-features.md).
|
||||
|
||||
## GStreamer and RTMP
|
||||
|
||||
@@ -38,7 +38,7 @@ videotestsrc ! video/x-raw,width=1280,height=720,format=I420 ! x264enc speed-pre
|
||||
audiotestsrc ! audioconvert ! avenc_aac ! mux.
|
||||
```
|
||||
|
||||
For advanced options, see [RTSP-specific features](../4-other/23-rtsp-specific-features.md).
|
||||
For advanced options, read [RTSP-specific features](../4-other/23-rtsp-specific-features.md).
|
||||
|
||||
## GStreamer and WebRTC
|
||||
|
||||
|
||||
@@ -8,6 +8,6 @@ srt://localhost:8890?streamid=read:mystream
|
||||
|
||||
Replace `mystream` with the path name.
|
||||
|
||||
If you need to use the standard stream ID syntax instead of the custom one in use by this server, see [Standard stream ID syntax](../4-other/21-srt-specific-features.md#standard-stream-id-syntax).
|
||||
If you need to use the standard stream ID syntax instead of the custom one in use by this server, read [Standard stream ID syntax](../4-other/21-srt-specific-features.md#standard-stream-id-syntax).
|
||||
|
||||
Some clients that can read with SRT are [FFmpeg](07-ffmpeg.md), [GStreamer](08-gstreamer.md) and [VLC](09-vlc.md).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# RTSP clients
|
||||
|
||||
RTSP is a protocol that allows to publish and read streams. It supports several underlying transport protocols and encryption (see [RTSP-specific features](../4-other/23-rtsp-specific-features.md)). In order to read a stream with the RTSP protocol, use this URL:
|
||||
RTSP is a protocol that allows to publish and read streams. It supports several underlying transport protocols and encryption (read [RTSP-specific features](../4-other/23-rtsp-specific-features.md)). In order to read a stream with the RTSP protocol, use this URL:
|
||||
|
||||
```
|
||||
rtsp://localhost:8554/mystream
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# RTMP clients
|
||||
|
||||
RTMP is a protocol that allows to read and publish streams. It supports encryption, see [RTMP-specific features](../4-other/24-rtmp-specific-features.md). Streams can be read from the server by using the URL:
|
||||
RTMP is a protocol that allows to read and publish streams. It supports encryption, read [RTMP-specific features](../4-other/24-rtmp-specific-features.md). Streams can be read from the server by using the URL:
|
||||
|
||||
```
|
||||
rtmp://localhost/mystream
|
||||
|
||||
@@ -8,7 +8,7 @@ GStreamer can read a stream from the server in several ways. The recommended one
|
||||
gst-launch-1.0 rtspsrc location=rtsp://127.0.0.1:8554/mystream latency=0 ! decodebin ! autovideosink
|
||||
```
|
||||
|
||||
For advanced options, see [RTSP-specific features](../4-other/23-rtsp-specific-features.md).
|
||||
For advanced options, read [RTSP-specific features](../4-other/23-rtsp-specific-features.md).
|
||||
|
||||
## GStreamer and WebRTC
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ vlc --network-caching=50 rtsp://localhost:8554/mystream
|
||||
|
||||
## RTSP and Ubuntu compatibility
|
||||
|
||||
The VLC shipped with Ubuntu 21.10 doesn't support playing RTSP due to a license issue (see [here](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=982299) and [here](https://stackoverflow.com/questions/69766748/cvlc-cannot-play-rtsp-omxplayer-instead-can)). To fix the issue, remove the default VLC instance and install the snap version:
|
||||
The VLC shipped with Ubuntu 21.10 doesn't support playing RTSP due to a license issue (read [here](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=982299) and [here](https://stackoverflow.com/questions/69766748/cvlc-cannot-play-rtsp-omxplayer-instead-can)). To fix the issue, remove the default VLC instance and install the snap version:
|
||||
|
||||
```sh
|
||||
sudo apt purge -y vlc
|
||||
|
||||
@@ -297,7 +297,7 @@ Username and password can be passed through the `Authorization: Basic` HTTP head
|
||||
Authorization: Basic base64(user:pass)
|
||||
```
|
||||
|
||||
When using a web browser, a dialog is first shown to users, asking for credentials, and then the header is automatically inserted into every request. If you need to automatically fill credentials from a parent web page, see [Embed streams in a website](14-embed-streams-in-a-website.md).
|
||||
When using a web browser, a dialog is first shown to users, asking for credentials, and then the header is automatically inserted into every request. If you need to automatically fill credentials from a parent web page, read [Embed streams in a website](14-embed-streams-in-a-website.md).
|
||||
|
||||
If the `Authorization: Basic` header cannot be used (for instance, in software like OBS Studio, which only allows to provide a "Bearer Token"), credentials can be passed through the `Authorization: Bearer` header (i.e. the "Bearer Token" in OBS), where the value is the concatenation of username and password, separated by a colon:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# SRT-specific features
|
||||
|
||||
SRT is a protocol that can be used for publishing and reading streams. Regarding specific tasks, see [Publish](../2-publish/02-srt-clients.md) and [Read](../3-read/02-srt.md). Features in this page are shared among both tasks.
|
||||
SRT is a protocol that can be used for publishing and reading streams. Regarding specific tasks, read [Publish](../2-publish/02-srt-clients.md) and [Read](../3-read/02-srt.md). Features in this page are shared among both tasks.
|
||||
|
||||
## Standard stream ID syntax
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# WebRTC-specific features
|
||||
|
||||
WebRTC is a protocol that can be used for publishing and reading streams. Regarding specific tasks, see [Publish](../2-publish/04-webrtc-clients.md) and [Read](../3-read/03-webrtc.md). Features in this page are shared among both tasks.
|
||||
WebRTC is a protocol that can be used for publishing and reading streams. Regarding specific tasks, read [Publish](../2-publish/04-webrtc-clients.md) and [Read](../3-read/03-webrtc.md). Features in this page are shared among both tasks.
|
||||
|
||||
## Codec support in browsers
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# RTSP-specific features
|
||||
|
||||
RTSP is a protocol that can be used for publishing and reading streams. Regarding specific tasks, see [Publish](../2-publish/06-rtsp-clients.md) and [Read](../3-read/04-rtsp.md). Features in this page are shared among both tasks.
|
||||
RTSP is a protocol that can be used for publishing and reading streams. Regarding specific tasks, read [Publish](../2-publish/06-rtsp-clients.md) and [Read](../3-read/04-rtsp.md). Features in this page are shared among both tasks.
|
||||
|
||||
## Transport protocols
|
||||
|
||||
@@ -104,3 +104,7 @@ paths:
|
||||
```
|
||||
|
||||
There are also the `rtsp+https`, `rtsp+ws`, `rtsp+wss` schemes to handle any combination.
|
||||
|
||||
## MPEG-TS inside RTSP
|
||||
|
||||
read [MPEG-TS inside RTSP](../2-publish/06-rtsp-clients.md#mpeg-ts-inside-rtsp) in the "Publish with RTSP clients" page.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# RTMP-specific features
|
||||
|
||||
RTMP is a protocol that can be used for publishing and reading streams. Regarding specific tasks, see [Publish](../2-publish/08-rtmp-clients.md) and [Read](../3-read/05-rtmp.md). Features in this page are shared among both tasks.
|
||||
RTMP is a protocol that can be used for publishing and reading streams. Regarding specific tasks, read [Publish](../2-publish/08-rtmp-clients.md) and [Read](../3-read/05-rtmp.md). Features in this page are shared among both tasks.
|
||||
|
||||
## Encryption
|
||||
|
||||
|
||||
@@ -210,6 +210,7 @@ type Path struct {
|
||||
OverridePublisher bool `json:"overridePublisher"`
|
||||
DisablePublisherOverride *bool `json:"disablePublisherOverride,omitempty" deprecated:"true"`
|
||||
SRTPublishPassphrase string `json:"srtPublishPassphrase"`
|
||||
RTSPDemuxMpegts bool `json:"rtspDemuxMpegts"`
|
||||
|
||||
// RTSP source
|
||||
RTSPTransport RTSPTransport `json:"rtspTransport"`
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
package rtsp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/pion/rtp"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/conf"
|
||||
"github.com/bluenviron/mediamtx/internal/defs"
|
||||
"github.com/bluenviron/mediamtx/internal/errordumper"
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/protocols/mpegts"
|
||||
"github.com/bluenviron/mediamtx/internal/stream"
|
||||
)
|
||||
|
||||
// mpegtsDemuxer demuxes an MPEG-TS stream received via RTP into component streams.
|
||||
type mpegtsDemuxer struct {
|
||||
session *session
|
||||
pathManager serverPathManager
|
||||
pathConf *conf.Path
|
||||
mpegtsMedia *description.Media
|
||||
mpegtsFormat *format.MPEGTS
|
||||
decodeErrors *errordumper.Dumper
|
||||
pathName string
|
||||
query string
|
||||
|
||||
pipeWriter *io.PipeWriter
|
||||
}
|
||||
|
||||
func (d *mpegtsDemuxer) initialize() error {
|
||||
decoder, err := d.mpegtsFormat.CreateDecoder()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create MPEG-TS decoder: %w", err)
|
||||
}
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
d.pipeWriter = pw
|
||||
|
||||
d.session.rsession.OnPacketRTP(d.mpegtsMedia, d.mpegtsFormat, func(pkt *rtp.Packet) {
|
||||
tsData, decErr := decoder.Decode(pkt)
|
||||
if decErr != nil {
|
||||
d.decodeErrors.Add(decErr)
|
||||
return
|
||||
}
|
||||
|
||||
for _, data := range tsData {
|
||||
_, err = pw.Write(data)
|
||||
if err != nil {
|
||||
d.session.Log(logger.Warn, "demuxer pipe write error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
go d.run(pr)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *mpegtsDemuxer) close() {
|
||||
d.pipeWriter.CloseWithError(io.EOF)
|
||||
}
|
||||
|
||||
func (d *mpegtsDemuxer) run(pr *io.PipeReader) {
|
||||
err := d.doRun(pr)
|
||||
if err != nil {
|
||||
d.session.Log(logger.Error, "MPEG-TS demuxer error: %v", err)
|
||||
d.session.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *mpegtsDemuxer) doRun(pr *io.PipeReader) error {
|
||||
r := &mpegts.EnhancedReader{R: pr}
|
||||
err := r.Initialize()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize MPEG-TS reader: %w", err)
|
||||
}
|
||||
|
||||
r.OnDecodeError(func(err error) {
|
||||
d.decodeErrors.Add(err)
|
||||
})
|
||||
|
||||
var subStream *stream.SubStream
|
||||
|
||||
medias, err := mpegts.ToStream(r, &subStream, d.session)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to map MPEG-TS to stream: %w", err)
|
||||
}
|
||||
|
||||
res, err := d.pathManager.AddPublisher(defs.PathAddPublisherReq{
|
||||
Author: d.session,
|
||||
Desc: &description.Session{Medias: medias},
|
||||
UseRTPPackets: false,
|
||||
ReplaceNTP: true,
|
||||
ConfToCompare: d.pathConf,
|
||||
AccessRequest: defs.PathAccessRequest{
|
||||
Name: d.pathName,
|
||||
Query: d.query,
|
||||
Publish: true,
|
||||
SkipAuth: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add publisher: %w", err)
|
||||
}
|
||||
|
||||
defer res.Path.RemovePublisher(defs.PathRemovePublisherReq{Author: d.session})
|
||||
|
||||
subStream = res.SubStream
|
||||
|
||||
for {
|
||||
err = r.Read()
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package rtsp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@@ -11,6 +13,8 @@ import (
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/base"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
mpegts "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
||||
tscodecs "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts/codecs"
|
||||
"github.com/bluenviron/mediamtx/internal/auth"
|
||||
"github.com/bluenviron/mediamtx/internal/conf"
|
||||
"github.com/bluenviron/mediamtx/internal/defs"
|
||||
@@ -206,6 +210,153 @@ func TestServerPublish(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerPublishMPEGTS(t *testing.T) {
|
||||
var strm *stream.Stream
|
||||
var reader *stream.Reader
|
||||
defer func() {
|
||||
if strm != nil && reader != nil {
|
||||
strm.RemoveReader(reader)
|
||||
}
|
||||
}()
|
||||
|
||||
dataReceived := make(chan struct{})
|
||||
|
||||
pathConf := &conf.Path{RTSPDemuxMpegts: true}
|
||||
|
||||
pathManager := &test.PathManager{
|
||||
FindPathConfImpl: func(req defs.PathFindPathConfReq) (*defs.PathFindPathConfRes, error) {
|
||||
require.Equal(t, "teststream", req.AccessRequest.Name)
|
||||
require.Equal(t, "param=value", req.AccessRequest.Query)
|
||||
return &defs.PathFindPathConfRes{Conf: pathConf}, nil
|
||||
},
|
||||
AddPublisherImpl: func(req defs.PathAddPublisherReq) (*defs.PathAddPublisherRes, error) {
|
||||
require.Equal(t, "teststream", req.AccessRequest.Name)
|
||||
require.Equal(t, "param=value", req.AccessRequest.Query)
|
||||
require.True(t, req.AccessRequest.SkipAuth)
|
||||
require.False(t, req.UseRTPPackets)
|
||||
require.True(t, req.ReplaceNTP)
|
||||
require.Same(t, pathConf, req.ConfToCompare)
|
||||
require.Equal(t, &description.Session{Medias: []*description.Media{{
|
||||
Type: description.MediaTypeVideo,
|
||||
Formats: []format.Format{&format.H264{
|
||||
PayloadTyp: 96,
|
||||
PacketizationMode: 1,
|
||||
}},
|
||||
}}}, req.Desc)
|
||||
|
||||
strm = &stream.Stream{
|
||||
Desc: req.Desc,
|
||||
WriteQueueSize: 512,
|
||||
RTPMaxPayloadSize: 1450,
|
||||
Parent: test.NilLogger,
|
||||
}
|
||||
err := strm.Initialize()
|
||||
require.NoError(t, err)
|
||||
|
||||
subStream := &stream.SubStream{
|
||||
Stream: strm,
|
||||
UseRTPPackets: false,
|
||||
}
|
||||
err = subStream.Initialize()
|
||||
require.NoError(t, err)
|
||||
|
||||
reader = &stream.Reader{Parent: test.NilLogger}
|
||||
n := 0
|
||||
|
||||
reader.OnData(
|
||||
strm.Desc.Medias[0],
|
||||
strm.Desc.Medias[0].Formats[0],
|
||||
func(u *unit.Unit) error {
|
||||
if n == 0 {
|
||||
require.Equal(t, unit.PayloadH264{
|
||||
test.FormatH264.SPS,
|
||||
test.FormatH264.PPS,
|
||||
{5, 1},
|
||||
}, u.Payload)
|
||||
close(dataReceived)
|
||||
}
|
||||
n++
|
||||
return nil
|
||||
})
|
||||
|
||||
strm.AddReader(reader)
|
||||
|
||||
return &defs.PathAddPublisherRes{Path: &dummyPath{}, SubStream: subStream}, nil
|
||||
},
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
Address: "127.0.0.1:8557",
|
||||
ReadTimeout: conf.Duration(10 * time.Second),
|
||||
WriteTimeout: conf.Duration(10 * time.Second),
|
||||
WriteQueueSize: 512,
|
||||
Transports: conf.RTSPTransports{gortsplib.ProtocolTCP: {}},
|
||||
PathManager: pathManager,
|
||||
Parent: test.NilLogger,
|
||||
}
|
||||
err := s.Initialize()
|
||||
require.NoError(t, err)
|
||||
defer s.Close()
|
||||
|
||||
source := gortsplib.Client{}
|
||||
|
||||
media0 := &description.Media{
|
||||
Type: description.MediaTypeApplication,
|
||||
Formats: []format.Format{&format.MPEGTS{}},
|
||||
}
|
||||
|
||||
err = source.StartRecording(
|
||||
"rtsp://127.0.0.1:8557/teststream?param=value",
|
||||
&description.Session{Medias: []*description.Media{media0}})
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
|
||||
track := &mpegts.Track{Codec: &tscodecs.H264{}}
|
||||
|
||||
var buf bytes.Buffer
|
||||
bw := bufio.NewWriter(&buf)
|
||||
w := &mpegts.Writer{W: bw, Tracks: []*mpegts.Track{track}}
|
||||
err = w.Initialize()
|
||||
require.NoError(t, err)
|
||||
|
||||
// the MPEG-TS muxer needs two PES packets in order to write the first one
|
||||
err = w.WriteH264(track, 0, 0, [][]byte{
|
||||
test.FormatH264.SPS,
|
||||
test.FormatH264.PPS,
|
||||
{5, 1},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = w.WriteH264(track, 0, 0, [][]byte{{5, 2}})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = bw.Flush()
|
||||
require.NoError(t, err)
|
||||
|
||||
raw := buf.Bytes()
|
||||
require.NotEmpty(t, raw)
|
||||
require.Zero(t, len(raw)%188)
|
||||
|
||||
tsPackets := make([][]byte, 0, len(raw)/188)
|
||||
for len(raw) > 0 {
|
||||
tsPackets = append(tsPackets, raw[:188:188])
|
||||
raw = raw[188:]
|
||||
}
|
||||
|
||||
encoder, err := media0.Formats[0].(*format.MPEGTS).CreateEncoder()
|
||||
require.NoError(t, err)
|
||||
|
||||
rtpPackets, err := encoder.Encode(tsPackets)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, pkt := range rtpPackets {
|
||||
err = source.WritePacketRTP(media0, pkt)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
<-dataReceived
|
||||
}
|
||||
|
||||
func TestServerRead(t *testing.T) {
|
||||
for _, ca := range []string{"basic", "digest", "basic+digest"} {
|
||||
t.Run(ca, func(t *testing.T) {
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"github.com/bluenviron/gortsplib/v5"
|
||||
rtspauth "github.com/bluenviron/gortsplib/v5/pkg/auth"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/base"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/headers"
|
||||
"github.com/google/uuid"
|
||||
|
||||
@@ -37,6 +39,19 @@ func profileLabel(p headers.TransportProfile) string {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func findSingleMPEGTSFormat(desc *description.Session) (*description.Media, *format.MPEGTS) {
|
||||
if len(desc.Medias) != 1 || len(desc.Medias[0].Formats) != 1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
forma := desc.Medias[0].Formats[0]
|
||||
if forma, ok := forma.(*format.MPEGTS); ok {
|
||||
return desc.Medias[0], forma
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type sessionParent interface {
|
||||
logger.Writer
|
||||
getConnByRConnUnsafe(rconn *gortsplib.ServerConn) *conn
|
||||
@@ -64,6 +79,7 @@ type session struct {
|
||||
outboundRTPPacketsDiscarded *counterdumper.Dumper
|
||||
mutex sync.RWMutex
|
||||
user string
|
||||
mpegtsDemuxer *mpegtsDemuxer
|
||||
}
|
||||
|
||||
func (s *session) initialize() {
|
||||
@@ -135,12 +151,18 @@ func (s *session) onClose(err error) {
|
||||
s.onUnreadHook()
|
||||
}
|
||||
|
||||
if s.mpegtsDemuxer != nil {
|
||||
s.mpegtsDemuxer.close()
|
||||
}
|
||||
|
||||
switch s.rsession.State() {
|
||||
case gortsplib.ServerSessionStatePrePlay, gortsplib.ServerSessionStatePlay:
|
||||
s.path.RemoveReader(defs.PathRemoveReaderReq{Author: s})
|
||||
|
||||
case gortsplib.ServerSessionStateRecord:
|
||||
s.path.RemovePublisher(defs.PathRemovePublisherReq{Author: s})
|
||||
if s.path != nil {
|
||||
s.path.RemovePublisher(defs.PathRemovePublisherReq{Author: s})
|
||||
}
|
||||
}
|
||||
|
||||
s.path = nil
|
||||
@@ -328,6 +350,34 @@ func (s *session) onPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, e
|
||||
|
||||
// onRecord is called by rtspServer.
|
||||
func (s *session) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
|
||||
if s.pathConf.RTSPDemuxMpegts {
|
||||
mpegtsMedia, mpegtsFormat := findSingleMPEGTSFormat(s.rsession.AnnouncedDescription())
|
||||
if mpegtsFormat != nil {
|
||||
s.Log(logger.Info, "MPEG-TS demux mode enabled, starting demuxer...")
|
||||
|
||||
s.mpegtsDemuxer = &mpegtsDemuxer{
|
||||
session: s,
|
||||
pathManager: s.pathManager,
|
||||
pathConf: s.pathConf,
|
||||
mpegtsMedia: mpegtsMedia,
|
||||
mpegtsFormat: mpegtsFormat,
|
||||
decodeErrors: s.inboundRTPPacketsInError,
|
||||
pathName: s.rsession.Path()[1:],
|
||||
query: s.rsession.Query(),
|
||||
}
|
||||
err := s.mpegtsDemuxer.initialize()
|
||||
if err != nil {
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusInternalServerError,
|
||||
}, err
|
||||
}
|
||||
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
res, err := s.pathManager.AddPublisher(defs.PathAddPublisherReq{
|
||||
Author: s,
|
||||
Desc: s.rsession.AnnouncedDescription(),
|
||||
@@ -364,6 +414,14 @@ func (s *session) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Respons
|
||||
|
||||
// onPause is called by rtspServer.
|
||||
func (s *session) onPause(_ *gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error) {
|
||||
// we can't close mpegtsDemuxer during pause because OnPacketRTP() is paused after onPause(),
|
||||
// therefore a call to pipeWriter.CloseWithError() would cause a race condition.
|
||||
if s.mpegtsDemuxer != nil {
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusBadRequest,
|
||||
}, fmt.Errorf("cannot pause in MPEG-TS demux mode")
|
||||
}
|
||||
|
||||
switch s.rsession.State() {
|
||||
case gortsplib.ServerSessionStatePlay:
|
||||
s.onUnreadHook()
|
||||
|
||||
@@ -548,6 +548,11 @@ pathDefaults:
|
||||
overridePublisher: true
|
||||
# SRT encryption passphrase required to publish to this path.
|
||||
srtPublishPassphrase:
|
||||
# Demux MPEG-TS over RTSP into elementary streams.
|
||||
# When enabled, RTSP publishers sending MP2T/90000 will be demultiplexed
|
||||
# and their elementary streams (H.264, H.265, AAC, etc.) exposed as native tracks.
|
||||
# This allows HLS, WebRTC, and other outputs to work transparently with MPEG-TS sources.
|
||||
rtspDemuxMpegts: false
|
||||
|
||||
###############################################
|
||||
# Default path settings -> RTSP source (when source is a RTSP or a RTSPS URL)
|
||||
|
||||
Reference in New Issue
Block a user