mirror of
https://github.com/ivabus/lonelyradio
synced 2024-11-24 17:15:10 +03:00
WIP sample rate
Signed-off-by: Ivan Bushchik <ivabus@ivabus.dev>
This commit is contained in:
parent
fd780cf28b
commit
dd59f53d6f
6 changed files with 299 additions and 113 deletions
|
@ -7,9 +7,9 @@ use std::net::TcpStream;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
// How many samples to cache before playing in samples (both channels) SHOULD BE EVEN
|
// How many samples to cache before playing in samples (both channels) SHOULD BE EVEN
|
||||||
const BUFFER_SIZE: usize = 2400;
|
const BUFFER_SIZE: usize = 96000;
|
||||||
// How many buffers to cache
|
// How many buffers to cache
|
||||||
const CACHE_SIZE: usize = 100;
|
const CACHE_SIZE: usize = 20;
|
||||||
|
|
||||||
enum Channel {
|
enum Channel {
|
||||||
Right,
|
Right,
|
||||||
|
@ -31,6 +31,14 @@ struct Args {
|
||||||
/// More verbose
|
/// More verbose
|
||||||
#[arg(short)]
|
#[arg(short)]
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
|
||||||
|
/// Stream in f32le instead of s16le
|
||||||
|
#[arg(short, long)]
|
||||||
|
float: bool,
|
||||||
|
|
||||||
|
/// Stream in custom sample rate
|
||||||
|
#[arg(short, long, default_value = "44100")]
|
||||||
|
sample_rate: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -53,13 +61,28 @@ fn main() {
|
||||||
};
|
};
|
||||||
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||||
let sink = Sink::try_new(&stream_handle).unwrap();
|
let sink = Sink::try_new(&stream_handle).unwrap();
|
||||||
let mut buffer = [0u8; 4];
|
let mut buffer = vec![
|
||||||
|
0u8;
|
||||||
|
if args.float {
|
||||||
|
8
|
||||||
|
} else {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
];
|
||||||
let mut samples = [0f32; BUFFER_SIZE];
|
let mut samples = [0f32; BUFFER_SIZE];
|
||||||
let mut index = 0usize;
|
let mut index = 0usize;
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
while stream.read_exact(&mut buffer).is_ok() {
|
while stream.read_exact(&mut buffer).is_ok() {
|
||||||
let sample_l = byteorder::LittleEndian::read_i16(&buffer[..2]) as f32 / 32768.0;
|
let sample_l = if args.float {
|
||||||
let sample_r = byteorder::LittleEndian::read_i16(&buffer[2..]) as f32 / 32768.0;
|
byteorder::LittleEndian::read_f32(&buffer[..4])
|
||||||
|
} else {
|
||||||
|
byteorder::LittleEndian::read_i16(&buffer[..2]) as f32 / 32768.0
|
||||||
|
};
|
||||||
|
let sample_r = if args.float {
|
||||||
|
byteorder::LittleEndian::read_f32(&buffer[4..])
|
||||||
|
} else {
|
||||||
|
byteorder::LittleEndian::read_i16(&buffer[2..]) as f32 / 32768.0
|
||||||
|
};
|
||||||
// Left channel
|
// Left channel
|
||||||
samples[index] = match channel {
|
samples[index] = match channel {
|
||||||
Channel::Left | Channel::Stereo => sample_l,
|
Channel::Left | Channel::Stereo => sample_l,
|
||||||
|
@ -102,14 +125,15 @@ fn main() {
|
||||||
} else {
|
} else {
|
||||||
1
|
1
|
||||||
} as f32) * BUFFER_SIZE as f32
|
} as f32) * BUFFER_SIZE as f32
|
||||||
/ 44100.0 / 2.0,
|
/ args.sample_rate as f32
|
||||||
|
/ 2.0,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
if first && args.verbose {
|
if first && args.verbose {
|
||||||
eprintln!("Started playing in {} ms", (Instant::now() - start).as_millis());
|
eprintln!("Started playing in {} ms", (Instant::now() - start).as_millis());
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
sink.append(SamplesBuffer::new(2, 44100, samples.as_slice()));
|
sink.append(SamplesBuffer::new(2, args.sample_rate, samples.as_slice()));
|
||||||
index = 0;
|
index = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
40
platform/swiftui/Launch Screen.storyboard
Normal file
40
platform/swiftui/Launch Screen.storyboard
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="obG-Y5-kRd">
|
||||||
|
<rect key="frame" x="0.0" y="832" width="393" height="0.0"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="obG-Y5-kRd" secondAttribute="centerX" id="5cz-MP-9tL"/>
|
||||||
|
<constraint firstItem="obG-Y5-kRd" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" symbolic="YES" id="SfN-ll-jLj"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="obG-Y5-kRd" secondAttribute="bottom" constant="20" id="Y44-ml-fuU"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="53" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
|
@ -25,8 +25,8 @@ class MonoLib {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@State private var server: String = ""
|
@State private var server: String = "ivabus.dev"
|
||||||
@State private var port: String = ""
|
@State private var port: String = "5894"
|
||||||
@State private var playing: Bool = true
|
@State private var playing: Bool = true
|
||||||
@State private var running: Bool = false
|
@State private var running: Bool = false
|
||||||
|
|
||||||
|
@ -56,13 +56,15 @@ struct ContentView: View {
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if running {
|
if running {
|
||||||
playing = !playing
|
|
||||||
toggle()
|
toggle()
|
||||||
}
|
playing = !playing
|
||||||
running = true
|
} else {
|
||||||
let a = MonoLib()
|
let a = MonoLib()
|
||||||
Task.init {
|
Task.init {
|
||||||
await a.run(server: server + ":" + port)
|
await a.run(server: server + ":" + port)
|
||||||
|
}
|
||||||
|
running = true
|
||||||
|
playing = true
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Image(
|
Image(
|
||||||
|
@ -73,8 +75,7 @@ struct ContentView: View {
|
||||||
.borderedProminent)
|
.borderedProminent)
|
||||||
Button(action: {
|
Button(action: {
|
||||||
reset()
|
reset()
|
||||||
running = false
|
(running, playing) = (false, true)
|
||||||
playing = true
|
|
||||||
}) { Image(systemName: "stop").font(.title3) }.buttonStyle(
|
}) { Image(systemName: "stop").font(.title3) }.buttonStyle(
|
||||||
.bordered
|
.bordered
|
||||||
).disabled(!running)
|
).disabled(!running)
|
||||||
|
|
|
@ -5,16 +5,23 @@ use std::ffi::CStr;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
use std::os::raw::c_char;
|
use std::os::raw::c_char;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
// How many samples to cache before playing in samples (both channels) SHOULD BE EVEN
|
// How many samples to cache before playing in samples (both channels) SHOULD BE EVEN
|
||||||
const BUFFER_SIZE: usize = 2400;
|
const BUFFER_SIZE: usize = 4410;
|
||||||
// How many buffers to cache
|
// How many buffers to cache
|
||||||
const CACHE_SIZE: usize = 40;
|
const CACHE_SIZE: usize = 20;
|
||||||
|
|
||||||
static mut SINK: Option<Box<Sink>> = None;
|
static mut SINK: Option<Box<Sink>> = None;
|
||||||
static mut RUNNING: bool = false;
|
static mut STATE: State = State::NotStarted;
|
||||||
static mut STOPPED: bool = false;
|
|
||||||
static mut RESET: bool = false;
|
#[derive(PartialEq)]
|
||||||
|
enum State {
|
||||||
|
NotStarted,
|
||||||
|
Resetting,
|
||||||
|
Playing,
|
||||||
|
Paused,
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn start(server: *const c_char) {
|
pub extern "C" fn start(server: *const c_char) {
|
||||||
|
@ -30,13 +37,13 @@ pub extern "C" fn start(server: *const c_char) {
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn toggle() {
|
pub extern "C" fn toggle() {
|
||||||
unsafe {
|
unsafe {
|
||||||
if !STOPPED {
|
if STATE == State::Playing {
|
||||||
STOPPED = true;
|
STATE = State::Paused;
|
||||||
if let Some(sink) = &SINK {
|
if let Some(sink) = &SINK {
|
||||||
sink.pause();
|
sink.pause();
|
||||||
}
|
}
|
||||||
} else {
|
} else if STATE == State::Paused {
|
||||||
STOPPED = false;
|
STATE = State::Playing;
|
||||||
if let Some(sink) = &SINK {
|
if let Some(sink) = &SINK {
|
||||||
sink.play();
|
sink.play();
|
||||||
}
|
}
|
||||||
|
@ -47,19 +54,43 @@ pub extern "C" fn toggle() {
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn reset() {
|
pub extern "C" fn reset() {
|
||||||
unsafe {
|
unsafe {
|
||||||
RESET = true;
|
STATE = State::Resetting;
|
||||||
|
if let Some(sink) = &SINK {
|
||||||
|
sink.pause();
|
||||||
|
}
|
||||||
// Blocking main thread
|
// Blocking main thread
|
||||||
while RESET {
|
while STATE == State::Resetting {
|
||||||
std::thread::sleep(std::time::Duration::from_secs_f32(0.02))
|
std::thread::sleep(std::time::Duration::from_secs_f32(0.01))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe fn _reset() {
|
||||||
|
if let Some(sink) = &SINK {
|
||||||
|
sink.pause();
|
||||||
|
sink.clear();
|
||||||
|
}
|
||||||
|
SINK = None;
|
||||||
|
STATE = State::NotStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset - true, not - false
|
||||||
|
unsafe fn watching_sleep(dur: f32) -> bool {
|
||||||
|
let start = Instant::now();
|
||||||
|
while Instant::now() < start + std::time::Duration::from_secs_f32(dur) {
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs_f32(0.0001));
|
||||||
|
if STATE == State::Resetting {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
unsafe fn run(server: &str) {
|
unsafe fn run(server: &str) {
|
||||||
if RUNNING {
|
if STATE == State::Playing || STATE == State::Paused {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
RUNNING = true;
|
STATE = State::Playing;
|
||||||
let mut stream = TcpStream::connect(server).unwrap();
|
let mut stream = TcpStream::connect(server).unwrap();
|
||||||
println!("Connected to {} from {}", stream.peer_addr().unwrap(), stream.local_addr().unwrap());
|
println!("Connected to {} from {}", stream.peer_addr().unwrap(), stream.local_addr().unwrap());
|
||||||
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||||
|
@ -78,19 +109,11 @@ unsafe fn run(server: &str) {
|
||||||
let mut samples = [0f32; BUFFER_SIZE];
|
let mut samples = [0f32; BUFFER_SIZE];
|
||||||
let mut index = 0usize;
|
let mut index = 0usize;
|
||||||
while stream.read_exact(&mut buffer).is_ok() {
|
while stream.read_exact(&mut buffer).is_ok() {
|
||||||
while STOPPED {
|
while STATE == State::Paused {
|
||||||
std::thread::sleep(std::time::Duration::from_secs_f32(0.5))
|
std::thread::sleep(std::time::Duration::from_secs_f32(0.25))
|
||||||
}
|
}
|
||||||
if RESET {
|
if STATE == State::Resetting {
|
||||||
RUNNING = false;
|
_reset();
|
||||||
STOPPED = false;
|
|
||||||
|
|
||||||
if let Some(sink) = &SINK {
|
|
||||||
sink.pause();
|
|
||||||
sink.clear();
|
|
||||||
}
|
|
||||||
SINK = None;
|
|
||||||
RESET = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let sample_l = byteorder::LittleEndian::read_i16(&buffer[..2]) as f32 / 32768.0;
|
let sample_l = byteorder::LittleEndian::read_i16(&buffer[..2]) as f32 / 32768.0;
|
||||||
|
@ -108,15 +131,18 @@ unsafe fn run(server: &str) {
|
||||||
// a lot (no upper limit, actualy) of buffered sound, waiting for playing in sink
|
// a lot (no upper limit, actualy) of buffered sound, waiting for playing in sink
|
||||||
if let Some(sink) = &SINK {
|
if let Some(sink) = &SINK {
|
||||||
while sink.len() >= CACHE_SIZE {
|
while sink.len() >= CACHE_SIZE {
|
||||||
// Sleeping exactly one buffer
|
// Sleeping exactly one buffer and watching for reset signal
|
||||||
std::thread::sleep(std::time::Duration::from_secs_f32(
|
if watching_sleep(
|
||||||
(if sink.len() >= 2 {
|
if sink.len() > 2 {
|
||||||
sink.len() - 2
|
sink.len() as f32 - 2.0
|
||||||
} else {
|
} else {
|
||||||
1
|
0.5
|
||||||
} as f32) * BUFFER_SIZE as f32
|
} * BUFFER_SIZE as f32 / 44100.0
|
||||||
/ 44100.0 / 2.0,
|
/ 2.0,
|
||||||
))
|
) {
|
||||||
|
_reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sink.append(SamplesBuffer::new(2, 44100, samples.as_slice()));
|
sink.append(SamplesBuffer::new(2, 44100, samples.as_slice()));
|
||||||
index = 0;
|
index = 0;
|
||||||
|
|
|
@ -4,4 +4,4 @@ void start(const char *server);
|
||||||
|
|
||||||
void toggle();
|
void toggle();
|
||||||
|
|
||||||
void reset();
|
void reset();
|
217
src/main.rs
217
src/main.rs
|
@ -1,10 +1,13 @@
|
||||||
use std::path::PathBuf;
|
use std::i16;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use samplerate::ConverterType;
|
use rubato::{
|
||||||
|
Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType, WindowFunction,
|
||||||
|
};
|
||||||
use symphonia::core::audio::SampleBuffer;
|
use symphonia::core::audio::SampleBuffer;
|
||||||
use symphonia::core::codecs::CODEC_TYPE_NULL;
|
use symphonia::core::codecs::CODEC_TYPE_NULL;
|
||||||
use symphonia::core::io::MediaSourceStream;
|
use symphonia::core::io::MediaSourceStream;
|
||||||
|
@ -24,6 +27,14 @@ struct Args {
|
||||||
|
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
war: bool,
|
war: bool,
|
||||||
|
|
||||||
|
/// Stream in f32le instead of s16le
|
||||||
|
#[arg(short, long)]
|
||||||
|
float: bool,
|
||||||
|
|
||||||
|
/// Stream in custom sample rate
|
||||||
|
#[arg(short, long, default_value = "44100")]
|
||||||
|
sample_rate: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -35,8 +46,7 @@ async fn main() {
|
||||||
.filter_entry(is_not_hidden)
|
.filter_entry(is_not_hidden)
|
||||||
.filter_map(|v| v.ok())
|
.filter_map(|v| v.ok())
|
||||||
.map(|x| x.into_path())
|
.map(|x| x.into_path())
|
||||||
.filter(track_valid)
|
.filter(|arg0: &std::path::PathBuf| track_valid(arg0))
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<PathBuf>>(),
|
.collect::<Vec<PathBuf>>(),
|
||||||
);
|
);
|
||||||
loop {
|
loop {
|
||||||
|
@ -48,7 +58,7 @@ fn is_not_hidden(entry: &DirEntry) -> bool {
|
||||||
entry.file_name().to_str().map(|s| entry.depth() == 0 || !s.starts_with('.')).unwrap_or(false)
|
entry.file_name().to_str().map(|s| entry.depth() == 0 || !s.starts_with('.')).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn track_valid(track: &PathBuf) -> bool {
|
fn track_valid(track: &Path) -> bool {
|
||||||
if !track.metadata().unwrap().is_file() {
|
if !track.metadata().unwrap().is_file() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -60,36 +70,139 @@ fn track_valid(track: &PathBuf) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn stream_samples(
|
||||||
|
args: &Args,
|
||||||
|
track_samples: Vec<f32>,
|
||||||
|
rate: u32,
|
||||||
|
s: &mut TcpStream,
|
||||||
|
) -> bool {
|
||||||
|
let params = SincInterpolationParameters {
|
||||||
|
sinc_len: 64,
|
||||||
|
f_cutoff: 0.96,
|
||||||
|
interpolation: SincInterpolationType::Quadratic,
|
||||||
|
oversampling_factor: 16,
|
||||||
|
window: WindowFunction::Blackman,
|
||||||
|
};
|
||||||
|
let target_rate = args.sample_rate;
|
||||||
|
let mut resampler = SincFixedIn::<f32>::new(
|
||||||
|
target_rate as f64 / rate as f64,
|
||||||
|
100.0,
|
||||||
|
params,
|
||||||
|
track_samples.len() / 2,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let start = std::time::Instant::now();
|
||||||
|
let (left, right): (Vec<&f32>, Vec<&f32>) =
|
||||||
|
(track_samples.iter().step_by(2).collect(), track_samples[1..].iter().step_by(2).collect());
|
||||||
|
eprintln!("Splitted channels in {} ms", (std::time::Instant::now() - start).as_millis());
|
||||||
|
let samples = if rate != target_rate {
|
||||||
|
eprintln!("Resampling {} samples from {} to {}", track_samples.len(), rate, target_rate);
|
||||||
|
let start = std::time::Instant::now();
|
||||||
|
if rate > target_rate {
|
||||||
|
let resampled_l =
|
||||||
|
samplerate::convert(rate, target_rate, 1, samplerate::ConverterType::Linear, &left)
|
||||||
|
.unwrap();
|
||||||
|
let resampled_r = samplerate::convert(
|
||||||
|
rate,
|
||||||
|
target_rate,
|
||||||
|
1,
|
||||||
|
samplerate::ConverterType::Linear,
|
||||||
|
right.as_slice(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
eprintln!("Resampled in {} ms", (std::time::Instant::now() - start).as_millis());
|
||||||
|
vec![resampled_l, resampled_r]
|
||||||
|
} else {
|
||||||
|
match resampler.process(&[&left, &right], None) {
|
||||||
|
Ok(s) => {
|
||||||
|
eprintln!(
|
||||||
|
"Resampled in {} ms",
|
||||||
|
(std::time::Instant::now() - start).as_millis()
|
||||||
|
);
|
||||||
|
s
|
||||||
|
}
|
||||||
|
Err(e) => panic!("{}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vec![left, right]
|
||||||
|
};
|
||||||
|
let (left, right) = (&samples[0], &samples[1]);
|
||||||
|
for (sample_l, sample_r) in left.iter().zip(right) {
|
||||||
|
if args.float {
|
||||||
|
let result = s
|
||||||
|
.write(
|
||||||
|
&(if args.war {
|
||||||
|
sample_l.signum()
|
||||||
|
} else {
|
||||||
|
*sample_l
|
||||||
|
})
|
||||||
|
.to_le_bytes(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match result {
|
||||||
|
Err(_) | Ok(0) => return true,
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
let result = s
|
||||||
|
.write(
|
||||||
|
&(if args.war {
|
||||||
|
sample_r.signum()
|
||||||
|
} else {
|
||||||
|
*sample_r
|
||||||
|
})
|
||||||
|
.to_le_bytes(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match result {
|
||||||
|
Err(_) | Ok(0) => return true,
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
let result = s
|
||||||
|
.write(
|
||||||
|
&(if args.war {
|
||||||
|
sample_l.signum() as i16 * 32767
|
||||||
|
} else {
|
||||||
|
(sample_l * 32768_f32) as i16
|
||||||
|
})
|
||||||
|
.to_le_bytes(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match result {
|
||||||
|
Err(_) | Ok(0) => return true,
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
let result = s
|
||||||
|
.write(
|
||||||
|
&(if args.war {
|
||||||
|
sample_r.signum() as i16 * 32767
|
||||||
|
} else {
|
||||||
|
(sample_r * 32768_f32) as i16
|
||||||
|
})
|
||||||
|
.to_le_bytes(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match result {
|
||||||
|
Err(_) | Ok(0) => return true,
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eprintln!("Exiting");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
|
async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
'track: loop {
|
loop {
|
||||||
|
s.writable().await.unwrap();
|
||||||
let track = tracklist.choose(&mut thread_rng()).unwrap();
|
let track = tracklist.choose(&mut thread_rng()).unwrap();
|
||||||
eprintln!(
|
|
||||||
"[{}] {} to {}:{}{}",
|
|
||||||
Local::now().to_rfc3339(),
|
|
||||||
track.to_str().unwrap(),
|
|
||||||
s.peer_addr().unwrap().ip(),
|
|
||||||
s.peer_addr().unwrap().port(),
|
|
||||||
if args.war {
|
|
||||||
" with WAR.rs"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if args.public_log {
|
if args.public_log {
|
||||||
println!(
|
println!("[{}] {}", Local::now().to_rfc3339(), track.to_str().unwrap(),);
|
||||||
"[{}] {} to {}{}",
|
|
||||||
Local::now().to_rfc3339(),
|
|
||||||
track.to_str().unwrap(),
|
|
||||||
s.peer_addr().unwrap().port(),
|
|
||||||
if args.war {
|
|
||||||
" with WAR.rs"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = Box::new(std::fs::File::open(track).unwrap());
|
let file = Box::new(std::fs::File::open(track).unwrap());
|
||||||
|
@ -118,11 +231,15 @@ async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
|
||||||
.expect("unsupported codec");
|
.expect("unsupported codec");
|
||||||
|
|
||||||
let track_id = track.id;
|
let track_id = track.id;
|
||||||
|
let mut track_samples = vec![];
|
||||||
|
let mut track_rate = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let packet = match format.next_packet() {
|
let packet = match format.next_packet() {
|
||||||
Ok(packet) => packet,
|
Ok(packet) => packet,
|
||||||
_ => continue 'track,
|
_ => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
while !format.metadata().is_latest() {
|
while !format.metadata().is_latest() {
|
||||||
|
@ -136,46 +253,24 @@ async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
|
||||||
match decoder.decode(&packet) {
|
match decoder.decode(&packet) {
|
||||||
Ok(decoded) => {
|
Ok(decoded) => {
|
||||||
let rate = decoded.spec().rate;
|
let rate = decoded.spec().rate;
|
||||||
|
track_rate = rate;
|
||||||
let mut byte_buf =
|
let mut byte_buf =
|
||||||
SampleBuffer::<f32>::new(decoded.capacity() as u64, *decoded.spec());
|
SampleBuffer::<f32>::new(decoded.capacity() as u64, *decoded.spec());
|
||||||
byte_buf.copy_interleaved_ref(decoded);
|
byte_buf.copy_interleaved_ref(decoded);
|
||||||
let samples = if rate != 44100 {
|
track_samples.append(&mut byte_buf.samples_mut().to_vec());
|
||||||
samplerate::convert(
|
|
||||||
rate,
|
|
||||||
44100,
|
|
||||||
2,
|
|
||||||
ConverterType::Linear,
|
|
||||||
byte_buf.samples(),
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
} else {
|
|
||||||
byte_buf.samples().to_vec()
|
|
||||||
};
|
|
||||||
for sample in samples {
|
|
||||||
let result = s
|
|
||||||
.write(
|
|
||||||
&(if args.war {
|
|
||||||
sample.signum() as i16 * 32767
|
|
||||||
} else {
|
|
||||||
(sample * 32768_f32) as i16
|
|
||||||
})
|
|
||||||
.to_le_bytes(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
match result {
|
|
||||||
Err(_) | Ok(0) => {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Handling any error as track skip
|
// Handling any error as track skip
|
||||||
continue 'track;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !track_samples.is_empty() {
|
||||||
|
if stream_samples(&args, track_samples, track_rate, &mut s).await {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue