mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 15:16:51 +08:00
add TrackPCMU
This commit is contained in:
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
70
examples/client-publish-pcmu/main.go
Normal file
70
examples/client-publish-pcmu/main.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
33
track.go
33
track.go
@@ -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 {
|
||||
rtpmapParts := strings.Split(rtpmap, " ")
|
||||
if len(rtpmapParts) != 2 {
|
||||
return "", 0
|
||||
}
|
||||
|
||||
tmp, err := strconv.ParseInt(rtpmapParts[0], 10, 64)
|
||||
if err == nil {
|
||||
if err != nil {
|
||||
return "", 0
|
||||
}
|
||||
payloadType := uint8(tmp)
|
||||
|
||||
return rtpmapParts[1], payloadType
|
||||
}()
|
||||
|
||||
switch {
|
||||
case md.MediaName.Media == "video":
|
||||
if rtpmapParts[1] == "H264/90000" {
|
||||
if rtpmapPart1 == "H264/90000" {
|
||||
return newTrackH264FromMediaDescription(payloadType, md)
|
||||
}
|
||||
|
||||
case md.MediaName.Media == "audio":
|
||||
switch {
|
||||
case strings.HasPrefix(strings.ToLower(rtpmapParts[1]), "mpeg4-generic/"):
|
||||
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(rtpmapParts[1], "opus/"):
|
||||
return newTrackOpusFromMediaDescription(payloadType, rtpmapParts[1], md)
|
||||
}
|
||||
}
|
||||
}
|
||||
case strings.HasPrefix(rtpmapPart1, "opus/"):
|
||||
return newTrackOpusFromMediaDescription(payloadType, rtpmapPart1, md)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
79
track_pcmu.go
Normal 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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@@ -40,11 +40,7 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
|
||||
Formats: []string{"0"},
|
||||
},
|
||||
},
|
||||
&TrackGeneric{
|
||||
clockRate: 8000,
|
||||
media: "audio",
|
||||
formats: []string{"0"},
|
||||
},
|
||||
&TrackPCMU{},
|
||||
},
|
||||
{
|
||||
"aac",
|
||||
|
@@ -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{
|
||||
&TrackPCMU{
|
||||
control: "rtsp://10.0.100.50/profile5/media.smp/trackID=a",
|
||||
clockRate: 8000,
|
||||
media: "audio",
|
||||
formats: []string{"0"},
|
||||
rtpmap: "0 PCMU/8000",
|
||||
},
|
||||
}, tracks)
|
||||
}
|
||||
|
Reference in New Issue
Block a user