Initial Commit
This commit is contained in:
commit
db5568c12c
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/rvoip-testing.iml" filepath="$PROJECT_DIR$/.idea/rvoip-testing.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
11
.idea/rvoip-testing.iml
Normal file
11
.idea/rvoip-testing.iml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="EMPTY_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
7
.idea/vcs.xml
Normal file
7
.idea/vcs.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
<mapping directory="/mnt/data/User Library/Documents/Projects/Rust/rvoip-testing" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
3938
Cargo.lock
generated
Normal file
3938
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "rvoip-testing"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cpal = "0.16.0"
|
||||||
|
rvoip-session-core = "0.1.12"
|
||||||
|
rvoip-media-core = {version = "0.1.12", features = ["all-codecs"]}
|
||||||
|
rvoip-rtp-core = "0.1.12"
|
||||||
|
tokio = { version = "1.46.1", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
|
anyhow = "1.0.98"
|
||||||
|
tracing-subscriber = "0.3.19"
|
||||||
|
futures = "0.3.31"
|
||||||
165
src/main.rs
Normal file
165
src/main.rs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
use std::cmp::min;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use cpal::{InputCallbackInfo, OutputCallbackInfo, StreamConfig, StreamInstant};
|
||||||
|
use rvoip_media_core::prelude::*;
|
||||||
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
|
use rvoip_media_core::codec::AudioCodec;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let the_beginning: StreamInstant = StreamInstant::new(0, 0);
|
||||||
|
|
||||||
|
let buffer_size = 32;
|
||||||
|
|
||||||
|
let opus_buffer: Arc<RingBuffer<Vec<u8>>> = Arc::new(RingBuffer::new(buffer_size).expect("Failed to create ring buffer"));
|
||||||
|
for _ in 0..(buffer_size/4)*3 {
|
||||||
|
opus_buffer.push(vec![0; 128]).await.expect("Failed to push to ring buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
let input_opus_codec = OpusCodec::new(SampleRate::Rate48000, 1, OpusConfig {
|
||||||
|
bitrate: 100000,
|
||||||
|
complexity: 4,
|
||||||
|
vbr: false,
|
||||||
|
application: OpusApplication::Voip,
|
||||||
|
frame_size_ms: 10.0,
|
||||||
|
}).expect("Failed to create Opus codec");
|
||||||
|
|
||||||
|
let output_opus_codec = OpusCodec::new(SampleRate::Rate48000, 1, OpusConfig {
|
||||||
|
bitrate: 100000,
|
||||||
|
complexity: 4,
|
||||||
|
vbr: false,
|
||||||
|
application: OpusApplication::Voip,
|
||||||
|
frame_size_ms: 10.0,
|
||||||
|
}).expect("Failed to create Opus codec");
|
||||||
|
|
||||||
|
let host = cpal::default_host();
|
||||||
|
|
||||||
|
let input_device = host.default_input_device().unwrap();
|
||||||
|
let input_device_config: StreamConfig = input_device.default_input_config().expect("Failed to get input config").into();
|
||||||
|
let output_device = host.default_output_device().unwrap();
|
||||||
|
let output_device_config: StreamConfig = output_device.default_output_config().expect("Failed to get output config").into();
|
||||||
|
|
||||||
|
let input_fn = {
|
||||||
|
let bufbuf = opus_buffer.clone();
|
||||||
|
let mut opus_codec = input_opus_codec;
|
||||||
|
let opus_info = opus_codec.get_info();
|
||||||
|
|
||||||
|
let sample_frame_size = (opus_info.sample_rate as usize/100)*(opus_info.channels as usize);
|
||||||
|
|
||||||
|
move |input_data: &[i16], icbi: &InputCallbackInfo| {
|
||||||
|
futures::executor::block_on(async {
|
||||||
|
let mut idx = 0;
|
||||||
|
while idx < input_data.len() {
|
||||||
|
let data_to_encode = if idx + sample_frame_size > input_data.len() {
|
||||||
|
let mut buffer = Vec::with_capacity(sample_frame_size);
|
||||||
|
buffer.extend_from_slice(&input_data[idx..]);
|
||||||
|
for _ in 0..(sample_frame_size - buffer.len()) {
|
||||||
|
buffer.push(0);
|
||||||
|
}
|
||||||
|
buffer
|
||||||
|
} else {
|
||||||
|
input_data[idx..min(idx + sample_frame_size, input_data.len())].to_vec()
|
||||||
|
};
|
||||||
|
|
||||||
|
let frame = AudioFrame::new(
|
||||||
|
data_to_encode,
|
||||||
|
input_device_config.sample_rate.0,
|
||||||
|
input_device_config.channels as u8,
|
||||||
|
icbi.timestamp().capture.duration_since(&the_beginning)
|
||||||
|
.expect("Failed to get timestamp").as_millis() as u32,
|
||||||
|
);
|
||||||
|
|
||||||
|
idx += sample_frame_size;
|
||||||
|
|
||||||
|
match opus_codec.encode(
|
||||||
|
&frame
|
||||||
|
) {
|
||||||
|
Ok(encoded_frame) => {
|
||||||
|
let _ = bufbuf.push(encoded_frame).await;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error while encoding: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let output_fn = {
|
||||||
|
let opus_buffer = opus_buffer.clone();
|
||||||
|
let mut opus_codec = output_opus_codec;
|
||||||
|
let opus_info = opus_codec.get_info();
|
||||||
|
|
||||||
|
let leftover_buffer: RingBuffer<Vec<i16>> = RingBuffer::new(128).expect("Failed to create ring buffer");
|
||||||
|
|
||||||
|
move |output_data: &mut [i16], ocbi: &OutputCallbackInfo| {
|
||||||
|
futures::executor::block_on(async {
|
||||||
|
let mut idx = 0;
|
||||||
|
let out_len = output_data.len();
|
||||||
|
while idx < out_len {
|
||||||
|
loop {
|
||||||
|
match leftover_buffer.peek().await {
|
||||||
|
Ok(data) => {
|
||||||
|
output_data[idx..idx+data.len()].copy_from_slice(
|
||||||
|
&(data)
|
||||||
|
);
|
||||||
|
idx += data.len();
|
||||||
|
leftover_buffer.pop().await.expect("Failed to pop from ring buffer");
|
||||||
|
}
|
||||||
|
Err(e) => break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match opus_buffer.peek().await {
|
||||||
|
Ok(data) => {
|
||||||
|
let frame = opus_codec.decode(&data).expect("Failed to decode");
|
||||||
|
|
||||||
|
if idx+frame.samples.len() > out_len {
|
||||||
|
output_data[idx..out_len].copy_from_slice(
|
||||||
|
&(frame.samples[0..out_len-idx])
|
||||||
|
);
|
||||||
|
|
||||||
|
let leftover = frame.samples[out_len-idx..].to_vec();
|
||||||
|
|
||||||
|
leftover_buffer.push(leftover).await.expect("Failed to push to ring buffer");
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
output_data[idx..idx+frame.samples.len()].copy_from_slice(
|
||||||
|
&(frame.samples)
|
||||||
|
);
|
||||||
|
idx += frame.samples.len();
|
||||||
|
opus_buffer.pop().await.expect("Failed to pop from ring buffer");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error while popping from ring buffer: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let input_stream = input_device.build_input_stream(
|
||||||
|
&input_device.default_input_config().expect("Failed to get input config").into(),
|
||||||
|
input_fn, err_fn, None
|
||||||
|
).expect("Failed to build input stream");
|
||||||
|
|
||||||
|
let output_stream = output_device.build_output_stream(
|
||||||
|
&output_device.default_output_config().expect("Failed to get output config").into(),
|
||||||
|
output_fn, err_fn, None
|
||||||
|
).expect("Failed to build output stream");
|
||||||
|
|
||||||
|
input_stream.play().expect("Failed to play input stream");
|
||||||
|
output_stream.play().expect("Failed to play output stream");
|
||||||
|
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn err_fn(err: cpal::StreamError) {
|
||||||
|
eprintln!("Error: {:?}", err);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user