Skip to content

Commit 327aa91

Browse files
committed
[*] website whep client
1 parent c51fbe1 commit 327aa91

File tree

12 files changed

+1139
-257
lines changed

12 files changed

+1139
-257
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/wrtc/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ log.workspace = true
1010
rand.workspace = true
1111
http.workspace = true
1212
bytes.workspace = true
13+
rubato.workspace = true
1314
anyhow.workspace = true
1415
webrtc.workspace = true
1516
bytesio.workspace = true
@@ -19,7 +20,6 @@ byteorder.workspace = true
1920
thiserror.workspace = true
2021
derive_setters.workspace = true
2122
tokio = { workspace = true, features = ["full"] }
22-
cutil = { workspace = true, features = ["crypto"] }
2323
serde = { workspace = true, features = ["derive"] }
2424

2525
[dev-dependencies]

lib/wrtc/data/test-44100.wav

861 KB
Binary file not shown.

lib/wrtc/examples/README.md

Lines changed: 3 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,3 @@
1-
# WRTC Examples
2-
3-
This directory contains example programs demonstrating various features of the wrtc library.
4-
5-
## Opus Codec Demo
6-
7-
The `opus_demo.rs` example demonstrates how to use the Opus audio codec for encoding and decoding audio data.
8-
9-
### Features
10-
11-
- Reads WAV audio files
12-
- Encodes audio data using Opus codec
13-
- Decodes encoded audio back to PCM
14-
- Writes decoded audio to new WAV file
15-
- Shows compression ratio statistics
16-
- Comprehensive logging with env_logger
17-
18-
### Usage
19-
20-
```bash
21-
# Run with default logging (info level)
22-
cargo run --example opus_demo
23-
24-
# Run with debug logging for detailed frame information
25-
RUST_LOG=debug cargo run --example opus_demo
26-
27-
# Run with only warnings and errors
28-
RUST_LOG=warn cargo run --example opus_demo
29-
30-
# Run with custom log filtering
31-
RUST_LOG=opus_demo=debug cargo run --example opus_demo
32-
```
33-
34-
### What it does
35-
36-
1. **Reads** `data/test.wav` (48kHz, stereo, 5 seconds)
37-
2. **Encodes** audio to Opus packets (250 frames)
38-
3. **Decodes** Opus packets back to audio
39-
4. **Saves** result to `/tmp/opus-coder.wav`
40-
5. **Shows** compression statistics (~31:1 compression ratio)
41-
42-
### Dependencies
43-
44-
- `hound` - WAV file I/O
45-
- `env_logger` - Structured logging
46-
- `log` - Logging facade
47-
- `wrtc::opus` - Opus codec implementation
48-
49-
### Log Levels
50-
51-
- **INFO**: Basic progress information (default)
52-
- **DEBUG**: Detailed frame-by-frame encoding/decoding info
53-
- **WARN**: Error messages for failed operations
54-
- **ERROR**: Critical errors that stop execution
1+
### How to test?
2+
- run: `RUST_LOG=debug cargo run --example whep_server_demo` or `RUST_LOG=debug cargo run --example whep_server_demo`
3+
- open web broswer and visit `http://localhost:9090`

lib/wrtc/examples/opus_demo.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use wrtc::opus::{OpusCoder, OpusCoderError};
66
fn main() -> Result<(), Box<dyn std::error::Error>> {
77
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
88

9-
let input_path = Path::new("data/test.wav");
10-
let output_path = "/tmp/opus-coder.wav";
9+
let input_path = Path::new("data/test-44100.wav");
10+
let output_path = "/tmp/opus-coder-44100.wav";
1111

1212
info!("Opus Codec Demo");
1313
info!("===============");
@@ -39,7 +39,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
3939

4040
let mut opus_encoder = OpusCoder::new(spec.sample_rate, channels)?;
4141
let mut opus_decoder = OpusCoder::new(spec.sample_rate, channels)?;
42-
info!(" Frame size: {} samples", opus_encoder.frame_size());
42+
info!(
43+
" Internal frame size: {} samples (48kHz)",
44+
opus_encoder.frame_size()
45+
);
46+
info!(
47+
" Input frame size: {} samples ({}Hz)",
48+
(spec.sample_rate as usize * 20) / 1000,
49+
spec.sample_rate
50+
);
4351

4452
// 3. Encode audio data
4553
info!("Encoding audio with Opus...");
@@ -52,9 +60,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
5260
info!(" Decoded {} samples", decoded_audio.len());
5361

5462
// 5. Write decoded audio to WAV file
55-
info!("Writing decoded audio to WAV file...");
56-
write_wav_file(output_path, &decoded_audio, spec)?;
63+
info!("Writing decoded audio to WAV file at 48kHz...");
64+
65+
// Opus always outputs at 48kHz, so we save at 48kHz regardless of input sample rate
66+
let mut output_spec = spec;
67+
output_spec.sample_rate = 48000;
68+
69+
write_wav_file(output_path, &decoded_audio, output_spec)?;
5770
info!(" Output written to: {}", output_path);
71+
info!(" Output sample rate: 48000 Hz (Opus native rate)");
5872

5973
info!("Demo completed successfully!");
6074

@@ -92,7 +106,7 @@ fn encode_audio(
92106
audio_data: &[f32],
93107
) -> Result<Vec<Vec<u8>>, OpusCoderError> {
94108
let mut encoded_packets = Vec::new();
95-
let samples_per_frame = opus_coder.samples_per_frame();
109+
let samples_per_frame = opus_coder.input_samples_per_frame();
96110
let total_frames = (audio_data.len() + samples_per_frame - 1) / samples_per_frame;
97111

98112
debug!(

lib/wrtc/examples/whep_server2_demo.rs

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use anyhow::{Result, bail};
2+
use hound::WavReader;
23
use image::{ImageBuffer, Rgb};
34
use std::{
45
fs::File,
5-
io::BufReader,
66
path::Path,
77
sync::{
88
Arc,
@@ -12,9 +12,9 @@ use std::{
1212
};
1313
use tokio::sync::broadcast::{self, Sender};
1414
use video_encoder::{EncodedFrame, VideoEncoderConfig};
15-
use webrtc::media::io::ogg_reader::OggReader;
1615
use wrtc::{
17-
Event, OPUS_SAMPLE_RATE, PacketData, session::WebRTCServerSessionConfig, webrtc::WebRTCServer,
16+
Event, PacketData, common::auth::Auth, opus::OpusCoder, session::WebRTCServerSessionConfig,
17+
webrtc::WebRTCServer,
1818
};
1919

2020
const IMG_WIDTH: u32 = 1920;
@@ -24,7 +24,7 @@ const IMG_HEIGHT: u32 = 1080;
2424
async fn main() -> Result<()> {
2525
env_logger::init();
2626

27-
let audio_path = "./data/test.ogg".to_string();
27+
let audio_path = "./data/test-44100.wav".to_string();
2828
let config = WebRTCServerSessionConfig::default();
2929
let (packet_sender, _) = broadcast::channel(128);
3030
let (event_sender, mut event_receiver) = broadcast::channel(16);
@@ -43,7 +43,7 @@ async fn main() -> Result<()> {
4343
Ok(Event::PeerConnected(_)) => {
4444
if connections.load(Ordering::Relaxed) == 0 {
4545
h264_streaming_thread(packet_sender_clone.clone(), connections.clone());
46-
ogg_stream_thread(packet_sender_clone.clone(), audio_path.clone(), connections.clone());
46+
wav_stream_thread(packet_sender_clone.clone(), audio_path.clone(), connections.clone());
4747
}
4848

4949
let count = connections.fetch_add(1, Ordering::Relaxed);
@@ -79,7 +79,7 @@ async fn main() -> Result<()> {
7979
let mut server = WebRTCServer::new(
8080
config,
8181
"0.0.0.0:9090".to_string(),
82-
None,
82+
Some(Auth::new("123".to_string())),
8383
packet_sender,
8484
event_sender,
8585
);
@@ -158,35 +158,83 @@ fn h264_streaming_thread(packet_sender: Sender<PacketData>, connections: Arc<Ato
158158
});
159159
}
160160

161-
fn ogg_stream_thread(
161+
fn wav_stream_thread(
162162
packet_sender: Sender<PacketData>,
163163
audio_file: String,
164164
connections: Arc<AtomicI32>,
165165
) {
166166
tokio::spawn(async move {
167167
'out: loop {
168168
let file = File::open(&audio_file).unwrap();
169-
let reader = BufReader::new(file);
170-
let (mut ogg, _) = OggReader::new(reader, true).unwrap();
171-
let page_duration = Duration::from_millis(20);
169+
let mut reader = WavReader::new(file).unwrap();
170+
let spec = reader.spec();
172171

173-
let mut last_granule = 0;
174-
let mut ticker = tokio::time::interval(page_duration);
172+
let channels = if spec.channels == 1 {
173+
audiopus::Channels::Mono
174+
} else if spec.channels == 2 {
175+
audiopus::Channels::Stereo
176+
} else {
177+
log::error!(
178+
"Only mono and stereo audio are supported, got {} channels",
179+
spec.channels
180+
);
181+
break;
182+
};
175183

176-
while let Ok((page_data, page_header)) = ogg.parse_next_page() {
177-
let sample_count = page_header.granule_position - last_granule;
178-
last_granule = page_header.granule_position;
179-
let sample_duration = Duration::from_millis(sample_count * 1000 / OPUS_SAMPLE_RATE);
184+
let mut opus_coder = OpusCoder::new(spec.sample_rate, channels)
185+
.expect("Failed to initialize Opus coder");
180186

181-
if let Err(e) = packet_sender.send(PacketData::Audio {
182-
timestamp: Instant::now(),
183-
duration: sample_duration,
184-
data: page_data.freeze().into(),
185-
}) {
186-
log::warn!("send audio data failed: {e}");
187+
let samples: Vec<f32> = reader
188+
.samples::<i16>()
189+
.map(|s| match s {
190+
Ok(sample) => sample as f32 / 32768.0,
191+
Err(e) => {
192+
log::warn!("Failed to read sample: {}", e);
193+
0.0
194+
}
195+
})
196+
.collect();
197+
198+
log::trace!(
199+
"Loaded WAV: {}Hz, {} channels, {} samples, {:.2}s",
200+
spec.sample_rate,
201+
spec.channels,
202+
samples.len(),
203+
samples.len() as f32 / (spec.sample_rate as f32 * spec.channels as f32)
204+
);
205+
206+
let samples_per_frame = opus_coder.input_samples_per_frame();
207+
let frame_duration_ms = 20;
208+
let frame_duration = Duration::from_millis(frame_duration_ms);
209+
let mut ticker = tokio::time::interval(frame_duration);
210+
211+
for (frame_idx, chunk) in samples.chunks(samples_per_frame).enumerate() {
212+
let mut frame = vec![0.0f32; samples_per_frame];
213+
frame[..chunk.len()].copy_from_slice(chunk);
214+
215+
match opus_coder.encode(&frame) {
216+
Ok(opus_data) => {
217+
if let Err(e) = packet_sender.send(PacketData::Audio {
218+
timestamp: Instant::now(),
219+
duration: frame_duration,
220+
data: opus_data.into(),
221+
}) {
222+
log::warn!("send audio data failed: {e}");
223+
}
224+
}
225+
Err(e) => {
226+
log::warn!("Encoding frame {} failed: {}", frame_idx + 1, e);
227+
if let Err(e) = packet_sender.send(PacketData::Audio {
228+
timestamp: Instant::now(),
229+
duration: frame_duration,
230+
data: vec![].into(),
231+
}) {
232+
log::warn!("send empty audio data failed: {e}");
233+
}
234+
}
187235
}
188236

189-
_ = ticker.tick().await;
237+
ticker.tick().await;
190238

191239
if connections.load(Ordering::Relaxed) <= 0 {
192240
break 'out;
@@ -197,6 +245,8 @@ fn ogg_stream_thread(
197245
break;
198246
}
199247
}
248+
249+
log::info!("wav_streaming_thread exit...");
200250
});
201251
}
202252

lib/wrtc/examples/whep_server_demo.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ use tokio::sync::broadcast::{self, Sender};
1313
use webrtc::media::io::h264_reader::H264Reader;
1414
use webrtc::media::io::ogg_reader::OggReader;
1515
use wrtc::{
16-
Event, OPUS_SAMPLE_RATE, PacketData, session::WebRTCServerSessionConfig, webrtc::WebRTCServer,
16+
Event, PacketData, opus::OPUS_SAMPLE_RATE, session::WebRTCServerSessionConfig,
17+
webrtc::WebRTCServer,
1718
};
1819

1920
#[tokio::main]

lib/wrtc/src/common/auth.rs

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::scanf;
22
use indexmap::IndexMap;
3-
use serde::Deserialize;
43

54
#[derive(Debug, thiserror::Error)]
65
pub enum AuthError {
@@ -14,16 +13,6 @@ pub enum AuthError {
1413
InvalidTokenFormat,
1514
}
1615

17-
#[derive(Debug, Deserialize, Clone, Default)]
18-
pub enum AuthAlgorithm {
19-
#[default]
20-
#[serde(rename = "simple")]
21-
Simple,
22-
23-
#[serde(rename = "md5")]
24-
Md5,
25-
}
26-
2716
pub enum SecretCarrier {
2817
Query(String),
2918
Bearer(String),
@@ -69,31 +58,21 @@ pub fn get_secret(carrier: &SecretCarrier) -> Result<String, AuthError> {
6958

7059
#[derive(Debug, Clone)]
7160
pub struct Auth {
72-
key: String,
73-
algorithm: AuthAlgorithm,
74-
password: String,
61+
token: String,
7562
}
7663

7764
impl Auth {
78-
pub fn new(key: String, password: String, algorithm: AuthAlgorithm) -> Self {
79-
Self {
80-
key,
81-
algorithm,
82-
password,
83-
}
65+
pub fn new(token: String) -> Self {
66+
Self { token }
8467
}
8568

86-
pub fn authenticate(
87-
&self,
88-
stream_name: &String,
89-
secret: &Option<SecretCarrier>,
90-
) -> Result<(), AuthError> {
69+
pub fn authenticate(&self, secret: &Option<SecretCarrier>) -> Result<(), AuthError> {
9170
let mut auth_err_reason: String = String::from("there is no token str found.");
9271
let mut err = AuthError::NoTokenFound;
9372

9473
if let Some(secret_value) = secret {
9574
let token = get_secret(secret_value)?;
96-
if self.check(stream_name, token.as_str()) {
75+
if self.check(token.as_str()) {
9776
return Ok(());
9877
}
9978
auth_err_reason = format!("token is not correct: {token}");
@@ -104,13 +83,7 @@ impl Auth {
10483
return Err(err);
10584
}
10685

107-
fn check(&self, stream_name: &String, auth_str: &str) -> bool {
108-
match self.algorithm {
109-
AuthAlgorithm::Simple => self.password == auth_str,
110-
AuthAlgorithm::Md5 => {
111-
let raw_data = format!("{}{}", self.key, stream_name);
112-
auth_str == cutil::crypto::md5(&raw_data).to_lowercase()
113-
}
114-
}
86+
fn check(&self, token: &str) -> bool {
87+
self.token == token
11588
}
11689
}

0 commit comments

Comments
 (0)