diff --git a/README.md b/README.md index ff956f3e..0aa1bf61 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ Features: * [client-read-options](examples/client-read-options/main.go) * [client-read-pause](examples/client-read-pause/main.go) * [client-read-republish](examples/client-read-republish/main.go) +* [client-publish-codec-g722](examples/client-publish-codec-g722/main.go) * [client-publish-codec-h264](examples/client-publish-codec-h264/main.go) * [client-publish-codec-mpeg4audio](examples/client-publish-codec-mpeg4audio/main.go) * [client-publish-codec-opus](examples/client-publish-codec-opus/main.go) diff --git a/examples/client-publish-codec-g722/main.go b/examples/client-publish-codec-g722/main.go new file mode 100644 index 00000000..b17c0e72 --- /dev/null +++ b/examples/client-publish-codec-g722/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "log" + "net" + + "github.com/aler9/gortsplib" + "github.com/pion/rtp" +) + +// This example shows how to +// 1. generate RTP/G722 packets with GStreamer +// 2. connect to a RTSP server, announce a G722 track +// 3. route the packets from GStreamer to the server + +func main() { + // open a listener to receive RTP/G722 packets + pc, err := net.ListenPacket("udp", "localhost:9000") + if err != nil { + panic(err) + } + defer pc.Close() + + log.Println("Waiting for a RTP/G722 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=16000" + + " ! avenc_g722 ! rtpg722pay ! udpsink host=127.0.0.1 port=9000") + + // wait for first packet + buf := make([]byte, 2048) + n, _, err := pc.ReadFrom(buf) + if err != nil { + panic(err) + } + log.Println("stream connected") + + // create a G722 track + track := &gortsplib.TrackG722{} + + c := gortsplib.Client{} + + // connect to the server and start publishing the track + err = c.StartPublishing("rtsp://localhost:8554/mystream", + gortsplib.Tracks{track}) + if err != nil { + panic(err) + } + defer c.Close() + + var pkt rtp.Packet + for { + // parse RTP packet + err = pkt.Unmarshal(buf[:n]) + if err != nil { + panic(err) + } + + // route RTP packet to the server + err = c.WritePacketRTP(0, &pkt) + if err != nil { + panic(err) + } + + // read another RTP packet from source + n, _, err = pc.ReadFrom(buf) + if err != nil { + panic(err) + } + } +} diff --git a/track.go b/track.go index 1442b173..654f7495 100644 --- a/track.go +++ b/track.go @@ -124,6 +124,9 @@ func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) { case payloadType == 8: return newTrackPCMAFromMediaDescription(control, clock) + case payloadType == 9: + return newTrackG722FromMediaDescription(control, clock) + case payloadType == 14: return newTrackMPEG2AudioFromMediaDescription(control) diff --git a/track_g722.go b/track_g722.go new file mode 100644 index 00000000..42929df2 --- /dev/null +++ b/track_g722.go @@ -0,0 +1,61 @@ +package gortsplib //nolint:dupl + +import ( + "fmt" + "strings" + + psdp "github.com/pion/sdp/v3" +) + +// TrackG722 is a G722 track. +type TrackG722 struct { + trackBase +} + +func newTrackG722FromMediaDescription( + control string, + rtpmapPart1 string) (*TrackG722, error, +) { + tmp := strings.Split(rtpmapPart1, "/") + if len(tmp) == 2 && tmp[1] != "1" { + return nil, fmt.Errorf("G722 tracks can have only one channel") + } + + return &TrackG722{ + trackBase: trackBase{ + control: control, + }, + }, nil +} + +// ClockRate returns the track clock rate. +func (t *TrackG722) ClockRate() int { + return 8000 +} + +// MediaDescription returns the track media description in SDP format. +func (t *TrackG722) MediaDescription() *psdp.MediaDescription { + return &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "audio", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"9"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "9 G722/8000", + }, + { + Key: "control", + Value: t.control, + }, + }, + } +} + +func (t *TrackG722) clone() Track { + return &TrackG722{ + trackBase: t.trackBase, + } +} diff --git a/track_g722_test.go b/track_g722_test.go new file mode 100644 index 00000000..51e56327 --- /dev/null +++ b/track_g722_test.go @@ -0,0 +1,44 @@ +package gortsplib //nolint:dupl + +import ( + "testing" + + psdp "github.com/pion/sdp/v3" + "github.com/stretchr/testify/require" +) + +func TestTrackG722Attributes(t *testing.T) { + track := &TrackG722{} + require.Equal(t, 8000, track.ClockRate()) + require.Equal(t, "", track.GetControl()) +} + +func TestTrackG722Clone(t *testing.T) { + track := &TrackG722{} + + clone := track.clone() + require.NotSame(t, track, clone) + require.Equal(t, track, clone) +} + +func TestTrackG722MediaDescription(t *testing.T) { + track := &TrackG722{} + + require.Equal(t, &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "audio", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"9"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "9 G722/8000", + }, + { + Key: "control", + Value: "", + }, + }, + }, track.MediaDescription()) +} diff --git a/track_pcma.go b/track_pcma.go index 901dc60e..ac34e9f9 100644 --- a/track_pcma.go +++ b/track_pcma.go @@ -18,7 +18,7 @@ func newTrackPCMAFromMediaDescription( ) { tmp := strings.Split(rtpmapPart1, "/") if len(tmp) == 2 && tmp[1] != "1" { - return nil, fmt.Errorf("PCMU tracks can have only one channel") + return nil, fmt.Errorf("PCMA tracks can have only one channel") } return &TrackPCMA{ diff --git a/track_test.go b/track_test.go index d8b5596a..fbd92c2d 100644 --- a/track_test.go +++ b/track_test.go @@ -38,6 +38,17 @@ func TestTrackNewFromMediaDescription(t *testing.T) { }, &TrackPCMU{}, }, + { + "g722", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "audio", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"9"}, + }, + }, + &TrackG722{}, + }, { "mpeg audio", &psdp.MediaDescription{