webrtc: reset recomputed audio PTS if it drifts too much (#6021)

This commit is contained in:
Alessandro Ros
2026-08-01 16:24:53 +00:00
committed by GitHub
parent 24dd8f44a5
commit 4bb896afe0
3 changed files with 348 additions and 361 deletions
+87 -37
View File
@@ -26,7 +26,8 @@ import (
)
const (
webrtcPayloadMaxSize = 1188 // 1200 - 12 (RTP header)
webrtcPayloadMaxSize = 1188 // 1200 - 12 (RTP header)
audioPTSDriftTolerance = 500 * time.Millisecond
)
var multichannelOpusSDP = map[int]string{
@@ -61,6 +62,11 @@ func timestampToDuration(t int64, clockRate int) time.Duration {
return multiplyAndDivide2(time.Duration(t), time.Second, time.Duration(clockRate))
}
func ptsDriftExceeded(pts uint32, firstPTS uint32, tolerance uint32) bool {
delta := int32(pts - firstPTS)
return delta > int32(tolerance) || delta < -int32(tolerance)
}
func setupVideoTrack(
desc *description.Session,
r *stream.Reader,
@@ -359,16 +365,28 @@ func setupAudioTrack(
Caps: caps,
}
curTimestamp, err := randUint32()
if err != nil {
return nil, err
}
var pts uint32
ptsInitialized := false
ptsTolerance := uint32(multiplyAndDivide2(
audioPTSDriftTolerance,
time.Duration(opusFormat.ClockRate()),
time.Second,
))
r.OnData(
media,
opusFormat,
func(u *unit.Unit) error {
baseTimestamp := curTimestamp
// recompute timestamp from scratch.
// Chrome requires a precise timestamp that FFmpeg doesn't provide.
// also reset in case of drifts.
firstPTS := u.RTPPackets[0].Timestamp
if !ptsInitialized || ptsDriftExceeded(pts, firstPTS, ptsTolerance) {
pts = firstPTS
ptsInitialized = true
}
baseTimestamp := pts
for _, orig := range u.RTPPackets {
// create a copy of the packet that we can edit freely
@@ -377,13 +395,12 @@ func setupAudioTrack(
Payload: orig.Payload,
}
// recompute timestamp from scratch.
// Chrome requires a precise timestamp that FFmpeg doesn't provide.
pkt.Timestamp = curTimestamp
curTimestamp += uint32(opus.PacketDuration2(pkt.Payload))
pkt.Timestamp = pts
ntp := u.NTP.Add(timestampToDuration(int64(pkt.Timestamp-baseTimestamp), 48000))
track.WriteRTPWithNTP(pkt, ntp) //nolint:errcheck
pts += uint32(opus.PacketDuration2(pkt.Payload))
}
return nil
@@ -482,16 +499,28 @@ func setupAudioTrack(
}
if g711Format.ClockRate() == 8000 {
curTimestamp, err := randUint32()
if err != nil {
return nil, err
}
var pts uint32
ptsInitialized := false
ptsTolerance := uint32(multiplyAndDivide2(
audioPTSDriftTolerance,
time.Duration(g711Format.ClockRate()),
time.Second,
))
r.OnData(
media,
g711Format,
func(u *unit.Unit) error {
baseTimestamp := curTimestamp
// recompute timestamp from scratch.
// Chrome requires a precise timestamp that FFmpeg doesn't provide.
// also reset in case of drifts.
firstPTS := u.RTPPackets[0].Timestamp
if !ptsInitialized || ptsDriftExceeded(pts, firstPTS, ptsTolerance) {
pts = firstPTS
ptsInitialized = true
}
baseTimestamp := pts
for _, orig := range u.RTPPackets {
// create a copy of the packet that we can edit freely
@@ -500,13 +529,12 @@ func setupAudioTrack(
Payload: orig.Payload,
}
// recompute timestamp from scratch.
// Chrome requires a precise timestamp that FFmpeg doesn't provide.
pkt.Timestamp = curTimestamp
curTimestamp += uint32(len(pkt.Payload)) / uint32(g711Format.ChannelCount)
pkt.Timestamp = pts
ntp := u.NTP.Add(timestampToDuration(int64(pkt.Timestamp-baseTimestamp), 8000))
track.WriteRTPWithNTP(pkt, ntp) //nolint:errcheck
pts += uint32(len(pkt.Payload)) / uint32(g711Format.ChannelCount)
}
return nil
@@ -523,10 +551,13 @@ func setupAudioTrack(
return nil, err
}
curTimestamp, err := randUint32()
if err != nil {
return nil, err
}
var pts uint32
ptsInitialized := false
ptsTolerance := uint32(multiplyAndDivide2(
audioPTSDriftTolerance,
time.Duration(g711Format.ClockRate()),
time.Second,
))
r.OnData(
media,
@@ -552,16 +583,24 @@ func setupAudioTrack(
return nil //nolint:nilerr
}
baseTimestamp := curTimestamp
// recompute timestamp from scratch.
// Chrome requires a precise timestamp that FFmpeg doesn't provide.
// also reset in case of drifts.
firstPTS := u.RTPPackets[0].Timestamp
if !ptsInitialized || ptsDriftExceeded(pts, firstPTS, ptsTolerance) {
pts = firstPTS
ptsInitialized = true
}
baseTimestamp := pts
for _, pkt := range packets {
// recompute timestamp from scratch.
// Chrome requires a precise timestamp that FFmpeg doesn't provide.
pkt.Timestamp = curTimestamp
curTimestamp += uint32(len(pkt.Payload)) / 2 / uint32(g711Format.ChannelCount)
pkt.Timestamp = pts
ntp := u.NTP.Add(timestampToDuration(int64(pkt.Timestamp-baseTimestamp), g711Format.ClockRate()))
track.WriteRTPWithNTP(pkt, ntp) //nolint:errcheck
pts += uint32(len(pkt.Payload)) / 2 / uint32(g711Format.ChannelCount)
}
return nil
@@ -609,10 +648,13 @@ func setupAudioTrack(
return nil, err
}
curTimestamp, err := randUint32()
if err != nil {
return nil, err
}
var pts uint32
ptsInitialized := false
ptsTolerance := uint32(multiplyAndDivide2(
audioPTSDriftTolerance,
time.Duration(lpcmFormat.ClockRate()),
time.Second,
))
r.OnData(
media,
@@ -627,16 +669,24 @@ func setupAudioTrack(
return nil //nolint:nilerr
}
baseTimestamp := curTimestamp
// recompute timestamp from scratch.
// Chrome requires a precise timestamp that FFmpeg doesn't provide.
// also reset in case of drifts.
firstPTS := u.RTPPackets[0].Timestamp
if !ptsInitialized || ptsDriftExceeded(pts, firstPTS, ptsTolerance) {
pts = firstPTS
ptsInitialized = true
}
baseTimestamp := pts
for _, pkt := range packets {
// recompute timestamp from scratch.
// Chrome requires a precise timestamp that FFmpeg doesn't provide.
pkt.Timestamp = curTimestamp
curTimestamp += uint32(len(pkt.Payload)) / 2 / uint32(lpcmFormat.ChannelCount)
pkt.Timestamp = pts
ntp := u.NTP.Add(timestampToDuration(int64(pkt.Timestamp-baseTimestamp), lpcmFormat.ClockRate()))
track.WriteRTPWithNTP(pkt, ntp) //nolint:errcheck
pts += uint32(len(pkt.Payload)) / 2 / uint32(lpcmFormat.ChannelCount)
}
return nil
+230 -296
View File
@@ -1,4 +1,4 @@
package webrtc
package webrtc_test
import (
"fmt"
@@ -8,6 +8,7 @@ import (
"github.com/bluenviron/gortsplib/v5/pkg/description"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/protocols/webrtc"
"github.com/bluenviron/mediamtx/internal/stream"
"github.com/bluenviron/mediamtx/internal/test"
"github.com/bluenviron/mediamtx/internal/unit"
@@ -27,10 +28,10 @@ func TestFromStreamNoSupportedCodecs(t *testing.T) {
}),
}
pc := &PeerConnection{}
pc := &webrtc.PeerConnection{}
err := FromStream(desc, r, pc)
require.Equal(t, errNoSupportedCodecsFrom, err)
err := webrtc.FromStream(desc, r, pc)
require.ErrorContains(t, err, "the stream doesn't contain any supported codec")
}
func TestFromStreamSkipUnsupportedTracks(t *testing.T) {
@@ -57,9 +58,9 @@ func TestFromStreamSkipUnsupportedTracks(t *testing.T) {
}),
}
pc := &PeerConnection{}
pc := &webrtc.PeerConnection{}
err := FromStream(desc, r, pc)
err := webrtc.FromStream(desc, r, pc)
require.NoError(t, err)
require.Equal(t, 1, n)
@@ -74,10 +75,10 @@ func TestFromStream(t *testing.T) {
}},
}
pc := &PeerConnection{}
pc := &webrtc.PeerConnection{}
r := &stream.Reader{Parent: test.NilLogger}
err := FromStream(desc, r, pc)
err := webrtc.FromStream(desc, r, pc)
require.NoError(t, err)
require.Equal(t, ca.webrtcCaps, pc.OutboundTracks[0].Caps)
@@ -85,302 +86,236 @@ func TestFromStream(t *testing.T) {
}
}
func TestFromStreamResampleOpus(t *testing.T) {
strm := &stream.Stream{
OrigDesc: &description.Session{Medias: []*description.Media{
{
Type: description.MediaTypeAudio,
Formats: []format.Format{&format.Opus{
ChannelCount: 2,
}},
func TestFromStreamResampleAudio(t *testing.T) {
for _, ca := range []struct {
name string
format format.Format
payloadType uint8
payload []byte
step time.Duration
expectedTSDelta uint32
}{
{
name: "opus stereo",
format: &format.Opus{
ChannelCount: 2,
},
}},
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
ReplaceNTP: false,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
subStream := &stream.SubStream{
Stream: strm,
UseRTPPackets: true,
}
err = subStream.Initialize()
require.NoError(t, err)
pc1 := &PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: false,
Log: test.NilLogger,
}
err = pc1.Start()
require.NoError(t, err)
defer pc1.Close()
pc2 := &PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: true,
Log: test.NilLogger,
}
r := &stream.Reader{Parent: nil}
err = FromStream(strm.OrigDesc, r, pc2)
require.NoError(t, err)
err = pc2.Start()
require.NoError(t, err)
defer pc2.Close()
offer, err := pc1.CreatePartialOffer(false)
require.NoError(t, err)
answer, err := pc2.CreateFullAnswer(offer, false)
require.NoError(t, err)
err = pc1.SetAnswer(answer)
require.NoError(t, err)
err = pc1.WaitUntilConnected(10 * time.Second)
require.NoError(t, err)
err = pc2.WaitUntilConnected(10 * time.Second)
require.NoError(t, err)
strm.AddReader(r)
defer strm.RemoveReader(r)
subStream.WriteUnit(strm.OrigDesc.Medias[0], strm.OrigDesc.Medias[0].Formats[0], &unit.Unit{
PTS: 0,
NTP: time.Now(),
RTPPackets: []*rtp.Packet{{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 111,
SequenceNumber: 1123,
Timestamp: 45343,
SSRC: 563424,
payloadType: 111,
payload: []byte{1},
step: 20 * time.Millisecond,
expectedTSDelta: 960,
},
{
name: "g711 pcma 8khz mono",
format: &format.G711{
PayloadTyp: 8,
SampleRate: 8000,
ChannelCount: 1,
},
Payload: []byte{1},
}},
})
subStream.WriteUnit(strm.OrigDesc.Medias[0], strm.OrigDesc.Medias[0].Formats[0], &unit.Unit{
PTS: 0,
NTP: time.Now(),
RTPPackets: []*rtp.Packet{{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 111,
SequenceNumber: 1124,
Timestamp: 45343,
SSRC: 563424,
payloadType: 8,
payload: make([]byte, 160),
step: 20 * time.Millisecond,
expectedTSDelta: 160,
},
{
name: "g711 pcmu 16khz stereo",
format: &format.G711{
MULaw: true,
PayloadTyp: 96,
SampleRate: 16000,
ChannelCount: 2,
},
Payload: []byte{1},
}},
})
err = pc1.GatherInboundTracks(2 * time.Second)
require.NoError(t, err)
tracks := pc1.InboundTracks()
done := make(chan struct{})
n := 0
var ts uint32
tracks[0].OnPacketRTP = func(pkt *rtp.Packet) {
n++
switch n {
case 1:
ts = pkt.Timestamp
case 2:
require.Equal(t, uint32(960), pkt.Timestamp-ts)
close(done)
}
}
pc1.StartReading()
<-done
}
func TestFromStreamResampleOpusAbsoluteTimestamp(t *testing.T) {
strm := &stream.Stream{
OrigDesc: &description.Session{Medias: []*description.Media{
{
Type: description.MediaTypeAudio,
Formats: []format.Format{&format.Opus{
ChannelCount: 2,
}},
payloadType: 96,
payload: make([]byte, 320),
step: 10 * time.Millisecond,
expectedTSDelta: 160,
},
{
name: "lpcm 16khz stereo",
format: &format.LPCM{
PayloadTyp: 96,
BitDepth: 16,
SampleRate: 16000,
ChannelCount: 2,
},
}},
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
ReplaceNTP: false,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
subStream := &stream.SubStream{
Stream: strm,
UseRTPPackets: true,
}
err = subStream.Initialize()
require.NoError(t, err)
pcReader := &PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: false,
Log: test.NilLogger,
}
err = pcReader.Start()
require.NoError(t, err)
t.Cleanup(pcReader.Close)
pcPublisher := &PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: true,
Log: test.NilLogger,
}
r := &stream.Reader{Parent: nil}
err = FromStream(strm.OrigDesc, r, pcPublisher)
require.NoError(t, err)
err = pcPublisher.Start()
require.NoError(t, err)
t.Cleanup(pcPublisher.Close)
offer, err := pcReader.CreatePartialOffer(false)
require.NoError(t, err)
answer, err := pcPublisher.CreateFullAnswer(offer, false)
require.NoError(t, err)
err = pcReader.SetAnswer(answer)
require.NoError(t, err)
err = pcReader.WaitUntilConnected(10 * time.Second)
require.NoError(t, err)
err = pcPublisher.WaitUntilConnected(10 * time.Second)
require.NoError(t, err)
strm.AddReader(r)
t.Cleanup(func() { strm.RemoveReader(r) })
baseNTP := time.Unix(1710000000, 0)
step := 20 * time.Millisecond
// prime the pipeline to allow track gathering
subStream.WriteUnit(strm.OrigDesc.Medias[0], strm.OrigDesc.Medias[0].Formats[0], &unit.Unit{
PTS: 0,
NTP: baseNTP,
RTPPackets: []*rtp.Packet{{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 111,
SequenceNumber: 1123,
Timestamp: 45343,
SSRC: 563424,
},
Payload: []byte{1},
}},
})
err = pcReader.GatherInboundTracks(2 * time.Second)
require.NoError(t, err)
tracks := pcReader.InboundTracks()
require.Len(t, tracks, 1)
done := make(chan struct{})
errCh := make(chan string, 1)
const startSeq = uint16(2000)
expectedNTP := func(seq uint16) (time.Time, bool) {
if seq < startSeq {
return time.Time{}, false
}
return baseNTP.Add(time.Duration(seq-startSeq) * step), true
}
tracks[0].OnPacketRTP = func(pkt *rtp.Packet) {
expected, ok := expectedNTP(pkt.SequenceNumber)
if !ok {
return
}
ntp, avail := tracks[0].PacketNTP(pkt)
if !avail {
return
}
if ntp.Sub(expected).Abs() > 50*time.Millisecond {
select {
case errCh <- fmt.Sprintf("absolute NTP mismatch for seq=%d: got=%v expected=%v",
pkt.SequenceNumber, ntp, expected):
default:
payloadType: 96,
payload: make([]byte, 640),
step: 10 * time.Millisecond,
expectedTSDelta: 160,
},
} {
t.Run(ca.name, func(t *testing.T) {
strm := &stream.Stream{
OrigDesc: &description.Session{Medias: []*description.Media{{
Type: description.MediaTypeAudio,
Formats: []format.Format{ca.format},
}}},
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
ReplaceNTP: false,
Parent: test.NilLogger,
}
return
}
err := strm.Initialize()
require.NoError(t, err)
t.Cleanup(strm.Close)
select {
case done <- struct{}{}:
default:
}
}
subStream := &stream.SubStream{
Stream: strm,
UseRTPPackets: true,
}
err = subStream.Initialize()
require.NoError(t, err)
pcReader.StartReading()
pcReader := &webrtc.PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: false,
Log: test.NilLogger,
}
err = pcReader.Start()
require.NoError(t, err)
t.Cleanup(pcReader.Close)
go func() {
ticker := time.NewTicker(step)
defer ticker.Stop()
pcPublisher := &webrtc.PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: true,
Log: test.NilLogger,
}
for i := range uint16(150) {
seq := startSeq + i
expected, _ := expectedNTP(seq)
r := &stream.Reader{Parent: nil}
subStream.WriteUnit(strm.OrigDesc.Medias[0], strm.OrigDesc.Medias[0].Formats[0], &unit.Unit{
PTS: 0,
NTP: expected,
RTPPackets: []*rtp.Packet{{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 111,
SequenceNumber: seq,
Timestamp: 45343,
SSRC: 563424,
},
Payload: []byte{1},
}},
})
err = webrtc.FromStream(strm.OrigDesc, r, pcPublisher)
require.NoError(t, err)
<-ticker.C
}
}()
err = pcPublisher.Start()
require.NoError(t, err)
t.Cleanup(pcPublisher.Close)
select {
case <-done:
case err := <-errCh:
t.Fatal(err)
case <-time.After(8 * time.Second):
t.Fatal("absolute timestamp mapping did not become available")
offer, err := pcReader.CreatePartialOffer(false)
require.NoError(t, err)
answer, err := pcPublisher.CreateFullAnswer(offer, false)
require.NoError(t, err)
err = pcReader.SetAnswer(answer)
require.NoError(t, err)
err = pcReader.WaitUntilConnected(10 * time.Second)
require.NoError(t, err)
err = pcPublisher.WaitUntilConnected(10 * time.Second)
require.NoError(t, err)
strm.AddReader(r)
t.Cleanup(func() { strm.RemoveReader(r) })
baseNTP := time.Unix(1710000000, 0)
step := ca.step
const initialTimestamp = uint32(45343)
makeUnit := func(seq uint16, ntp time.Time) *unit.Unit {
return &unit.Unit{
PTS: 0,
NTP: ntp,
RTPPackets: []*rtp.Packet{{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: ca.payloadType,
SequenceNumber: seq,
Timestamp: initialTimestamp,
SSRC: 563424,
},
Payload: append([]byte(nil), ca.payload...),
}},
}
}
// prime the pipeline to allow track gathering
subStream.WriteUnit(strm.OrigDesc.Medias[0], strm.OrigDesc.Medias[0].Formats[0],
makeUnit(1123, baseNTP))
err = pcReader.GatherInboundTracks(2 * time.Second)
require.NoError(t, err)
tracks := pcReader.InboundTracks()
require.Len(t, tracks, 1)
done := make(chan struct{})
errCh := make(chan string, 1)
const startSeq = uint16(2000)
var recvIndex int
var prevTS uint32
prevTSValid := false
sawTSDelta := false
sawNTP := false
tracks[0].OnPacketRTP = func(pkt *rtp.Packet) {
if prevTSValid {
if pkt.Timestamp-prevTS != ca.expectedTSDelta {
select {
case errCh <- fmt.Sprintf("timestamp delta mismatch for packet=%d: got=%d expected=%d",
recvIndex, pkt.Timestamp-prevTS, ca.expectedTSDelta):
default:
}
return
}
sawTSDelta = true
}
prevTS = pkt.Timestamp
prevTSValid = true
ntp, avail := tracks[0].PacketNTP(pkt)
if avail {
expected := baseNTP.Add(time.Duration(recvIndex) * step)
if ntp.Sub(expected).Abs() > 50*time.Millisecond {
select {
case errCh <- fmt.Sprintf("absolute NTP mismatch for packet=%d: got=%v expected=%v",
recvIndex, ntp, expected):
default:
}
return
}
sawNTP = true
}
recvIndex++
if sawTSDelta && sawNTP {
select {
case done <- struct{}{}:
default:
}
}
}
pcReader.StartReading()
go func() {
ticker := time.NewTicker(step)
defer ticker.Stop()
for i := range uint16(150) {
seq := startSeq + i
expected := baseNTP.Add(time.Duration(i) * step)
subStream.WriteUnit(strm.OrigDesc.Medias[0], strm.OrigDesc.Medias[0].Formats[0],
makeUnit(seq, expected))
<-ticker.C
}
}()
select {
case <-done:
case err := <-errCh:
t.Fatal(err)
case <-time.After(8 * time.Second):
t.Fatal("audio timestamp mapping did not become available")
}
})
}
}
@@ -449,7 +384,7 @@ func TestFromStreamDoesNotMutateSharedRTPPackets(t *testing.T) {
err = subStream.Initialize()
require.NoError(t, err)
pcReader := &PeerConnection{
pcReader := &webrtc.PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: false,
@@ -459,7 +394,7 @@ func TestFromStreamDoesNotMutateSharedRTPPackets(t *testing.T) {
require.NoError(t, err)
t.Cleanup(pcReader.Close)
pcPublisher := &PeerConnection{
pcPublisher := &webrtc.PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: true,
@@ -468,7 +403,7 @@ func TestFromStreamDoesNotMutateSharedRTPPackets(t *testing.T) {
r := &stream.Reader{Parent: test.NilLogger}
err = FromStream(strm.OrigDesc, r, pcPublisher)
err = webrtc.FromStream(strm.OrigDesc, r, pcPublisher)
require.NoError(t, err)
err = pcPublisher.Start()
@@ -477,7 +412,6 @@ func TestFromStreamDoesNotMutateSharedRTPPackets(t *testing.T) {
const originalSSRC = uint32(563424)
require.NotEmpty(t, pcPublisher.OutboundTracks)
require.NotEqual(t, originalSSRC, pcPublisher.OutboundTracks[0].ssrc)
offer, err := pcReader.CreatePartialOffer(false)
require.NoError(t, err)
+31 -28
View File
@@ -1,22 +1,25 @@
package webrtc
package webrtc_test
import (
"testing"
"time"
"github.com/bluenviron/mediamtx/internal/protocols/webrtc"
pwebrtc "github.com/pion/webrtc/v4"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/stream"
"github.com/bluenviron/mediamtx/internal/test"
"github.com/pion/rtp"
"github.com/pion/webrtc/v4"
"github.com/stretchr/testify/require"
)
func TestToStreamNoSupportedCodecs(t *testing.T) {
pc := &PeerConnection{}
_, err := ToStream(pc, &conf.Path{}, nil, nil)
require.Equal(t, errNoSupportedCodecsTo, err)
pc := &webrtc.PeerConnection{}
_, err := webrtc.ToStream(pc, &conf.Path{}, nil, nil)
require.ErrorContains(t, err, "the stream doesn't contain any supported codec")
}
// this is impossible to test since unsupported tracks cause an error
@@ -26,7 +29,7 @@ func TestToStreamNoSupportedCodecs(t *testing.T) {
var toFromStreamCases = []struct {
name string
in format.Format
webrtcCaps webrtc.RTPCodecCapability
webrtcCaps pwebrtc.RTPCodecCapability
out format.Format
}{
{
@@ -34,7 +37,7 @@ var toFromStreamCases = []struct {
&format.AV1{
PayloadTyp: 96,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "video/AV1",
ClockRate: 90000,
},
@@ -47,7 +50,7 @@ var toFromStreamCases = []struct {
&format.VP9{
PayloadTyp: 96,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "video/VP9",
ClockRate: 90000,
SDPFmtpLine: "profile-id=0",
@@ -61,7 +64,7 @@ var toFromStreamCases = []struct {
&format.VP8{
PayloadTyp: 96,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "video/VP8",
ClockRate: 90000,
},
@@ -74,7 +77,7 @@ var toFromStreamCases = []struct {
&format.H265{
PayloadTyp: 96,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "video/H265",
ClockRate: 90000,
SDPFmtpLine: "level-id=93;profile-id=1;tier-flag=0;tx-mode=SRST",
@@ -86,7 +89,7 @@ var toFromStreamCases = []struct {
{
"h264",
test.FormatH264,
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "video/H264",
ClockRate: 90000,
SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f",
@@ -102,7 +105,7 @@ var toFromStreamCases = []struct {
PayloadTyp: 96,
ChannelCount: 6,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "audio/multiopus",
ClockRate: 48000,
Channels: 6,
@@ -119,7 +122,7 @@ var toFromStreamCases = []struct {
PayloadTyp: 96,
ChannelCount: 2,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "audio/opus",
ClockRate: 48000,
Channels: 2,
@@ -136,7 +139,7 @@ var toFromStreamCases = []struct {
PayloadTyp: 96,
ChannelCount: 1,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "audio/opus",
ClockRate: 48000,
Channels: 2,
@@ -150,7 +153,7 @@ var toFromStreamCases = []struct {
{
"g722",
&format.G722{},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "audio/G722",
ClockRate: 8000,
},
@@ -163,7 +166,7 @@ var toFromStreamCases = []struct {
SampleRate: 8000,
ChannelCount: 1,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "audio/PCMA",
ClockRate: 8000,
},
@@ -181,7 +184,7 @@ var toFromStreamCases = []struct {
SampleRate: 8000,
ChannelCount: 1,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "audio/PCMU",
ClockRate: 8000,
},
@@ -199,7 +202,7 @@ var toFromStreamCases = []struct {
SampleRate: 8000,
ChannelCount: 2,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "audio/PCMA",
ClockRate: 8000,
Channels: 2,
@@ -218,7 +221,7 @@ var toFromStreamCases = []struct {
SampleRate: 8000,
ChannelCount: 2,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "audio/PCMU",
ClockRate: 8000,
Channels: 2,
@@ -237,7 +240,7 @@ var toFromStreamCases = []struct {
SampleRate: 16000,
ChannelCount: 2,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "audio/L16",
ClockRate: 16000,
Channels: 2,
@@ -257,7 +260,7 @@ var toFromStreamCases = []struct {
SampleRate: 16000,
ChannelCount: 2,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "audio/L16",
ClockRate: 16000,
Channels: 2,
@@ -277,7 +280,7 @@ var toFromStreamCases = []struct {
SampleRate: 8000,
ChannelCount: 2,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "audio/L16",
ClockRate: 8000,
Channels: 2,
@@ -297,7 +300,7 @@ var toFromStreamCases = []struct {
SampleRate: 16000,
ChannelCount: 2,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "audio/L16",
ClockRate: 16000,
Channels: 2,
@@ -317,7 +320,7 @@ var toFromStreamCases = []struct {
SampleRate: 48000,
ChannelCount: 2,
},
webrtc.RTPCodecCapability{
pwebrtc.RTPCodecCapability{
MimeType: "audio/L16",
ClockRate: 48000,
Channels: 2,
@@ -334,11 +337,11 @@ var toFromStreamCases = []struct {
func TestToStream(t *testing.T) {
for _, ca := range toFromStreamCases {
t.Run(ca.name, func(t *testing.T) {
pc1 := &PeerConnection{
pc1 := &webrtc.PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: true,
OutboundTracks: []*OutboundTrack{{
OutboundTracks: []*webrtc.OutboundTrack{{
Caps: ca.webrtcCaps,
}},
Log: test.NilLogger,
@@ -347,7 +350,7 @@ func TestToStream(t *testing.T) {
require.NoError(t, err)
defer pc1.Close()
pc2 := &PeerConnection{
pc2 := &webrtc.PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: false,
@@ -402,7 +405,7 @@ func TestToStream(t *testing.T) {
require.NoError(t, err)
var subStream *stream.SubStream
medias, err := ToStream(pc2, &conf.Path{}, &subStream, nil)
medias, err := webrtc.ToStream(pc2, &conf.Path{}, &subStream, nil)
require.NoError(t, err)
require.Equal(t, ca.out, medias[0].Formats[0])
})