add TrackPCMU

This commit is contained in:
aler9
2022-03-15 11:51:58 +01:00
parent bf74ea8ec7
commit 0ba73bacab
9 changed files with 188 additions and 34 deletions

View File

@@ -59,6 +59,7 @@ Features:
* [client-read-aac](examples/client-read-aac/main.go)
* [client-read-republish](examples/client-read-republish/main.go)
* [client-publish-h264](examples/client-publish-h264/main.go)
* [client-publish-pcmu](examples/client-publish-pcmu/main.go)
* [client-publish-aac](examples/client-publish-aac/main.go)
* [client-publish-opus](examples/client-publish-opus/main.go)
* [client-publish-options](examples/client-publish-options/main.go)

View File

@@ -23,8 +23,7 @@ func main() {
log.Println("Waiting for a RTP/AAC 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=48000" +
" ! avenc_aac bitrate=128000" +
" ! rtpmp4gpay ! udpsink host=127.0.0.1 port=9000")
" ! avenc_aac bitrate=128000 ! rtpmp4gpay ! udpsink host=127.0.0.1 port=9000")
// wait for first packet
buf := make([]byte, 2048)

View File

@@ -23,8 +23,7 @@ func main() {
log.Println("Waiting for a RTP/Opus 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=48000" +
" ! opusenc" +
" ! rtpopuspay ! udpsink host=127.0.0.1 port=9000")
" ! opusenc ! rtpopuspay ! udpsink host=127.0.0.1 port=9000")
// wait for first packet
buf := make([]byte, 2048)

View File

@@ -0,0 +1,70 @@
package main
import (
"log"
"net"
"github.com/aler9/gortsplib"
"github.com/pion/rtp/v2"
)
// This example shows how to
// 1. generate RTP/PCMU packets with GStreamer
// 2. connect to a RTSP server, announce a PCMU track
// 3. route the packets from GStreamer to the server
func main() {
// open a listener to receive RTP/PCMU packets
pc, err := net.ListenPacket("udp", "localhost:9000")
if err != nil {
panic(err)
}
defer pc.Close()
log.Println("Waiting for a RTP/PCMU 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" +
" ! mulawenc ! rtppcmupay ! udpsink host=127.0.0.1 port=9000")
// wait for first packet
buf := make([]byte, 2048)
_, _, err = pc.ReadFrom(buf)
if err != nil {
panic(err)
}
log.Println("stream connected")
// create a PCMU track
track := gortsplib.NewTrackPCMU()
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()
buf = make([]byte, 2048)
var pkt rtp.Packet
for {
// read packets from the source
n, _, err := pc.ReadFrom(buf)
if err != nil {
panic(err)
}
// marshal RTP packets
err = pkt.Unmarshal(buf[:n])
if err != nil {
panic(err)
}
// route RTP packets to the server
err = c.WritePacketRTP(0, &pkt)
if err != nil {
panic(err)
}
}
}

View File

@@ -25,30 +25,43 @@ type Track interface {
}
func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) {
if rtpmap, ok := md.Attribute("rtpmap"); ok {
rtpmapPart1, payloadType := func() (string, uint8) {
rtpmap, ok := md.Attribute("rtpmap")
if !ok {
return "", 0
}
rtpmap = strings.TrimSpace(rtpmap)
if rtpmapParts := strings.Split(rtpmap, " "); len(rtpmapParts) == 2 {
tmp, err := strconv.ParseInt(rtpmapParts[0], 10, 64)
if err == nil {
payloadType := uint8(tmp)
rtpmapParts := strings.Split(rtpmap, " ")
if len(rtpmapParts) != 2 {
return "", 0
}
switch {
case md.MediaName.Media == "video":
if rtpmapParts[1] == "H264/90000" {
return newTrackH264FromMediaDescription(payloadType, md)
}
tmp, err := strconv.ParseInt(rtpmapParts[0], 10, 64)
if err != nil {
return "", 0
}
payloadType := uint8(tmp)
case md.MediaName.Media == "audio":
switch {
case strings.HasPrefix(strings.ToLower(rtpmapParts[1]), "mpeg4-generic/"):
return newTrackAACFromMediaDescription(payloadType, md)
return rtpmapParts[1], payloadType
}()
case strings.HasPrefix(rtpmapParts[1], "opus/"):
return newTrackOpusFromMediaDescription(payloadType, rtpmapParts[1], md)
}
}
}
switch {
case md.MediaName.Media == "video":
if rtpmapPart1 == "H264/90000" {
return newTrackH264FromMediaDescription(payloadType, md)
}
case md.MediaName.Media == "audio":
switch {
case len(md.MediaName.Formats) == 1 && md.MediaName.Formats[0] == "0":
return newTrackPCMUFromMediaDescription(rtpmapPart1, md)
case strings.HasPrefix(strings.ToLower(rtpmapPart1), "mpeg4-generic/"):
return newTrackAACFromMediaDescription(payloadType, md)
case strings.HasPrefix(rtpmapPart1, "opus/"):
return newTrackOpusFromMediaDescription(payloadType, rtpmapPart1, md)
}
}

View File

@@ -32,6 +32,7 @@ func newTrackOpusFromMediaDescription(
rtpmapPart1 string,
md *psdp.MediaDescription) (*TrackOpus, error) {
control := trackFindControl(md)
tmp := strings.SplitN(rtpmapPart1, "/", 3)
if len(tmp) != 3 {
return nil, fmt.Errorf("invalid rtpmap (%v)", rtpmapPart1)

79
track_pcmu.go Normal file
View File

@@ -0,0 +1,79 @@
package gortsplib
import (
"fmt"
"strings"
psdp "github.com/pion/sdp/v3"
"github.com/aler9/gortsplib/pkg/base"
)
// TrackPCMU is a PCMU track.
type TrackPCMU struct {
control string
}
// NewTrackPCMU allocates a TrackPCMU.
func NewTrackPCMU() *TrackPCMU {
return &TrackPCMU{}
}
func newTrackPCMUFromMediaDescription(rtpmapPart1 string,
md *psdp.MediaDescription) (*TrackPCMU, error,
) {
control := trackFindControl(md)
tmp := strings.Split(rtpmapPart1, "/")
if len(tmp) >= 3 && tmp[2] != "1" {
return nil, fmt.Errorf("PCMU tracks must have only one channel")
}
return &TrackPCMU{
control: control,
}, nil
}
// ClockRate returns the track clock rate.
func (t *TrackPCMU) ClockRate() int {
return 8000
}
func (t *TrackPCMU) clone() Track {
return &TrackPCMU{}
}
// GetControl returns the track control.
func (t *TrackPCMU) GetControl() string {
return t.control
}
// SetControl sets the track control.
func (t *TrackPCMU) SetControl(c string) {
t.control = c
}
func (t *TrackPCMU) url(contentBase *base.URL) (*base.URL, error) {
return trackURL(t, contentBase)
}
// MediaDescription returns the media description in SDP format.
func (t *TrackPCMU) MediaDescription() *psdp.MediaDescription {
return &psdp.MediaDescription{
MediaName: psdp.MediaName{
Media: "audio",
Protos: []string{"RTP", "AVP"},
Formats: []string{"0"},
},
Attributes: []psdp.Attribute{
{
Key: "rtpmap",
Value: "0 PCMU/8000",
},
{
Key: "control",
Value: t.control,
},
},
}
}

View File

@@ -40,11 +40,7 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
Formats: []string{"0"},
},
},
&TrackGeneric{
clockRate: 8000,
media: "audio",
formats: []string{"0"},
},
&TrackPCMU{},
},
{
"aac",

View File

@@ -76,12 +76,8 @@ func TestTracksReadSkipGenericTracksWithoutClockRate(t *testing.T) {
sps: []byte{0x67, 0x64, 0x00, 0x28, 0xac, 0xb4, 0x03, 0xc0, 0x11, 0x3f, 0x2a},
pps: []byte{0x68, 0xee, 0x01, 0x9e, 0x2c},
},
&TrackGeneric{
control: "rtsp://10.0.100.50/profile5/media.smp/trackID=a",
clockRate: 8000,
media: "audio",
formats: []string{"0"},
rtpmap: "0 PCMU/8000",
&TrackPCMU{
control: "rtsp://10.0.100.50/profile5/media.smp/trackID=a",
},
}, tracks)
}