convert Tracks into Medias and Formats (#155)

* split tracks from medias

* move tracks into dedicated package

* move media into dedicated package

* edit Medias.Marshal() in order to return SDP

* add medias.Find() and simplify examples

* improve coverage

* fix rebase errors

* replace TrackIDs with MediaIDs

* implement media-specific and track-specific callbacks for reading RTCP and RTP packets

* rename publish into record, read into play

* add v2 tag

* rename tracks into formats
This commit is contained in:
Alessandro Ros
2022-12-11 22:03:22 +01:00
committed by GitHub
parent 2a5b3e3ee5
commit a1396206b5
177 changed files with 6872 additions and 7333 deletions

View File

@@ -14,12 +14,12 @@ Go ≥ 1.17 is required.
Features: Features:
* Client * Client
* Query servers about available streams and tracks * Query servers about available streams and mediastreams
* Read * Read
* Read streams from servers with the UDP, UDP-multicast or TCP transport protocol * Read media streams from servers with the UDP, UDP-multicast or TCP transport protocol
* Read TLS-encrypted streams (TCP only) * Read TLS-encrypted streams (TCP only)
* Switch transport protocol automatically * Switch transport protocol automatically
* Read only selected tracks of a stream * Read only selected media streams
* Pause or seek without disconnecting from the server * Pause or seek without disconnecting from the server
* Generate RTCP receiver reports (UDP only) * Generate RTCP receiver reports (UDP only)
* Reorder incoming RTP packets (UDP only) * Reorder incoming RTP packets (UDP only)
@@ -33,28 +33,19 @@ Features:
* Handle requests from clients * Handle requests from clients
* Sessions and connections are independent * Sessions and connections are independent
* Publish * Publish
* Read streams from clients with the UDP or TCP transport protocol * Read media streams from clients with the UDP or TCP transport protocol
* Read TLS-encrypted streams (TCP only) * Read TLS-encrypted streams (TCP only)
* Generate RTCP receiver reports (UDP only) * Generate RTCP receiver reports (UDP only)
* Reorder incoming RTP packets (UDP only) * Reorder incoming RTP packets (UDP only)
* Read * Read
* Write streams to clients with the UDP, UDP-multicast or TCP transport protocol * Write media streams to clients with the UDP, UDP-multicast or TCP transport protocol
* Write TLS-encrypted streams * Write TLS-encrypted streams
* Compute and provide SSRC, RTP-Info to clients * Compute and provide SSRC, RTP-Info to clients
* Generate RTCP sender reports * Generate RTCP sender reports
* Utilities * Utilities
* Encode and decode codec-specific frames into/from RTP packets. The following codecs are supported: * Encode and decode codec-specific frames into/from RTP packets. The following codecs are supported:
* Video * Video: H264, H265, VP8, VP9
* H264 * Audio: G711 (PCMA, PCMU), G722, LPCM, MPEG4-audio (AAC), Opus
* H265
* VP8
* VP9
* Audio
* G711
* G722
* LPCM
* MPEG4-audio (AAC)
* Opus
* Parse RTSP elements: requests, responses, SDP * Parse RTSP elements: requests, responses, SDP
* Parse H264 elements and formats: Annex-B, AVCC, anti-competition, DTS * Parse H264 elements and formats: Annex-B, AVCC, anti-competition, DTS
* Parse MPEG4-audio (AAC) element and formats: ADTS, MPEG4-audio configurations * Parse MPEG4-audio (AAC) element and formats: ADTS, MPEG4-audio configurations
@@ -70,31 +61,30 @@ Features:
* [client-query](examples/client-query/main.go) * [client-query](examples/client-query/main.go)
* [client-read](examples/client-read/main.go) * [client-read](examples/client-read/main.go)
* [client-read-options](examples/client-read-options/main.go) * [client-read-options](examples/client-read-options/main.go)
* [client-read-partial](examples/client-read-partial/main.go)
* [client-read-pause](examples/client-read-pause/main.go) * [client-read-pause](examples/client-read-pause/main.go)
* [client-read-republish](examples/client-read-republish/main.go) * [client-read-republish](examples/client-read-republish/main.go)
* [client-read-track-g711](examples/client-read-track-g711/main.go) * [client-read-format-g711](examples/client-read-format-g711/main.go)
* [client-read-track-g722](examples/client-read-track-g722/main.go) * [client-read-format-g722](examples/client-read-format-g722/main.go)
* [client-read-track-h264](examples/client-read-track-h264/main.go) * [client-read-format-h264](examples/client-read-format-h264/main.go)
* [client-read-track-h264-convert-to-jpeg](examples/client-read-track-h264-convert-to-jpeg/main.go) * [client-read-format-h264-convert-to-jpeg](examples/client-read-format-h264-convert-to-jpeg/main.go)
* [client-read-track-h264-save-to-disk](examples/client-read-track-h264-save-to-disk/main.go) * [client-read-format-h264-save-to-disk](examples/client-read-format-h264-save-to-disk/main.go)
* [client-read-track-h265](examples/client-read-track-h265/main.go) * [client-read-format-h265](examples/client-read-format-h265/main.go)
* [client-read-track-lpcm](examples/client-read-track-lpcm/main.go) * [client-read-format-lpcm](examples/client-read-format-lpcm/main.go)
* [client-read-track-mpeg4audio](examples/client-read-track-mpeg4audio/main.go) * [client-read-format-mpeg4audio](examples/client-read-format-mpeg4audio/main.go)
* [client-read-track-opus](examples/client-read-track-opus/main.go) * [client-read-format-opus](examples/client-read-format-opus/main.go)
* [client-read-track-vp8](examples/client-read-track-vp8/main.go) * [client-read-format-vp8](examples/client-read-format-vp8/main.go)
* [client-read-track-vp9](examples/client-read-track-vp9/main.go) * [client-read-format-vp9](examples/client-read-format-vp9/main.go)
* [client-publish-options](examples/client-publish-options/main.go) * [client-publish-options](examples/client-publish-options/main.go)
* [client-publish-pause](examples/client-publish-pause/main.go) * [client-publish-pause](examples/client-publish-pause/main.go)
* [client-publish-track-g711](examples/client-publish-track-g711/main.go) * [client-publish-format-g711](examples/client-publish-format-g711/main.go)
* [client-publish-track-g722](examples/client-publish-track-g722/main.go) * [client-publish-format-g722](examples/client-publish-format-g722/main.go)
* [client-publish-track-h264](examples/client-publish-track-h264/main.go) * [client-publish-format-h264](examples/client-publish-format-h264/main.go)
* [client-publish-track-h265](examples/client-publish-track-h265/main.go) * [client-publish-format-h265](examples/client-publish-format-h265/main.go)
* [client-publish-track-lpcm](examples/client-publish-track-lpcm/main.go) * [client-publish-format-lpcm](examples/client-publish-format-lpcm/main.go)
* [client-publish-track-mpeg4audio](examples/client-publish-track-mpeg4audio/main.go) * [client-publish-format-mpeg4audio](examples/client-publish-format-mpeg4audio/main.go)
* [client-publish-track-opus](examples/client-publish-track-opus/main.go) * [client-publish-format-opus](examples/client-publish-format-opus/main.go)
* [client-publish-track-vp8](examples/client-publish-track-vp8/main.go) * [client-publish-format-vp8](examples/client-publish-format-vp8/main.go)
* [client-publish-track-vp9](examples/client-publish-track-vp9/main.go) * [client-publish-format-vp9](examples/client-publish-format-vp9/main.go)
* [server](examples/server/main.go) * [server](examples/server/main.go)
* [server-tls](examples/server-tls/main.go) * [server-tls](examples/server-tls/main.go)
* [server-h264-save-to-disk](examples/server-h264-save-to-disk/main.go) * [server-h264-save-to-disk](examples/server-h264-save-to-disk/main.go)

698
client.go

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -12,15 +12,28 @@ import (
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/v2/pkg/base"
"github.com/aler9/gortsplib/pkg/conn" "github.com/aler9/gortsplib/v2/pkg/conn"
"github.com/aler9/gortsplib/pkg/headers" "github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/headers"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/aler9/gortsplib/v2/pkg/url"
) )
var testH264Media = &media.Media{
Type: media.TypeVideo,
Formats: []format.Format{&format.H264{
PayloadTyp: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []byte{0x01, 0x02, 0x03, 0x04},
PacketizationMode: 1,
}},
}
var testRTPPacket = rtp.Packet{ var testRTPPacket = rtp.Packet{
Header: rtp.Header{ Header: rtp.Header{
Version: 2, Version: 2,
PayloadType: 97, PayloadType: 96,
CSRC: []uint32{}, CSRC: []uint32{},
SSRC: 0x38F27A2F, SSRC: 0x38F27A2F,
}, },
@@ -51,7 +64,43 @@ var testRTCPPacketMarshaled = func() []byte {
return byts return byts
}() }()
func TestClientPublishSerial(t *testing.T) { func record(c *Client, ur string, medias media.Medias, cb func(*media.Media, rtcp.Packet)) error {
u, err := url.Parse(ur)
if err != nil {
return err
}
err = c.Start(u.Scheme, u.Host)
if err != nil {
return err
}
_, err = c.Announce(u, medias)
if err != nil {
c.Close()
return err
}
err = c.SetupAll(medias, u)
if err != nil {
c.Close()
return err
}
if cb != nil {
c.OnPacketRTCPAny(cb)
}
_, err = c.Record()
if err != nil {
c.Close()
return err
}
return nil
}
func TestClientRecordSerial(t *testing.T) {
for _, transport := range []string{ for _, transport := range []string{
"udp", "udp",
"tcp", "tcp",
@@ -114,7 +163,7 @@ func TestClientPublishSerial(t *testing.T) {
req, err = conn.ReadRequest() req, err = conn.ReadRequest()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, base.Setup, req.Method) require.Equal(t, base.Setup, req.Method)
require.Equal(t, mustParseURL(scheme+"://localhost:8554/teststream/trackID=0"), req.URL) require.Equal(t, mustParseURL(scheme+"://localhost:8554/teststream/mediaID=0"), req.URL)
var inTH headers.Transport var inTH headers.Transport
err = inTH.Unmarshal(req.Header["Transport"]) err = inTH.Unmarshal(req.Header["Transport"])
@@ -224,22 +273,16 @@ func TestClientPublishSerial(t *testing.T) {
v := TransportTCP v := TransportTCP
return &v return &v
}(), }(),
OnPacketRTCP: func(ctx *ClientOnPacketRTCPCtx) { }
require.Equal(t, 0, ctx.TrackID)
require.Equal(t, &testRTCPPacket, ctx.Packet) medi := testH264Media.Clone()
medias := media.Medias{medi}
err = record(&c, scheme+"://localhost:8554/teststream", medias,
func(medi *media.Media, pkt rtcp.Packet) {
require.Equal(t, &testRTCPPacket, pkt)
close(recvDone) close(recvDone)
}, })
}
track := &TrackH264{
PayloadType: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []byte{0x01, 0x02, 0x03, 0x04},
PacketizationMode: 1,
}
err = c.StartPublishing(scheme+"://localhost:8554/teststream",
Tracks{track})
require.NoError(t, err) require.NoError(t, err)
done := make(chan struct{}) done := make(chan struct{})
@@ -248,20 +291,20 @@ func TestClientPublishSerial(t *testing.T) {
c.Wait() c.Wait()
}() }()
err = c.WritePacketRTP(0, &testRTPPacket) err = c.WritePacketRTP(medi, &testRTPPacket)
require.NoError(t, err) require.NoError(t, err)
<-recvDone <-recvDone
c.Close() c.Close()
<-done <-done
err = c.WritePacketRTP(0, &testRTPPacket) err = c.WritePacketRTP(medi, &testRTPPacket)
require.Error(t, err) require.Error(t, err)
}) })
} }
} }
func TestClientPublishParallel(t *testing.T) { func TestClientRecordParallel(t *testing.T) {
for _, transport := range []string{ for _, transport := range []string{
"udp", "udp",
"tcp", "tcp",
@@ -384,18 +427,11 @@ func TestClientPublishParallel(t *testing.T) {
}(), }(),
} }
track := &TrackH264{
PayloadType: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []byte{0x01, 0x02, 0x03, 0x04},
PacketizationMode: 1,
}
writerDone := make(chan struct{}) writerDone := make(chan struct{})
defer func() { <-writerDone }() defer func() { <-writerDone }()
err = c.StartPublishing(scheme+"://localhost:8554/teststream", medi := testH264Media.Clone()
Tracks{track}) err = record(&c, scheme+"://localhost:8554/teststream", media.Medias{medi}, nil)
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
@@ -406,7 +442,7 @@ func TestClientPublishParallel(t *testing.T) {
defer t.Stop() defer t.Stop()
for range t.C { for range t.C {
err := c.WritePacketRTP(0, &testRTPPacket) err := c.WritePacketRTP(medi, &testRTPPacket)
if err != nil { if err != nil {
return return
} }
@@ -418,7 +454,7 @@ func TestClientPublishParallel(t *testing.T) {
} }
} }
func TestClientPublishPauseSerial(t *testing.T) { func TestClientRecordPauseSerial(t *testing.T) {
for _, transport := range []string{ for _, transport := range []string{
"udp", "udp",
"tcp", "tcp",
@@ -544,19 +580,12 @@ func TestClientPublishPauseSerial(t *testing.T) {
}(), }(),
} }
track := &TrackH264{ medi := testH264Media.Clone()
PayloadType: 96, err = record(&c, "rtsp://localhost:8554/teststream", media.Medias{medi}, nil)
SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []byte{0x01, 0x02, 0x03, 0x04},
PacketizationMode: 1,
}
err = c.StartPublishing("rtsp://localhost:8554/teststream",
Tracks{track})
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
err = c.WritePacketRTP(0, &testRTPPacket) err = c.WritePacketRTP(medi, &testRTPPacket)
require.NoError(t, err) require.NoError(t, err)
_, err = c.Pause() _, err = c.Pause()
@@ -565,13 +594,13 @@ func TestClientPublishPauseSerial(t *testing.T) {
_, err = c.Record() _, err = c.Record()
require.NoError(t, err) require.NoError(t, err)
err = c.WritePacketRTP(0, &testRTPPacket) err = c.WritePacketRTP(medi, &testRTPPacket)
require.NoError(t, err) require.NoError(t, err)
}) })
} }
} }
func TestClientPublishPauseParallel(t *testing.T) { func TestClientRecordPauseParallel(t *testing.T) {
for _, transport := range []string{ for _, transport := range []string{
"udp", "udp",
"tcp", "tcp",
@@ -679,15 +708,8 @@ func TestClientPublishPauseParallel(t *testing.T) {
}(), }(),
} }
track := &TrackH264{ medi := testH264Media.Clone()
PayloadType: 96, err = record(&c, "rtsp://localhost:8554/teststream", media.Medias{medi}, nil)
SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []byte{0x01, 0x02, 0x03, 0x04},
PacketizationMode: 1,
}
err = c.StartPublishing("rtsp://localhost:8554/teststream",
Tracks{track})
require.NoError(t, err) require.NoError(t, err)
writerDone := make(chan struct{}) writerDone := make(chan struct{})
@@ -698,7 +720,7 @@ func TestClientPublishPauseParallel(t *testing.T) {
defer t.Stop() defer t.Stop()
for range t.C { for range t.C {
err := c.WritePacketRTP(0, &testRTPPacket) err := c.WritePacketRTP(medi, &testRTPPacket)
if err != nil { if err != nil {
return return
} }
@@ -716,7 +738,7 @@ func TestClientPublishPauseParallel(t *testing.T) {
} }
} }
func TestClientPublishAutomaticProtocol(t *testing.T) { func TestClientRecordAutomaticProtocol(t *testing.T) {
l, err := net.Listen("tcp", "localhost:8554") l, err := net.Listen("tcp", "localhost:8554")
require.NoError(t, err) require.NoError(t, err)
defer l.Close() defer l.Close()
@@ -821,25 +843,18 @@ func TestClientPublishAutomaticProtocol(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
}() }()
track := &TrackH264{
PayloadType: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []byte{0x01, 0x02, 0x03, 0x04},
PacketizationMode: 1,
}
c := Client{} c := Client{}
err = c.StartPublishing("rtsp://localhost:8554/teststream", medi := testH264Media.Clone()
Tracks{track}) err = record(&c, "rtsp://localhost:8554/teststream", media.Medias{medi}, nil)
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
err = c.WritePacketRTP(0, &testRTPPacket) err = c.WritePacketRTP(medi, &testRTPPacket)
require.NoError(t, err) require.NoError(t, err)
} }
func TestClientPublishDecodeErrors(t *testing.T) { func TestClientRecordDecodeErrors(t *testing.T) {
for _, ca := range []struct { for _, ca := range []struct {
proto string proto string
name string name string
@@ -1010,15 +1025,7 @@ func TestClientPublishDecodeErrors(t *testing.T) {
}, },
} }
track := &TrackH264{ err = record(&c, "rtsp://localhost:8554/stream", media.Medias{testH264Media.Clone()}, nil)
PayloadType: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []byte{0x01, 0x02, 0x03, 0x04},
PacketizationMode: 1,
}
err = c.StartPublishing("rtsp://localhost:8554/stream",
Tracks{track})
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
@@ -1027,7 +1034,7 @@ func TestClientPublishDecodeErrors(t *testing.T) {
} }
} }
func TestClientPublishRTCPReport(t *testing.T) { func TestClientRecordRTCPReport(t *testing.T) {
for _, ca := range []string{"udp", "tcp"} { for _, ca := range []string{"udp", "tcp"} {
t.Run(ca, func(t *testing.T) { t.Run(ca, func(t *testing.T) {
reportReceived := make(chan struct{}) reportReceived := make(chan struct{})
@@ -1170,21 +1177,16 @@ func TestClientPublishRTCPReport(t *testing.T) {
v := TransportTCP v := TransportTCP
return &v return &v
}(), }(),
udpSenderReportPeriod: 500 * time.Millisecond, senderReportPeriod: 500 * time.Millisecond,
} }
err = c.StartPublishing("rtsp://localhost:8554/teststream", medi := testH264Media.Clone()
Tracks{&TrackH264{ err = record(&c, "rtsp://localhost:8554/teststream", media.Medias{medi}, nil)
PayloadType: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []byte{0x01, 0x02, 0x03, 0x04},
PacketizationMode: 1,
}})
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
err = c.WritePacketRTP(0, &rtp.Packet{ err = c.WritePacketRTP(medi, &rtp.Packet{
Header: rtp.Header{ Header: rtp.Header{
Version: 2, Version: 2,
PayloadType: 96, PayloadType: 96,
@@ -1200,7 +1202,7 @@ func TestClientPublishRTCPReport(t *testing.T) {
} }
} }
func TestClientPublishIgnoreTCPRTPPackets(t *testing.T) { func TestClientRecordIgnoreTCPRTPPackets(t *testing.T) {
l, err := net.Listen("tcp", "localhost:8554") l, err := net.Listen("tcp", "localhost:8554")
require.NoError(t, err) require.NoError(t, err)
defer l.Close() defer l.Close()
@@ -1303,23 +1305,14 @@ func TestClientPublishIgnoreTCPRTPPackets(t *testing.T) {
v := TransportTCP v := TransportTCP
return &v return &v
}(), }(),
OnPacketRTP: func(ctx *ClientOnPacketRTPCtx) { }
t.Errorf("should not happen")
}, medias := media.Medias{testH264Media.Clone()}
OnPacketRTCP: func(ctx *ClientOnPacketRTCPCtx) {
err = record(&c, "rtsp://localhost:8554/teststream", medias,
func(medi *media.Media, pkt rtcp.Packet) {
close(rtcpReceived) close(rtcpReceived)
}, })
}
track := &TrackH264{
PayloadType: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []byte{0x01, 0x02, 0x03, 0x04},
PacketizationMode: 1,
}
err = c.StartPublishing("rtsp://localhost:8554/teststream",
Tracks{track})
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()

View File

@@ -8,10 +8,11 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aler9/gortsplib/pkg/auth" "github.com/aler9/gortsplib/v2/pkg/auth"
"github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/v2/pkg/base"
"github.com/aler9/gortsplib/pkg/conn" "github.com/aler9/gortsplib/v2/pkg/conn"
"github.com/aler9/gortsplib/pkg/url" "github.com/aler9/gortsplib/v2/pkg/media"
"github.com/aler9/gortsplib/v2/pkg/url"
) )
func mustParseURL(s string) *url.URL { func mustParseURL(s string) *url.URL {
@@ -105,15 +106,8 @@ func TestClientSession(t *testing.T) {
require.Equal(t, base.HeaderValue{"123456"}, req.Header["Session"]) require.Equal(t, base.HeaderValue{"123456"}, req.Header["Session"])
track := &TrackH264{ medias := media.Medias{testH264Media.Clone()}
PayloadType: 96, medias.SetControls()
SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []byte{0x01, 0x02, 0x03, 0x04},
PacketizationMode: 1,
}
tracks := Tracks{track}
tracks.setControls()
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -121,7 +115,7 @@ func TestClientSession(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Session": base.HeaderValue{"123456"}, "Session": base.HeaderValue{"123456"},
}, },
Body: tracks.Marshal(false), Body: mustMarshalSDP(medias.Marshal(false)),
}) })
require.NoError(t, err) require.NoError(t, err)
}() }()
@@ -189,22 +183,15 @@ func TestClientAuth(t *testing.T) {
err = v.ValidateRequest(req) err = v.ValidateRequest(req)
require.NoError(t, err) require.NoError(t, err)
track := &TrackH264{ medias := media.Medias{testH264Media.Clone()}
PayloadType: 96, medias.SetControls()
SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []byte{0x01, 0x02, 0x03, 0x04},
PacketizationMode: 1,
}
tracks := Tracks{track}
tracks.setControls()
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
Header: base.Header{ Header: base.Header{
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
}, },
Body: tracks.Marshal(false), Body: mustMarshalSDP(medias.Marshal(false)),
}) })
require.NoError(t, err) require.NoError(t, err)
}() }()
@@ -256,12 +243,7 @@ func TestClientDescribeCharset(t *testing.T) {
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
require.Equal(t, mustParseURL("rtsp://localhost:8554/teststream"), req.URL) require.Equal(t, mustParseURL("rtsp://localhost:8554/teststream"), req.URL)
track1 := &TrackH264{ medias := media.Medias{testH264Media.Clone()}
PayloadType: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []byte{0x01, 0x02, 0x03, 0x04},
PacketizationMode: 1,
}
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -269,7 +251,7 @@ func TestClientDescribeCharset(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp; charset=utf-8"}, "Content-Type": base.HeaderValue{"application/sdp; charset=utf-8"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: Tracks{track1}.Marshal(false), Body: mustMarshalSDP(medias.Marshal(false)),
}) })
require.NoError(t, err) require.NoError(t, err)
}() }()

341
clientmedia.go Normal file
View File

@@ -0,0 +1,341 @@
package gortsplib
import (
"fmt"
"sync/atomic"
"time"
"github.com/pion/rtcp"
"github.com/aler9/gortsplib/v2/pkg/base"
"github.com/aler9/gortsplib/v2/pkg/media"
)
type clientMedia struct {
c *Client
media *media.Media
formats map[uint8]*clientFormat
tcpChannel int
udpRTPListener *clientUDPListener
udpRTCPListener *clientUDPListener
tcpRTPFrame *base.InterleavedFrame
tcpRTCPFrame *base.InterleavedFrame
tcpBuffer []byte
writePacketRTPInQueue func([]byte)
writePacketRTCPInQueue func([]byte)
readRTP func([]byte) error
readRTCP func([]byte) error
onPacketRTCP func(rtcp.Packet)
}
func newClientMedia(c *Client) *clientMedia {
return &clientMedia{
c: c,
onPacketRTCP: func(rtcp.Packet) {},
}
}
func (cm *clientMedia) close() {
if cm.udpRTPListener != nil {
cm.udpRTPListener.close()
cm.udpRTCPListener.close()
}
}
func (cm *clientMedia) allocateUDPListeners(multicast bool, rtpAddress string, rtcpAddress string) error {
if rtpAddress != ":0" {
l1, err := newClientUDPListener(
cm.c, multicast, rtpAddress,
cm, true)
if err != nil {
return err
}
l2, err := newClientUDPListener(
cm.c, multicast, rtcpAddress,
cm, false)
if err != nil {
l1.close()
return err
}
cm.udpRTPListener, cm.udpRTCPListener = l1, l2
return nil
}
cm.udpRTPListener, cm.udpRTCPListener = newClientUDPListenerPair(cm.c, cm)
return nil
}
func (cm *clientMedia) setMedia(medi *media.Media) {
cm.media = medi
cm.formats = make(map[uint8]*clientFormat)
for _, trak := range medi.Formats {
cm.formats[trak.PayloadType()] = newClientFormat(cm, trak)
}
}
func (cm *clientMedia) start() {
if cm.udpRTPListener != nil {
cm.writePacketRTPInQueue = cm.writePacketRTPInQueueUDP
cm.writePacketRTCPInQueue = cm.writePacketRTCPInQueueUDP
if cm.c.state == clientStatePlay {
cm.readRTP = cm.readRTPUDPPlay
cm.readRTCP = cm.readRTCPUDPPlay
} else {
cm.readRTP = cm.readRTPUDPRecord
cm.readRTCP = cm.readRTCPUDPRecord
}
} else {
cm.writePacketRTPInQueue = cm.writePacketRTPInQueueTCP
cm.writePacketRTCPInQueue = cm.writePacketRTCPInQueueTCP
if cm.c.state == clientStatePlay {
cm.readRTP = cm.readRTPTCPPlay
cm.readRTCP = cm.readRTCPTCPPlay
} else {
cm.readRTP = cm.readRTPTCPRecord
cm.readRTCP = cm.readRTCPTCPRecord
}
cm.tcpRTPFrame = &base.InterleavedFrame{Channel: cm.tcpChannel}
cm.tcpRTCPFrame = &base.InterleavedFrame{Channel: cm.tcpChannel + 1}
cm.tcpBuffer = make([]byte, maxPacketSize+4)
}
for _, ct := range cm.formats {
ct.start(cm)
}
if cm.udpRTPListener != nil {
cm.udpRTPListener.start(cm.c.state == clientStatePlay)
cm.udpRTCPListener.start(cm.c.state == clientStatePlay)
}
for _, ct := range cm.formats {
ct.startWriting()
}
}
func (cm *clientMedia) stop() {
if cm.udpRTPListener != nil {
cm.udpRTPListener.stop()
cm.udpRTCPListener.stop()
}
for _, ct := range cm.formats {
ct.stop()
}
}
func (cm *clientMedia) findFormatWithSSRC(ssrc uint32) *clientFormat {
for _, format := range cm.formats {
tssrc, ok := format.udpRTCPReceiver.LastSSRC()
if ok && tssrc == ssrc {
return format
}
}
return nil
}
func (cm *clientMedia) writePacketRTPInQueueUDP(payload []byte) {
atomic.AddUint64(cm.c.BytesSent, uint64(len(payload)))
cm.udpRTPListener.write(payload)
}
func (cm *clientMedia) writePacketRTCPInQueueUDP(payload []byte) {
atomic.AddUint64(cm.c.BytesSent, uint64(len(payload)))
cm.udpRTCPListener.write(payload)
}
func (cm *clientMedia) writePacketRTPInQueueTCP(payload []byte) {
atomic.AddUint64(cm.c.BytesSent, uint64(len(payload)))
cm.tcpRTPFrame.Payload = payload
cm.c.nconn.SetWriteDeadline(time.Now().Add(cm.c.WriteTimeout))
cm.c.conn.WriteInterleavedFrame(cm.tcpRTPFrame, cm.tcpBuffer)
}
func (cm *clientMedia) writePacketRTCPInQueueTCP(payload []byte) {
atomic.AddUint64(cm.c.BytesSent, uint64(len(payload)))
cm.tcpRTCPFrame.Payload = payload
cm.c.nconn.SetWriteDeadline(time.Now().Add(cm.c.WriteTimeout))
cm.c.conn.WriteInterleavedFrame(cm.tcpRTCPFrame, cm.tcpBuffer)
}
func (cm *clientMedia) writePacketRTCP(pkt rtcp.Packet) error {
byts, err := pkt.Marshal()
if err != nil {
return err
}
cm.c.writeMutex.RLock()
defer cm.c.writeMutex.RUnlock()
ok := cm.c.writer.queue(func() {
cm.writePacketRTCPInQueue(byts)
})
if !ok {
select {
case <-cm.c.done:
return cm.c.closeError
default:
return nil
}
}
return nil
}
func (cm *clientMedia) readRTPTCPPlay(payload []byte) error {
now := time.Now()
atomic.StoreInt64(cm.c.tcpLastFrameTime, now.Unix())
pkt := cm.c.rtpPacketBuffer.next()
err := pkt.Unmarshal(payload)
if err != nil {
return err
}
trak, ok := cm.formats[pkt.PayloadType]
if !ok {
return nil
}
trak.readRTPTCP(pkt)
return nil
}
func (cm *clientMedia) readRTCPTCPPlay(payload []byte) error {
now := time.Now()
atomic.StoreInt64(cm.c.tcpLastFrameTime, now.Unix())
if len(payload) > maxPacketSize {
cm.c.OnDecodeError(fmt.Errorf("RTCP packet size (%d) is greater than maximum allowed (%d)",
len(payload), maxPacketSize))
return nil
}
packets, err := rtcp.Unmarshal(payload)
if err != nil {
cm.c.OnDecodeError(err)
return nil
}
for _, pkt := range packets {
cm.onPacketRTCP(pkt)
}
return nil
}
func (cm *clientMedia) readRTPTCPRecord(payload []byte) error {
return nil
}
func (cm *clientMedia) readRTCPTCPRecord(payload []byte) error {
if len(payload) > maxPacketSize {
cm.c.OnDecodeError(fmt.Errorf("RTCP packet size (%d) is greater than maximum allowed (%d)",
len(payload), maxPacketSize))
return nil
}
packets, err := rtcp.Unmarshal(payload)
if err != nil {
cm.c.OnDecodeError(err)
return nil
}
for _, pkt := range packets {
cm.onPacketRTCP(pkt)
}
return nil
}
func (cm *clientMedia) readRTPUDPPlay(payload []byte) error {
plen := len(payload)
atomic.AddUint64(cm.c.BytesReceived, uint64(plen))
if plen == (maxPacketSize + 1) {
cm.c.OnDecodeError(fmt.Errorf("RTP packet is too big to be read with UDP"))
return nil
}
pkt := cm.c.rtpPacketBuffer.next()
err := pkt.Unmarshal(payload)
if err != nil {
cm.c.OnDecodeError(err)
return nil
}
trak, ok := cm.formats[pkt.PayloadType]
if !ok {
cm.c.OnDecodeError(fmt.Errorf("received RTP packet with unknown payload type (%d)", pkt.PayloadType))
return nil
}
trak.readRTPUDP(pkt)
return nil
}
func (cm *clientMedia) readRTCPUDPPlay(payload []byte) error {
now := time.Now()
plen := len(payload)
atomic.AddUint64(cm.c.BytesReceived, uint64(plen))
if plen == (maxPacketSize + 1) {
cm.c.OnDecodeError(fmt.Errorf("RTCP packet is too big to be read with UDP"))
return nil
}
packets, err := rtcp.Unmarshal(payload)
if err != nil {
cm.c.OnDecodeError(err)
return nil
}
for _, pkt := range packets {
if sr, ok := pkt.(*rtcp.SenderReport); ok {
format := cm.findFormatWithSSRC(sr.SSRC)
if format != nil {
format.udpRTCPReceiver.ProcessSenderReport(sr, now)
}
}
cm.onPacketRTCP(pkt)
}
return nil
}
func (cm *clientMedia) readRTPUDPRecord(payload []byte) error {
return nil
}
func (cm *clientMedia) readRTCPUDPRecord(payload []byte) error {
plen := len(payload)
atomic.AddUint64(cm.c.BytesReceived, uint64(plen))
if plen == (maxPacketSize + 1) {
cm.c.OnDecodeError(fmt.Errorf("RTCP packet is too big to be read with UDP"))
return nil
}
packets, err := rtcp.Unmarshal(payload)
if err != nil {
cm.c.OnDecodeError(err)
return nil
}
for _, pkt := range packets {
cm.onPacketRTCP(pkt)
}
return nil
}

119
clienttrack.go Normal file
View File

@@ -0,0 +1,119 @@
package gortsplib
import (
"fmt"
"time"
"github.com/pion/rtcp"
"github.com/pion/rtp"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/rtcpreceiver"
"github.com/aler9/gortsplib/v2/pkg/rtcpsender"
"github.com/aler9/gortsplib/v2/pkg/rtpreorderer"
)
type clientFormat struct {
c *Client
cm *clientMedia
format format.Format
udpReorderer *rtpreorderer.Reorderer // play
udpRTCPReceiver *rtcpreceiver.RTCPReceiver // play
rtcpSender *rtcpsender.RTCPSender // record
onPacketRTP func(*rtp.Packet)
}
func newClientFormat(cm *clientMedia, trak format.Format) *clientFormat {
return &clientFormat{
c: cm.c,
cm: cm,
format: trak,
onPacketRTP: func(*rtp.Packet) {},
}
}
func (ct *clientFormat) start(cm *clientMedia) {
if cm.c.state == clientStatePlay {
if cm.udpRTPListener != nil {
ct.udpReorderer = rtpreorderer.New()
ct.udpRTCPReceiver = rtcpreceiver.New(
cm.c.udpReceiverReportPeriod,
nil,
ct.format.ClockRate(), func(pkt rtcp.Packet) {
cm.writePacketRTCP(pkt)
})
}
} else {
ct.rtcpSender = rtcpsender.New(
ct.format.ClockRate(),
func(pkt rtcp.Packet) {
cm.writePacketRTCP(pkt)
})
}
}
// start RTCP senders after write() has been allocated in order to avoid a crash
func (ct *clientFormat) startWriting() {
if ct.c.state != clientStatePlay && !ct.c.DisableRTCPSenderReports {
ct.rtcpSender.Start(ct.c.senderReportPeriod)
}
}
func (ct *clientFormat) stop() {
if ct.udpRTCPReceiver != nil {
ct.udpRTCPReceiver.Close()
ct.udpRTCPReceiver = nil
}
if ct.rtcpSender != nil {
ct.rtcpSender.Close()
ct.rtcpSender = nil
}
}
func (ct *clientFormat) writePacketRTPWithNTP(pkt *rtp.Packet, ntp time.Time) error {
byts := make([]byte, maxPacketSize)
n, err := pkt.MarshalTo(byts)
if err != nil {
return err
}
byts = byts[:n]
ct.c.writeMutex.RLock()
defer ct.c.writeMutex.RUnlock()
ok := ct.c.writer.queue(func() {
ct.cm.writePacketRTPInQueue(byts)
})
if !ok {
select {
case <-ct.c.done:
return ct.c.closeError
default:
return nil
}
}
ct.rtcpSender.ProcessPacket(pkt, ntp, ct.format.PTSEqualsDTS(pkt))
return nil
}
func (ct *clientFormat) readRTPUDP(pkt *rtp.Packet) {
packets, missing := ct.udpReorderer.Process(pkt)
if missing != 0 {
ct.c.OnDecodeError(fmt.Errorf("%d RTP packet(s) lost", missing))
// do not return
}
now := time.Now()
for _, pkt := range packets {
ct.udpRTCPReceiver.ProcessPacket(pkt, now, ct.format.PTSEqualsDTS(pkt))
ct.onPacketRTP(pkt)
}
}
func (ct *clientFormat) readRTPTCP(pkt *rtp.Packet) {
ct.onPacketRTP(pkt)
}

View File

@@ -2,13 +2,11 @@ package gortsplib
import ( import (
"crypto/rand" "crypto/rand"
"fmt"
"net" "net"
"strconv" "strconv"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/pion/rtcp"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
) )
@@ -25,7 +23,7 @@ func randIntn(n int) int {
type clientUDPListener struct { type clientUDPListener struct {
c *Client c *Client
pc *net.UDPConn pc *net.UDPConn
ct *clientTrack cm *clientMedia
isRTP bool isRTP bool
readIP net.IP readIP net.IP
@@ -38,7 +36,7 @@ type clientUDPListener struct {
readerDone chan struct{} readerDone chan struct{}
} }
func newClientUDPListenerPair(c *Client, ct *clientTrack) (*clientUDPListener, *clientUDPListener) { func newClientUDPListenerPair(c *Client, cm *clientMedia) (*clientUDPListener, *clientUDPListener) {
// choose two consecutive ports in range 65535-10000 // choose two consecutive ports in range 65535-10000
// RTP port must be even and RTCP port odd // RTP port must be even and RTCP port odd
for { for {
@@ -47,7 +45,7 @@ func newClientUDPListenerPair(c *Client, ct *clientTrack) (*clientUDPListener, *
c, c,
false, false,
":"+strconv.FormatInt(int64(rtpPort), 10), ":"+strconv.FormatInt(int64(rtpPort), 10),
ct, cm,
true) true)
if err != nil { if err != nil {
continue continue
@@ -58,7 +56,7 @@ func newClientUDPListenerPair(c *Client, ct *clientTrack) (*clientUDPListener, *
c, c,
false, false,
":"+strconv.FormatInt(int64(rtcpPort), 10), ":"+strconv.FormatInt(int64(rtcpPort), 10),
ct, cm,
false) false)
if err != nil { if err != nil {
rtpListener.close() rtpListener.close()
@@ -73,7 +71,7 @@ func newClientUDPListener(
c *Client, c *Client,
multicast bool, multicast bool,
address string, address string,
ct *clientTrack, cm *clientMedia,
isRTP bool, isRTP bool,
) (*clientUDPListener, error) { ) (*clientUDPListener, error) {
var pc *net.UDPConn var pc *net.UDPConn
@@ -124,7 +122,7 @@ func newClientUDPListener(
return &clientUDPListener{ return &clientUDPListener{
c: c, c: c,
pc: pc, pc: pc,
ct: ct, cm: cm,
isRTP: isRTP, isRTP: isRTP,
lastPacketTime: func() *int64 { lastPacketTime: func() *int64 {
v := int64(0) v := int64(0)
@@ -159,15 +157,11 @@ func (u *clientUDPListener) stop() {
func (u *clientUDPListener) runReader(forPlay bool) { func (u *clientUDPListener) runReader(forPlay bool) {
defer close(u.readerDone) defer close(u.readerDone)
var processFunc func(time.Time, []byte) var readFunc func([]byte) error
if forPlay { if u.isRTP {
if u.isRTP { readFunc = u.cm.readRTP
processFunc = u.processPlayRTP
} else {
processFunc = u.processPlayRTCP
}
} else { } else {
processFunc = u.processRecordRTCP readFunc = u.cm.readRTCP
} }
for { for {
@@ -186,90 +180,7 @@ func (u *clientUDPListener) runReader(forPlay bool) {
now := time.Now() now := time.Now()
atomic.StoreInt64(u.lastPacketTime, now.Unix()) atomic.StoreInt64(u.lastPacketTime, now.Unix())
processFunc(now, buf[:n]) readFunc(buf[:n])
}
}
func (u *clientUDPListener) processPlayRTP(now time.Time, payload []byte) {
plen := len(payload)
atomic.AddUint64(u.c.BytesReceived, uint64(plen))
if plen == (maxPacketSize + 1) {
u.c.OnDecodeError(fmt.Errorf("RTP packet is too big to be read with UDP"))
return
}
pkt := u.ct.udpRTPPacketBuffer.next()
err := pkt.Unmarshal(payload)
if err != nil {
u.c.OnDecodeError(err)
return
}
packets, missing := u.ct.reorderer.Process(pkt)
if missing != 0 {
u.c.OnDecodeError(fmt.Errorf("%d RTP packet(s) lost", missing))
// do not return
}
for _, pkt := range packets {
ptsEqualsDTS := ptsEqualsDTS(u.ct.track, pkt)
u.ct.udpRTCPReceiver.ProcessPacketRTP(time.Now(), pkt, ptsEqualsDTS)
u.c.OnPacketRTP(&ClientOnPacketRTPCtx{
TrackID: u.ct.id,
Packet: pkt,
})
}
}
func (u *clientUDPListener) processPlayRTCP(now time.Time, payload []byte) {
plen := len(payload)
atomic.AddUint64(u.c.BytesReceived, uint64(plen))
if plen == (maxPacketSize + 1) {
u.c.OnDecodeError(fmt.Errorf("RTCP packet is too big to be read with UDP"))
return
}
packets, err := rtcp.Unmarshal(payload)
if err != nil {
u.c.OnDecodeError(err)
return
}
for _, pkt := range packets {
u.ct.udpRTCPReceiver.ProcessPacketRTCP(now, pkt)
u.c.OnPacketRTCP(&ClientOnPacketRTCPCtx{
TrackID: u.ct.id,
Packet: pkt,
})
}
}
func (u *clientUDPListener) processRecordRTCP(now time.Time, payload []byte) {
plen := len(payload)
atomic.AddUint64(u.c.BytesReceived, uint64(plen))
if plen == (maxPacketSize + 1) {
u.c.OnDecodeError(fmt.Errorf("RTCP packet is too big to be read with UDP"))
return
}
packets, err := rtcp.Unmarshal(payload)
if err != nil {
u.c.OnDecodeError(err)
return
}
for _, pkt := range packets {
u.c.OnPacketRTCP(&ClientOnPacketRTCPCtx{
TrackID: u.ct.id,
Packet: pkt,
})
} }
} }

60
clientwriter.go Normal file
View File

@@ -0,0 +1,60 @@
package gortsplib
import (
"github.com/aler9/gortsplib/v2/pkg/ringbuffer"
)
// this struct contains a queue that allows to detach the routine that is reading a stream
// from the routine that is writing a stream.
type clientWriter struct {
allowWriting bool
buffer *ringbuffer.RingBuffer
done chan struct{}
}
func (cw *clientWriter) start(c *Client) {
if c.state == clientStatePlay {
// when reading, buffer is only used to send RTCP receiver reports,
// that are much smaller than RTP packets and are sent at a fixed interval.
// decrease RAM consumption by allocating less buffers.
cw.buffer, _ = ringbuffer.New(8)
} else {
cw.buffer, _ = ringbuffer.New(uint64(c.WriteBufferCount))
}
cw.done = make(chan struct{})
go cw.run()
cw.allowWriting = true
}
func (cw *clientWriter) stop() {
cw.allowWriting = false
cw.buffer.Close()
<-cw.done
cw.buffer = nil
}
func (cw *clientWriter) run() {
defer close(cw.done)
for {
tmp, ok := cw.buffer.Pull()
if !ok {
return
}
tmp.(func())()
}
}
func (cw *clientWriter) queue(cb func()) bool {
if !cw.allowWriting {
return false
}
cw.buffer.Push(cb)
return true
}

View File

@@ -4,24 +4,26 @@ import (
"log" "log"
"net" "net"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. generate RTP/PCMA packets with GStreamer // 1. generate RTP/G711 packets with GStreamer
// 2. connect to a RTSP server, announce a PCMA track // 2. connect to a RTSP server, announce a G711 media
// 3. route the packets from GStreamer to the server // 3. route the packets from GStreamer to the server
func main() { func main() {
// open a listener to receive RTP/PCMA packets // open a listener to receive RTP/G711 packets
pc, err := net.ListenPacket("udp", "localhost:9000") pc, err := net.ListenPacket("udp", "localhost:9000")
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer pc.Close() defer pc.Close()
log.Println("Waiting for a RTP/PCMA stream on UDP port 9000 - you can send one with GStreamer:\n" + log.Println("Waiting for a RTP/G711 stream on UDP port 9000 - you can send one with GStreamer:\n" +
"gst-launch-1.0 audiotestsrc freq=300 ! audioconvert ! audioresample ! audio/x-raw,rate=8000" + "gst-launch-1.0 audiotestsrc freq=300 ! audioconvert ! audioresample ! audio/x-raw,rate=8000" +
" ! alawenc ! rtppcmapay ! udpsink host=127.0.0.1 port=9000") " ! alawenc ! rtppcmapay ! udpsink host=127.0.0.1 port=9000")
@@ -33,14 +35,17 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a PCMA track // create a media that contains a G711 format
track := &gortsplib.TrackG711{} medi := &media.Media{
Type: media.TypeAudio,
Formats: []format.Format{&format.G711{}},
}
c := gortsplib.Client{} c := gortsplib.Client{}
// connect to the server and start publishing the track // connect to the server and start recording the media
err = c.StartPublishing("rtsp://localhost:8554/mystream", err = c.StartRecording("rtsp://localhost:8554/mystream",
gortsplib.Tracks{track}) media.Medias{medi})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -55,7 +60,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(0, &pkt) err = c.WritePacketRTP(medi, &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -4,13 +4,15 @@ import (
"log" "log"
"net" "net"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. generate RTP/G722 packets with GStreamer // 1. generate RTP/G722 packets with GStreamer
// 2. connect to a RTSP server, announce a G722 track // 2. connect to a RTSP server, announce a G722 media
// 3. route the packets from GStreamer to the server // 3. route the packets from GStreamer to the server
func main() { func main() {
@@ -33,14 +35,17 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a G722 track // create a media that contains a G722 format
track := &gortsplib.TrackG722{} medi := &media.Media{
Type: media.TypeAudio,
Formats: []format.Format{&format.G722{}},
}
c := gortsplib.Client{} c := gortsplib.Client{}
// connect to the server and start publishing the track // connect to the server and start recording the media
err = c.StartPublishing("rtsp://localhost:8554/mystream", err = c.StartRecording("rtsp://localhost:8554/mystream",
gortsplib.Tracks{track}) media.Medias{medi})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -55,7 +60,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(0, &pkt) err = c.WritePacketRTP(medi, &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -4,13 +4,15 @@ import (
"log" "log"
"net" "net"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. generate RTP/H264 packets with GStreamer // 1. generate RTP/H264 packets with GStreamer
// 2. connect to a RTSP server, announce an H264 track // 2. connect to a RTSP server, announce an H264 media
// 3. route the packets from GStreamer to the server // 3. route the packets from GStreamer to the server
func main() { func main() {
@@ -34,16 +36,19 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create an H264 track // create a media that contains a H264 format
track := &gortsplib.TrackH264{ medi := &media.Media{
PayloadType: 96, Type: media.TypeVideo,
PacketizationMode: 1, Formats: []format.Format{&format.H264{
PayloadTyp: 96,
PacketizationMode: 1,
}},
} }
// connect to the server and start publishing the track // connect to the server and start recording the media
c := gortsplib.Client{} c := gortsplib.Client{}
err = c.StartPublishing("rtsp://localhost:8554/mystream", err = c.StartRecording("rtsp://localhost:8554/mystream",
gortsplib.Tracks{track}) media.Medias{medi})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -58,7 +63,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(0, &pkt) err = c.WritePacketRTP(medi, &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -4,13 +4,15 @@ import (
"log" "log"
"net" "net"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. generate RTP/H265 packets with GStreamer // 1. generate RTP/H265 packets with GStreamer
// 2. connect to a RTSP server, announce an H265 track // 2. connect to a RTSP server, announce an H265 media
// 3. route the packets from GStreamer to the server // 3. route the packets from GStreamer to the server
func main() { func main() {
@@ -34,15 +36,18 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create an H265 track // create a media that contains a H265 format
track := &gortsplib.TrackH265{ medi := &media.Media{
PayloadType: 96, Type: media.TypeVideo,
Formats: []format.Format{&format.H265{
PayloadTyp: 96,
}},
} }
// connect to the server and start publishing the track // connect to the server and start recording the media
c := gortsplib.Client{} c := gortsplib.Client{}
err = c.StartPublishing("rtsp://localhost:8554/mystream", err = c.StartRecording("rtsp://localhost:8554/mystream",
gortsplib.Tracks{track}) media.Medias{medi})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -57,7 +62,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(0, &pkt) err = c.WritePacketRTP(medi, &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -4,13 +4,15 @@ import (
"log" "log"
"net" "net"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. generate RTP/LPCM packets with GStreamer // 1. generate RTP/LPCM packets with GStreamer
// 2. connect to a RTSP server, announce an LPCM track // 2. connect to a RTSP server, announce an LPCM media
// 3. route the packets from GStreamer to the server // 3. route the packets from GStreamer to the server
func main() { func main() {
@@ -33,19 +35,22 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create an LPCM track // create a media that contains a LPCM format
track := &gortsplib.TrackLPCM{ medi := &media.Media{
PayloadType: 96, Type: media.TypeAudio,
BitDepth: 16, Formats: []format.Format{&format.LPCM{
SampleRate: 44100, PayloadTyp: 96,
ChannelCount: 1, BitDepth: 16,
SampleRate: 44100,
ChannelCount: 1,
}},
} }
c := gortsplib.Client{} c := gortsplib.Client{}
// connect to the server and start publishing the track // connect to the server and start recording the media
err = c.StartPublishing("rtsp://localhost:8554/mystream", err = c.StartRecording("rtsp://localhost:8554/mystream",
gortsplib.Tracks{track}) media.Medias{medi})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -60,7 +65,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(0, &pkt) err = c.WritePacketRTP(medi, &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -4,14 +4,16 @@ import (
"log" "log"
"net" "net"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/pkg/mpeg4audio" "github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. generate RTP/MPEG4-audio packets with GStreamer // 1. generate RTP/MPEG4-audio packets with GStreamer
// 2. connect to a RTSP server, announce an MPEG4-audio track // 2. connect to a RTSP server, announce an MPEG4-audio media
// 3. route the packets from GStreamer to the server // 3. route the packets from GStreamer to the server
func main() { func main() {
@@ -34,23 +36,26 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create an MPEG4-audio track // create a media that contains a MPEG4-audio format
track := &gortsplib.TrackMPEG4Audio{ medi := &media.Media{
PayloadType: 96, Type: media.TypeAudio,
Config: &mpeg4audio.Config{ Formats: []format.Format{&format.MPEG4Audio{
Type: mpeg4audio.ObjectTypeAACLC, PayloadTyp: 96,
SampleRate: 48000, Config: &mpeg4audio.Config{
ChannelCount: 2, Type: mpeg4audio.ObjectTypeAACLC,
}, SampleRate: 48000,
SizeLength: 13, ChannelCount: 2,
IndexLength: 3, },
IndexDeltaLength: 3, SizeLength: 13,
IndexLength: 3,
IndexDeltaLength: 3,
}},
} }
// connect to the server and start publishing the track // connect to the server and start recording the media
c := gortsplib.Client{} c := gortsplib.Client{}
err = c.StartPublishing("rtsp://localhost:8554/mystream", err = c.StartRecording("rtsp://localhost:8554/mystream",
gortsplib.Tracks{track}) media.Medias{medi})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -65,7 +70,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(0, &pkt) err = c.WritePacketRTP(medi, &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -4,13 +4,15 @@ import (
"log" "log"
"net" "net"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. generate RTP/Opus packets with GStreamer // 1. generate RTP/Opus packets with GStreamer
// 2. connect to a RTSP server, announce an Opus track // 2. connect to a RTSP server, announce an Opus media
// 3. route the packets from GStreamer to the server // 3. route the packets from GStreamer to the server
func main() { func main() {
@@ -33,18 +35,21 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create an Opus track // create a media that contains a Opus format
track := &gortsplib.TrackOpus{ medi := &media.Media{
PayloadType: 96, Type: media.TypeAudio,
SampleRate: 48000, Formats: []format.Format{&format.Opus{
ChannelCount: 2, PayloadTyp: 96,
SampleRate: 48000,
ChannelCount: 2,
}},
} }
c := gortsplib.Client{} c := gortsplib.Client{}
// connect to the server and start publishing the track // connect to the server and start recording the media
err = c.StartPublishing("rtsp://localhost:8554/mystream", err = c.StartRecording("rtsp://localhost:8554/mystream",
gortsplib.Tracks{track}) media.Medias{medi})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -59,7 +64,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(0, &pkt) err = c.WritePacketRTP(medi, &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -4,13 +4,15 @@ import (
"log" "log"
"net" "net"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. generate RTP/VP8 packets with GStreamer // 1. generate RTP/VP8 packets with GStreamer
// 2. connect to a RTSP server, announce an VP8 track // 2. connect to a RTSP server, announce an VP8 media
// 3. route the packets from GStreamer to the server // 3. route the packets from GStreamer to the server
func main() { func main() {
@@ -34,15 +36,18 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a VP8 track // create a media that contains a VP8 format
track := &gortsplib.TrackVP8{ medi := &media.Media{
PayloadType: 96, Type: media.TypeVideo,
Formats: []format.Format{&format.VP8{
PayloadTyp: 96,
}},
} }
// connect to the server and start publishing the track // connect to the server and start recording the media
c := gortsplib.Client{} c := gortsplib.Client{}
err = c.StartPublishing("rtsp://localhost:8554/mystream", err = c.StartRecording("rtsp://localhost:8554/mystream",
gortsplib.Tracks{track}) media.Medias{medi})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -57,7 +62,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(0, &pkt) err = c.WritePacketRTP(medi, &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -4,13 +4,15 @@ import (
"log" "log"
"net" "net"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. generate RTP/VP9 packets with GStreamer // 1. generate RTP/VP9 packets with GStreamer
// 2. connect to a RTSP server, announce an VP9 track // 2. connect to a RTSP server, announce an VP9 media
// 3. route the packets from GStreamer to the server // 3. route the packets from GStreamer to the server
func main() { func main() {
@@ -34,15 +36,18 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a VP9 track // create a media that contains a VP9 format
track := &gortsplib.TrackVP9{ medi := &media.Media{
PayloadType: 96, Type: media.TypeVideo,
Formats: []format.Format{&format.VP9{
PayloadTyp: 96,
}},
} }
// connect to the server and start publishing the track // connect to the server and start recording the media
c := gortsplib.Client{} c := gortsplib.Client{}
err = c.StartPublishing("rtsp://localhost:8554/mystream", err = c.StartRecording("rtsp://localhost:8554/mystream",
gortsplib.Tracks{track}) media.Medias{medi})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -57,7 +62,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(0, &pkt) err = c.WritePacketRTP(medi, &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -5,14 +5,16 @@ import (
"net" "net"
"time" "time"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. set additional client options // 1. set additional client options
// 2. generate RTP/H264 frames from a file with GStreamer // 2. generate RTP/H264 frames from a file with GStreamer
// 3. connect to a RTSP server, announce an H264 track // 3. connect to a RTSP server, announce an H264 media
// 4. write the frames to the server // 4. write the frames to the server
func main() { func main() {
@@ -35,10 +37,13 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create an H264 track // create a media that contains a H264 media
track := &gortsplib.TrackH264{ medi := &media.Media{
PayloadType: 96, Type: media.TypeVideo,
PacketizationMode: 1, Formats: []format.Format{&format.H264{
PayloadTyp: 96,
PacketizationMode: 1,
}},
} }
// Client allows to set additional client options // Client allows to set additional client options
@@ -51,9 +56,9 @@ func main() {
WriteTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second,
} }
// connect to the server and start publishing the track // connect to the server and start recording the media
err = c.StartPublishing("rtsp://localhost:8554/mystream", err = c.StartRecording("rtsp://localhost:8554/mystream",
gortsplib.Tracks{track}) media.Medias{medi})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -68,7 +73,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(0, &pkt) err = c.WritePacketRTP(medi, &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -5,13 +5,15 @@ import (
"net" "net"
"time" "time"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. generate RTP/H264 frames from a file with GStreamer // 1. generate RTP/H264 frames from a file with GStreamer
// 2. connect to a RTSP server, announce an H264 track // 2. connect to a RTSP server, announce an H264 media
// 3. write the frames to the server for 5 seconds // 3. write the frames to the server for 5 seconds
// 4. pause for 5 seconds // 4. pause for 5 seconds
// 5. repeat // 5. repeat
@@ -36,16 +38,19 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create an H264 track // create a media that contains a H264 format
track := &gortsplib.TrackH264{ medi := &media.Media{
PayloadType: 96, Type: media.TypeVideo,
PacketizationMode: 1, Formats: []format.Format{&format.H264{
PayloadTyp: 96,
PacketizationMode: 1,
}},
} }
// connect to the server and start publishing the track // connect to the server and start recording the media
c := gortsplib.Client{} c := gortsplib.Client{}
err = c.StartPublishing("rtsp://localhost:8554/mystream", err = c.StartRecording("rtsp://localhost:8554/mystream",
gortsplib.Tracks{track}) media.Medias{medi})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -62,7 +67,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
c.WritePacketRTP(0, &pkt) c.WritePacketRTP(medi, &pkt)
// read another RTP packet from source // read another RTP packet from source
n, _, err = pc.ReadFrom(buf) n, _, err = pc.ReadFrom(buf)

View File

@@ -3,13 +3,13 @@ package main
import ( import (
"log" "log"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/pkg/url" "github.com/aler9/gortsplib/v2/pkg/url"
) )
// This example shows how to // This example shows how to
// 1. connect to a RTSP server // 1. connect to a RTSP server
// 2. get and print informations about tracks published on a path. // 2. get and print informations about medias published on a path.
func main() { func main() {
c := gortsplib.Client{} c := gortsplib.Client{}
@@ -25,10 +25,10 @@ func main() {
} }
defer c.Close() defer c.Close()
tracks, _, _, err := c.Describe(u) medias, _, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
log.Printf("available tracks: %v\n", tracks) log.Printf("available medias: %v\n", medias)
} }

View File

@@ -0,0 +1,75 @@
package main
import (
"log"
"github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp"
)
// This example shows how to
// 1. connect to a RTSP server
// 2. check if there's a G711 media
// 3. get G711 frames of that media
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published medias
medias, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the G711 media and format
var trak *format.G711
medi := medias.Find(&trak)
if medi == nil {
panic("media not found")
}
// setup decoder
rtpDec := trak.CreateDecoder()
// setup the chosen media only
_, err = c.Setup(medi, baseURL, 0, 0)
if err != nil {
panic(err)
}
// called when a RTP packet arrives
c.OnPacketRTP(medi, trak, func(pkt *rtp.Packet) {
// decode a G711 packet from the RTP packet
op, _, err := rtpDec.Decode(pkt)
if err != nil {
return
}
// print
log.Printf("received G711 frame of size %d\n", len(op))
})
// start playing
_, err = c.Play(nil)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -0,0 +1,75 @@
package main
import (
"log"
"github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp"
)
// This example shows how to
// 1. connect to a RTSP server
// 2. check if there's a G722 media
// 3. get G722 frames of that media
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published medias
medias, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the G722 media and format
var trak *format.G722
medi := medias.Find(&trak)
if medi == nil {
panic("media not found")
}
// setup decoder
rtpDec := trak.CreateDecoder()
// setup the chosen media only
_, err = c.Setup(medi, baseURL, 0, 0)
if err != nil {
panic(err)
}
// called when a RTP packet arrives
c.OnPacketRTP(medi, trak, func(pkt *rtp.Packet) {
// decode a G722 packet from the RTP packet
op, _, err := rtpDec.Decode(pkt)
if err != nil {
return
}
// print
log.Printf("received G722 frame of size %d\n", len(op))
})
// start playing
_, err = c.Play(nil)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -8,13 +8,15 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/pkg/url" "github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. connect to a RTSP server and read all tracks on a path // 1. connect to a RTSP server
// 2. check if there's a H264 track // 2. check if there's a H264 media
// 3. decode H264 into RGBA frames // 3. decode H264 into RGBA frames
// 4. encode the frames into JPEG images and save them on disk // 4. encode the frames into JPEG images and save them on disk
@@ -54,27 +56,21 @@ func main() {
} }
defer c.Close() defer c.Close()
// find published tracks // find published medias
tracks, baseURL, _, err := c.Describe(u) medias, baseURL, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the H264 track // find the H264 media and format
track := func() *gortsplib.TrackH264 { var trak *format.H264
for _, track := range tracks { medi := medias.Find(&trak)
if track, ok := track.(*gortsplib.TrackH264); ok { if medi == nil {
return track panic("media not found")
}
}
return nil
}()
if track == nil {
panic("H264 track not found")
} }
// setup RTP/H264->H264 decoder // setup RTP/H264->H264 decoder
rtpDec := track.CreateDecoder() rtpDec := trak.CreateDecoder()
// setup H264->raw frames decoder // setup H264->raw frames decoder
h264RawDec, err := newH264Decoder() h264RawDec, err := newH264Decoder()
@@ -84,20 +80,26 @@ func main() {
defer h264RawDec.close() defer h264RawDec.close()
// if SPS and PPS are present into the SDP, send them to the decoder // if SPS and PPS are present into the SDP, send them to the decoder
sps := track.SafeSPS() sps := trak.SafeSPS()
if sps != nil { if sps != nil {
h264RawDec.decode(sps) h264RawDec.decode(sps)
} }
pps := track.SafePPS() pps := trak.SafePPS()
if pps != nil { if pps != nil {
h264RawDec.decode(pps) h264RawDec.decode(pps)
} }
// setup the chosen media only
_, err = c.Setup(medi, baseURL, 0, 0)
if err != nil {
panic(err)
}
// called when a RTP packet arrives // called when a RTP packet arrives
saveCount := 0 saveCount := 0
c.OnPacketRTP = func(ctx *gortsplib.ClientOnPacketRTPCtx) { c.OnPacketRTP(medi, trak, func(pkt *rtp.Packet) {
// convert RTP packets into NALUs // convert RTP packets into NALUs
nalus, _, err := rtpDec.Decode(ctx.Packet) nalus, _, err := rtpDec.Decode(pkt)
if err != nil { if err != nil {
return return
} }
@@ -126,10 +128,10 @@ func main() {
os.Exit(1) os.Exit(1)
} }
} }
} })
// setup and read the H264 track only // start playing
err = c.SetupAndPlay(gortsplib.Tracks{track}, baseURL) _, err = c.Play(nil)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -0,0 +1,79 @@
package main
import (
"github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp"
)
// This example shows how to
// 1. connect to a RTSP server
// 2. check if there's a H264 media
// 3. save the content of the H264 media into a file in MPEG-TS format
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published medias
medias, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the H264 media and format
var trak *format.H264
medi := medias.Find(&trak)
if medi == nil {
panic("media not found")
}
// setup RTP/H264->H264 decoder
rtpDec := trak.CreateDecoder()
// setup H264->MPEGTS muxer
mpegtsMuxer, err := newMPEGTSMuxer(trak.SafeSPS(), trak.SafePPS())
if err != nil {
panic(err)
}
// setup the chosen media only
_, err = c.Setup(medi, baseURL, 0, 0)
if err != nil {
panic(err)
}
// called when a RTP packet arrives
c.OnPacketRTP(medi, trak, func(pkt *rtp.Packet) {
// convert RTP packets into NALUs
nalus, pts, err := rtpDec.Decode(pkt)
if err != nil {
return
}
// encode H264 NALUs into MPEG-TS
mpegtsMuxer.encode(nalus, pts)
})
// start playing
_, err = c.Play(nil)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -7,7 +7,7 @@ import (
"os" "os"
"time" "time"
"github.com/aler9/gortsplib/pkg/h264" "github.com/aler9/gortsplib/v2/pkg/h264"
"github.com/asticode/go-astits" "github.com/asticode/go-astits"
) )

View File

@@ -3,13 +3,15 @@ package main
import ( import (
"log" "log"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/pkg/url" "github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. connect to a RTSP server and read all tracks on a path // 1. connect to a RTSP server
// 2. check if there's an H264 track // 2. check if there's an H264 media
// 3. decode H264 into RGBA frames // 3. decode H264 into RGBA frames
// This example requires the ffmpeg libraries, that can be installed in this way: // This example requires the ffmpeg libraries, that can be installed in this way:
@@ -31,27 +33,21 @@ func main() {
} }
defer c.Close() defer c.Close()
// find published tracks // find published medias
tracks, baseURL, _, err := c.Describe(u) medias, baseURL, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the H264 track // find the H264 media and format
track := func() *gortsplib.TrackH264 { var trak *format.H264
for _, track := range tracks { medi := medias.Find(&trak)
if track, ok := track.(*gortsplib.TrackH264); ok { if medi == nil {
return track panic("media not found")
}
}
return nil
}()
if track == nil {
panic("H264 track not found")
} }
// setup RTP/H264->H264 decoder // setup RTP/H264->H264 decoder
rtpDec := track.CreateDecoder() rtpDec := trak.CreateDecoder()
// setup H264->raw frames decoder // setup H264->raw frames decoder
h264RawDec, err := newH264Decoder() h264RawDec, err := newH264Decoder()
@@ -61,19 +57,25 @@ func main() {
defer h264RawDec.close() defer h264RawDec.close()
// if SPS and PPS are present into the SDP, send them to the decoder // if SPS and PPS are present into the SDP, send them to the decoder
sps := track.SafeSPS() sps := trak.SafeSPS()
if sps != nil { if sps != nil {
h264RawDec.decode(sps) h264RawDec.decode(sps)
} }
pps := track.SafePPS() pps := trak.SafePPS()
if pps != nil { if pps != nil {
h264RawDec.decode(pps) h264RawDec.decode(pps)
} }
// setup the chosen media only
_, err = c.Setup(medi, baseURL, 0, 0)
if err != nil {
panic(err)
}
// called when a RTP packet arrives // called when a RTP packet arrives
c.OnPacketRTP = func(ctx *gortsplib.ClientOnPacketRTPCtx) { c.OnPacketRTP(medi, trak, func(pkt *rtp.Packet) {
// convert RTP packets into NALUs // convert RTP packets into NALUs
nalus, _, err := rtpDec.Decode(ctx.Packet) nalus, _, err := rtpDec.Decode(pkt)
if err != nil { if err != nil {
return return
} }
@@ -92,10 +94,10 @@ func main() {
log.Printf("decoded frame with size %v", img.Bounds().Max) log.Printf("decoded frame with size %v", img.Bounds().Max)
} }
} })
// setup and read the H264 track only // start playing
err = c.SetupAndPlay(gortsplib.Tracks{track}, baseURL) _, err = c.Play(nil)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -0,0 +1,76 @@
package main
import (
"log"
"github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp"
)
// This example shows how to
// 1. connect to a RTSP server
// 2. check if there's an H265 media
// 3. get access units of that media
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published medias
medias, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the H265 media and format
var trak *format.H265
medi := medias.Find(&trak)
if medi == nil {
panic("media not found")
}
// setup RTP/H265->H265 decoder
rtpDec := trak.CreateDecoder()
// setup the chosen media only
_, err = c.Setup(medi, baseURL, 0, 0)
if err != nil {
panic(err)
}
// called when a RTP packet arrives
c.OnPacketRTP(medi, trak, func(pkt *rtp.Packet) {
// convert RTP packets into NALUs
nalus, pts, err := rtpDec.Decode(pkt)
if err != nil {
return
}
for _, nalu := range nalus {
log.Printf("received NALU with PTS %v and size %d\n", pts, len(nalu))
}
})
// start playing
_, err = c.Play(nil)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -0,0 +1,75 @@
package main
import (
"log"
"github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp"
)
// This example shows how to
// 1. connect to a RTSP server
// 2. check if there's an LPCM media
// 3. get LPCM packets of that media
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published medias
medias, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the LPCM media and format
var trak *format.LPCM
medi := medias.Find(&trak)
if medi == nil {
panic("media not found")
}
// setup decoder
rtpDec := trak.CreateDecoder()
// setup the chosen media only
_, err = c.Setup(medi, baseURL, 0, 0)
if err != nil {
panic(err)
}
// called when a RTP packet arrives
c.OnPacketRTP(medi, trak, func(pkt *rtp.Packet) {
// decode LPCM samples from the RTP packet
op, _, err := rtpDec.Decode(pkt)
if err != nil {
return
}
// print
log.Printf("received LPCM samples of size %d\n", len(op))
})
// start playing
_, err = c.Play(nil)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -0,0 +1,77 @@
package main
import (
"log"
"github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp"
)
// This example shows how to
// 1. connect to a RTSP server
// 2. check if there's an MPEG4-audio media
// 3. get access units of that media
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published medias
medias, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the MPEG4-audio media and format
var trak *format.MPEG4Audio
medi := medias.Find(&trak)
if medi == nil {
panic("media not found")
}
// setup decoder
rtpDec := trak.CreateDecoder()
// setup the chosen media only
_, err = c.Setup(medi, baseURL, 0, 0)
if err != nil {
panic(err)
}
// called when a RTP packet arrives
c.OnPacketRTP(medi, trak, func(pkt *rtp.Packet) {
// decode MPEG4-audio AUs from the RTP packet
aus, _, err := rtpDec.Decode(pkt)
if err != nil {
return
}
// print AUs
for _, au := range aus {
log.Printf("received MPEG4-audio AU of size %d\n", len(au))
}
})
// start playing
_, err = c.Play(nil)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -0,0 +1,75 @@
package main
import (
"log"
"github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp"
)
// This example shows how to
// 1. connect to a RTSP server
// 2. check if there's an Opus media
// 3. get Opus packets of that media
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published medias
medias, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the Opus media and format
var trak *format.Opus
medi := medias.Find(&trak)
if medi == nil {
panic("media not found")
}
// setup decoder
rtpDec := trak.CreateDecoder()
// setup the chosen media only
_, err = c.Setup(medi, baseURL, 0, 0)
if err != nil {
panic(err)
}
// called when a RTP packet arrives
c.OnPacketRTP(medi, trak, func(pkt *rtp.Packet) {
// decode an Opus packet from the RTP packet
op, _, err := rtpDec.Decode(pkt)
if err != nil {
return
}
// print
log.Printf("received Opus packet of size %d\n", len(op))
})
// start playing
_, err = c.Play(nil)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -0,0 +1,74 @@
package main
import (
"log"
"github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp"
)
// This example shows how to
// 1. connect to a RTSP server
// 2. check if there's an VP8 media
// 3. get access units of that media
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published medias
medias, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the VP8 media and format
var trak *format.VP8
medi := medias.Find(&trak)
if medi == nil {
panic("media not found")
}
// setup decoder
rtpDec := trak.CreateDecoder()
// setup the chosen media only
_, err = c.Setup(medi, baseURL, 0, 0)
if err != nil {
panic(err)
}
// called when a RTP packet arrives
c.OnPacketRTP(medi, trak, func(pkt *rtp.Packet) {
// decode a VP8 frame from the RTP packet
vf, _, err := rtpDec.Decode(pkt)
if err != nil {
return
}
log.Printf("received frame of size %d\n", len(vf))
})
// start playing
_, err = c.Play(nil)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -0,0 +1,74 @@
package main
import (
"log"
"github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp"
)
// This example shows how to
// 1. connect to a RTSP server
// 2. check if there's an VP9 media
// 3. get access units of that media
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published medias
medias, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the VP9 media and format
var trak *format.VP9
medi := medias.Find(&trak)
if medi == nil {
panic("media not found")
}
// setup decoder
rtpDec := trak.CreateDecoder()
// setup the chosen media only
_, err = c.Setup(medi, baseURL, 0, 0)
if err != nil {
panic(err)
}
// called when a RTP packet arrives
c.OnPacketRTP(medi, trak, func(pkt *rtp.Packet) {
// decode a VP9 frame from the RTP packet
vf, _, err := rtpDec.Decode(pkt)
if err != nil {
return
}
log.Printf("received frame of size %d\n", len(vf))
})
// start playing
_, err = c.Play(nil)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -4,13 +4,17 @@ import (
"log" "log"
"time" "time"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/pkg/url" "github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtcp"
"github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. set additional client options // 1. set additional client options
// 2. connect to a RTSP server and read all tracks on a path // 2. connect to a RTSP server and read all medias on a path
func main() { func main() {
// Client allows to set additional client options // Client allows to set additional client options
@@ -21,14 +25,6 @@ func main() {
ReadTimeout: 10 * time.Second, ReadTimeout: 10 * time.Second,
// timeout of write operations // timeout of write operations
WriteTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second,
// called when a RTP packet arrives
OnPacketRTP: func(ctx *gortsplib.ClientOnPacketRTPCtx) {
log.Printf("RTP packet from track %d, payload type %d\n", ctx.TrackID, ctx.Packet.Header.PayloadType)
},
// called when a RTCP packet arrives
OnPacketRTCP: func(ctx *gortsplib.ClientOnPacketRTCPCtx) {
log.Printf("RTCP packet from track %d, type %T\n", ctx.TrackID, ctx.Packet)
},
} }
// parse URL // parse URL
@@ -44,14 +40,30 @@ func main() {
} }
defer c.Close() defer c.Close()
// find published tracks // find published medias
tracks, baseURL, _, err := c.Describe(u) medias, baseURL, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// setup and read all tracks // setup all medias
err = c.SetupAndPlay(tracks, baseURL) err = c.SetupAll(medias, baseURL)
if err != nil {
panic(err)
}
// called when a RTP packet arrives
c.OnPacketRTPAny(func(medi *media.Media, trak format.Format, pkt *rtp.Packet) {
log.Printf("RTP packet from media %v\n", medi)
})
// called when a RTCP packet arrives
c.OnPacketRTCPAny(func(medi *media.Media, pkt rtcp.Packet) {
log.Printf("RTCP packet from media %v, type %T\n", medi, pkt)
})
// start playing
_, err = c.Play(nil)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -1,65 +0,0 @@
package main
import (
"fmt"
"log"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/url"
)
// This example shows how to
// 1. connect to a RTSP server
// 2. get tracks published on a path
// 3. read only the H264 track
func main() {
c := gortsplib.Client{
// called when a RTP packet arrives
OnPacketRTP: func(ctx *gortsplib.ClientOnPacketRTPCtx) {
log.Printf("RTP packet from track %d, payload type %d\n", ctx.TrackID, ctx.Packet.Header.PayloadType)
},
// called when a RTCP packet arrives
OnPacketRTCP: func(ctx *gortsplib.ClientOnPacketRTCPCtx) {
log.Printf("RTCP packet from track %d, type %T\n", ctx.TrackID, ctx.Packet)
},
}
u, err := url.Parse("rtsp://myserver/mypath")
if err != nil {
panic(err)
}
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
tracks, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the H264 track
h264Track := func() gortsplib.Track {
for _, t := range tracks {
if _, ok := t.(*gortsplib.TrackH264); ok {
return t
}
}
return nil
}()
if h264Track == nil {
panic(fmt.Errorf("H264 track not found"))
}
// setup and play the H264 track only
err = c.SetupAndPlay(gortsplib.Tracks{h264Track}, baseURL)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -4,27 +4,22 @@ import (
"log" "log"
"time" "time"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/pkg/url" "github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtcp"
"github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. connect to a RTSP server and read all tracks on a path // 1. connect to a RTSP server and read all medias on a path
// 2. wait for 5 seconds // 2. wait for 5 seconds
// 3. pause for 5 seconds // 3. pause for 5 seconds
// 4. repeat // 4. repeat
func main() { func main() {
c := gortsplib.Client{ c := gortsplib.Client{}
// called when a RTP packet arrives
OnPacketRTP: func(ctx *gortsplib.ClientOnPacketRTPCtx) {
log.Printf("RTP packet from track %d, payload type %d\n", ctx.TrackID, ctx.Packet.Header.PayloadType)
},
// called when a RTCP packet arrives
OnPacketRTCP: func(ctx *gortsplib.ClientOnPacketRTCPCtx) {
log.Printf("RTCP packet from track %d, type %T\n", ctx.TrackID, ctx.Packet)
},
}
// parse URL // parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream") u, err := url.Parse("rtsp://localhost:8554/mystream")
@@ -39,14 +34,30 @@ func main() {
} }
defer c.Close() defer c.Close()
// find published tracks // find published medias
tracks, baseURL, _, err := c.Describe(u) medias, baseURL, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// setup and read all tracks // setup all medias
err = c.SetupAndPlay(tracks, baseURL) err = c.SetupAll(medias, baseURL)
if err != nil {
panic(err)
}
// called when a RTP packet arrives
c.OnPacketRTPAny(func(medi *media.Media, trak format.Format, pkt *rtp.Packet) {
log.Printf("RTP packet from media %v\n", medi)
})
// called when a RTCP packet arrives
c.OnPacketRTCPAny(func(medi *media.Media, pkt rtcp.Packet) {
log.Printf("RTCP packet from media %v, type %T\n", medi, pkt)
})
// start playing
_, err = c.Play(nil)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -3,13 +3,16 @@ package main
import ( import (
"log" "log"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/pkg/url" "github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp"
) )
// This example shows how to // This example shows how to
// 1. connect to a RTSP server and read all tracks on a path // 1. connect to a RTSP server and read all medias on a path
// 2. re-publish all tracks on another path. // 2. re-publish all medias on another path.
func main() { func main() {
reader := gortsplib.Client{} reader := gortsplib.Client{}
@@ -27,30 +30,35 @@ func main() {
} }
defer reader.Close() defer reader.Close()
// find published tracks // find published medias
tracks, baseURL, _, err := reader.Describe(sourceURL) medias, baseURL, _, err := reader.Describe(sourceURL)
if err != nil { if err != nil {
panic(err) panic(err)
} }
log.Printf("republishing %d tracks", len(tracks)) log.Printf("republishing %d medias", len(medias))
// connect to the server and start recording the same medias
publisher := gortsplib.Client{} publisher := gortsplib.Client{}
err = publisher.StartRecording("rtsp://localhost:8554/mystream2", medias)
// connect to the server and start publishing
err = publisher.StartPublishing("rtsp://localhost:8554/mystream2", tracks)
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer publisher.Close() defer publisher.Close()
// called when a RTP packet arrives // setup all medias
reader.OnPacketRTP = func(ctx *gortsplib.ClientOnPacketRTPCtx) { err = reader.SetupAll(medias, baseURL)
publisher.WritePacketRTP(ctx.TrackID, ctx.Packet) if err != nil {
panic(err)
} }
// setup and read all tracks // read RTP packets from reader and write them to publisher
err = reader.SetupAndPlay(tracks, baseURL) reader.OnPacketRTPAny(func(medi *media.Media, trak format.Format, pkt *rtp.Packet) {
publisher.WritePacketRTP(medi, pkt)
})
// start playing
_, err = reader.Play(nil)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -1,73 +0,0 @@
package main
import (
"log"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/url"
)
// This example shows how to
// 1. connect to a RTSP server and read all tracks on a path
// 2. check if there's a PCMA track
// 3. get PCMA frames of that track
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published tracks
tracks, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the PCMA track
track := func() *gortsplib.TrackG711 {
for _, track := range tracks {
if tt, ok := track.(*gortsplib.TrackG711); ok {
return tt
}
}
return nil
}()
if track == nil {
panic("PCMA track not found")
}
// setup decoder
dec := track.CreateDecoder()
// called when a RTP packet arrives
c.OnPacketRTP = func(ctx *gortsplib.ClientOnPacketRTPCtx) {
// decode an PCMA packet from the RTP packet
op, _, err := dec.Decode(ctx.Packet)
if err != nil {
return
}
// print
log.Printf("received PCMA frame of size %d\n", len(op))
}
// setup and read the PCMA track only
err = c.SetupAndPlay(gortsplib.Tracks{track}, baseURL)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -1,73 +0,0 @@
package main
import (
"log"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/url"
)
// This example shows how to
// 1. connect to a RTSP server and read all tracks on a path
// 2. check if there's a G722 track
// 3. get G722 frames of that track
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published tracks
tracks, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the G722 track
track := func() *gortsplib.TrackG722 {
for _, track := range tracks {
if tt, ok := track.(*gortsplib.TrackG722); ok {
return tt
}
}
return nil
}()
if track == nil {
panic("G722 track not found")
}
// setup decoder
dec := track.CreateDecoder()
// called when a RTP packet arrives
c.OnPacketRTP = func(ctx *gortsplib.ClientOnPacketRTPCtx) {
// decode an G722 packet from the RTP packet
op, _, err := dec.Decode(ctx.Packet)
if err != nil {
return
}
// print
log.Printf("received G722 frame of size %d\n", len(op))
}
// setup and read the G722 track only
err = c.SetupAndPlay(gortsplib.Tracks{track}, baseURL)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -1,77 +0,0 @@
package main
import (
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/url"
)
// This example shows how to
// 1. connect to a RTSP server and read all tracks on a path
// 2. check if there's a H264 track
// 3. save the content of the H264 track into a file in MPEG-TS format
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published tracks
tracks, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the H264 track
track := func() *gortsplib.TrackH264 {
for _, track := range tracks {
if track, ok := track.(*gortsplib.TrackH264); ok {
return track
}
}
return nil
}()
if track == nil {
panic("H264 track not found")
}
// setup RTP/H264->H264 decoder
rtpDec := track.CreateDecoder()
// setup H264->MPEGTS muxer
mpegtsMuxer, err := newMPEGTSMuxer(track.SafeSPS(), track.SafePPS())
if err != nil {
panic(err)
}
// called when a RTP packet arrives
c.OnPacketRTP = func(ctx *gortsplib.ClientOnPacketRTPCtx) {
// convert RTP packets into NALUs
nalus, pts, err := rtpDec.Decode(ctx.Packet)
if err != nil {
return
}
// encode H264 NALUs into MPEG-TS
mpegtsMuxer.encode(nalus, pts)
}
// setup and read the H264 track only
err = c.SetupAndPlay(gortsplib.Tracks{track}, baseURL)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -1,74 +0,0 @@
package main
import (
"log"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/url"
)
// This example shows how to
// 1. connect to a RTSP server and read all tracks on a path
// 2. check if there's an H265 track
// 3. get access units of that track
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published tracks
tracks, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the H265 track
track := func() *gortsplib.TrackH265 {
for _, track := range tracks {
if track, ok := track.(*gortsplib.TrackH265); ok {
return track
}
}
return nil
}()
if track == nil {
panic("H265 track not found")
}
// setup RTP/H265->H265 decoder
dec := track.CreateDecoder()
// called when a RTP packet arrives
c.OnPacketRTP = func(ctx *gortsplib.ClientOnPacketRTPCtx) {
// convert RTP packets into NALUs
nalus, pts, err := dec.Decode(ctx.Packet)
if err != nil {
return
}
for _, nalu := range nalus {
log.Printf("received NALU with PTS %v and size %d\n", pts, len(nalu))
}
}
// setup and read the H265 track only
err = c.SetupAndPlay(gortsplib.Tracks{track}, baseURL)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -1,73 +0,0 @@
package main
import (
"log"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/url"
)
// This example shows how to
// 1. connect to a RTSP server and read all tracks on a path
// 2. check if there's an LPCM track
// 3. get LPCM packets of that track
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published tracks
tracks, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the LPCM track
track := func() *gortsplib.TrackLPCM {
for _, track := range tracks {
if tt, ok := track.(*gortsplib.TrackLPCM); ok {
return tt
}
}
return nil
}()
if track == nil {
panic("LPCM track not found")
}
// setup decoder
dec := track.CreateDecoder()
// called when a RTP packet arrives
c.OnPacketRTP = func(ctx *gortsplib.ClientOnPacketRTPCtx) {
// decode LPCM samples from the RTP packet
op, _, err := dec.Decode(ctx.Packet)
if err != nil {
return
}
// print
log.Printf("received LPCM samples of size %d\n", len(op))
}
// setup and read the LPCM track only
err = c.SetupAndPlay(gortsplib.Tracks{track}, baseURL)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -1,75 +0,0 @@
package main
import (
"log"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/url"
)
// This example shows how to
// 1. connect to a RTSP server and read all tracks on a path
// 2. check if there's an MPEG4-audio track
// 3. get access units of that track
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published tracks
tracks, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the MPEG4-audio track
track := func() *gortsplib.TrackMPEG4Audio {
for _, track := range tracks {
if tt, ok := track.(*gortsplib.TrackMPEG4Audio); ok {
return tt
}
}
return nil
}()
if track == nil {
panic("MPEG4-audio track not found")
}
// setup decoder
dec := track.CreateDecoder()
// called when a RTP packet arrives
c.OnPacketRTP = func(ctx *gortsplib.ClientOnPacketRTPCtx) {
// decode MPEG4-audio AUs from the RTP packet
aus, _, err := dec.Decode(ctx.Packet)
if err != nil {
return
}
// print AUs
for _, au := range aus {
log.Printf("received MPEG4-audio AU of size %d\n", len(au))
}
}
// setup and read the MPEG4-audio track only
err = c.SetupAndPlay(gortsplib.Tracks{track}, baseURL)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -1,73 +0,0 @@
package main
import (
"log"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/url"
)
// This example shows how to
// 1. connect to a RTSP server and read all tracks on a path
// 2. check if there's an Opus track
// 3. get Opus packets of that track
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published tracks
tracks, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the Opus track
track := func() *gortsplib.TrackOpus {
for _, track := range tracks {
if tt, ok := track.(*gortsplib.TrackOpus); ok {
return tt
}
}
return nil
}()
if track == nil {
panic("Opus track not found")
}
// setup decoder
dec := track.CreateDecoder()
// called when a RTP packet arrives
c.OnPacketRTP = func(ctx *gortsplib.ClientOnPacketRTPCtx) {
// decode an Opus packet from the RTP packet
op, _, err := dec.Decode(ctx.Packet)
if err != nil {
return
}
// print
log.Printf("received Opus packet of size %d\n", len(op))
}
// setup and read the Opus track only
err = c.SetupAndPlay(gortsplib.Tracks{track}, baseURL)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -1,72 +0,0 @@
package main
import (
"log"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/url"
)
// This example shows how to
// 1. connect to a RTSP server and read all tracks on a path
// 2. check if there's an VP8 track
// 3. get access units of that track
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published tracks
tracks, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the VP8 track
track := func() *gortsplib.TrackVP8 {
for _, track := range tracks {
if tt, ok := track.(*gortsplib.TrackVP8); ok {
return tt
}
}
return nil
}()
if track == nil {
panic("VP8 track not found")
}
// setup decoder
dec := track.CreateDecoder()
// called when a RTP packet arrives
c.OnPacketRTP = func(ctx *gortsplib.ClientOnPacketRTPCtx) {
// decode a VP8 frame from the RTP packet
vf, _, err := dec.Decode(ctx.Packet)
if err != nil {
return
}
log.Printf("received frame of size %d\n", len(vf))
}
// setup and read the VP8 track only
err = c.SetupAndPlay(gortsplib.Tracks{track}, baseURL)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -1,72 +0,0 @@
package main
import (
"log"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/url"
)
// This example shows how to
// 1. connect to a RTSP server and read all tracks on a path
// 2. check if there's an VP9 track
// 3. get access units of that track
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published tracks
tracks, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the VP9 track
track := func() *gortsplib.TrackVP9 {
for _, track := range tracks {
if tt, ok := track.(*gortsplib.TrackVP9); ok {
return tt
}
}
return nil
}()
if track == nil {
panic("VP9 track not found")
}
// setup decoder
dec := track.CreateDecoder()
// called when a RTP packet arrives
c.OnPacketRTP = func(ctx *gortsplib.ClientOnPacketRTPCtx) {
// decode a VP9 frame from the RTP packet
vf, _, err := dec.Decode(ctx.Packet)
if err != nil {
return
}
log.Printf("received frame of size %d\n", len(vf))
}
// setup and read the VP9 track only
err = c.SetupAndPlay(gortsplib.Tracks{track}, baseURL)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -3,24 +3,19 @@ package main
import ( import (
"log" "log"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/pkg/url" "github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtcp"
"github.com/pion/rtp"
) )
// This example shows how to connect to a RTSP server // This example shows how to connect to a RTSP server
// and read all tracks on a path. // and read all medias on a path.
func main() { func main() {
c := gortsplib.Client{ c := gortsplib.Client{}
// called when a RTP packet arrives
OnPacketRTP: func(ctx *gortsplib.ClientOnPacketRTPCtx) {
log.Printf("RTP packet from track %d, payload type %d\n", ctx.TrackID, ctx.Packet.Header.PayloadType)
},
// called when a RTCP packet arrives
OnPacketRTCP: func(ctx *gortsplib.ClientOnPacketRTCPCtx) {
log.Printf("RTCP packet from track %d, type %T\n", ctx.TrackID, ctx.Packet)
},
}
// parse URL // parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream") u, err := url.Parse("rtsp://localhost:8554/mystream")
@@ -35,14 +30,30 @@ func main() {
} }
defer c.Close() defer c.Close()
// find published tracks // find published medias
tracks, baseURL, _, err := c.Describe(u) medias, baseURL, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// setup and read all tracks // setup all medias
err = c.SetupAndPlay(tracks, baseURL) err = c.SetupAll(medias, baseURL)
if err != nil {
panic(err)
}
// called when a RTP packet arrives
c.OnPacketRTPAny(func(medi *media.Media, trak format.Format, pkt *rtp.Packet) {
log.Printf("RTP packet from media %v\n", medi)
})
// called when a RTCP packet arrives
c.OnPacketRTCPAny(func(medi *media.Media, pkt rtcp.Packet) {
log.Printf("RTCP packet from media %v, type %T\n", medi, pkt)
})
// start playing
_, err = c.Play(nil)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -5,21 +5,25 @@ import (
"log" "log"
"sync" "sync"
"github.com/aler9/gortsplib" "github.com/pion/rtp"
"github.com/aler9/gortsplib/pkg/base"
"github.com/aler9/gortsplib/pkg/rtpcodecs/rtph264" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/base"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/aler9/gortsplib/v2/pkg/rtpcodecs/rtph264"
) )
// This example shows how to // This example shows how to
// 1. create a RTSP server which accepts plain connections // 1. create a RTSP server which accepts plain connections
// 2. allow a single client to publish a stream, containing a H264 track, with TCP or UDP // 2. allow a single client to publish a stream, containing a H264 media, with TCP or UDP
// 3. save the content of the H264 track into a file in MPEG-TS format // 3. save the content of the H264 media into a file in MPEG-TS format
type serverHandler struct { type serverHandler struct {
mutex sync.Mutex mutex sync.Mutex
publisher *gortsplib.ServerSession publisher *gortsplib.ServerSession
h264TrackID int media *media.Media
h264track *gortsplib.TrackH264 format *format.H264
rtpDec *rtph264.Decoder rtpDec *rtph264.Decoder
mpegtsMuxer *mpegtsMuxer mpegtsMuxer *mpegtsMuxer
} }
@@ -50,7 +54,7 @@ func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionClo
sh.mpegtsMuxer.close() sh.mpegtsMuxer.close()
} }
// called after receiving an ANNOUNCE request. // called when receiving an ANNOUNCE request.
func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) { func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
log.Printf("announce request") log.Printf("announce request")
@@ -62,26 +66,20 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
sh.mpegtsMuxer.close() sh.mpegtsMuxer.close()
} }
// find the H264 track // find the H264 media and format
h264TrackID, h264track := func() (int, *gortsplib.TrackH264) { var trak *format.H264
for i, track := range ctx.Tracks { medi := ctx.Medias.Find(&trak)
if h264track, ok := track.(*gortsplib.TrackH264); ok { if medi == nil {
return i, h264track
}
}
return -1, nil
}()
if h264TrackID < 0 {
return &base.Response{ return &base.Response{
StatusCode: base.StatusBadRequest, StatusCode: base.StatusBadRequest,
}, fmt.Errorf("H264 track not found") }, fmt.Errorf("H264 media not found")
} }
// setup RTP/H264->H264 decoder // setup RTP/H264->H264 decoder
rtpDec := h264track.CreateDecoder() rtpDec := trak.CreateDecoder()
// setup H264->MPEGTS muxer // setup H264->MPEGTS muxer
mpegtsMuxer, err := newMPEGTSMuxer(h264track.SafeSPS(), h264track.SafePPS()) mpegtsMuxer, err := newMPEGTSMuxer(trak.SafeSPS(), trak.SafePPS())
if err != nil { if err != nil {
return &base.Response{ return &base.Response{
StatusCode: base.StatusBadRequest, StatusCode: base.StatusBadRequest,
@@ -89,7 +87,8 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
} }
sh.publisher = ctx.Session sh.publisher = ctx.Session
sh.h264TrackID = h264TrackID sh.media = medi
sh.format = trak
sh.rtpDec = rtpDec sh.rtpDec = rtpDec
sh.mpegtsMuxer = mpegtsMuxer sh.mpegtsMuxer = mpegtsMuxer
@@ -98,7 +97,7 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
}, nil }, nil
} }
// called after receiving a SETUP request. // called when receiving a SETUP request.
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) { func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
log.Printf("setup request") log.Printf("setup request")
@@ -107,33 +106,26 @@ func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.
}, nil, nil }, nil, nil
} }
// called after receiving a RECORD request. // called when receiving a RECORD request.
func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) { func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
log.Printf("record request") log.Printf("record request")
// called when receiving a RTP packet
ctx.Session.OnPacketRTP(sh.media, sh.format, func(pkt *rtp.Packet) {
nalus, pts, err := sh.rtpDec.Decode(pkt)
if err != nil {
return
}
// encode H264 NALUs into MPEG-TS
sh.mpegtsMuxer.encode(nalus, pts)
})
return &base.Response{ return &base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
}, nil }, nil
} }
// called after receiving a RTP packet.
func (sh *serverHandler) OnPacketRTP(ctx *gortsplib.ServerHandlerOnPacketRTPCtx) {
sh.mutex.Lock()
defer sh.mutex.Unlock()
if ctx.TrackID != sh.h264TrackID {
return
}
nalus, pts, err := sh.rtpDec.Decode(ctx.Packet)
if err != nil {
return
}
// encode H264 NALUs into MPEG-TS
sh.mpegtsMuxer.encode(nalus, pts)
}
func main() { func main() {
// configure server // configure server
s := &gortsplib.Server{ s := &gortsplib.Server{

View File

@@ -7,7 +7,7 @@ import (
"os" "os"
"time" "time"
"github.com/aler9/gortsplib/pkg/h264" "github.com/aler9/gortsplib/v2/pkg/h264"
"github.com/asticode/go-astits" "github.com/asticode/go-astits"
) )

View File

@@ -5,8 +5,12 @@ import (
"log" "log"
"sync" "sync"
"github.com/aler9/gortsplib" "github.com/pion/rtp"
"github.com/aler9/gortsplib/pkg/base"
"github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/base"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
) )
// This example shows how to // This example shows how to
@@ -50,7 +54,7 @@ func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionClo
} }
} }
// called after receiving a DESCRIBE request. // called when receiving a DESCRIBE request.
func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) { func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
log.Printf("describe request") log.Printf("describe request")
@@ -64,13 +68,13 @@ func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (
}, nil, nil }, nil, nil
} }
// send the track list that is being published to the client // send medias that are being published to the client
return &base.Response{ return &base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
}, sh.stream, nil }, sh.stream, nil
} }
// called after receiving an ANNOUNCE request. // called when receiving an ANNOUNCE request.
func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) { func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
log.Printf("announce request") log.Printf("announce request")
@@ -84,7 +88,7 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
} }
// create the stream and save the publisher // create the stream and save the publisher
sh.stream = gortsplib.NewServerStream(ctx.Tracks) sh.stream = gortsplib.NewServerStream(ctx.Medias)
sh.publisher = ctx.Session sh.publisher = ctx.Session
return &base.Response{ return &base.Response{
@@ -92,7 +96,7 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
}, nil }, nil
} }
// called after receiving a SETUP request. // called when receiving a SETUP request.
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) { func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
log.Printf("setup request") log.Printf("setup request")
@@ -108,7 +112,7 @@ func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.
}, sh.stream, nil }, sh.stream, nil
} }
// called after receiving a PLAY request. // called when receiving a PLAY request.
func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) { func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
log.Printf("play request") log.Printf("play request")
@@ -117,26 +121,21 @@ func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Re
}, nil }, nil
} }
// called after receiving a RECORD request. // called when receiving a RECORD request.
func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) { func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
log.Printf("record request") log.Printf("record request")
// called when receiving a RTP packet
ctx.Session.OnPacketRTPAny(func(medi *media.Media, trak format.Format, pkt *rtp.Packet) {
// route the RTP packet to all readers
sh.stream.WritePacketRTP(medi, pkt)
})
return &base.Response{ return &base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
}, nil }, nil
} }
// called after receiving a RTP packet.
func (sh *serverHandler) OnPacketRTP(ctx *gortsplib.ServerHandlerOnPacketRTPCtx) {
sh.mutex.Lock()
defer sh.mutex.Unlock()
// if we are the publisher, route the RTP packet to all readers
if ctx.Session == sh.publisher {
sh.stream.WritePacketRTP(ctx.TrackID, ctx.Packet)
}
}
func main() { func main() {
// setup certificates - they can be generated with // setup certificates - they can be generated with
// openssl genrsa -out server.key 2048 // openssl genrsa -out server.key 2048

View File

@@ -4,8 +4,12 @@ import (
"log" "log"
"sync" "sync"
"github.com/aler9/gortsplib" "github.com/pion/rtp"
"github.com/aler9/gortsplib/pkg/base"
"github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/base"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
) )
// This example shows how to // This example shows how to
@@ -49,7 +53,7 @@ func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionClo
} }
} }
// called after receiving a DESCRIBE request. // called when receiving a DESCRIBE request.
func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) { func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
log.Printf("describe request") log.Printf("describe request")
@@ -63,13 +67,13 @@ func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (
}, nil, nil }, nil, nil
} }
// send the track list that is being published to the client // send medias that are being published to the client
return &base.Response{ return &base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
}, sh.stream, nil }, sh.stream, nil
} }
// called after receiving an ANNOUNCE request. // called when receiving an ANNOUNCE request.
func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) { func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
log.Printf("announce request") log.Printf("announce request")
@@ -83,7 +87,7 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
} }
// create the stream and save the publisher // create the stream and save the publisher
sh.stream = gortsplib.NewServerStream(ctx.Tracks) sh.stream = gortsplib.NewServerStream(ctx.Medias)
sh.publisher = ctx.Session sh.publisher = ctx.Session
return &base.Response{ return &base.Response{
@@ -91,7 +95,7 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
}, nil }, nil
} }
// called after receiving a SETUP request. // called when receiving a SETUP request.
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) { func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
log.Printf("setup request") log.Printf("setup request")
@@ -107,7 +111,7 @@ func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.
}, sh.stream, nil }, sh.stream, nil
} }
// called after receiving a PLAY request. // called when receiving a PLAY request.
func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) { func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
log.Printf("play request") log.Printf("play request")
@@ -116,26 +120,21 @@ func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Re
}, nil }, nil
} }
// called after receiving a RECORD request. // called when receiving a RECORD request.
func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) { func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
log.Printf("record request") log.Printf("record request")
// called when receiving a RTP packet
ctx.Session.OnPacketRTPAny(func(medi *media.Media, trak format.Format, pkt *rtp.Packet) {
// route the RTP packet to all readers
sh.stream.WritePacketRTP(medi, pkt)
})
return &base.Response{ return &base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
}, nil }, nil
} }
// called after receiving a RTP packet.
func (sh *serverHandler) OnPacketRTP(ctx *gortsplib.ServerHandlerOnPacketRTPCtx) {
sh.mutex.Lock()
defer sh.mutex.Unlock()
// if we are the publisher, route the RTP packet to all readers
if ctx.Session == sh.publisher {
sh.stream.WritePacketRTP(ctx.TrackID, ctx.Packet)
}
}
func main() { func main() {
// configure server // configure server
s := &gortsplib.Server{ s := &gortsplib.Server{

2
go.mod
View File

@@ -1,4 +1,4 @@
module github.com/aler9/gortsplib module github.com/aler9/gortsplib/v2
go 1.17 go 1.17

View File

@@ -14,10 +14,13 @@ import (
"testing" "testing"
"time" "time"
"github.com/pion/rtp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/v2/pkg/base"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
) )
var serverCert = []byte(`-----BEGIN CERTIFICATE----- var serverCert = []byte(`-----BEGIN CERTIFICATE-----
@@ -85,8 +88,6 @@ type testServerHandler struct {
onPlay func(*gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) onPlay func(*gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error)
onRecord func(*gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) onRecord func(*gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error)
onPause func(*gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error) onPause func(*gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error)
onPacketRTP func(*gortsplib.ServerHandlerOnPacketRTPCtx)
onPacketRTCP func(*gortsplib.ServerHandlerOnPacketRTCPCtx)
onSetParameter func(*gortsplib.ServerHandlerOnSetParameterCtx) (*base.Response, error) onSetParameter func(*gortsplib.ServerHandlerOnSetParameterCtx) (*base.Response, error)
onGetParameter func(*gortsplib.ServerHandlerOnGetParameterCtx) (*base.Response, error) onGetParameter func(*gortsplib.ServerHandlerOnGetParameterCtx) (*base.Response, error)
} }
@@ -157,18 +158,6 @@ func (sh *testServerHandler) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*b
return nil, fmt.Errorf("unimplemented") return nil, fmt.Errorf("unimplemented")
} }
func (sh *testServerHandler) OnPacketRTP(ctx *gortsplib.ServerHandlerOnPacketRTPCtx) {
if sh.onPacketRTP != nil {
sh.onPacketRTP(ctx)
}
}
func (sh *testServerHandler) OnPacketRTCP(ctx *gortsplib.ServerHandlerOnPacketRTCPCtx) {
if sh.onPacketRTCP != nil {
sh.onPacketRTCP(ctx)
}
}
func (sh *testServerHandler) OnSetParameter(ctx *gortsplib.ServerHandlerOnSetParameterCtx) (*base.Response, error) { func (sh *testServerHandler) OnSetParameter(ctx *gortsplib.ServerHandlerOnSetParameterCtx) (*base.Response, error) {
if sh.onSetParameter != nil { if sh.onSetParameter != nil {
return sh.onSetParameter(ctx) return sh.onSetParameter(ctx)
@@ -238,7 +227,7 @@ func buildImage(image string) error {
return ecmd.Run() return ecmd.Run()
} }
func TestServerPublishRead(t *testing.T) { func TestServerRecordRead(t *testing.T) {
files, err := os.ReadDir("images") files, err := os.ReadDir("images")
require.NoError(t, err) require.NoError(t, err)
@@ -339,7 +328,7 @@ func TestServerPublishRead(t *testing.T) {
}, fmt.Errorf("someone is already publishing") }, fmt.Errorf("someone is already publishing")
} }
stream = gortsplib.NewServerStream(ctx.Tracks) stream = gortsplib.NewServerStream(ctx.Medias)
publisher = ctx.Session publisher = ctx.Session
return &base.Response{ return &base.Response{
@@ -396,18 +385,14 @@ func TestServerPublishRead(t *testing.T) {
}, fmt.Errorf("invalid query (%s)", ctx.Query) }, fmt.Errorf("invalid query (%s)", ctx.Query)
} }
ctx.Session.OnPacketRTPAny(func(medi *media.Media, trak format.Format, pkt *rtp.Packet) {
stream.WritePacketRTP(medi, pkt)
})
return &base.Response{ return &base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
}, nil }, nil
}, },
onPacketRTP: func(ctx *gortsplib.ServerHandlerOnPacketRTPCtx) {
mutex.Lock()
defer mutex.Unlock()
if ctx.Session == publisher {
stream.WritePacketRTP(ctx.TrackID, ctx.Packet)
}
},
}, },
RTSPAddress: "localhost:8554", RTSPAddress: "localhost:8554",
} }

View File

@@ -5,9 +5,9 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/v2/pkg/base"
"github.com/aler9/gortsplib/pkg/headers" "github.com/aler9/gortsplib/v2/pkg/headers"
"github.com/aler9/gortsplib/pkg/url" "github.com/aler9/gortsplib/v2/pkg/url"
) )
func mustParseURL(s string) *url.URL { func mustParseURL(s string) *url.URL {

View File

@@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/v2/pkg/base"
"github.com/aler9/gortsplib/pkg/headers" "github.com/aler9/gortsplib/v2/pkg/headers"
) )
// Sender allows to generate credentials for a Validator. // Sender allows to generate credentials for a Validator.

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/v2/pkg/base"
) )
func TestSenderErrors(t *testing.T) { func TestSenderErrors(t *testing.T) {

View File

@@ -6,9 +6,9 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/v2/pkg/base"
"github.com/aler9/gortsplib/pkg/headers" "github.com/aler9/gortsplib/v2/pkg/headers"
"github.com/aler9/gortsplib/pkg/url" "github.com/aler9/gortsplib/v2/pkg/url"
) )
func stringsReverseIndex(s, substr string) int { func stringsReverseIndex(s, substr string) int {

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/v2/pkg/base"
) )
func TestValidatorErrors(t *testing.T) { func TestValidatorErrors(t *testing.T) {

View File

@@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/aler9/gortsplib/pkg/url" "github.com/aler9/gortsplib/v2/pkg/url"
) )
const ( const (

View File

@@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aler9/gortsplib/pkg/url" "github.com/aler9/gortsplib/v2/pkg/url"
) )
func mustParseURL(s string) *url.URL { func mustParseURL(s string) *url.URL {

View File

@@ -5,7 +5,7 @@ import (
"bufio" "bufio"
"io" "io"
"github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/v2/pkg/base"
) )
const ( const (

View File

@@ -6,8 +6,8 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/v2/pkg/base"
"github.com/aler9/gortsplib/pkg/url" "github.com/aler9/gortsplib/v2/pkg/url"
) )
func mustParseURL(s string) *url.URL { func mustParseURL(s string) *url.URL {

128
pkg/format/format.go Normal file
View File

@@ -0,0 +1,128 @@
// Package format contains format definitions.
package format
import (
"strconv"
"strings"
"github.com/pion/rtp"
psdp "github.com/pion/sdp/v3"
)
func getFormatAttribute(attributes []psdp.Attribute, payloadType uint8, key string) string {
for _, attr := range attributes {
if attr.Key == key {
v := strings.TrimSpace(attr.Value)
if parts := strings.SplitN(v, " ", 2); len(parts) == 2 {
if tmp, err := strconv.ParseInt(parts[0], 10, 8); err == nil && uint8(tmp) == payloadType {
return parts[1]
}
}
}
}
return ""
}
func getCodecAndClock(rtpMap string) (string, string) {
parts2 := strings.SplitN(rtpMap, "/", 2)
if len(parts2) != 2 {
return "", ""
}
return parts2[0], parts2[1]
}
// Format is a RTSP format.
type Format interface {
// String returns a description of the format.
String() string
// ClockRate returns the clock rate.
ClockRate() int
// PayloadType returns the payload type.
PayloadType() uint8
unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error
// Marshal encodes the format in SDP format.
Marshal() (string, string)
// Clone clones the format.
Clone() Format
// PTSEqualsDTS checks whether PTS is equal to DTS in the RTP packet.
PTSEqualsDTS(*rtp.Packet) bool
}
// Unmarshal decodes a format from a media description.
func Unmarshal(md *psdp.MediaDescription, payloadTypeStr string) (Format, error) {
tmp, err := strconv.ParseInt(payloadTypeStr, 10, 8)
if err != nil {
return nil, err
}
payloadType := uint8(tmp)
rtpMap := getFormatAttribute(md.Attributes, payloadType, "rtpmap")
codec, clock := getCodecAndClock(rtpMap)
codec = strings.ToLower(codec)
fmtp := getFormatAttribute(md.Attributes, payloadType, "fmtp")
format := func() Format {
switch {
case md.MediaName.Media == "video":
switch {
case payloadType == 26:
return &JPEG{}
case payloadType == 32:
return &MPEG2Video{}
case codec == "h264" && clock == "90000":
return &H264{}
case codec == "h265" && clock == "90000":
return &H265{}
case codec == "vp8" && clock == "90000":
return &VP8{}
case codec == "vp9" && clock == "90000":
return &VP9{}
}
case md.MediaName.Media == "audio":
switch {
case payloadType == 0, payloadType == 8:
return &G711{}
case payloadType == 9:
return &G722{}
case payloadType == 14:
return &MPEG2Audio{}
case codec == "l8", codec == "l16", codec == "l24":
return &LPCM{}
case codec == "mpeg4-generic":
return &MPEG4Audio{}
case codec == "vorbis":
return &Vorbis{}
case codec == "opus":
return &Opus{}
}
}
return &Generic{}
}()
err = format.unmarshal(payloadType, clock, codec, rtpMap, fmtp)
if err != nil {
return nil, err
}
return format, nil
}

84
pkg/format/g711.go Normal file
View File

@@ -0,0 +1,84 @@
package format
import (
"fmt"
"strings"
"github.com/pion/rtp"
"github.com/aler9/gortsplib/v2/pkg/rtpcodecs/rtpsimpleaudio"
)
// G711 is a G711 format, encoded with mu-law or A-law.
type G711 struct {
// whether to use mu-law. Otherwise, A-law is used.
MULaw bool
}
// String implements Format.
func (t *G711) String() string {
return "G711"
}
// ClockRate implements Format.
func (t *G711) ClockRate() int {
return 8000
}
// PayloadType implements Format.
func (t *G711) PayloadType() uint8 {
if t.MULaw {
return 0
}
return 8
}
func (t *G711) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
tmp := strings.Split(clock, "/")
if len(tmp) == 2 && tmp[1] != "1" {
return fmt.Errorf("G711 formats can have only one channel")
}
t.MULaw = (payloadType == 0)
return nil
}
// Marshal implements Format.
func (t *G711) Marshal() (string, string) {
if t.MULaw {
return "PCMU/8000", ""
}
return "PCMA/8000", ""
}
// Clone implements Format.
func (t *G711) Clone() Format {
return &G711{
MULaw: t.MULaw,
}
}
// PTSEqualsDTS implements Format.
func (t *G711) PTSEqualsDTS(*rtp.Packet) bool {
return true
}
// CreateDecoder creates a decoder able to decode the content of the format.
func (t *G711) CreateDecoder() *rtpsimpleaudio.Decoder {
d := &rtpsimpleaudio.Decoder{
SampleRate: 8000,
}
d.Init()
return d
}
// CreateEncoder creates an encoder able to encode the content of the format.
func (t *G711) CreateEncoder() *rtpsimpleaudio.Encoder {
e := &rtpsimpleaudio.Encoder{
PayloadType: t.PayloadType(),
SampleRate: 8000,
}
e.Init()
return e
}

49
pkg/format/g711_test.go Normal file
View File

@@ -0,0 +1,49 @@
package format
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestG711Attributes(t *testing.T) {
format := &G711{}
require.Equal(t, "G711", format.String())
require.Equal(t, 8000, format.ClockRate())
require.Equal(t, uint8(8), format.PayloadType())
format = &G711{
MULaw: true,
}
require.Equal(t, "G711", format.String())
require.Equal(t, 8000, format.ClockRate())
require.Equal(t, uint8(0), format.PayloadType())
}
func TestG711Clone(t *testing.T) {
format := &G711{}
clone := format.Clone()
require.NotSame(t, format, clone)
require.Equal(t, format, clone)
}
func TestG711MediaDescription(t *testing.T) {
t.Run("pcma", func(t *testing.T) {
format := &G711{}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "PCMA/8000", rtpmap)
require.Equal(t, "", fmtp)
})
t.Run("pcmu", func(t *testing.T) {
format := &G711{
MULaw: true,
}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "PCMU/8000", rtpmap)
require.Equal(t, "", fmtp)
})
}

71
pkg/format/g722.go Normal file
View File

@@ -0,0 +1,71 @@
package format
import (
"fmt"
"strings"
"github.com/pion/rtp"
"github.com/aler9/gortsplib/v2/pkg/rtpcodecs/rtpsimpleaudio"
)
// G722 is a G722 format.
type G722 struct{}
// String implements Format.
func (t *G722) String() string {
return "G722"
}
// ClockRate implements Format.
func (t *G722) ClockRate() int {
return 8000
}
// PayloadType implements Format.
func (t *G722) PayloadType() uint8 {
return 9
}
func (t *G722) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
tmp := strings.Split(clock, "/")
if len(tmp) == 2 && tmp[1] != "1" {
return fmt.Errorf("G722 formats can have only one channel")
}
return nil
}
// Marshal implements Format.
func (t *G722) Marshal() (string, string) {
return "G722/8000", ""
}
// Clone implements Format.
func (t *G722) Clone() Format {
return &G722{}
}
// PTSEqualsDTS implements Format.
func (t *G722) PTSEqualsDTS(*rtp.Packet) bool {
return true
}
// CreateDecoder creates a decoder able to decode the content of the format.
func (t *G722) CreateDecoder() *rtpsimpleaudio.Decoder {
d := &rtpsimpleaudio.Decoder{
SampleRate: 8000,
}
d.Init()
return d
}
// CreateEncoder creates an encoder able to encode the content of the format.
func (t *G722) CreateEncoder() *rtpsimpleaudio.Encoder {
e := &rtpsimpleaudio.Encoder{
PayloadType: 9,
SampleRate: 8000,
}
e.Init()
return e
}

30
pkg/format/g722_test.go Normal file
View File

@@ -0,0 +1,30 @@
package format
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestG722Attributes(t *testing.T) {
format := &G722{}
require.Equal(t, "G722", format.String())
require.Equal(t, 8000, format.ClockRate())
require.Equal(t, uint8(9), format.PayloadType())
}
func TestG722Clone(t *testing.T) {
format := &G722{}
clone := format.Clone()
// require.NotSame(t, format, clone)
require.Equal(t, format, clone)
}
func TestG722MediaDescription(t *testing.T) {
format := &G722{}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "G722/8000", rtpmap)
require.Equal(t, "", fmtp)
}

111
pkg/format/generic.go Normal file
View File

@@ -0,0 +1,111 @@
package format
import (
"fmt"
"strconv"
"strings"
"github.com/pion/rtp"
)
func findClockRate(payloadType uint8, rtpMap string) (int, error) {
// get clock rate from payload type
// https://en.wikipedia.org/wiki/RTP_payload_formats
switch payloadType {
case 0, 1, 2, 3, 4, 5, 7, 8, 9, 12, 13, 15, 18:
return 8000, nil
case 6:
return 16000, nil
case 10, 11:
return 44100, nil
case 14, 25, 26, 28, 31, 32, 33, 34:
return 90000, nil
case 16:
return 11025, nil
case 17:
return 22050, nil
}
// get clock rate from rtpmap
// https://tools.ietf.org/html/rfc4566
// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
if rtpMap == "" {
return 0, fmt.Errorf("attribute 'rtpmap' not found")
}
tmp := strings.Split(rtpMap, "/")
if len(tmp) != 2 && len(tmp) != 3 {
return 0, fmt.Errorf("invalid rtpmap (%v)", rtpMap)
}
v, err := strconv.ParseInt(tmp[1], 10, 64)
if err != nil {
return 0, err
}
return int(v), nil
}
// Generic is a generic format.
type Generic struct {
PayloadTyp uint8
RTPMap string
FMTP string
// clock rate of the format. Filled automatically.
ClockRat int
}
// Init initializes a Generic.
func (t *Generic) Init() error {
t.ClockRat, _ = findClockRate(t.PayloadTyp, t.RTPMap)
return nil
}
// String returns a description of the format.
func (t *Generic) String() string {
return "Generic"
}
// ClockRate implements Format.
func (t *Generic) ClockRate() int {
return t.ClockRat
}
// PayloadType implements Format.
func (t *Generic) PayloadType() uint8 {
return t.PayloadTyp
}
func (t *Generic) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
t.PayloadTyp = payloadType
t.RTPMap = rtpmap
t.FMTP = fmtp
return t.Init()
}
// Marshal implements Format.
func (t *Generic) Marshal() (string, string) {
return t.RTPMap, t.FMTP
}
// Clone implements Format.
func (t *Generic) Clone() Format {
return &Generic{
PayloadTyp: t.PayloadTyp,
RTPMap: t.RTPMap,
FMTP: t.FMTP,
ClockRat: t.ClockRat,
}
}
// PTSEqualsDTS implements Format.
func (t *Generic) PTSEqualsDTS(*rtp.Packet) bool {
return true
}

View File

@@ -0,0 +1,53 @@
package format
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestGenericAttributes(t *testing.T) {
format := &Generic{
PayloadTyp: 98,
RTPMap: "H265/90000",
FMTP: "profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " +
"sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=",
}
err := format.Init()
require.NoError(t, err)
require.Equal(t, "Generic", format.String())
require.Equal(t, 90000, format.ClockRate())
require.Equal(t, uint8(98), format.PayloadType())
}
func TestGenericClone(t *testing.T) {
format := &Generic{
PayloadTyp: 98,
RTPMap: "H265/90000",
FMTP: "profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " +
"sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=",
}
err := format.Init()
require.NoError(t, err)
clone := format.Clone()
require.NotSame(t, format, clone)
require.Equal(t, format, clone)
}
func TestGenericMediaDescription(t *testing.T) {
format := &Generic{
PayloadTyp: 98,
RTPMap: "H265/90000",
FMTP: "profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " +
"sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=",
}
err := format.Init()
require.NoError(t, err)
rtpmap, fmtp := format.Marshal()
require.Equal(t, "H265/90000", rtpmap)
require.Equal(t, "profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; "+
"sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=", fmtp)
}

240
pkg/format/h264.go Normal file
View File

@@ -0,0 +1,240 @@
package format
import (
"encoding/base64"
"encoding/hex"
"fmt"
"strconv"
"strings"
"sync"
"github.com/pion/rtp"
"github.com/aler9/gortsplib/v2/pkg/h264"
"github.com/aler9/gortsplib/v2/pkg/rtpcodecs/rtph264"
)
// check whether a RTP/H264 packet contains a IDR, without decoding the packet.
func rtpH264ContainsIDR(pkt *rtp.Packet) bool {
if len(pkt.Payload) == 0 {
return false
}
typ := h264.NALUType(pkt.Payload[0] & 0x1F)
switch typ {
case h264.NALUTypeIDR:
return true
case 24: // STAP-A
payload := pkt.Payload[1:]
for len(payload) > 0 {
if len(payload) < 2 {
return false
}
size := uint16(payload[0])<<8 | uint16(payload[1])
payload = payload[2:]
if size == 0 || int(size) > len(payload) {
return false
}
nalu := payload[:size]
payload = payload[size:]
typ = h264.NALUType(nalu[0] & 0x1F)
if typ == h264.NALUTypeIDR {
return true
}
}
return false
case 28: // FU-A
if len(pkt.Payload) < 2 {
return false
}
start := pkt.Payload[1] >> 7
if start != 1 {
return false
}
typ := h264.NALUType(pkt.Payload[1] & 0x1F)
return (typ == h264.NALUTypeIDR)
default:
return false
}
}
// H264 is a H264 format.
type H264 struct {
PayloadTyp uint8
SPS []byte
PPS []byte
PacketizationMode int
mutex sync.RWMutex
}
// String implements Format.
func (t *H264) String() string {
return "H264"
}
// ClockRate implements Format.
func (t *H264) ClockRate() int {
return 90000
}
// PayloadType implements Format.
func (t *H264) PayloadType() uint8 {
return t.PayloadTyp
}
func (t *H264) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
t.PayloadTyp = payloadType
if fmtp == "" {
return nil // do not return any error
}
for _, kv := range strings.Split(fmtp, ";") {
kv = strings.Trim(kv, " ")
if len(kv) == 0 {
continue
}
tmp := strings.SplitN(kv, "=", 2)
if len(tmp) != 2 {
return fmt.Errorf("invalid fmtp attribute (%v)", fmtp)
}
switch tmp[0] {
case "sprop-parameter-sets":
tmp := strings.Split(tmp[1], ",")
if len(tmp) < 2 {
return fmt.Errorf("invalid sprop-parameter-sets (%v)", fmtp)
}
sps, err := base64.StdEncoding.DecodeString(tmp[0])
if err != nil {
return fmt.Errorf("invalid sprop-parameter-sets (%v)", fmtp)
}
pps, err := base64.StdEncoding.DecodeString(tmp[1])
if err != nil {
return fmt.Errorf("invalid sprop-parameter-sets (%v)", fmtp)
}
t.SPS = sps
t.PPS = pps
case "packetization-mode":
tmp, err := strconv.ParseInt(tmp[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid packetization-mode (%v)", fmtp)
}
t.PacketizationMode = int(tmp)
}
}
return nil
}
// Marshal implements Format.
func (t *H264) Marshal() (string, string) {
t.mutex.RLock()
defer t.mutex.RUnlock()
var tmp []string
if t.PacketizationMode != 0 {
tmp = append(tmp, "packetization-mode="+strconv.FormatInt(int64(t.PacketizationMode), 10))
}
var tmp2 []string
if t.SPS != nil {
tmp2 = append(tmp2, base64.StdEncoding.EncodeToString(t.SPS))
}
if t.PPS != nil {
tmp2 = append(tmp2, base64.StdEncoding.EncodeToString(t.PPS))
}
if tmp2 != nil {
tmp = append(tmp, "sprop-parameter-sets="+strings.Join(tmp2, ","))
}
if len(t.SPS) >= 4 {
tmp = append(tmp, "profile-level-id="+strings.ToUpper(hex.EncodeToString(t.SPS[1:4])))
}
var fmtp string
if tmp != nil {
fmtp = strings.Join(tmp, "; ")
}
return "H264/90000", fmtp
}
// Clone implements Format.
func (t *H264) Clone() Format {
return &H264{
PayloadTyp: t.PayloadTyp,
SPS: t.SPS,
PPS: t.PPS,
PacketizationMode: t.PacketizationMode,
}
}
// PTSEqualsDTS implements Format.
func (t *H264) PTSEqualsDTS(pkt *rtp.Packet) bool {
return rtpH264ContainsIDR(pkt)
}
// CreateDecoder creates a decoder able to decode the content of the format.
func (t *H264) CreateDecoder() *rtph264.Decoder {
d := &rtph264.Decoder{
PacketizationMode: t.PacketizationMode,
}
d.Init()
return d
}
// CreateEncoder creates an encoder able to encode the content of the format.
func (t *H264) CreateEncoder() *rtph264.Encoder {
e := &rtph264.Encoder{
PayloadType: t.PayloadTyp,
PacketizationMode: t.PacketizationMode,
}
e.Init()
return e
}
// SafeSPS returns the format SPS.
func (t *H264) SafeSPS() []byte {
t.mutex.RLock()
defer t.mutex.RUnlock()
return t.SPS
}
// SafePPS returns the format PPS.
func (t *H264) SafePPS() []byte {
t.mutex.RLock()
defer t.mutex.RUnlock()
return t.PPS
}
// SafeSetSPS sets the format SPS.
func (t *H264) SafeSetSPS(v []byte) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.SPS = v
}
// SafeSetPPS sets the format PPS.
func (t *H264) SafeSetPPS(v []byte) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.PPS = v
}

72
pkg/format/h264_test.go Normal file
View File

@@ -0,0 +1,72 @@
package format
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestH264Attributes(t *testing.T) {
format := &H264{
PayloadTyp: 96,
SPS: []byte{0x01, 0x02},
PPS: []byte{0x03, 0x04},
PacketizationMode: 1,
}
require.Equal(t, "H264", format.String())
require.Equal(t, 90000, format.ClockRate())
require.Equal(t, uint8(96), format.PayloadType())
require.Equal(t, []byte{0x01, 0x02}, format.SafeSPS())
require.Equal(t, []byte{0x03, 0x04}, format.SafePPS())
format.SafeSetSPS([]byte{0x07, 0x08})
format.SafeSetPPS([]byte{0x09, 0x0A})
require.Equal(t, []byte{0x07, 0x08}, format.SafeSPS())
require.Equal(t, []byte{0x09, 0x0A}, format.SafePPS())
}
func TestH264Clone(t *testing.T) {
format := &H264{
PayloadTyp: 96,
SPS: []byte{0x01, 0x02},
PPS: []byte{0x03, 0x04},
PacketizationMode: 1,
}
clone := format.Clone()
require.NotSame(t, format, clone)
require.Equal(t, format, clone)
}
func TestH264MediaDescription(t *testing.T) {
t.Run("standard", func(t *testing.T) {
format := &H264{
PayloadTyp: 96,
SPS: []byte{
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
0x00, 0x03, 0x00, 0x3d, 0x08,
},
PPS: []byte{
0x68, 0xee, 0x3c, 0x80,
},
PacketizationMode: 1,
}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "H264/90000", rtpmap)
require.Equal(t, "packetization-mode=1; "+
"sprop-parameter-sets=Z2QADKw7ULBLQgAAAwACAAADAD0I,aO48gA==; profile-level-id=64000C", fmtp)
})
t.Run("no sps/pps", func(t *testing.T) {
format := &H264{
PayloadTyp: 96,
PacketizationMode: 1,
}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "H264/90000", rtpmap)
require.Equal(t, "packetization-mode=1", fmtp)
})
}

195
pkg/format/h265.go Normal file
View File

@@ -0,0 +1,195 @@
package format
import (
"encoding/base64"
"fmt"
"strconv"
"strings"
"sync"
"github.com/pion/rtp"
"github.com/aler9/gortsplib/v2/pkg/rtpcodecs/rtph265"
)
// H265 is a H265 format.
type H265 struct {
PayloadTyp uint8
VPS []byte
SPS []byte
PPS []byte
MaxDONDiff int
mutex sync.RWMutex
}
// String implements Format.
func (t *H265) String() string {
return "H265"
}
// ClockRate implements Format.
func (t *H265) ClockRate() int {
return 90000
}
// PayloadType implements Format.
func (t *H265) PayloadType() uint8 {
return t.PayloadTyp
}
func (t *H265) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
t.PayloadTyp = payloadType
if fmtp == "" {
return nil // do not return any error
}
for _, kv := range strings.Split(fmtp, ";") {
kv = strings.Trim(kv, " ")
if len(kv) == 0 {
continue
}
tmp := strings.SplitN(kv, "=", 2)
if len(tmp) != 2 {
return fmt.Errorf("invalid fmtp attribute (%v)", fmtp)
}
switch tmp[0] {
case "sprop-vps":
var err error
t.VPS, err = base64.StdEncoding.DecodeString(tmp[1])
if err != nil {
return fmt.Errorf("invalid sprop-vps (%v)", fmtp)
}
case "sprop-sps":
var err error
t.SPS, err = base64.StdEncoding.DecodeString(tmp[1])
if err != nil {
return fmt.Errorf("invalid sprop-sps (%v)", fmtp)
}
case "sprop-pps":
var err error
t.PPS, err = base64.StdEncoding.DecodeString(tmp[1])
if err != nil {
return fmt.Errorf("invalid sprop-pps (%v)", fmtp)
}
case "sprop-max-don-diff":
tmp, err := strconv.ParseInt(tmp[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid sprop-max-don-diff (%v)", fmtp)
}
t.MaxDONDiff = int(tmp)
}
}
return nil
}
// Marshal implements Format.
func (t *H265) Marshal() (string, string) {
t.mutex.RLock()
defer t.mutex.RUnlock()
var tmp []string
if t.VPS != nil {
tmp = append(tmp, "sprop-vps="+base64.StdEncoding.EncodeToString(t.VPS))
}
if t.SPS != nil {
tmp = append(tmp, "sprop-sps="+base64.StdEncoding.EncodeToString(t.SPS))
}
if t.PPS != nil {
tmp = append(tmp, "sprop-pps="+base64.StdEncoding.EncodeToString(t.PPS))
}
if t.MaxDONDiff != 0 {
tmp = append(tmp, "sprop-max-don-diff="+strconv.FormatInt(int64(t.MaxDONDiff), 10))
}
var fmtp string
if tmp != nil {
fmtp = strings.Join(tmp, "; ")
}
return "H265/90000", fmtp
}
// Clone implements Format.
func (t *H265) Clone() Format {
return &H265{
PayloadTyp: t.PayloadTyp,
VPS: t.VPS,
SPS: t.SPS,
PPS: t.PPS,
MaxDONDiff: t.MaxDONDiff,
}
}
// PTSEqualsDTS implements Format.
func (t *H265) PTSEqualsDTS(*rtp.Packet) bool {
return true
}
// CreateDecoder creates a decoder able to decode the content of the format.
func (t *H265) CreateDecoder() *rtph265.Decoder {
d := &rtph265.Decoder{
MaxDONDiff: t.MaxDONDiff,
}
d.Init()
return d
}
// CreateEncoder creates an encoder able to encode the content of the format.
func (t *H265) CreateEncoder() *rtph265.Encoder {
e := &rtph265.Encoder{
PayloadType: t.PayloadTyp,
MaxDONDiff: t.MaxDONDiff,
}
e.Init()
return e
}
// SafeVPS returns the format VPS.
func (t *H265) SafeVPS() []byte {
t.mutex.RLock()
defer t.mutex.RUnlock()
return t.VPS
}
// SafeSPS returns the format SPS.
func (t *H265) SafeSPS() []byte {
t.mutex.RLock()
defer t.mutex.RUnlock()
return t.SPS
}
// SafePPS returns the format PPS.
func (t *H265) SafePPS() []byte {
t.mutex.RLock()
defer t.mutex.RUnlock()
return t.PPS
}
// SafeSetVPS sets the format VPS.
func (t *H265) SafeSetVPS(v []byte) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.VPS = v
}
// SafeSetSPS sets the format SPS.
func (t *H265) SafeSetSPS(v []byte) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.SPS = v
}
// SafeSetPPS sets the format PPS.
func (t *H265) SafeSetPPS(v []byte) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.PPS = v
}

55
pkg/format/h265_test.go Normal file
View File

@@ -0,0 +1,55 @@
package format
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestH265Attributes(t *testing.T) {
format := &H265{
PayloadTyp: 96,
VPS: []byte{0x01, 0x02},
SPS: []byte{0x03, 0x04},
PPS: []byte{0x05, 0x06},
}
require.Equal(t, "H265", format.String())
require.Equal(t, 90000, format.ClockRate())
require.Equal(t, uint8(96), format.PayloadType())
require.Equal(t, []byte{0x01, 0x02}, format.SafeVPS())
require.Equal(t, []byte{0x03, 0x04}, format.SafeSPS())
require.Equal(t, []byte{0x05, 0x06}, format.SafePPS())
format.SafeSetVPS([]byte{0x07, 0x08})
format.SafeSetSPS([]byte{0x09, 0x0A})
format.SafeSetPPS([]byte{0x0B, 0x0C})
require.Equal(t, []byte{0x07, 0x08}, format.SafeVPS())
require.Equal(t, []byte{0x09, 0x0A}, format.SafeSPS())
require.Equal(t, []byte{0x0B, 0x0C}, format.SafePPS())
}
func TestH265Clone(t *testing.T) {
format := &H265{
PayloadTyp: 96,
VPS: []byte{0x01, 0x02},
SPS: []byte{0x03, 0x04},
PPS: []byte{0x05, 0x06},
}
clone := format.Clone()
require.NotSame(t, format, clone)
require.Equal(t, format, clone)
}
func TestH265MediaDescription(t *testing.T) {
format := &H265{
PayloadTyp: 96,
VPS: []byte{0x01, 0x02},
SPS: []byte{0x03, 0x04},
PPS: []byte{0x05, 0x06},
}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "H265/90000", rtpmap)
require.Equal(t, "sprop-vps=AQI=; sprop-sps=AwQ=; sprop-pps=BQY=", fmtp)
}

42
pkg/format/jpeg.go Normal file
View File

@@ -0,0 +1,42 @@
package format
import (
"github.com/pion/rtp"
)
// JPEG is a JPEG format.
type JPEG struct{}
// String implements Format.
func (t *JPEG) String() string {
return "JPEG"
}
// ClockRate implements Format.
func (t *JPEG) ClockRate() int {
return 90000
}
// PayloadType implements Format.
func (t *JPEG) PayloadType() uint8 {
return 26
}
func (t *JPEG) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
return nil
}
// Marshal implements Format.
func (t *JPEG) Marshal() (string, string) {
return "JPEG/90000", ""
}
// Clone implements Format.
func (t *JPEG) Clone() Format {
return &JPEG{}
}
// PTSEqualsDTS implements Format.
func (t *JPEG) PTSEqualsDTS(*rtp.Packet) bool {
return true
}

30
pkg/format/jpeg_test.go Normal file
View File

@@ -0,0 +1,30 @@
package format
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestJPEGAttributes(t *testing.T) {
format := &JPEG{}
require.Equal(t, "JPEG", format.String())
require.Equal(t, 90000, format.ClockRate())
require.Equal(t, uint8(26), format.PayloadType())
}
func TestJPEGClone(t *testing.T) {
format := &JPEG{}
clone := format.Clone()
// require.NotSame(t, format, clone)
require.Equal(t, format, clone)
}
func TestJPEGMediaDescription(t *testing.T) {
format := &JPEG{}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "JPEG/90000", rtpmap)
require.Equal(t, "", fmtp)
}

124
pkg/format/lpcm.go Normal file
View File

@@ -0,0 +1,124 @@
package format
import (
"fmt"
"strconv"
"strings"
"github.com/pion/rtp"
"github.com/aler9/gortsplib/v2/pkg/rtpcodecs/rtplpcm"
)
// LPCM is an uncompressed, Linear PCM format.
type LPCM struct {
PayloadTyp uint8
BitDepth int
SampleRate int
ChannelCount int
}
// String implements Format.
func (t *LPCM) String() string {
return "LPCM"
}
// ClockRate implements Format.
func (t *LPCM) ClockRate() int {
return t.SampleRate
}
// PayloadType implements Format.
func (t *LPCM) PayloadType() uint8 {
return t.PayloadTyp
}
func (t *LPCM) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
t.PayloadTyp = payloadType
switch codec {
case "l8":
t.BitDepth = 8
case "l16":
t.BitDepth = 16
case "l24":
t.BitDepth = 24
}
tmp := strings.SplitN(clock, "/", 32)
if len(tmp) != 2 {
return fmt.Errorf("invalid clock (%v)", clock)
}
sampleRate, err := strconv.ParseInt(tmp[0], 10, 64)
if err != nil {
return err
}
t.SampleRate = int(sampleRate)
channelCount, err := strconv.ParseInt(tmp[1], 10, 64)
if err != nil {
return err
}
t.ChannelCount = int(channelCount)
return nil
}
// Marshal implements Format.
func (t *LPCM) Marshal() (string, string) {
var codec string
switch t.BitDepth {
case 8:
codec = "L8"
case 16:
codec = "L16"
case 24:
codec = "L24"
}
return codec + "/" + strconv.FormatInt(int64(t.SampleRate), 10) +
"/" + strconv.FormatInt(int64(t.ChannelCount), 10), ""
}
// Clone implements Format.
func (t *LPCM) Clone() Format {
return &LPCM{
PayloadTyp: t.PayloadTyp,
BitDepth: t.BitDepth,
SampleRate: t.SampleRate,
ChannelCount: t.ChannelCount,
}
}
// PTSEqualsDTS implements Format.
func (t *LPCM) PTSEqualsDTS(*rtp.Packet) bool {
return true
}
// CreateDecoder creates a decoder able to decode the content of the format.
func (t *LPCM) CreateDecoder() *rtplpcm.Decoder {
d := &rtplpcm.Decoder{
BitDepth: t.BitDepth,
SampleRate: t.SampleRate,
ChannelCount: t.ChannelCount,
}
d.Init()
return d
}
// CreateEncoder creates an encoder able to encode the content of the format.
func (t *LPCM) CreateEncoder() *rtplpcm.Encoder {
e := &rtplpcm.Encoder{
PayloadType: t.PayloadTyp,
BitDepth: t.BitDepth,
SampleRate: t.SampleRate,
ChannelCount: t.ChannelCount,
}
e.Init()
return e
}

45
pkg/format/lpcm_test.go Normal file
View File

@@ -0,0 +1,45 @@
package format
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestLPCMAttributes(t *testing.T) {
format := &LPCM{
PayloadTyp: 96,
BitDepth: 24,
SampleRate: 44100,
ChannelCount: 2,
}
require.Equal(t, "LPCM", format.String())
require.Equal(t, 44100, format.ClockRate())
require.Equal(t, uint8(96), format.PayloadType())
}
func TestTracLPCMClone(t *testing.T) {
format := &LPCM{
PayloadTyp: 96,
BitDepth: 16,
SampleRate: 48000,
ChannelCount: 2,
}
clone := format.Clone()
require.NotSame(t, format, clone)
require.Equal(t, format, clone)
}
func TestLPCMMediaDescription(t *testing.T) {
format := &LPCM{
PayloadTyp: 96,
BitDepth: 24,
SampleRate: 96000,
ChannelCount: 2,
}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "L24/96000/2", rtpmap)
require.Equal(t, "", fmtp)
}

42
pkg/format/mpeg2audio.go Normal file
View File

@@ -0,0 +1,42 @@
package format
import (
"github.com/pion/rtp"
)
// MPEG2Audio is a MPEG-1 or MPEG-2 audio format.
type MPEG2Audio struct{}
// String implements Format.
func (t *MPEG2Audio) String() string {
return "MPEG2-audio"
}
// ClockRate implements Format.
func (t *MPEG2Audio) ClockRate() int {
return 90000
}
// PayloadType implements Format.
func (t *MPEG2Audio) PayloadType() uint8 {
return 14
}
func (t *MPEG2Audio) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
return nil
}
// Marshal implements Format.
func (t *MPEG2Audio) Marshal() (string, string) {
return "", ""
}
// Clone implements Format.
func (t *MPEG2Audio) Clone() Format {
return &MPEG2Audio{}
}
// PTSEqualsDTS implements Format.
func (t *MPEG2Audio) PTSEqualsDTS(*rtp.Packet) bool {
return true
}

View File

@@ -0,0 +1,30 @@
package format
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestMPEG2AudioAttributes(t *testing.T) {
format := &MPEG2Audio{}
require.Equal(t, "MPEG2-audio", format.String())
require.Equal(t, 90000, format.ClockRate())
require.Equal(t, uint8(14), format.PayloadType())
}
func TestMPEG2AudioClone(t *testing.T) {
format := &MPEG2Audio{}
clone := format.Clone()
// require.NotSame(t, format, clone)
require.Equal(t, format, clone)
}
func TestMPEG2AudioMediaDescription(t *testing.T) {
format := &MPEG2Audio{}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "", rtpmap)
require.Equal(t, "", fmtp)
}

42
pkg/format/mpeg2video.go Normal file
View File

@@ -0,0 +1,42 @@
package format
import (
"github.com/pion/rtp"
)
// MPEG2Video is a MPEG-1 or MPEG-2 video format.
type MPEG2Video struct{}
// String implements Format.
func (t *MPEG2Video) String() string {
return "MPEG2-video"
}
// ClockRate implements Format.
func (t *MPEG2Video) ClockRate() int {
return 90000
}
// PayloadType implements Format.
func (t *MPEG2Video) PayloadType() uint8 {
return 32
}
func (t *MPEG2Video) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
return nil
}
// Marshal implements Format.
func (t *MPEG2Video) Marshal() (string, string) {
return "", ""
}
// Clone implements Format.
func (t *MPEG2Video) Clone() Format {
return &MPEG2Video{}
}
// PTSEqualsDTS implements Format.
func (t *MPEG2Video) PTSEqualsDTS(*rtp.Packet) bool {
return true
}

View File

@@ -0,0 +1,30 @@
package format
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestMPEG2VideoAttributes(t *testing.T) {
format := &MPEG2Video{}
require.Equal(t, "MPEG2-video", format.String())
require.Equal(t, 90000, format.ClockRate())
require.Equal(t, uint8(32), format.PayloadType())
}
func TestMPEG2VideoClone(t *testing.T) {
format := &MPEG2Video{}
clone := format.Clone()
// require.NotSame(t, format, clone)
require.Equal(t, format, clone)
}
func TestMPEG2VideoMediaDescription(t *testing.T) {
format := &MPEG2Video{}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "", rtpmap)
require.Equal(t, "", fmtp)
}

182
pkg/format/mpeg4audio.go Normal file
View File

@@ -0,0 +1,182 @@
package format
import (
"encoding/hex"
"fmt"
"strconv"
"strings"
"github.com/pion/rtp"
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
"github.com/aler9/gortsplib/v2/pkg/rtpcodecs/rtpmpeg4audio"
)
// MPEG4Audio is a MPEG-4 audio format.
type MPEG4Audio struct {
PayloadTyp uint8
Config *mpeg4audio.Config
SizeLength int
IndexLength int
IndexDeltaLength int
}
// String implements Format.
func (t *MPEG4Audio) String() string {
return "MPEG4-audio"
}
// ClockRate implements Format.
func (t *MPEG4Audio) ClockRate() int {
return t.Config.SampleRate
}
// PayloadType implements Format.
func (t *MPEG4Audio) PayloadType() uint8 {
return t.PayloadTyp
}
func (t *MPEG4Audio) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
t.PayloadTyp = payloadType
if fmtp == "" {
return fmt.Errorf("fmtp attribute is missing")
}
for _, kv := range strings.Split(fmtp, ";") {
kv = strings.Trim(kv, " ")
if len(kv) == 0 {
continue
}
tmp := strings.SplitN(kv, "=", 2)
if len(tmp) != 2 {
return fmt.Errorf("invalid fmtp (%v)", fmtp)
}
switch strings.ToLower(tmp[0]) {
case "config":
enc, err := hex.DecodeString(tmp[1])
if err != nil {
return fmt.Errorf("invalid AAC config (%v)", tmp[1])
}
t.Config = &mpeg4audio.Config{}
err = t.Config.Unmarshal(enc)
if err != nil {
return fmt.Errorf("invalid AAC config (%v)", tmp[1])
}
case "sizelength":
val, err := strconv.ParseUint(tmp[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid AAC SizeLength (%v)", tmp[1])
}
t.SizeLength = int(val)
case "indexlength":
val, err := strconv.ParseUint(tmp[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid AAC IndexLength (%v)", tmp[1])
}
t.IndexLength = int(val)
case "indexdeltalength":
val, err := strconv.ParseUint(tmp[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid AAC IndexDeltaLength (%v)", tmp[1])
}
t.IndexDeltaLength = int(val)
}
}
if t.Config == nil {
return fmt.Errorf("config is missing (%v)", fmtp)
}
if t.SizeLength == 0 {
return fmt.Errorf("sizelength is missing (%v)", fmtp)
}
return nil
}
// Marshal implements Format.
func (t *MPEG4Audio) Marshal() (string, string) {
enc, err := t.Config.Marshal()
if err != nil {
return "", ""
}
sampleRate := t.Config.SampleRate
if t.Config.ExtensionSampleRate != 0 {
sampleRate = t.Config.ExtensionSampleRate
}
fmtp := "profile-level-id=1; " +
"mode=AAC-hbr; " +
func() string {
if t.SizeLength > 0 {
return fmt.Sprintf("sizelength=%d; ", t.SizeLength)
}
return ""
}() +
func() string {
if t.IndexLength > 0 {
return fmt.Sprintf("indexlength=%d; ", t.IndexLength)
}
return ""
}() +
func() string {
if t.IndexDeltaLength > 0 {
return fmt.Sprintf("indexdeltalength=%d; ", t.IndexDeltaLength)
}
return ""
}() +
"config=" + hex.EncodeToString(enc)
return "mpeg4-generic/" + strconv.FormatInt(int64(sampleRate), 10) +
"/" + strconv.FormatInt(int64(t.Config.ChannelCount), 10), fmtp
}
// Clone implements Format.
func (t *MPEG4Audio) Clone() Format {
return &MPEG4Audio{
PayloadTyp: t.PayloadTyp,
Config: t.Config,
SizeLength: t.SizeLength,
IndexLength: t.IndexLength,
IndexDeltaLength: t.IndexDeltaLength,
}
}
// PTSEqualsDTS implements Format.
func (t *MPEG4Audio) PTSEqualsDTS(*rtp.Packet) bool {
return true
}
// CreateDecoder creates a decoder able to decode the content of the format.
func (t *MPEG4Audio) CreateDecoder() *rtpmpeg4audio.Decoder {
d := &rtpmpeg4audio.Decoder{
SampleRate: t.Config.SampleRate,
SizeLength: t.SizeLength,
IndexLength: t.IndexLength,
IndexDeltaLength: t.IndexDeltaLength,
}
d.Init()
return d
}
// CreateEncoder creates an encoder able to encode the content of the format.
func (t *MPEG4Audio) CreateEncoder() *rtpmpeg4audio.Encoder {
e := &rtpmpeg4audio.Encoder{
PayloadType: t.PayloadTyp,
SampleRate: t.Config.SampleRate,
SizeLength: t.SizeLength,
IndexLength: t.IndexLength,
IndexDeltaLength: t.IndexDeltaLength,
}
e.Init()
return e
}

View File

@@ -0,0 +1,63 @@
package format
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
)
func TestMPEG4AudioAttributes(t *testing.T) {
format := &MPEG4Audio{
PayloadTyp: 96,
Config: &mpeg4audio.Config{
Type: mpeg4audio.ObjectTypeAACLC,
SampleRate: 48000,
ChannelCount: 2,
},
SizeLength: 13,
IndexLength: 3,
IndexDeltaLength: 3,
}
require.Equal(t, "MPEG4-audio", format.String())
require.Equal(t, 48000, format.ClockRate())
require.Equal(t, uint8(96), format.PayloadType())
}
func TestMPEG4AudioClone(t *testing.T) {
format := &MPEG4Audio{
PayloadTyp: 96,
Config: &mpeg4audio.Config{
Type: mpeg4audio.ObjectTypeAACLC,
SampleRate: 48000,
ChannelCount: 2,
},
SizeLength: 13,
IndexLength: 3,
IndexDeltaLength: 3,
}
clone := format.Clone()
require.NotSame(t, format, clone)
require.Equal(t, format, clone)
}
func TestMPEG4AudioMediaDescription(t *testing.T) {
format := &MPEG4Audio{
PayloadTyp: 96,
Config: &mpeg4audio.Config{
Type: mpeg4audio.ObjectTypeAACLC,
SampleRate: 48000,
ChannelCount: 2,
},
SizeLength: 13,
IndexLength: 3,
IndexDeltaLength: 3,
}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "mpeg4-generic/48000/2", rtpmap)
require.Equal(t, "profile-level-id=1; mode=AAC-hbr; sizelength=13;"+
" indexlength=3; indexdeltalength=3; config=1190", fmtp)
}

102
pkg/format/opus.go Normal file
View File

@@ -0,0 +1,102 @@
package format
import (
"fmt"
"strconv"
"strings"
"github.com/pion/rtp"
"github.com/aler9/gortsplib/v2/pkg/rtpcodecs/rtpsimpleaudio"
)
// Opus is a Opus format.
type Opus struct {
PayloadTyp uint8
SampleRate int
ChannelCount int
}
// String implements Format.
func (t *Opus) String() string {
return "Opus"
}
// ClockRate implements Format.
func (t *Opus) ClockRate() int {
return t.SampleRate
}
// PayloadType implements Format.
func (t *Opus) PayloadType() uint8 {
return t.PayloadTyp
}
func (t *Opus) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
t.PayloadTyp = payloadType
tmp := strings.SplitN(clock, "/", 32)
if len(tmp) != 2 {
return fmt.Errorf("invalid clock (%v)", clock)
}
sampleRate, err := strconv.ParseInt(tmp[0], 10, 64)
if err != nil {
return err
}
t.SampleRate = int(sampleRate)
channelCount, err := strconv.ParseInt(tmp[1], 10, 64)
if err != nil {
return err
}
t.ChannelCount = int(channelCount)
return nil
}
// Marshal implements Format.
func (t *Opus) Marshal() (string, string) {
fmtp := "sprop-stereo=" + func() string {
if t.ChannelCount == 2 {
return "1"
}
return "0"
}()
return "opus/" + strconv.FormatInt(int64(t.SampleRate), 10) +
"/" + strconv.FormatInt(int64(t.ChannelCount), 10), fmtp
}
// Clone implements Format.
func (t *Opus) Clone() Format {
return &Opus{
PayloadTyp: t.PayloadTyp,
SampleRate: t.SampleRate,
ChannelCount: t.ChannelCount,
}
}
// PTSEqualsDTS implements Format.
func (t *Opus) PTSEqualsDTS(*rtp.Packet) bool {
return true
}
// CreateDecoder creates a decoder able to decode the content of the format.
func (t *Opus) CreateDecoder() *rtpsimpleaudio.Decoder {
d := &rtpsimpleaudio.Decoder{
SampleRate: t.SampleRate,
}
d.Init()
return d
}
// CreateEncoder creates an encoder able to encode the content of the format.
func (t *Opus) CreateEncoder() *rtpsimpleaudio.Encoder {
e := &rtpsimpleaudio.Encoder{
PayloadType: t.PayloadTyp,
SampleRate: 8000,
}
e.Init()
return e
}

42
pkg/format/opus_test.go Normal file
View File

@@ -0,0 +1,42 @@
package format
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestOpusAttributes(t *testing.T) {
format := &Opus{
PayloadTyp: 96,
SampleRate: 48000,
ChannelCount: 2,
}
require.Equal(t, "Opus", format.String())
require.Equal(t, 48000, format.ClockRate())
require.Equal(t, uint8(96), format.PayloadType())
}
func TestTracOpusClone(t *testing.T) {
format := &Opus{
PayloadTyp: 96,
SampleRate: 48000,
ChannelCount: 2,
}
clone := format.Clone()
require.NotSame(t, format, clone)
require.Equal(t, format, clone)
}
func TestOpusMediaDescription(t *testing.T) {
format := &Opus{
PayloadTyp: 96,
SampleRate: 48000,
ChannelCount: 2,
}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "opus/48000/2", rtpmap)
require.Equal(t, "sprop-stereo=1", fmtp)
}

View File

@@ -1,4 +1,4 @@
package gortsplib package format
import ( import (
"testing" "testing"
@@ -6,18 +6,17 @@ import (
psdp "github.com/pion/sdp/v3" psdp "github.com/pion/sdp/v3"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aler9/gortsplib/pkg/mpeg4audio" "github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
"github.com/aler9/gortsplib/pkg/url"
) )
func TestTrackNewFromMediaDescription(t *testing.T) { func TestNewFromMediaDescription(t *testing.T) {
for _, ca := range []struct { for _, ca := range []struct {
name string name string
md *psdp.MediaDescription md *psdp.MediaDescription
track Track format Format
}{ }{
{ {
"pcma", "audio g711 pcma",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "audio", Media: "audio",
@@ -25,10 +24,10 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
Formats: []string{"8"}, Formats: []string{"8"},
}, },
}, },
&TrackG711{}, &G711{},
}, },
{ {
"pcmu", "audio g711 pcmu",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "audio", Media: "audio",
@@ -36,12 +35,12 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
Formats: []string{"0"}, Formats: []string{"0"},
}, },
}, },
&TrackG711{ &G711{
MULaw: true, MULaw: true,
}, },
}, },
{ {
"g722", "audio g722",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "audio", Media: "audio",
@@ -49,10 +48,10 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
Formats: []string{"9"}, Formats: []string{"9"},
}, },
}, },
&TrackG722{}, &G722{},
}, },
{ {
"lpcm 8", "audio lpcm 8",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "audio", Media: "audio",
@@ -66,15 +65,15 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackLPCM{ &LPCM{
PayloadType: 97, PayloadTyp: 97,
BitDepth: 8, BitDepth: 8,
SampleRate: 48000, SampleRate: 48000,
ChannelCount: 2, ChannelCount: 2,
}, },
}, },
{ {
"lpcm 16", "audio lpcm 16",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "audio", Media: "audio",
@@ -88,15 +87,15 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackLPCM{ &LPCM{
PayloadType: 97, PayloadTyp: 97,
BitDepth: 16, BitDepth: 16,
SampleRate: 96000, SampleRate: 96000,
ChannelCount: 2, ChannelCount: 2,
}, },
}, },
{ {
"lpcm 24", "audio lpcm 24",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "audio", Media: "audio",
@@ -110,15 +109,15 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackLPCM{ &LPCM{
PayloadType: 98, PayloadTyp: 98,
BitDepth: 24, BitDepth: 24,
SampleRate: 44100, SampleRate: 44100,
ChannelCount: 4, ChannelCount: 4,
}, },
}, },
{ {
"mpeg audio", "audio mpeg2 audio",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "audio", Media: "audio",
@@ -126,10 +125,10 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
Formats: []string{"14"}, Formats: []string{"14"},
}, },
}, },
&TrackMPEG2Audio{}, &MPEG2Audio{},
}, },
{ {
"aac", "audio aac",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "audio", Media: "audio",
@@ -151,8 +150,8 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackMPEG4Audio{ &MPEG4Audio{
PayloadType: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
Type: mpeg4audio.ObjectTypeAACLC, Type: mpeg4audio.ObjectTypeAACLC,
SampleRate: 48000, SampleRate: 48000,
@@ -164,7 +163,7 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
{ {
"aac vlc rtsp server", "audio aac vlc rtsp server",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "audio", Media: "audio",
@@ -182,8 +181,8 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackMPEG4Audio{ &MPEG4Audio{
PayloadType: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
Type: mpeg4audio.ObjectTypeAACLC, Type: mpeg4audio.ObjectTypeAACLC,
SampleRate: 48000, SampleRate: 48000,
@@ -195,7 +194,7 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
{ {
"aac without indexlength", "audio aac without indexlength",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "audio", Media: "audio",
@@ -217,8 +216,8 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackMPEG4Audio{ &MPEG4Audio{
PayloadType: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
Type: mpeg4audio.ObjectTypeAACLC, Type: mpeg4audio.ObjectTypeAACLC,
SampleRate: 48000, SampleRate: 48000,
@@ -230,7 +229,7 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
{ {
"vorbis", "audio vorbis",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "audio", Media: "audio",
@@ -248,15 +247,15 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackVorbis{ &Vorbis{
PayloadType: 96, PayloadTyp: 96,
SampleRate: 44100, SampleRate: 44100,
ChannelCount: 2, ChannelCount: 2,
Configuration: []byte{0x01, 0x02, 0x03, 0x04}, Configuration: []byte{0x01, 0x02, 0x03, 0x04},
}, },
}, },
{ {
"opus", "audio opus",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "audio", Media: "audio",
@@ -274,14 +273,14 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackOpus{ &Opus{
PayloadType: 96, PayloadTyp: 96,
SampleRate: 48000, SampleRate: 48000,
ChannelCount: 2, ChannelCount: 2,
}, },
}, },
{ {
"jpeg", "video jpeg",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "video", Media: "video",
@@ -289,10 +288,10 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
Formats: []string{"26"}, Formats: []string{"26"},
}, },
}, },
&TrackJPEG{}, &JPEG{},
}, },
{ {
"mpeg video", "video mpeg2 video",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "video", Media: "video",
@@ -300,10 +299,10 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
Formats: []string{"32"}, Formats: []string{"32"},
}, },
}, },
&TrackMPEG2Video{}, &MPEG2Video{},
}, },
{ {
"h264", "video h264",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "video", Media: "video",
@@ -322,8 +321,8 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackH264{ &H264{
PayloadType: 96, PayloadTyp: 96,
SPS: []byte{ SPS: []byte{
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0, 0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
@@ -336,7 +335,7 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
{ {
"h264 with a space at the end of rtpmap", "video h264 with a space at the end of rtpmap",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "video", Media: "video",
@@ -350,12 +349,12 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackH264{ &H264{
PayloadType: 96, PayloadTyp: 96,
}, },
}, },
{ {
"h264 vlc rtsp server", "video h264 vlc rtsp server",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "video", Media: "video",
@@ -374,8 +373,8 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackH264{ &H264{
PayloadType: 96, PayloadTyp: 96,
SPS: []byte{ SPS: []byte{
0x67, 0x64, 0x00, 0x1f, 0xac, 0xd9, 0x40, 0x50, 0x67, 0x64, 0x00, 0x1f, 0xac, 0xd9, 0x40, 0x50,
0x05, 0xbb, 0x01, 0x6c, 0x80, 0x00, 0x00, 0x03, 0x05, 0xbb, 0x01, 0x6c, 0x80, 0x00, 0x00, 0x03,
@@ -389,7 +388,7 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
{ {
"h264 sprop-parameter-sets with extra data", "video h264 sprop-parameter-sets with extra data",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "video", Media: "video",
@@ -408,8 +407,8 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackH264{ &H264{
PayloadType: 96, PayloadTyp: 96,
SPS: []byte{ SPS: []byte{
0x67, 0x64, 0x00, 0x29, 0xac, 0x13, 0x31, 0x40, 0x67, 0x64, 0x00, 0x29, 0xac, 0x13, 0x31, 0x40,
0x78, 0x04, 0x47, 0xde, 0x03, 0xea, 0x02, 0x02, 0x78, 0x04, 0x47, 0xde, 0x03, 0xea, 0x02, 0x02,
@@ -423,7 +422,7 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
{ {
"h265", "video h265",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "video", Media: "video",
@@ -443,8 +442,8 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackH265{ &H265{
PayloadType: 96, PayloadTyp: 96,
VPS: []byte{ VPS: []byte{
0x40, 0x1, 0xc, 0x1, 0xff, 0xff, 0x1, 0x60, 0x40, 0x1, 0xc, 0x1, 0xff, 0xff, 0x1, 0x60,
0x0, 0x0, 0x3, 0x0, 0x90, 0x0, 0x0, 0x3, 0x0, 0x0, 0x3, 0x0, 0x90, 0x0, 0x0, 0x3,
@@ -465,7 +464,7 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
{ {
"vp8", "video vp8",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "video", Media: "video",
@@ -483,8 +482,8 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackVP8{ &VP8{
PayloadType: 96, PayloadTyp: 96,
MaxFR: func() *int { MaxFR: func() *int {
v := 123 v := 123
return &v return &v
@@ -496,7 +495,7 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
{ {
"vp9", "video vp9",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "video", Media: "video",
@@ -514,8 +513,8 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
}, },
&TrackVP9{ &VP9{
PayloadType: 96, PayloadTyp: 96,
MaxFR: func() *int { MaxFR: func() *int {
v := 123 v := 123
return &v return &v
@@ -531,74 +530,58 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
}, },
}, },
{ {
"multiple formats", "application",
&psdp.MediaDescription{ &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "video", Media: "application",
Protos: []string{"RTP", "AVP"}, Protos: []string{"RTP", "AVP"},
Formats: []string{"96", "98"}, Formats: []string{"98"},
}, },
Attributes: []psdp.Attribute{ Attributes: []psdp.Attribute{
{ {
Key: "rtpmap", Key: "rtpmap",
Value: "96 H264/90000", Value: "98 MetaData/80000",
},
{
Key: "rtpmap",
Value: "98 MetaData",
}, },
{ {
Key: "rtcp-mux", Key: "rtcp-mux",
}, },
{
Key: "fmtp",
Value: "96 packetization-mode=1;profile-level-id=4d002a;" +
"sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAgQA==,aO48gA==",
},
}, },
}, },
&TrackGeneric{ &Generic{
Media: "video", PayloadTyp: 98,
Payloads: []TrackGenericPayload{ RTPMap: "MetaData/80000",
{ ClockRat: 80000,
Type: 96, },
RTPMap: "H264/90000", },
FMTP: "packetization-mode=1;profile-level-id=4d002a;sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAgQA==,aO48gA==", {
}, "application without clock rate",
{ &psdp.MediaDescription{
Type: 98, MediaName: psdp.MediaName{
RTPMap: "MetaData", Media: "application",
}, Protos: []string{"RTP", "AVP"},
Formats: []string{"107"},
}, },
clockRate: 90000, },
&Generic{
PayloadTyp: 107,
ClockRat: 0,
}, },
}, },
} { } {
t.Run(ca.name, func(t *testing.T) { t.Run(ca.name, func(t *testing.T) {
track, err := newTrackFromMediaDescription(ca.md) format, err := Unmarshal(ca.md, ca.md.MediaName.Formats[0])
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, ca.track, track) require.Equal(t, ca.format, format)
}) })
} }
} }
func TestTrackNewFromMediaDescriptionErrors(t *testing.T) { func TestNewFromMediaDescriptionErrors(t *testing.T) {
for _, ca := range []struct { for _, ca := range []struct {
name string name string
md *psdp.MediaDescription md *psdp.MediaDescription
err string err string
}{ }{
{
"no formats",
&psdp.MediaDescription{
MediaName: psdp.MediaName{
Media: "audio",
Protos: []string{"RTP", "AVP"},
Formats: []string{},
},
},
"no media formats found",
},
{ {
"aac missing fmtp", "aac missing fmtp",
&psdp.MediaDescription{ &psdp.MediaDescription{
@@ -635,7 +618,7 @@ func TestTrackNewFromMediaDescriptionErrors(t *testing.T) {
}, },
}, },
}, },
"invalid fmtp (96)", "fmtp attribute is missing",
}, },
{ {
"aac fmtp without key", "aac fmtp without key",
@@ -656,7 +639,7 @@ func TestTrackNewFromMediaDescriptionErrors(t *testing.T) {
}, },
}, },
}, },
"invalid fmtp (96 profile-level-id)", "invalid fmtp (profile-level-id)",
}, },
{ {
"aac missing config", "aac missing config",
@@ -677,7 +660,7 @@ func TestTrackNewFromMediaDescriptionErrors(t *testing.T) {
}, },
}, },
}, },
"config is missing (96 profile-level-id=1)", "config is missing (profile-level-id=1)",
}, },
{ {
"aac invalid config 1", "aac invalid config 1",
@@ -740,7 +723,7 @@ func TestTrackNewFromMediaDescriptionErrors(t *testing.T) {
}, },
}, },
}, },
"sizelength is missing (96 profile-level-id=1; config=1190)", "sizelength is missing (profile-level-id=1; config=1190)",
}, },
{ {
"opus invalid 1", "opus invalid 1",
@@ -795,126 +778,8 @@ func TestTrackNewFromMediaDescriptionErrors(t *testing.T) {
}, },
} { } {
t.Run(ca.name, func(t *testing.T) { t.Run(ca.name, func(t *testing.T) {
_, err := newTrackFromMediaDescription(ca.md) _, err := Unmarshal(ca.md, ca.md.MediaName.Formats[0])
require.EqualError(t, err, ca.err) require.EqualError(t, err, ca.err)
}) })
} }
} }
func TestTrackURL(t *testing.T) {
for _, ca := range []struct {
name string
sdp []byte
baseURL *url.URL
ur *url.URL
}{
{
"missing control",
[]byte("v=0\r\n" +
"m=video 0 RTP/AVP 96\r\n" +
"a=rtpmap:96 H264/90000\r\n"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/path/"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/path/"),
},
{
"absolute control",
[]byte("v=0\r\n" +
"m=video 0 RTP/AVP 96\r\n" +
"a=rtpmap:96 H264/90000\r\n" +
"a=control:rtsp://localhost/path/trackID=7"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/path/"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/path/trackID=7"),
},
{
"relative control",
[]byte("v=0\r\n" +
"m=video 0 RTP/AVP 96\r\n" +
"a=rtpmap:96 H264/90000\r\n" +
"a=control:trackID=5"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/path/"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/path/trackID=5"),
},
{
"relative control, subpath",
[]byte("v=0\r\n" +
"m=video 0 RTP/AVP 96\r\n" +
"a=rtpmap:96 H264/90000\r\n" +
"a=control:trackID=5"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/sub/path/"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/sub/path/trackID=5"),
},
{
"relative control, url without slash",
[]byte("v=0\r\n" +
"m=video 0 RTP/AVP 96\r\n" +
"a=rtpmap:96 H264/90000\r\n" +
"a=control:trackID=5"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/sub/path"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/sub/path/trackID=5"),
},
{
"relative control, url with query",
[]byte("v=0\r\n" +
"m=video 0 RTP/AVP 96\r\n" +
"a=rtpmap:96 H264/90000\r\n" +
"a=control:trackID=5"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/" +
"test?user=tmp&password=BagRep1&channel=1&stream=0.sdp"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/" +
"test?user=tmp&password=BagRep1&channel=1&stream=0.sdp/trackID=5"),
},
{
"relative control, url with special chars and query",
[]byte("v=0\r\n" +
"m=video 0 RTP/AVP 96\r\n" +
"a=rtpmap:96 H264/90000\r\n" +
"a=control:trackID=5"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/" +
"te!st?user=tmp&password=BagRep1!&channel=1&stream=0.sdp"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/" +
"te!st?user=tmp&password=BagRep1!&channel=1&stream=0.sdp/trackID=5"),
},
{
"relative control, url with query without question mark",
[]byte("v=0\r\n" +
"m=video 0 RTP/AVP 96\r\n" +
"a=rtpmap:96 H264/90000\r\n" +
"a=control:trackID=5"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp"),
mustParseURL("rtsp://myuser:mypass@192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp/trackID=5"),
},
{
"relative control, control is query",
[]byte("v=0\r\n" +
"m=video 0 RTP/AVP 96\r\n" +
"a=rtpmap:96 H264/90000\r\n" +
"a=control:?ctype=video"),
mustParseURL("rtsp://192.168.1.99:554/test"),
mustParseURL("rtsp://192.168.1.99:554/test?ctype=video"),
},
{
"relative control, control is query and no path",
[]byte("v=0\r\n" +
"m=video 0 RTP/AVP 96\r\n" +
"a=rtpmap:96 H264/90000\r\n" +
"a=control:?ctype=video"),
mustParseURL("rtsp://192.168.1.99:554/"),
mustParseURL("rtsp://192.168.1.99:554/?ctype=video"),
},
} {
t.Run(ca.name, func(t *testing.T) {
var tracks Tracks
_, err := tracks.Unmarshal(ca.sdp)
require.NoError(t, err)
ur, err := tracks[0].url(ca.baseURL)
require.NoError(t, err)
require.Equal(t, ca.ur, ur)
})
}
}
func TestTrackURLError(t *testing.T) {
track := &TrackH264{}
_, err := track.url(nil)
require.EqualError(t, err, "Content-Base header not provided")
}

108
pkg/format/vorbis.go Normal file
View File

@@ -0,0 +1,108 @@
package format
import (
"encoding/base64"
"fmt"
"strconv"
"strings"
"github.com/pion/rtp"
)
// Vorbis is a Vorbis format.
type Vorbis struct {
PayloadTyp uint8
SampleRate int
ChannelCount int
Configuration []byte
}
// String implements Format.
func (t *Vorbis) String() string {
return "Vorbis"
}
// ClockRate implements Format.
func (t *Vorbis) ClockRate() int {
return t.SampleRate
}
// PayloadType implements Format.
func (t *Vorbis) PayloadType() uint8 {
return t.PayloadTyp
}
func (t *Vorbis) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
t.PayloadTyp = payloadType
tmp := strings.SplitN(clock, "/", 32)
if len(tmp) != 2 {
return fmt.Errorf("invalid clock (%v)", clock)
}
sampleRate, err := strconv.ParseInt(tmp[0], 10, 64)
if err != nil {
return err
}
t.SampleRate = int(sampleRate)
channelCount, err := strconv.ParseInt(tmp[1], 10, 64)
if err != nil {
return err
}
t.ChannelCount = int(channelCount)
if fmtp == "" {
return fmt.Errorf("fmtp attribute is missing")
}
for _, kv := range strings.Split(fmtp, ";") {
kv = strings.Trim(kv, " ")
if len(kv) == 0 {
continue
}
tmp := strings.SplitN(kv, "=", 2)
if len(tmp) != 2 {
return fmt.Errorf("invalid fmtp (%v)", fmtp)
}
if tmp[0] == "configuration" {
conf, err := base64.StdEncoding.DecodeString(tmp[1])
if err != nil {
return fmt.Errorf("invalid AAC config (%v)", tmp[1])
}
t.Configuration = conf
}
}
if t.Configuration == nil {
return fmt.Errorf("config is missing (%v)", fmtp)
}
return nil
}
// Marshal implements Format.
func (t *Vorbis) Marshal() (string, string) {
return "VORBIS/" + strconv.FormatInt(int64(t.SampleRate), 10) +
"/" + strconv.FormatInt(int64(t.ChannelCount), 10),
"configuration=" + base64.StdEncoding.EncodeToString(t.Configuration)
}
// Clone implements Format.
func (t *Vorbis) Clone() Format {
return &Vorbis{
PayloadTyp: t.PayloadTyp,
SampleRate: t.SampleRate,
ChannelCount: t.ChannelCount,
Configuration: t.Configuration,
}
}
// PTSEqualsDTS implements Format.
func (t *Vorbis) PTSEqualsDTS(*rtp.Packet) bool {
return true
}

45
pkg/format/vorbis_test.go Normal file
View File

@@ -0,0 +1,45 @@
package format
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestVorbisAttributes(t *testing.T) {
format := &Vorbis{
PayloadTyp: 96,
SampleRate: 48000,
ChannelCount: 2,
Configuration: []byte{0x01, 0x02, 0x03, 0x04},
}
require.Equal(t, "Vorbis", format.String())
require.Equal(t, 48000, format.ClockRate())
require.Equal(t, uint8(96), format.PayloadType())
}
func TestTracVorbisClone(t *testing.T) {
format := &Vorbis{
PayloadTyp: 96,
SampleRate: 48000,
ChannelCount: 2,
Configuration: []byte{0x01, 0x02, 0x03, 0x04},
}
clone := format.Clone()
require.NotSame(t, format, clone)
require.Equal(t, format, clone)
}
func TestVorbisMediaDescription(t *testing.T) {
format := &Vorbis{
PayloadTyp: 96,
SampleRate: 48000,
ChannelCount: 2,
Configuration: []byte{0x01, 0x02, 0x03, 0x04},
}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "VORBIS/48000/2", rtpmap)
require.Equal(t, "configuration=AQIDBA==", fmtp)
}

110
pkg/format/vp8.go Normal file
View File

@@ -0,0 +1,110 @@
package format
import (
"fmt"
"strconv"
"strings"
"github.com/pion/rtp"
"github.com/aler9/gortsplib/v2/pkg/rtpcodecs/rtpvp8"
)
// VP8 is a VP8 format.
type VP8 struct {
PayloadTyp uint8
MaxFR *int
MaxFS *int
}
// String implements Format.
func (t *VP8) String() string {
return "VP8"
}
// ClockRate implements Format.
func (t *VP8) ClockRate() int {
return 90000
}
// PayloadType implements Format.
func (t *VP8) PayloadType() uint8 {
return t.PayloadTyp
}
func (t *VP8) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
t.PayloadTyp = payloadType
if fmtp != "" {
for _, kv := range strings.Split(fmtp, ";") {
kv = strings.Trim(kv, " ")
if len(kv) == 0 {
continue
}
tmp := strings.SplitN(kv, "=", 2)
if len(tmp) != 2 {
return fmt.Errorf("invalid fmtp attribute (%v)", fmtp)
}
switch tmp[0] {
case "max-fr":
val, err := strconv.ParseUint(tmp[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid max-fr (%v)", tmp[1])
}
v2 := int(val)
t.MaxFR = &v2
case "max-fs":
val, err := strconv.ParseUint(tmp[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid max-fs (%v)", tmp[1])
}
v2 := int(val)
t.MaxFS = &v2
}
}
}
return nil
}
// Marshal implements Format.
func (t *VP8) Marshal() (string, string) {
var tmp []string
if t.MaxFR != nil {
tmp = append(tmp, "max-fr="+strconv.FormatInt(int64(*t.MaxFR), 10))
}
if t.MaxFS != nil {
tmp = append(tmp, "max-fs="+strconv.FormatInt(int64(*t.MaxFS), 10))
}
var fmtp string
if tmp != nil {
fmtp = strings.Join(tmp, ";")
}
return "VP8/90000", fmtp
}
// Clone implements Format.
func (t *VP8) Clone() Format {
return &VP8{
PayloadTyp: t.PayloadTyp,
MaxFR: t.MaxFR,
MaxFS: t.MaxFS,
}
}
// PTSEqualsDTS implements Format.
func (t *VP8) PTSEqualsDTS(*rtp.Packet) bool {
return true
}
// CreateDecoder creates a decoder able to decode the content of the format.
func (t *VP8) CreateDecoder() *rtpvp8.Decoder {
d := &rtpvp8.Decoder{}
d.Init()
return d
}

44
pkg/format/vp8_test.go Normal file
View File

@@ -0,0 +1,44 @@
package format
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestVP8ttributes(t *testing.T) {
format := &VP8{
PayloadTyp: 99,
}
require.Equal(t, "VP8", format.String())
require.Equal(t, 90000, format.ClockRate())
require.Equal(t, uint8(99), format.PayloadType())
}
func TestVP8Clone(t *testing.T) {
maxFR := 123
maxFS := 456
format := &VP8{
PayloadTyp: 96,
MaxFR: &maxFR,
MaxFS: &maxFS,
}
clone := format.Clone()
require.NotSame(t, format, clone)
require.Equal(t, format, clone)
}
func TestVP8MediaDescription(t *testing.T) {
maxFR := 123
maxFS := 456
format := &VP8{
PayloadTyp: 96,
MaxFR: &maxFR,
MaxFS: &maxFS,
}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "VP8/90000", rtpmap)
require.Equal(t, "max-fr=123;max-fs=456", fmtp)
}

123
pkg/format/vp9.go Normal file
View File

@@ -0,0 +1,123 @@
package format
import (
"fmt"
"strconv"
"strings"
"github.com/pion/rtp"
"github.com/aler9/gortsplib/v2/pkg/rtpcodecs/rtpvp9"
)
// VP9 is a VP9 format.
type VP9 struct {
PayloadTyp uint8
MaxFR *int
MaxFS *int
ProfileID *int
}
// String implements Format.
func (t *VP9) String() string {
return "VP9"
}
// ClockRate implements Format.
func (t *VP9) ClockRate() int {
return 90000
}
// PayloadType implements Format.
func (t *VP9) PayloadType() uint8 {
return t.PayloadTyp
}
func (t *VP9) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
t.PayloadTyp = payloadType
if fmtp != "" {
for _, kv := range strings.Split(fmtp, ";") {
kv = strings.Trim(kv, " ")
if len(kv) == 0 {
continue
}
tmp := strings.SplitN(kv, "=", 2)
if len(tmp) != 2 {
return fmt.Errorf("invalid fmtp attribute (%v)", fmtp)
}
switch tmp[0] {
case "max-fr":
val, err := strconv.ParseUint(tmp[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid max-fr (%v)", tmp[1])
}
v2 := int(val)
t.MaxFR = &v2
case "max-fs":
val, err := strconv.ParseUint(tmp[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid max-fs (%v)", tmp[1])
}
v2 := int(val)
t.MaxFS = &v2
case "profile-id":
val, err := strconv.ParseUint(tmp[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid profile-id (%v)", tmp[1])
}
v2 := int(val)
t.ProfileID = &v2
}
}
}
return nil
}
// Marshal implements Format.
func (t *VP9) Marshal() (string, string) {
var tmp []string
if t.MaxFR != nil {
tmp = append(tmp, "max-fr="+strconv.FormatInt(int64(*t.MaxFR), 10))
}
if t.MaxFS != nil {
tmp = append(tmp, "max-fs="+strconv.FormatInt(int64(*t.MaxFS), 10))
}
if t.ProfileID != nil {
tmp = append(tmp, "profile-id="+strconv.FormatInt(int64(*t.ProfileID), 10))
}
var fmtp string
if tmp != nil {
fmtp = strings.Join(tmp, ";")
}
return "VP9/90000", fmtp
}
// Clone implements Format.
func (t *VP9) Clone() Format {
return &VP9{
PayloadTyp: t.PayloadTyp,
MaxFR: t.MaxFR,
MaxFS: t.MaxFS,
ProfileID: t.ProfileID,
}
}
// PTSEqualsDTS implements Format.
func (t *VP9) PTSEqualsDTS(*rtp.Packet) bool {
return true
}
// CreateDecoder creates a decoder able to decode the content of the format.
func (t *VP9) CreateDecoder() *rtpvp9.Decoder {
d := &rtpvp9.Decoder{}
d.Init()
return d
}

48
pkg/format/vp9_test.go Normal file
View File

@@ -0,0 +1,48 @@
package format
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestVP9Attributes(t *testing.T) {
format := &VP9{
PayloadTyp: 100,
}
require.Equal(t, "VP9", format.String())
require.Equal(t, 90000, format.ClockRate())
require.Equal(t, uint8(100), format.PayloadType())
}
func TestVP9Clone(t *testing.T) {
maxFR := 123
maxFS := 456
profileID := 789
format := &VP9{
PayloadTyp: 96,
MaxFR: &maxFR,
MaxFS: &maxFS,
ProfileID: &profileID,
}
clone := format.Clone()
require.NotSame(t, format, clone)
require.Equal(t, format, clone)
}
func TestVP9MediaDescription(t *testing.T) {
maxFR := 123
maxFS := 456
profileID := 789
format := &VP9{
PayloadTyp: 96,
MaxFR: &maxFR,
MaxFS: &maxFS,
ProfileID: &profileID,
}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "VP9/90000", rtpmap)
require.Equal(t, "max-fr=123;max-fs=456;profile-id=789", fmtp)
}

View File

@@ -6,7 +6,7 @@ import (
"math" "math"
"time" "time"
"github.com/aler9/gortsplib/pkg/bits" "github.com/aler9/gortsplib/v2/pkg/bits"
) )
func getPOC(buf []byte, sps *SPS) (uint32, error) { func getPOC(buf []byte, sps *SPS) (uint32, error) {

View File

@@ -3,7 +3,7 @@ package h264
import ( import (
"fmt" "fmt"
"github.com/aler9/gortsplib/pkg/bits" "github.com/aler9/gortsplib/v2/pkg/bits"
) )
func readScalingList(buf []byte, pos *int, size int) ([]int32, bool, error) { func readScalingList(buf []byte, pos *int, size int) ([]int32, bool, error) {

View File

@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/v2/pkg/base"
) )
// AuthMethod is an authentication method. // AuthMethod is an authentication method.

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/v2/pkg/base"
) )
var casesAuthenticate = []struct { var casesAuthenticate = []struct {

View File

@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/v2/pkg/base"
) )
// Authorization is an Authorization header. // Authorization is an Authorization header.

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/v2/pkg/base"
) )
var casesAuthorization = []struct { var casesAuthorization = []struct {

Some files were not shown because too many files have changed in this diff Show More