Skip to content

Commit a293fd5

Browse files
committed
[bwe-test] Add video_file_reader and test
1 parent f696e23 commit a293fd5

File tree

17 files changed

+1334
-164
lines changed

17 files changed

+1334
-164
lines changed

.github/.ci.conf

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# SPDX-FileCopyrightText: 2025 The Pion community <https://pion.ly>
2+
# SPDX-License-Identifier: MIT
3+
4+
PRE_TEST_HOOK=_install_dependencies_hook
5+
PRE_LINT_HOOK=_install_dependencies_hook
6+
GO_MOD_VERSION_EXPECTED=1.24
7+
SKIP_i386_TESTS=true
8+
SKIP_WINDOWS_TESTS=true
9+
SKIP_API_DIFF=true
10+
11+
function _install_dependencies_hook(){
12+
set -e
13+
14+
sudo apt-get update
15+
sudo apt-get install -y libvpx-dev pkg-config
16+
}

.github/workflows/ci.yaml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#
2+
# Custom test workflow for bwe-test with VPX codec support
3+
# This extends the standard Pion test workflow with libvpx dependencies
4+
#
5+
# SPDX-FileCopyrightText: 2025 The Pion community <https://pion.ly>
6+
# SPDX-License-Identifier: MIT
7+
8+
name: Test with VPX
9+
on:
10+
push:
11+
branches:
12+
- master
13+
pull_request:
14+
15+
jobs:
16+
test-with-vpx-linux:
17+
runs-on: ubuntu-latest
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
go: ["1.25", "1.24"] # auto-update/supported-go-version-list
22+
name: Linux Go ${{ matrix.go }} with VPX
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v4
26+
- name: Setup Go
27+
uses: actions/setup-go@v5
28+
with:
29+
go-version: ${{ matrix.go }}
30+
- name: Install VPX dependencies
31+
run: |
32+
sudo apt-get update -qq \
33+
&& sudo apt-get install --no-install-recommends -y \
34+
libvpx-dev \
35+
pkg-config
36+
- name: Run tests
37+
run: go test -v ./...
38+
- name: Build
39+
run: go build ./...
40+
41+
test-with-vpx-darwin:
42+
runs-on: macos-latest
43+
strategy:
44+
fail-fast: false
45+
matrix:
46+
go: ["1.25", "1.24"]
47+
name: Darwin Go ${{ matrix.go }} with VPX
48+
steps:
49+
- name: Checkout
50+
uses: actions/checkout@v4
51+
- name: Setup Go
52+
uses: actions/setup-go@v5
53+
with:
54+
go-version: ${{ matrix.go }}
55+
- name: Install VPX dependencies
56+
run: |
57+
brew install \
58+
pkg-config \
59+
libvpx
60+
- name: Run tests
61+
run: go test -v ./...
62+
- name: Build
63+
run: go build ./...

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@
1414
bin/
1515
vendor/
1616
node_modules/
17+
data/
1718

1819
### Files ###
1920
#############
2021
*.ivf
2122
*.ogg
23+
*.y4m
2224
tags
2325
cover.out
2426
*.sw[poe]
2527
*.wasm
2628
examples/sfu-ws/cert.pem
2729
examples/sfu-ws/key.pem
2830
wasm_exec.js
31+
bwe-test

.reuse/dep5

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
22
Upstream-Name: Pion
33
Source: https://github.com/pion/
44

5-
Files: README.md DESIGN.md **/README.md AUTHORS.txt renovate.json go.mod go.sum **/go.mod **/go.sum .eslintrc.json package.json examples.json sfu-ws/flutter/.gitignore sfu-ws/flutter/pubspec.yaml c-data-channels/webrtc.h examples/examples.json yarn.lock
6-
Copyright: 2023 The Pion community <https://pion.ly>
5+
Files: README.md DESIGN.md **/README.md AUTHORS.txt renovate.json go.mod go.sum **/go.mod **/go.sum .eslintrc.json package.json examples.json sfu-ws/flutter/.gitignore sfu-ws/flutter/pubspec.yaml c-data-channels/webrtc.h examples/examples.json yarn.lock vnet/phases/*.json
6+
Copyright: 2025 The Pion community <https://pion.ly>
77
License: MIT
88

99
Files: testdata/seed/* testdata/fuzz/* **/testdata/fuzz/* api/*.txt

README.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,22 @@ real-time media described in [RFC8867](https://www.rfc-editor.org/rfc/rfc8867.ht
2222
### Implemented/Planned Test Cases and Applications
2323
The current implementation uses [`vnet.Net` from
2424
pion/transport](https://github.com/pion/transport) to simulate network
25-
constraints. There are two test applications, one using a simple simulcast-like
26-
setup and the other one using adaptive bitrate streaming with a synthetic
27-
encoder.
25+
constraints. There are three test applications:
26+
27+
1. **Simulcast-like setup** - Uses a simple simulcast configuration
28+
2. **Adaptive bitrate streaming** - Uses synthetic encoder for adaptive bitrate
29+
3. **Video file reader** - Reads numbered JPG image files and sends them as video frames
30+
31+
#### Video File Reader
32+
The video file reader (`VideoFileReader`) reads series of numbered JPG image files
33+
(e.g., `frame_000.jpg`, `frame_001.jpg`, etc.) from a directory and sends them as
34+
individual video frames using an `RTCSender`. This allows testing with real video
35+
content while maintaining precise control over frame timing and network conditions.
36+
37+
To use the video file reader test:
38+
- Create directories with numbered JPG files (e.g., `../sample_videos_0/`, `../sample_videos_1/`)
39+
- Files should be named with sequential numbers (frame_000.jpg, frame_001.jpg, etc.)
40+
- The reader automatically discovers, sorts, and cycles through the frames
2841

2942
To run the simulcast test, you must create three input video files as described
3043
in the [bandwidth-esimation-from-disk
@@ -33,6 +46,7 @@ and place them in the `vnet` directory.
3346

3447
- [ ] **Variable Available Capacity with a Single Flow**
3548
- [ ] **Variable Available Capacity with Multiple Flows**
49+
- [x] **Dual Video Tracks with Variable Available Capacity** - Uses video file reader with multiple video tracks
3650
- [ ] **Congested Feedback Link with Bi-directional Media Flows**
3751
- [ ] **Competing Media Flows with the Same Congestion Control Algorithm**
3852
- [ ] **Round Trip Time Fairness**
@@ -53,6 +67,17 @@ interface. In future, we might automate the evaluation.
5367
### Running
5468
To run the tests, run `go test -v ./vnet/`.
5569

70+
To run the main test application with all test cases (including the video file reader test):
71+
```bash
72+
cd vnet
73+
go run .
74+
```
75+
76+
The application will run multiple test scenarios including:
77+
- ABR (Adaptive Bitrate) tests with single and multiple flows
78+
- Simulcast tests with single and multiple flows
79+
- Video file reader test with dual video tracks (requires sample video directories)
80+
5681
### Roadmap
5782
The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
5883

go.mod

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.21
55
toolchain go1.25.1
66

77
require (
8+
github.com/bamiaux/rez v0.0.0-20170731184118-29f4463c688b
89
github.com/pion/interceptor v0.1.41
910
github.com/pion/logging v0.2.4
1011
github.com/pion/mediadevices v0.7.2
@@ -16,24 +17,21 @@ require (
1617
golang.org/x/sync v0.11.0
1718
)
1819

19-
require (
20-
github.com/pion/dtls/v3 v3.0.7 // indirect
21-
github.com/pion/ice/v4 v4.0.10 // indirect
22-
github.com/pion/mdns/v2 v2.0.7 // indirect
23-
github.com/pion/srtp/v3 v3.0.7 // indirect
24-
github.com/pion/stun/v3 v3.0.0 // indirect
25-
github.com/pion/turn/v4 v4.1.1 // indirect
26-
github.com/wlynxg/anet v0.0.5 // indirect
27-
)
28-
2920
require (
3021
github.com/davecgh/go-spew v1.1.1 // indirect
3122
github.com/google/uuid v1.6.0 // indirect
3223
github.com/pion/datachannel v1.5.10 // indirect
24+
github.com/pion/dtls/v3 v3.0.7 // indirect
25+
github.com/pion/ice/v4 v4.0.10 // indirect
26+
github.com/pion/mdns/v2 v2.0.7 // indirect
3327
github.com/pion/randutil v0.1.0 // indirect
3428
github.com/pion/sctp v1.8.39 // indirect
3529
github.com/pion/sdp/v3 v3.0.15 // indirect
30+
github.com/pion/srtp/v3 v3.0.7 // indirect
31+
github.com/pion/stun/v3 v3.0.0 // indirect
32+
github.com/pion/turn/v4 v4.1.1 // indirect
3633
github.com/pmezard/go-difflib v1.0.0 // indirect
34+
github.com/wlynxg/anet v0.0.5 // indirect
3735
golang.org/x/crypto v0.33.0 // indirect
3836
golang.org/x/image v0.23.0 // indirect
3937
golang.org/x/net v0.35.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/bamiaux/rez v0.0.0-20170731184118-29f4463c688b h1:5Ci5wpOL75rYF6RQGRoqhEAU6xLJ6n/D4SckXX1yB74=
2+
github.com/bamiaux/rez v0.0.0-20170731184118-29f4463c688b/go.mod h1:obBQGGIFbbv9KWg92Qu9UHeD94JXmHD1jovY/z6I3O8=
13
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
24
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
35
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=

vnet/flow.go

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
"errors"
1212
"fmt"
1313
"io"
14+
"os"
15+
"path/filepath"
16+
"strings"
1417

1518
"github.com/pion/bwe-test/logging"
1619
"github.com/pion/bwe-test/receiver"
@@ -31,8 +34,9 @@ func NewSimpleFlow(
3134
id int,
3235
senderMode senderMode,
3336
dataDir string,
37+
videoFiles ...VideoFileInfo,
3438
) (Flow, error) {
35-
snd, err := newSender(loggerFactory, nm, id, senderMode, dataDir)
39+
snd, err := newSender(loggerFactory, nm, id, senderMode, dataDir, videoFiles...)
3640
if err != nil {
3741
return Flow{}, fmt.Errorf("new sender: %w", err)
3842
}
@@ -47,7 +51,21 @@ func NewSimpleFlow(
4751
return Flow{}, fmt.Errorf("sender create offer: %w", err)
4852
}
4953

50-
rc, err := newReceiver(nm, id, dataDir)
54+
// Create output file path for received video
55+
outputVideoPath := ""
56+
if senderMode == videoFileEncoderMode {
57+
// Create a subdirectory for received videos
58+
receivedDir := filepath.Join(dataDir, "received_video")
59+
if err := os.MkdirAll(receivedDir, 0o750); err != nil {
60+
return Flow{}, fmt.Errorf("mkdir received_video: %w", err)
61+
}
62+
63+
// For multiple tracks, we'll create output files for all tracks
64+
// The receiver will handle multiple tracks automatically
65+
outputVideoPath = filepath.Join(receivedDir, fmt.Sprintf("received_%d.ivf", id))
66+
}
67+
68+
rc, err := newReceiver(nm, id, dataDir, outputVideoPath, loggerFactory)
5169
if err != nil {
5270
return Flow{}, fmt.Errorf("new sender: %w", err)
5371
}
@@ -91,7 +109,7 @@ func (f Flow) Close() error {
91109
var errUnknownSenderMode = errors.New("unknown sender mode")
92110

93111
type sndr struct {
94-
sender *sender.Sender
112+
sender sender.WebRTCSender
95113
ccLogger io.WriteCloser
96114
senderRTPLogger io.WriteCloser
97115
senderRTCPLogger io.WriteCloser
@@ -124,6 +142,7 @@ func newSender(
124142
id int,
125143
senderMode senderMode,
126144
dataDir string,
145+
videoFiles ...VideoFileInfo,
127146
) (sndr, error) {
128147
leftVnet, publicIPLeft, err := nm.GetLeftNet()
129148
if err != nil {
@@ -145,7 +164,7 @@ func newSender(
145164
return sndr{}, fmt.Errorf("get sender rtcp log file: %w", err)
146165
}
147166

148-
var snd *sender.Sender
167+
var snd sender.WebRTCSender
149168
switch senderMode {
150169
case abrSenderMode:
151170
snd, err = sender.NewSender(
@@ -171,10 +190,27 @@ func newSender(
171190
if err != nil {
172191
return sndr{}, fmt.Errorf("new simulcast sender: %w", err)
173192
}
193+
case videoFileEncoderMode:
194+
// Require explicit video files for video file encoder mode
195+
if len(videoFiles) == 0 {
196+
return sndr{}, fmt.Errorf("videoFileEncoderMode requires at least one video file")
197+
}
198+
199+
snd, err = NewVideoFileSender(
200+
videoFiles,
201+
sender.SetVnet(leftVnet, []string{publicIPLeft}),
202+
sender.PacketLogWriter(senderRTPLogger, senderRTCPLogger),
203+
sender.CCLogWriter(ccLogger),
204+
sender.SetLoggerFactory(loggerFactory),
205+
)
206+
if err != nil {
207+
return sndr{}, fmt.Errorf("new video file sender: %w", err)
208+
}
174209
default:
175210
return sndr{}, fmt.Errorf("%w: %v", errUnknownSenderMode, senderMode)
176211
}
177212

213+
// Return the sender for non-videoFileEncoderMode cases
178214
return sndr{
179215
sender: snd,
180216
ccLogger: ccLogger,
@@ -187,6 +223,7 @@ type recv struct {
187223
receiver *receiver.Receiver
188224
receiverRTPLogger io.WriteCloser
189225
receiverRTCPLogger io.WriteCloser
226+
videoOutputFile *os.File
190227
}
191228

192229
func (s recv) Close() error {
@@ -207,13 +244,17 @@ func (s recv) Close() error {
207244
errs = append(errs, err)
208245
}
209246

247+
// videoOutputFile is no longer used - receiver handles file closing internally
248+
210249
return errors.Join(errs...)
211250
}
212251

213252
func newReceiver(
214253
nm *NetworkManager,
215254
id int,
216255
dataDir string,
256+
outputVideoPath string,
257+
loggerFactory plogging.LoggerFactory,
217258
) (recv, error) {
218259
rightVnet, publicIPRight, err := nm.GetRightNet()
219260
if err != nil {
@@ -230,11 +271,22 @@ func newReceiver(
230271
return recv{}, fmt.Errorf("get receiver rtcp log file: %w", err)
231272
}
232273

233-
rc, err := receiver.NewReceiver(
274+
// Create options for the receiver
275+
opts := []receiver.Option{
234276
receiver.SetVnet(rightVnet, []string{publicIPRight}),
235277
receiver.PacketLogWriter(receiverRTPLogger, receiverRTCPLogger),
236278
receiver.DefaultInterceptors(),
237-
)
279+
receiver.SetLoggerFactory(loggerFactory),
280+
}
281+
282+
// Add video saving option if outputVideoPath is set
283+
if outputVideoPath != "" {
284+
// Use SaveVideo for video saving - remove .ivf extension from base path
285+
baseOutputPath := strings.TrimSuffix(outputVideoPath, ".ivf")
286+
opts = append(opts, receiver.SaveVideo(baseOutputPath))
287+
}
288+
289+
rc, err := receiver.NewReceiver(opts...)
238290
if err != nil {
239291
return recv{}, fmt.Errorf("new receiver: %w", err)
240292
}
@@ -243,5 +295,6 @@ func newReceiver(
243295
receiver: rc,
244296
receiverRTPLogger: receiverRTPLogger,
245297
receiverRTCPLogger: receiverRTCPLogger,
298+
videoOutputFile: nil, // No longer used with multiple track approach
246299
}, nil
247300
}

0 commit comments

Comments
 (0)