add TrackLPCM and client-publish-codec-lpcm example

This commit is contained in:
aler9
2022-11-14 22:46:23 +01:00
parent cb05e71b46
commit f07fad893f
13 changed files with 332 additions and 10 deletions

View File

@@ -73,6 +73,7 @@ Features:
* [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-lpcm](examples/client-publish-codec-lpcm/main.go)
* [client-publish-codec-mpeg4audio](examples/client-publish-codec-mpeg4audio/main.go)
* [client-publish-codec-opus](examples/client-publish-codec-opus/main.go)
* [client-publish-codec-pcma](examples/client-publish-codec-pcma/main.go)

View File

@@ -0,0 +1,74 @@
package main
import (
"log"
"net"
"github.com/aler9/gortsplib"
"github.com/pion/rtp"
)
// This example shows how to
// 1. generate RTP/LPCM packets with GStreamer
// 2. connect to a RTSP server, announce an LPCM track
// 3. route the packets from GStreamer to the server
func main() {
// open a listener to receive RTP/LPCM packets
pc, err := net.ListenPacket("udp", "localhost:9000")
if err != nil {
panic(err)
}
defer pc.Close()
log.Println("Waiting for a RTP/LPCM stream on UDP port 9000 - you can send one with GStreamer:\n" +
"gst-launch-1.0 audiotestsrc freq=300 ! audioconvert ! audioresample ! audio/x-raw,format=S16BE,rate=44100" +
" ! rtpL16pay ! 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 an LPCM track
track := &gortsplib.TrackLPCM{
PayloadType: 96,
BitDepth: 16,
SampleRate: 44100,
ChannelCount: 1,
}
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)
}
}
}

View File

@@ -130,11 +130,13 @@ func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) {
case payloadType == 14:
return newTrackMPEG2AudioFromMediaDescription(control)
case codec == "L8", codec == "L16", codec == "L24":
return newTrackLPCMFromMediaDescription(control, payloadType, codec, clock)
case strings.ToLower(codec) == "mpeg4-generic":
return newTrackMPEG4AudioFromMediaDescription(control, payloadType, md)
case codec == "opus":
return newTrackOpusFromMediaDescription(control, payloadType, clock, md)
return newTrackOpusFromMediaDescription(control, payloadType, clock)
}
}
}

View File

@@ -14,9 +14,10 @@ type TrackG722 struct {
func newTrackG722FromMediaDescription(
control string,
rtpmapPart1 string) (*TrackG722, error,
clock string,
) (*TrackG722, error,
) {
tmp := strings.Split(rtpmapPart1, "/")
tmp := strings.Split(clock, "/")
if len(tmp) == 2 && tmp[1] != "1" {
return nil, fmt.Errorf("G722 tracks can have only one channel")
}

View File

@@ -10,7 +10,8 @@ type TrackJPEG struct {
}
func newTrackJPEGFromMediaDescription(
control string) (*TrackJPEG, error,
control string,
) (*TrackJPEG, error,
) {
return &TrackJPEG{
trackBase: trackBase{

115
track_lpcm.go Normal file
View File

@@ -0,0 +1,115 @@
package gortsplib //nolint:dupl
import (
"fmt"
"strconv"
"strings"
psdp "github.com/pion/sdp/v3"
)
// TrackLPCM is an uncompressed, Linear PCM track.
type TrackLPCM struct {
PayloadType uint8
BitDepth int
SampleRate int
ChannelCount int
trackBase
}
func newTrackLPCMFromMediaDescription(
control string,
payloadType uint8,
codec string,
clock string,
) (*TrackLPCM, error,
) {
var bitDepth int
switch codec {
case "L8":
bitDepth = 8
case "L16":
bitDepth = 16
case "L24":
bitDepth = 24
}
tmp := strings.SplitN(clock, "/", 32)
if len(tmp) != 2 {
return nil, fmt.Errorf("invalid clock (%v)", clock)
}
sampleRate, err := strconv.ParseInt(tmp[0], 10, 64)
if err != nil {
return nil, err
}
channelCount, err := strconv.ParseInt(tmp[1], 10, 64)
if err != nil {
return nil, err
}
return &TrackLPCM{
PayloadType: payloadType,
BitDepth: bitDepth,
SampleRate: int(sampleRate),
ChannelCount: int(channelCount),
trackBase: trackBase{
control: control,
},
}, nil
}
// ClockRate returns the track clock rate.
func (t *TrackLPCM) ClockRate() int {
return t.SampleRate
}
// MediaDescription returns the track media description in SDP format.
func (t *TrackLPCM) MediaDescription() *psdp.MediaDescription {
typ := strconv.FormatInt(int64(t.PayloadType), 10)
var codec string
switch t.BitDepth {
case 8:
codec = "L8"
case 16:
codec = "L16"
case 24:
codec = "L24"
}
return &psdp.MediaDescription{
MediaName: psdp.MediaName{
Media: "audio",
Protos: []string{"RTP", "AVP"},
Formats: []string{typ},
},
Attributes: []psdp.Attribute{
{
Key: "rtpmap",
Value: typ + " " + codec + "/" + strconv.FormatInt(int64(t.SampleRate), 10) +
"/" + strconv.FormatInt(int64(t.ChannelCount), 10),
},
{
Key: "control",
Value: t.control,
},
},
}
}
func (t *TrackLPCM) clone() Track {
return &TrackLPCM{
PayloadType: t.PayloadType,
BitDepth: t.BitDepth,
SampleRate: t.SampleRate,
ChannelCount: t.ChannelCount,
trackBase: t.trackBase,
}
}

59
track_lpcm_test.go Normal file
View File

@@ -0,0 +1,59 @@
package gortsplib
import (
"testing"
psdp "github.com/pion/sdp/v3"
"github.com/stretchr/testify/require"
)
func TestTrackLPCMAttributes(t *testing.T) {
track := &TrackLPCM{
PayloadType: 96,
BitDepth: 24,
SampleRate: 44100,
ChannelCount: 2,
}
require.Equal(t, 44100, track.ClockRate())
require.Equal(t, "", track.GetControl())
}
func TestTracLPCMClone(t *testing.T) {
track := &TrackLPCM{
PayloadType: 96,
BitDepth: 16,
SampleRate: 48000,
ChannelCount: 2,
}
clone := track.clone()
require.NotSame(t, track, clone)
require.Equal(t, track, clone)
}
func TestTrackLPCMMediaDescription(t *testing.T) {
track := &TrackLPCM{
PayloadType: 96,
BitDepth: 24,
SampleRate: 96000,
ChannelCount: 2,
}
require.Equal(t, &psdp.MediaDescription{
MediaName: psdp.MediaName{
Media: "audio",
Protos: []string{"RTP", "AVP"},
Formats: []string{"96"},
},
Attributes: []psdp.Attribute{
{
Key: "rtpmap",
Value: "96 L24/96000/2",
},
{
Key: "control",
Value: "",
},
},
}, track.MediaDescription())
}

View File

@@ -10,7 +10,8 @@ type TrackMPEG2Audio struct {
}
func newTrackMPEG2AudioFromMediaDescription(
control string) (*TrackMPEG2Audio, error,
control string,
) (*TrackMPEG2Audio, error,
) {
return &TrackMPEG2Audio{
trackBase: trackBase{

View File

@@ -10,7 +10,8 @@ type TrackMPEG2Video struct {
}
func newTrackMPEG2VideoFromMediaDescription(
control string) (*TrackMPEG2Video, error,
control string,
) (*TrackMPEG2Video, error,
) {
return &TrackMPEG2Video{
trackBase: trackBase{

View File

@@ -23,7 +23,6 @@ func newTrackOpusFromMediaDescription(
control string,
payloadType uint8,
clock string,
md *psdp.MediaDescription,
) (*TrackOpus, error) {
tmp := strings.SplitN(clock, "/", 32)
if len(tmp) != 2 {

View File

@@ -14,9 +14,10 @@ type TrackPCMA struct {
func newTrackPCMAFromMediaDescription(
control string,
rtpmapPart1 string) (*TrackPCMA, error,
clock string,
) (*TrackPCMA, error,
) {
tmp := strings.Split(rtpmapPart1, "/")
tmp := strings.Split(clock, "/")
if len(tmp) == 2 && tmp[1] != "1" {
return nil, fmt.Errorf("PCMA tracks can have only one channel")
}

View File

@@ -14,7 +14,8 @@ type TrackPCMU struct {
func newTrackPCMUFromMediaDescription(
control string,
clock string) (*TrackPCMU, error,
clock string,
) (*TrackPCMU, error,
) {
tmp := strings.SplitN(clock, "/", 2)
if len(tmp) == 2 && tmp[1] != "1" {

View File

@@ -49,6 +49,72 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
},
&TrackG722{},
},
{
"lpcm 8",
&psdp.MediaDescription{
MediaName: psdp.MediaName{
Media: "audio",
Protos: []string{"RTP", "AVP"},
Formats: []string{"97"},
},
Attributes: []psdp.Attribute{
{
Key: "rtpmap",
Value: "97 L8/48000/2",
},
},
},
&TrackLPCM{
PayloadType: 97,
BitDepth: 8,
SampleRate: 48000,
ChannelCount: 2,
},
},
{
"lpcm 16",
&psdp.MediaDescription{
MediaName: psdp.MediaName{
Media: "audio",
Protos: []string{"RTP", "AVP"},
Formats: []string{"97"},
},
Attributes: []psdp.Attribute{
{
Key: "rtpmap",
Value: "97 L16/96000/2",
},
},
},
&TrackLPCM{
PayloadType: 97,
BitDepth: 16,
SampleRate: 96000,
ChannelCount: 2,
},
},
{
"lpcm 24",
&psdp.MediaDescription{
MediaName: psdp.MediaName{
Media: "audio",
Protos: []string{"RTP", "AVP"},
Formats: []string{"98"},
},
Attributes: []psdp.Attribute{
{
Key: "rtpmap",
Value: "98 L24/44100/4",
},
},
},
&TrackLPCM{
PayloadType: 98,
BitDepth: 24,
SampleRate: 44100,
ChannelCount: 4,
},
},
{
"mpeg audio",
&psdp.MediaDescription{