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
|
deprecated: true
|
||||||
srtPublishPassphrase:
|
srtPublishPassphrase:
|
||||||
type: string
|
type: string
|
||||||
|
rtspDemuxMpegts:
|
||||||
|
type: boolean
|
||||||
|
|
||||||
# RTSP source
|
# RTSP source
|
||||||
rtspTransport:
|
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`.
|
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.
|
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 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
|
rtsp://localhost:8554/mystream
|
||||||
@@ -9,3 +9,20 @@ rtsp://localhost:8554/mystream
|
|||||||
The resulting stream will be available on path `/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).
|
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).
|
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 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
|
rtmp://localhost/mystream
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ ffmpeg -re -f lavfi -i testsrc=size=1280x720:rate=30 \
|
|||||||
|
|
||||||
## FFmpeg and RTP over UDP
|
## 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
|
```sh
|
||||||
ffmpeg -re -f lavfi -i testsrc=size=1280x720:rate=30 \
|
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`.
|
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
|
## 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.
|
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
|
## GStreamer and WebRTC
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ srt://localhost:8890?streamid=read:mystream
|
|||||||
|
|
||||||
Replace `mystream` with the path name.
|
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).
|
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 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
|
rtsp://localhost:8554/mystream
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# RTMP clients
|
# 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
|
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
|
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
|
## GStreamer and WebRTC
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ vlc --network-caching=50 rtsp://localhost:8554/mystream
|
|||||||
|
|
||||||
## RTSP and Ubuntu compatibility
|
## 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
|
```sh
|
||||||
sudo apt purge -y vlc
|
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)
|
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:
|
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-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
|
## Standard stream ID syntax
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# WebRTC-specific features
|
# 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
|
## Codec support in browsers
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# RTSP-specific features
|
# 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
|
## Transport protocols
|
||||||
|
|
||||||
@@ -104,3 +104,7 @@ paths:
|
|||||||
```
|
```
|
||||||
|
|
||||||
There are also the `rtsp+https`, `rtsp+ws`, `rtsp+wss` schemes to handle any combination.
|
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-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
|
## Encryption
|
||||||
|
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ type Path struct {
|
|||||||
OverridePublisher bool `json:"overridePublisher"`
|
OverridePublisher bool `json:"overridePublisher"`
|
||||||
DisablePublisherOverride *bool `json:"disablePublisherOverride,omitempty" deprecated:"true"`
|
DisablePublisherOverride *bool `json:"disablePublisherOverride,omitempty" deprecated:"true"`
|
||||||
SRTPublishPassphrase string `json:"srtPublishPassphrase"`
|
SRTPublishPassphrase string `json:"srtPublishPassphrase"`
|
||||||
|
RTSPDemuxMpegts bool `json:"rtspDemuxMpegts"`
|
||||||
|
|
||||||
// RTSP source
|
// RTSP source
|
||||||
RTSPTransport RTSPTransport `json:"rtspTransport"`
|
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
|
package rtsp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -11,6 +13,8 @@ import (
|
|||||||
"github.com/bluenviron/gortsplib/v5/pkg/base"
|
"github.com/bluenviron/gortsplib/v5/pkg/base"
|
||||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
"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/auth"
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
"github.com/bluenviron/mediamtx/internal/defs"
|
"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) {
|
func TestServerRead(t *testing.T) {
|
||||||
for _, ca := range []string{"basic", "digest", "basic+digest"} {
|
for _, ca := range []string{"basic", "digest", "basic+digest"} {
|
||||||
t.Run(ca, func(t *testing.T) {
|
t.Run(ca, func(t *testing.T) {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
"github.com/bluenviron/gortsplib/v5"
|
"github.com/bluenviron/gortsplib/v5"
|
||||||
rtspauth "github.com/bluenviron/gortsplib/v5/pkg/auth"
|
rtspauth "github.com/bluenviron/gortsplib/v5/pkg/auth"
|
||||||
"github.com/bluenviron/gortsplib/v5/pkg/base"
|
"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/bluenviron/gortsplib/v5/pkg/headers"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
@@ -37,6 +39,19 @@ func profileLabel(p headers.TransportProfile) string {
|
|||||||
return "unknown"
|
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 {
|
type sessionParent interface {
|
||||||
logger.Writer
|
logger.Writer
|
||||||
getConnByRConnUnsafe(rconn *gortsplib.ServerConn) *conn
|
getConnByRConnUnsafe(rconn *gortsplib.ServerConn) *conn
|
||||||
@@ -64,6 +79,7 @@ type session struct {
|
|||||||
outboundRTPPacketsDiscarded *counterdumper.Dumper
|
outboundRTPPacketsDiscarded *counterdumper.Dumper
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
user string
|
user string
|
||||||
|
mpegtsDemuxer *mpegtsDemuxer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) initialize() {
|
func (s *session) initialize() {
|
||||||
@@ -135,12 +151,18 @@ func (s *session) onClose(err error) {
|
|||||||
s.onUnreadHook()
|
s.onUnreadHook()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.mpegtsDemuxer != nil {
|
||||||
|
s.mpegtsDemuxer.close()
|
||||||
|
}
|
||||||
|
|
||||||
switch s.rsession.State() {
|
switch s.rsession.State() {
|
||||||
case gortsplib.ServerSessionStatePrePlay, gortsplib.ServerSessionStatePlay:
|
case gortsplib.ServerSessionStatePrePlay, gortsplib.ServerSessionStatePlay:
|
||||||
s.path.RemoveReader(defs.PathRemoveReaderReq{Author: s})
|
s.path.RemoveReader(defs.PathRemoveReaderReq{Author: s})
|
||||||
|
|
||||||
case gortsplib.ServerSessionStateRecord:
|
case gortsplib.ServerSessionStateRecord:
|
||||||
s.path.RemovePublisher(defs.PathRemovePublisherReq{Author: s})
|
if s.path != nil {
|
||||||
|
s.path.RemovePublisher(defs.PathRemovePublisherReq{Author: s})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.path = nil
|
s.path = nil
|
||||||
@@ -328,6 +350,34 @@ func (s *session) onPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, e
|
|||||||
|
|
||||||
// onRecord is called by rtspServer.
|
// onRecord is called by rtspServer.
|
||||||
func (s *session) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
|
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{
|
res, err := s.pathManager.AddPublisher(defs.PathAddPublisherReq{
|
||||||
Author: s,
|
Author: s,
|
||||||
Desc: s.rsession.AnnouncedDescription(),
|
Desc: s.rsession.AnnouncedDescription(),
|
||||||
@@ -364,6 +414,14 @@ func (s *session) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Respons
|
|||||||
|
|
||||||
// onPause is called by rtspServer.
|
// onPause is called by rtspServer.
|
||||||
func (s *session) onPause(_ *gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error) {
|
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() {
|
switch s.rsession.State() {
|
||||||
case gortsplib.ServerSessionStatePlay:
|
case gortsplib.ServerSessionStatePlay:
|
||||||
s.onUnreadHook()
|
s.onUnreadHook()
|
||||||
|
|||||||
@@ -548,6 +548,11 @@ pathDefaults:
|
|||||||
overridePublisher: true
|
overridePublisher: true
|
||||||
# SRT encryption passphrase required to publish to this path.
|
# SRT encryption passphrase required to publish to this path.
|
||||||
srtPublishPassphrase:
|
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)
|
# Default path settings -> RTSP source (when source is a RTSP or a RTSPS URL)
|
||||||
|
|||||||
Reference in New Issue
Block a user