Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
421 changes: 238 additions & 183 deletions Cargo.lock

Large diffs are not rendered by default.

18 changes: 7 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ homepage = "https://getstream.io/video/"
documentation = "https://docs.rs/getstream"
keywords = ["video", "webrtc", "sfu", "stream", "rtc"]
categories = ["api-bindings", "multimedia::video", "network-programming"]

# A published crate's own `.cargo/config.toml` is never read by Cargo (config is
# discovered from the *consumer's* directory tree), so it is deliberately absent
# from this list — see the note above `[dependencies]`.
Expand Down Expand Up @@ -44,15 +45,13 @@ rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]
# system dependency (libvpx) and the vendored-opus build tools.

[dependencies]
# Opus encode/decode for the audio media path. `opusic-sys` is a maintained
# libopus binding (tracks libopus 1.6.1) that bundles + static-links a *vendored*
# libopus, so the SDK build needs no system libopus at link or runtime. It
# replaces the unmaintained `audiopus_sys` (RUSTSEC-2026-0150); see
# `src/rtc/opus.rs` for the thin safe wrapper. The bundled CMake build needs
# `cmake` and a C compiler on PATH and nothing else: its `CMakeLists.txt`
# declares `cmake_minimum_required(VERSION 3.16)`, which satisfies CMake 4's
# Opus encode/decode for the audio media path. A safe wrapper over `opusic-sys`,
# which bundles + static-links a *vendored* libopus, so the SDK build needs no
# system libopus at link or runtime. The bundled CMake build needs `cmake` and a
# C compiler on PATH and nothing else: its `CMakeLists.txt` declares
# `cmake_minimum_required(VERSION 3.16)`, which satisfies CMake 4's
# minimum-policy floor without any build-time environment override.
opusic-sys = "0.7.4"
opus = "0.4"
base64 = "0.23.1"
bytes = "1.12.1"
futures-util = "0.3.34"
Expand Down Expand Up @@ -86,9 +85,6 @@ flate2 = "1.1.9"
openh264 = "=0.8.1"

[dev-dependencies]
# Example-only convenience for the two executable demos. OpenAI-specific bridge
# orchestration stays in `examples/gpt_realtime_bot.rs`; `tokio` here adds the
# runtime and signal features used only by the examples.
anyhow = "1.0.86"
criterion = "0.8.2"
dotenvy = "0.15.7"
Expand Down
13 changes: 9 additions & 4 deletions benches/media_baseline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ fn bench_resampling(criterion: &mut Criterion) {

fn bench_opus(criterion: &mut Criterion) {
let pcm = tone(48_000, 1, 20).samples;
let mut encoder = rtc::opus::Encoder::new_voip_mono().expect("create Opus encoder");
let mut encoder = opus::Encoder::new(48_000, opus::Channels::Mono, opus::Application::Voip)
.expect("create Opus encoder");
let mut encoded = vec![0u8; 1_500];

let mut group = criterion.benchmark_group("opus");
Expand All @@ -111,7 +112,8 @@ fn bench_opus(criterion: &mut Criterion) {
.encode(&pcm, &mut encoded)
.expect("encode Opus decode fixture");
encoded.truncate(encoded_length);
let mut decoder = rtc::opus::Decoder::new_mono().expect("create Opus decoder");
let mut decoder =
opus::Decoder::new(48_000, opus::Channels::Mono).expect("create Opus decoder");
let mut decoded = vec![0i16; 5_760];
group.throughput(Throughput::Bytes(encoded.len() as u64));
group.bench_function("decode_48k_mono_20ms", |bencher| {
Expand Down Expand Up @@ -274,8 +276,11 @@ fn bench_multitrack_load(criterion: &mut Criterion) {
const VIDEO_H: u32 = 360;

let pcm = tone(48_000, 1, 20).samples;
let mut audio_encoders: Vec<rtc::opus::Encoder> = (0..AUDIO_TRACKS)
.map(|_| rtc::opus::Encoder::new_voip_mono().expect("create multitrack Opus encoder"))
let mut audio_encoders: Vec<opus::Encoder> = (0..AUDIO_TRACKS)
.map(|_| {
opus::Encoder::new(48_000, opus::Channels::Mono, opus::Application::Voip)
.expect("create multitrack Opus encoder")
})
.collect();
let mut audio_scratch = vec![0u8; 1_500];

Expand Down
2 changes: 0 additions & 2 deletions benches/support/rtc_sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ pub(crate) mod error {

#[path = "../../src/rtc/h264.rs"]
pub(crate) mod h264;
#[path = "../../src/rtc/opus.rs"]
pub(crate) mod opus;
#[path = "../../src/rtc/rtp_h264.rs"]
pub(crate) mod rtp_h264;
#[path = "../../src/rtc/rtp_vpx.rs"]
Expand Down
8 changes: 3 additions & 5 deletions benches/timer_drift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

use std::time::Duration;

#[allow(dead_code)]
#[path = "../src/rtc/opus.rs"]
mod opus;

const PERIOD: Duration = Duration::from_millis(20);
const TICKS: usize = 500;

Expand All @@ -26,7 +22,9 @@ async fn measure_timer_drift() {
(12_000.0 * (std::f64::consts::TAU * 440.0 * time).sin()) as i16
})
.collect();
let mut encoder = opus::Encoder::new_voip_mono().expect("create Opus encoder");
let mut encoder =
opus::Encoder::new(sample_rate, opus::Channels::Mono, opus::Application::Voip)
.expect("create Opus encoder");
let mut encoded = vec![0u8; 1_500];

let mut interval = tokio::time::interval(PERIOD);
Expand Down
4 changes: 1 addition & 3 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
# from software licenses.

[advisories]
# Fail on all security advisories. There are no ignores: the previously
# unmaintained Opus binding (`audiopus_sys`, RUSTSEC-2026-0150) was replaced by
# the maintained `opusic-sys`, so no advisory needs suppressing.
# Fail on all security advisories. There are no ignores.

[licenses]
# Dependency-license policy. If a new dependency introduces an unreviewed
Expand Down
55 changes: 47 additions & 8 deletions src/rtc/local_track.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ struct AudioInner {
/// Resampled 48 kHz mono PCM awaiting the 20 ms pacer.
pcm: StdMutex<VecDeque<i16>>,
resampler: StdMutex<StreamResampler>,
encoder: StdMutex<super::opus::Encoder>,
encoder: StdMutex<opus::Encoder>,
pacer: StdMutex<Option<JoinHandle<()>>>,
pacer_started: AtomicBool,
pcm_pacing: AtomicBool,
Expand Down Expand Up @@ -350,7 +350,12 @@ impl LocalAudioTrack {
};
let track_id = format!("audio-{}", uuid::Uuid::new_v4().simple());
let core = TrackCore::new(codec, track_id, "stream-rust-audio".to_owned())?;
let encoder = super::opus::Encoder::new_voip_mono().map_err(RtcError::Media)?;
let encoder = opus::Encoder::new(
OPUS_SAMPLE_RATE,
opus::Channels::Mono,
opus::Application::Voip,
)
.map_err(|error| RtcError::Media(error.to_string()))?;
Ok(Self {
inner: Arc::new(AudioInner {
core,
Expand Down Expand Up @@ -601,12 +606,10 @@ fn push_bounded_pcm(queue: &mut VecDeque<i16>, samples: Vec<i16>) -> usize {
overflow
}

fn encode_opus_into(
encoder: &mut super::opus::Encoder,
pcm: &[i16],
output: &mut [u8],
) -> Result<usize> {
encoder.encode(pcm, output).map_err(RtcError::Media)
fn encode_opus_into(encoder: &mut opus::Encoder, pcm: &[i16], output: &mut [u8]) -> Result<usize> {
encoder
.encode(pcm, output)
.map_err(|error| RtcError::Media(error.to_string()))
}

// Video
Expand Down Expand Up @@ -1915,6 +1918,42 @@ impl LocalTrack {
mod tests {
use super::*;

#[test]
fn encode_opus_into_produces_a_packet_for_a_20ms_mono_frame() {
let mut encoder = opus::Encoder::new(
OPUS_SAMPLE_RATE,
opus::Channels::Mono,
opus::Application::Voip,
)
.expect("encoder");
let pcm = vec![1_000i16; FRAME_SAMPLES_20MS];
let mut output = vec![0u8; 1_500];

let length = encode_opus_into(&mut encoder, &pcm, &mut output).expect("encode");

assert!(
length > 0 && length <= output.len(),
"packet length {length}"
);
}

#[test]
fn encode_opus_into_rejects_a_bogus_frame_size() {
let mut encoder = opus::Encoder::new(
OPUS_SAMPLE_RATE,
opus::Channels::Mono,
opus::Application::Voip,
)
.expect("encoder");
let mut output = vec![0u8; 1_500];

// 137 samples is not a valid 48 kHz Opus frame size.
let error = encode_opus_into(&mut encoder, &vec![0i16; 137], &mut output)
.expect_err("bogus frame size must be rejected");

assert!(matches!(error, RtcError::Media(_)), "error was: {error}");
}

#[tokio::test]
async fn opus_track_builds_and_writes_sample() {
let track = LocalAudioTrack::opus().expect("opus track");
Expand Down
1 change: 0 additions & 1 deletion src/rtc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ pub mod identity;
pub mod join;
mod layers;
pub mod local_track;
mod opus;
pub mod pcm;
pub mod peer;
pub mod proto;
Expand Down
Loading
Loading