mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 23:26:54 +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-aac](examples/client-read-aac/main.go)
|
||||||
* [client-read-republish](examples/client-read-republish/main.go)
|
* [client-read-republish](examples/client-read-republish/main.go)
|
||||||
* [client-publish-h264](examples/client-publish-h264/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-aac](examples/client-publish-aac/main.go)
|
||||||
* [client-publish-opus](examples/client-publish-opus/main.go)
|
* [client-publish-opus](examples/client-publish-opus/main.go)
|
||||||
* [client-publish-options](examples/client-publish-options/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" +
|
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" +
|
"gst-launch-1.0 audiotestsrc freq=300 ! audioconvert ! audioresample ! audio/x-raw,rate=48000" +
|
||||||
" ! avenc_aac bitrate=128000" +
|
" ! avenc_aac bitrate=128000 ! rtpmp4gpay ! udpsink host=127.0.0.1 port=9000")
|
||||||
" ! rtpmp4gpay ! udpsink host=127.0.0.1 port=9000")
|
|
||||||
|
|
||||||
// wait for first packet
|
// wait for first packet
|
||||||
buf := make([]byte, 2048)
|
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" +
|
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" +
|
"gst-launch-1.0 audiotestsrc freq=300 ! audioconvert ! audioresample ! audio/x-raw,rate=48000" +
|
||||||
" ! opusenc" +
|
" ! opusenc ! rtpopuspay ! udpsink host=127.0.0.1 port=9000")
|
||||||
" ! rtpopuspay ! udpsink host=127.0.0.1 port=9000")
|
|
||||||
|
|
||||||
// wait for first packet
|
// wait for first packet
|
||||||
buf := make([]byte, 2048)
|
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) {
|
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)
|
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)
|
tmp, err := strconv.ParseInt(rtpmapParts[0], 10, 64)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
|
return "", 0
|
||||||
|
}
|
||||||
payloadType := uint8(tmp)
|
payloadType := uint8(tmp)
|
||||||
|
|
||||||
|
return rtpmapParts[1], payloadType
|
||||||
|
}()
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case md.MediaName.Media == "video":
|
case md.MediaName.Media == "video":
|
||||||
if rtpmapParts[1] == "H264/90000" {
|
if rtpmapPart1 == "H264/90000" {
|
||||||
return newTrackH264FromMediaDescription(payloadType, md)
|
return newTrackH264FromMediaDescription(payloadType, md)
|
||||||
}
|
}
|
||||||
|
|
||||||
case md.MediaName.Media == "audio":
|
case md.MediaName.Media == "audio":
|
||||||
switch {
|
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)
|
return newTrackAACFromMediaDescription(payloadType, md)
|
||||||
|
|
||||||
case strings.HasPrefix(rtpmapParts[1], "opus/"):
|
case strings.HasPrefix(rtpmapPart1, "opus/"):
|
||||||
return newTrackOpusFromMediaDescription(payloadType, rtpmapParts[1], md)
|
return newTrackOpusFromMediaDescription(payloadType, rtpmapPart1, md)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -32,6 +32,7 @@ func newTrackOpusFromMediaDescription(
|
|||||||
rtpmapPart1 string,
|
rtpmapPart1 string,
|
||||||
md *psdp.MediaDescription) (*TrackOpus, error) {
|
md *psdp.MediaDescription) (*TrackOpus, error) {
|
||||||
control := trackFindControl(md)
|
control := trackFindControl(md)
|
||||||
|
|
||||||
tmp := strings.SplitN(rtpmapPart1, "/", 3)
|
tmp := strings.SplitN(rtpmapPart1, "/", 3)
|
||||||
if len(tmp) != 3 {
|
if len(tmp) != 3 {
|
||||||
return nil, fmt.Errorf("invalid rtpmap (%v)", rtpmapPart1)
|
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"},
|
Formats: []string{"0"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&TrackGeneric{
|
&TrackPCMU{},
|
||||||
clockRate: 8000,
|
|
||||||
media: "audio",
|
|
||||||
formats: []string{"0"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"aac",
|
"aac",
|
||||||
|
@@ -76,12 +76,8 @@ func TestTracksReadSkipGenericTracksWithoutClockRate(t *testing.T) {
|
|||||||
sps: []byte{0x67, 0x64, 0x00, 0x28, 0xac, 0xb4, 0x03, 0xc0, 0x11, 0x3f, 0x2a},
|
sps: []byte{0x67, 0x64, 0x00, 0x28, 0xac, 0xb4, 0x03, 0xc0, 0x11, 0x3f, 0x2a},
|
||||||
pps: []byte{0x68, 0xee, 0x01, 0x9e, 0x2c},
|
pps: []byte{0x68, 0xee, 0x01, 0x9e, 0x2c},
|
||||||
},
|
},
|
||||||
&TrackGeneric{
|
&TrackPCMU{
|
||||||
control: "rtsp://10.0.100.50/profile5/media.smp/trackID=a",
|
control: "rtsp://10.0.100.50/profile5/media.smp/trackID=a",
|
||||||
clockRate: 8000,
|
|
||||||
media: "audio",
|
|
||||||
formats: []string{"0"},
|
|
||||||
rtpmap: "0 PCMU/8000",
|
|
||||||
},
|
},
|
||||||
}, tracks)
|
}, tracks)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user