snapshot of https://github.com/livekit/livekit.git @ 08c5433449c106943231037d0b892895f6e92d3b (2026-09-15)
This commit is contained in:
1390
test/client/client.go
Normal file
1390
test/client/client.go
Normal file
File diff suppressed because it is too large
Load Diff
31
test/client/datachannel_reader.go
Normal file
31
test/client/datachannel_reader.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/sfu/datachannel"
|
||||
)
|
||||
|
||||
type DataChannelReader struct {
|
||||
bitrate *datachannel.BitrateCalculator
|
||||
target int
|
||||
}
|
||||
|
||||
func NewDataChannelReader(bitrate int) *DataChannelReader {
|
||||
return &DataChannelReader{
|
||||
target: bitrate,
|
||||
bitrate: datachannel.NewBitrateCalculator(datachannel.BitrateDuration*5, datachannel.BitrateWindow, datachannel.BitrateModeWallClock),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DataChannelReader) Read(p []byte, sid string) {
|
||||
for {
|
||||
if bitrate, ok := d.bitrate.ForceBitrate(time.Now()); ok && bitrate > 0 && bitrate > d.target {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
d.bitrate.AddBytes(0, 0, time.Now())
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
d.bitrate.AddBytes(len(p), 0, time.Now())
|
||||
}
|
||||
84
test/client/datatrack_remote.go
Normal file
84
test/client/datatrack_remote.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/frostbyte73/core"
|
||||
"github.com/livekit/protocol/datatrack"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
type DataTrackRemote struct {
|
||||
publisherIdentity livekit.ParticipantIdentity
|
||||
publisherID livekit.ParticipantID
|
||||
handle uint16
|
||||
trackID livekit.TrackID
|
||||
logger logger.Logger
|
||||
numReceivedPackets atomic.Uint32
|
||||
|
||||
closed core.Fuse
|
||||
}
|
||||
|
||||
func NewDataTrackRemote(
|
||||
publisherIdentity livekit.ParticipantIdentity,
|
||||
publisherID livekit.ParticipantID,
|
||||
handle uint16,
|
||||
trackID livekit.TrackID,
|
||||
logger logger.Logger,
|
||||
) *DataTrackRemote {
|
||||
logger.Infow(
|
||||
"creating data track remote",
|
||||
"publisherIdentity", publisherIdentity,
|
||||
"publisherID", publisherID,
|
||||
"handle", handle,
|
||||
"trackID", trackID,
|
||||
)
|
||||
return &DataTrackRemote{
|
||||
publisherIdentity: publisherIdentity,
|
||||
publisherID: publisherID,
|
||||
handle: handle,
|
||||
trackID: trackID,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DataTrackRemote) Close() {
|
||||
d.logger.Infow(
|
||||
"closing data track remote",
|
||||
"publisherIdentity", d.publisherIdentity,
|
||||
"publisherID", d.publisherID,
|
||||
"handle", d.handle,
|
||||
"trackID", d.trackID,
|
||||
)
|
||||
d.closed.Break()
|
||||
}
|
||||
|
||||
func (d *DataTrackRemote) Handle() uint16 {
|
||||
return d.handle
|
||||
}
|
||||
|
||||
func (d *DataTrackRemote) ID() livekit.TrackID {
|
||||
return d.trackID
|
||||
}
|
||||
|
||||
func (d *DataTrackRemote) PacketReceived(packet *datatrack.Packet) {
|
||||
if d.closed.IsBroken() {
|
||||
return
|
||||
}
|
||||
|
||||
valid := len(packet.Payload) != 0
|
||||
|
||||
for i := range packet.Payload {
|
||||
if packet.Payload[i] != byte(255-i) {
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if valid {
|
||||
d.numReceivedPackets.Inc()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DataTrackRemote) NumReceivedPackets() uint32 {
|
||||
return d.numReceivedPackets.Load()
|
||||
}
|
||||
80
test/client/datatrack_writer.go
Normal file
80
test/client/datatrack_writer.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/livekit/protocol/datatrack"
|
||||
"github.com/livekit/protocol/datatrack/datatracktest"
|
||||
"github.com/livekit/protocol/logger"
|
||||
)
|
||||
|
||||
type dataTrackWriter struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
handle uint16
|
||||
transport types.DataTrackTransport
|
||||
}
|
||||
|
||||
func NewDataTrackWriter(ctx context.Context, handle uint16, transport types.DataTrackTransport) TrackWriter {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &dataTrackWriter{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
handle: handle,
|
||||
transport: transport,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dataTrackWriter) Start() error {
|
||||
go d.writeFrames()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dataTrackWriter) Stop() {
|
||||
d.cancel()
|
||||
}
|
||||
|
||||
func (d *dataTrackWriter) writeFrames() {
|
||||
seqNum := uint16(0)
|
||||
frameNum := uint16(0)
|
||||
for {
|
||||
select {
|
||||
case <-d.ctx.Done():
|
||||
return
|
||||
|
||||
default:
|
||||
packets := datatracktest.GenerateRawDataPackets(d.handle, seqNum, frameNum, 1, rand.Intn(2048)+1, 100*time.Millisecond)
|
||||
for _, packet := range packets {
|
||||
if err := d.transport.SendDataTrackMessage(packet); err != nil {
|
||||
logger.Errorw("could not send data track packet", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(packets) != 0 {
|
||||
var lastPacket datatrack.Packet
|
||||
if err := lastPacket.Unmarshal(packets[len(packets)-1]); err == nil {
|
||||
seqNum = lastPacket.SequenceNumber + 1
|
||||
frameNum = lastPacket.FrameNumber + 1
|
||||
}
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
191
test/client/trackwriter.go
Normal file
191
test/client/trackwriter.go
Normal file
@@ -0,0 +1,191 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pion/webrtc/v4"
|
||||
"github.com/pion/webrtc/v4/pkg/media"
|
||||
"github.com/pion/webrtc/v4/pkg/media/h264reader"
|
||||
"github.com/pion/webrtc/v4/pkg/media/ivfreader"
|
||||
"github.com/pion/webrtc/v4/pkg/media/oggreader"
|
||||
|
||||
"github.com/livekit/protocol/codecs/mime"
|
||||
"github.com/livekit/protocol/logger"
|
||||
)
|
||||
|
||||
type TrackWriter interface {
|
||||
Start() error
|
||||
Stop()
|
||||
}
|
||||
|
||||
// Writes a file to an RTP track.
|
||||
// makes it easier to debug and create RTP streams
|
||||
type trackWriter struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
track *webrtc.TrackLocalStaticSample
|
||||
filePath string
|
||||
mime mime.MimeType
|
||||
|
||||
ogg *oggreader.OggReader
|
||||
ivfheader *ivfreader.IVFFileHeader
|
||||
ivf *ivfreader.IVFReader
|
||||
h264 *h264reader.H264Reader
|
||||
}
|
||||
|
||||
func NewTrackWriter(ctx context.Context, track *webrtc.TrackLocalStaticSample, filePath string) TrackWriter {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &trackWriter{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
track: track,
|
||||
filePath: filePath,
|
||||
mime: mime.NormalizeMimeType(track.Codec().MimeType),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *trackWriter) Start() error {
|
||||
if w.filePath == "" {
|
||||
go w.writeNull()
|
||||
return nil
|
||||
}
|
||||
|
||||
file, err := os.Open(w.filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Debugw(
|
||||
"starting track writer",
|
||||
"trackID", w.track.ID(),
|
||||
"mime", w.mime,
|
||||
)
|
||||
switch w.mime {
|
||||
case mime.MimeTypeOpus:
|
||||
w.ogg, _, err = oggreader.NewWith(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go w.writeOgg()
|
||||
case mime.MimeTypeVP8:
|
||||
w.ivf, w.ivfheader, err = ivfreader.NewWith(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go w.writeVP8()
|
||||
case mime.MimeTypeH264:
|
||||
w.h264, err = h264reader.NewReader(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go w.writeH264()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *trackWriter) Stop() {
|
||||
w.cancel()
|
||||
}
|
||||
|
||||
func (w *trackWriter) writeNull() {
|
||||
defer w.onWriteComplete()
|
||||
sample := media.Sample{Data: []byte{0x0, 0xff, 0xff, 0xff, 0xff}, Duration: 30 * time.Millisecond}
|
||||
h264Sample := media.Sample{Data: []byte{0x00, 0x00, 0x00, 0x01, 0x7, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x8, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x5, 0xff, 0xff, 0xff, 0xff}, Duration: 30 * time.Millisecond}
|
||||
for {
|
||||
select {
|
||||
case <-time.After(20 * time.Millisecond):
|
||||
if w.mime == mime.MimeTypeH264 {
|
||||
w.track.WriteSample(h264Sample)
|
||||
} else {
|
||||
w.track.WriteSample(sample)
|
||||
}
|
||||
case <-w.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *trackWriter) writeOgg() {
|
||||
// Keep track of last granule, the difference is the amount of samples in the buffer
|
||||
var lastGranule uint64
|
||||
for {
|
||||
if w.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
pageData, pageHeader, err := w.ogg.ParseNextPage()
|
||||
if err == io.EOF {
|
||||
logger.Debugw("all audio samples parsed and sent")
|
||||
w.onWriteComplete()
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Errorw("could not parse ogg page", err)
|
||||
return
|
||||
}
|
||||
|
||||
// The amount of samples is the difference between the last and current timestamp
|
||||
sampleCount := float64(pageHeader.GranulePosition - lastGranule)
|
||||
lastGranule = pageHeader.GranulePosition
|
||||
sampleDuration := time.Duration((sampleCount/48000)*1000) * time.Millisecond
|
||||
|
||||
if err = w.track.WriteSample(media.Sample{Data: pageData, Duration: sampleDuration}); err != nil {
|
||||
logger.Errorw("could not write sample", err)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(sampleDuration)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *trackWriter) writeVP8() {
|
||||
// Send our video file frame at a time. Pace our sending such that we send it at the same speed it should be played back as.
|
||||
// This isn't required since the video is timestamped, but we will such much higher loss if we send all at once.
|
||||
sleepTime := time.Millisecond * time.Duration((float32(w.ivfheader.TimebaseNumerator)/float32(w.ivfheader.TimebaseDenominator))*1000)
|
||||
for {
|
||||
if w.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
frame, _, err := w.ivf.ParseNextFrame()
|
||||
if err == io.EOF {
|
||||
logger.Debugw("all video frames parsed and sent")
|
||||
w.onWriteComplete()
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Errorw("could not parse VP8 frame", err)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(sleepTime)
|
||||
if err = w.track.WriteSample(media.Sample{Data: frame, Duration: time.Second}); err != nil {
|
||||
logger.Errorw("could not write sample", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *trackWriter) writeH264() {
|
||||
// TODO: this is harder
|
||||
}
|
||||
|
||||
func (w *trackWriter) onWriteComplete() {
|
||||
}
|
||||
Reference in New Issue
Block a user