0.1.7: switch logs err/out, bump symphonia, buffer wisely

Signed-off-by: Ivan Bushchik <ivabus@ivabus.dev>
This commit is contained in:
Ivan Bushchik 2024-02-28 17:50:24 +03:00
parent 31c0db5d99
commit 6daba088b8
No known key found for this signature in database
GPG key ID: 2F16FBF3262E090C
4 changed files with 211 additions and 90 deletions

151
Cargo.lock generated
View file

@ -387,6 +387,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "extended"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365"
[[package]]
name = "getrandom"
version = "0.2.12"
@ -568,7 +574,7 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "lonelyradio"
version = "0.1.6"
version = "0.1.7"
dependencies = [
"chrono",
"clap",
@ -679,6 +685,15 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "num-complex"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214"
dependencies = [
"num-traits",
]
[[package]]
name = "num-derive"
version = "0.3.3"
@ -690,6 +705,15 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.17"
@ -815,6 +839,15 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "primal-check"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df7f93fd637f083201473dab4fee2db4c429d32e55e3299980ab3957ab916a0"
dependencies = [
"num-integer",
]
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
@ -938,6 +971,21 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustfft"
version = "6.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43806561bc506d0c5d160643ad742e3161049ac01027b5e6d7524091fd401d86"
dependencies = [
"num-complex",
"num-integer",
"num-traits",
"primal-check",
"strength_reduce",
"transpose",
"version_check",
]
[[package]]
name = "same-file"
version = "1.0.6"
@ -984,6 +1032,12 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "strength_reduce"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
[[package]]
name = "strsim"
version = "0.10.0"
@ -992,9 +1046,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "symphonia"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62e48dba70095f265fdb269b99619b95d04c89e619538138383e63310b14d941"
checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9"
dependencies = [
"lazy_static",
"symphonia-bundle-flac",
@ -1005,18 +1059,19 @@ dependencies = [
"symphonia-codec-pcm",
"symphonia-codec-vorbis",
"symphonia-core",
"symphonia-format-caf",
"symphonia-format-isomp4",
"symphonia-format-mkv",
"symphonia-format-ogg",
"symphonia-format-wav",
"symphonia-format-riff",
"symphonia-metadata",
]
[[package]]
name = "symphonia-bundle-flac"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f23b0482a7cb18fcdf9981ab0b78df800ef0080187d294650023c462439058d"
checksum = "72e34f34298a7308d4397a6c7fbf5b84c5d491231ce3dd379707ba673ab3bd97"
dependencies = [
"log",
"symphonia-core",
@ -1026,11 +1081,10 @@ dependencies = [
[[package]]
name = "symphonia-bundle-mp3"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f31d7fece546f1e6973011a9eceae948133bbd18fd3d52f6073b1e38ae6368a"
checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4"
dependencies = [
"bitflags 1.3.2",
"lazy_static",
"log",
"symphonia-core",
@ -1039,9 +1093,9 @@ dependencies = [
[[package]]
name = "symphonia-codec-aac"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68bdd75b25ce4b84b12a4bd20bfea2460c2dbd7fc1d227ef5533504d3168109d"
checksum = "cdbf25b545ad0d3ee3e891ea643ad115aff4ca92f6aec472086b957a58522f70"
dependencies = [
"lazy_static",
"log",
@ -1050,9 +1104,9 @@ dependencies = [
[[package]]
name = "symphonia-codec-adpcm"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "870e7dc1865d818c7b6318879d060553a73a3b2a3b8443dff90910f10ac41150"
checksum = "c94e1feac3327cd616e973d5be69ad36b3945f16b06f19c6773fc3ac0b426a0f"
dependencies = [
"log",
"symphonia-core",
@ -1060,9 +1114,9 @@ dependencies = [
[[package]]
name = "symphonia-codec-alac"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a27e8763d1c9eff666faf903e73a99d4de2f7a93fca4e3c214c1d68432903b9"
checksum = "2d8a6666649a08412906476a8b0efd9b9733e241180189e9f92b09c08d0e38f3"
dependencies = [
"log",
"symphonia-core",
@ -1070,9 +1124,9 @@ dependencies = [
[[package]]
name = "symphonia-codec-pcm"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47f1fbd220a06a641c8ce2ddad10f5ef6ee5cc0c54d9044d25d43b0d3119deaa"
checksum = "f395a67057c2ebc5e84d7bb1be71cce1a7ba99f64e0f0f0e303a03f79116f89b"
dependencies = [
"log",
"symphonia-core",
@ -1080,9 +1134,9 @@ dependencies = [
[[package]]
name = "symphonia-codec-vorbis"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3953397e3506aa01350c4205817e4f95b58d476877a42f0458d07b665749e203"
checksum = "5a98765fb46a0a6732b007f7e2870c2129b6f78d87db7987e6533c8f164a9f30"
dependencies = [
"log",
"symphonia-core",
@ -1091,22 +1145,34 @@ dependencies = [
[[package]]
name = "symphonia-core"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c73eb88fee79705268cc7b742c7bc93a7b76e092ab751d0833866970754142"
checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3"
dependencies = [
"arrayvec",
"bitflags 1.3.2",
"bytemuck",
"lazy_static",
"log",
"rustfft",
]
[[package]]
name = "symphonia-format-caf"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e43c99c696a388295a29fe71b133079f5d8b18041cf734c5459c35ad9097af50"
dependencies = [
"log",
"symphonia-core",
"symphonia-metadata",
]
[[package]]
name = "symphonia-format-isomp4"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf14bae5cf352032416bc64151e5d6242d29d33cbf3238513b44d4427a1efb"
checksum = "abfdf178d697e50ce1e5d9b982ba1b94c47218e03ec35022d9f0e071a16dc844"
dependencies = [
"encoding_rs",
"log",
@ -1117,9 +1183,9 @@ dependencies = [
[[package]]
name = "symphonia-format-mkv"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5c61dfc851ad25d4043d8c231d8617e8f7cd02a6cc0edad21ade21848d58895"
checksum = "1bb43471a100f7882dc9937395bd5ebee8329298e766250b15b3875652fe3d6f"
dependencies = [
"lazy_static",
"log",
@ -1130,9 +1196,9 @@ dependencies = [
[[package]]
name = "symphonia-format-ogg"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bf1a00ccd11452d44048a0368828040f778ae650418dbd9d8765b7ee2574c8d"
checksum = "ada3505789516bcf00fc1157c67729eded428b455c27ca370e41f4d785bfa931"
dependencies = [
"log",
"symphonia-core",
@ -1141,11 +1207,12 @@ dependencies = [
]
[[package]]
name = "symphonia-format-wav"
version = "0.5.3"
name = "symphonia-format-riff"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da76614728fa27c003bdcdfbac51396bd8fcbf94c95fe8e62f1d2bac58ef03a4"
checksum = "05f7be232f962f937f4b7115cbe62c330929345434c834359425e043bfd15f50"
dependencies = [
"extended",
"log",
"symphonia-core",
"symphonia-metadata",
@ -1153,9 +1220,9 @@ dependencies = [
[[package]]
name = "symphonia-metadata"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89c3e1937e31d0e068bbe829f66b2f2bfaa28d056365279e0ef897172c3320c0"
checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c"
dependencies = [
"encoding_rs",
"lazy_static",
@ -1165,9 +1232,9 @@ dependencies = [
[[package]]
name = "symphonia-utils-xiph"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a450ca645b80d69aff8b35576cbfdc7f20940b29998202aab910045714c951f8"
checksum = "484472580fa49991afda5f6550ece662237b00c6f562c7d9638d1b086ed010fe"
dependencies = [
"symphonia-core",
"symphonia-metadata",
@ -1260,6 +1327,16 @@ dependencies = [
"winnow",
]
[[package]]
name = "transpose"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6522d49d03727ffb138ae4cbc1283d3774f0d10aa7f9bf52e6784c45daf9b23"
dependencies = [
"num-integer",
"strength_reduce",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
@ -1272,6 +1349,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.4.0"

View file

@ -4,7 +4,7 @@ members = ["monoclient"]
[package]
name = "lonelyradio"
description = "TCP radio for singles"
version = "0.1.6"
version = "0.1.7"
edition = "2021"
license = "MIT"
authors = ["Ivan Bushchik <ivabus@ivabus.dev>"]
@ -22,11 +22,10 @@ tokio = { version = "1.35.1", features = [
"macros",
] }
walkdir = "2.4.0"
symphonia = { version = "0.5.3", features = [
symphonia = { version = "0.5.4", features = [
"all-codecs",
"all-formats",
"pcm",
"symphonia-codec-pcm",
"opt-simd",
] }
samplerate = "0.2.4"
chrono = "0.4"

View file

@ -4,9 +4,12 @@ use rodio::buffer::SamplesBuffer;
use rodio::{OutputStream, Sink};
use std::io::Read;
use std::net::TcpStream;
use std::time::Instant;
// How many samples to cache before playing (half a second)
const BUFFER_SIZE: usize = 44100;
// How many samples to cache before playing in samples (both channels) SHOULD BE EVEN
const BUFFER_SIZE: usize = 2400;
// How many buffers to cache
const CACHE_SIZE: usize = 100;
enum Channel {
Right,
@ -18,16 +21,29 @@ enum Channel {
struct Args {
/// Remote address
address: String,
#[arg(short, long, default_value = "s")]
/// L, R or S for Left, Right or Stereo
channel: String,
#[arg(short)]
/// Play only on specified channel, with it if channel = Right => L=0 and R=R, without L=R and R=R. No effect on Stereo
single: bool,
/// More verbose
#[arg(short)]
verbose: bool,
}
fn main() {
let start = Instant::now();
let args = Args::parse();
let mut stream = TcpStream::connect(args.address).unwrap();
if args.verbose {
eprintln!(
"Connected to {} from {}",
stream.peer_addr().unwrap(),
stream.local_addr().unwrap()
)
}
let channel = match args.channel.to_ascii_lowercase().as_str() {
"l" => Channel::Left,
@ -38,12 +54,14 @@ fn main() {
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
let sink = Sink::try_new(&stream_handle).unwrap();
let mut buffer = [0u8; 4];
let mut samples = vec![];
let mut samples = [0f32; BUFFER_SIZE];
let mut index = 0usize;
let mut first = true;
while stream.read_exact(&mut buffer).is_ok() {
let sample_l = byteorder::LittleEndian::read_i16(&buffer[..2]) as f32 / 32768.0;
let sample_r = byteorder::LittleEndian::read_i16(&buffer[2..]) as f32 / 32768.0;
// Left channel
samples.push(match channel {
samples[index] = match channel {
Channel::Left | Channel::Stereo => sample_l,
Channel::Right => {
if args.single {
@ -52,9 +70,10 @@ fn main() {
sample_r
}
}
});
};
index += 1;
// Right channel
samples.push(match channel {
samples[index] = match channel {
Channel::Right | Channel::Stereo => sample_r,
Channel::Left => {
if args.single {
@ -63,20 +82,35 @@ fn main() {
sample_l
}
}
});
if samples.len() >= BUFFER_SIZE {
// What is next three lines?
// Sink's thread is detached from main thread, so we need to somehow synchronize with it
};
index += 1;
if index == BUFFER_SIZE {
let mut first_wait_iteration = true;
// Sink's thread is detached from main thread, so we need to synchronize with it
// Why we should synchronize with it?
// Let's say, that if we don't synchronize with it, we would have
// a lot (no upper limit, actualy) of buffered sound, waiting for playing in sink
while sink.len() >= 3
// buffered >= 1.5 sec
{
std::thread::sleep(std::time::Duration::from_secs_f32(0.25))
while sink.len() >= CACHE_SIZE {
if args.verbose && first_wait_iteration {
eprint!(".");
first_wait_iteration = false;
}
sink.append(SamplesBuffer::new(2, 44100, samples));
samples = vec![];
// Sleeping exactly one buffer
std::thread::sleep(std::time::Duration::from_secs_f32(
(if sink.len() >= 2 {
sink.len() - 2
} else {
1
} as f32) * BUFFER_SIZE as f32
/ 44100.0 / 2.0,
))
}
if first && args.verbose {
eprintln!("Started playing in {} ms", (Instant::now() - start).as_millis());
first = false;
}
sink.append(SamplesBuffer::new(2, 44100, samples.as_slice()));
index = 0;
}
}
}

View file

@ -1,4 +1,5 @@
use std::path::PathBuf;
use std::sync::Arc;
use chrono::Local;
use clap::Parser;
@ -6,9 +7,7 @@ use rand::prelude::*;
use samplerate::ConverterType;
use symphonia::core::audio::SampleBuffer;
use symphonia::core::codecs::CODEC_TYPE_NULL;
use symphonia::core::formats::FormatOptions;
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataOptions;
use symphonia::core::probe::Hint;
use tokio::io::AsyncWriteExt;
use tokio::net::{TcpListener, TcpStream};
@ -30,42 +29,43 @@ struct Args {
#[tokio::main]
async fn main() {
let listener = TcpListener::bind(Args::parse().address).await.unwrap();
let tracklist = Arc::new(
walkdir::WalkDir::new(Args::parse().dir)
.into_iter()
.filter_entry(is_not_hidden)
.filter_map(|v| v.ok())
.map(|x| x.into_path())
.filter(track_valid)
.into_iter()
.collect::<Vec<PathBuf>>(),
);
loop {
let (socket, _) = listener.accept().await.unwrap();
tokio::spawn(stream(socket));
tokio::spawn(stream(socket, tracklist.clone()));
}
}
fn is_not_hidden(entry: &DirEntry) -> bool {
entry.file_name().to_str().map(|s| entry.depth() == 0 || !s.starts_with('.')).unwrap_or(false)
}
// Recursively finding music file
fn pick_track(tracklist: &Vec<PathBuf>) -> &PathBuf {
let mut track = tracklist.choose(&mut thread_rng()).unwrap();
while !track.metadata().unwrap().is_file() {
track = pick_track(tracklist)
fn track_valid(track: &PathBuf) -> bool {
if !track.metadata().unwrap().is_file() {
return false;
}
// Skipping "images" (covers)
while "jpgjpegpngwebp"
.contains(&track.extension().unwrap().to_str().unwrap().to_ascii_lowercase())
if "jpgjpegpngwebp".contains(&track.extension().unwrap().to_str().unwrap().to_ascii_lowercase())
{
track = pick_track(tracklist)
return false;
}
track
true
}
async fn stream(mut s: TcpStream) {
async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
let args = Args::parse();
let tracklist = walkdir::WalkDir::new(Args::parse().dir)
.into_iter()
.filter_entry(is_not_hidden)
.filter_map(|v| v.ok())
.map(|x| x.into_path())
.collect::<Vec<PathBuf>>();
'track: loop {
let track = pick_track(&tracklist);
println!(
let track = tracklist.choose(&mut thread_rng()).unwrap();
eprintln!(
"[{}] {} to {}:{}{}",
Local::now().to_rfc3339(),
track.to_str().unwrap(),
@ -79,7 +79,7 @@ async fn stream(mut s: TcpStream) {
);
if args.public_log {
eprintln!(
println!(
"[{}] {} to {}{}",
Local::now().to_rfc3339(),
track.to_str().unwrap(),
@ -95,13 +95,14 @@ async fn stream(mut s: TcpStream) {
let file = Box::new(std::fs::File::open(track).unwrap());
let mut hint = Hint::new();
hint.with_extension(track.extension().unwrap().to_str().unwrap());
let mss = MediaSourceStream::new(file, Default::default());
let meta_opts: MetadataOptions = Default::default();
let fmt_opts: FormatOptions = Default::default();
let probed = symphonia::default::get_probe()
.format(&hint, mss, &fmt_opts, &meta_opts)
.format(
&hint,
MediaSourceStream::new(file, Default::default()),
&Default::default(),
&Default::default(),
)
.expect("unsupported format");
let mut format = probed.format;
@ -138,14 +139,18 @@ async fn stream(mut s: TcpStream) {
let mut byte_buf =
SampleBuffer::<f32>::new(decoded.capacity() as u64, *decoded.spec());
byte_buf.copy_interleaved_ref(decoded);
let samples = samplerate::convert(
let samples = if rate != 44100 {
samplerate::convert(
rate,
44100,
2,
ConverterType::Linear,
byte_buf.samples(),
)
.unwrap();
.unwrap()
} else {
byte_buf.samples().to_vec()
};
for sample in samples {
let result = s
.write(