♻️ refactor: extract AudioOutput

main
_ 2021-11-13 19:34:41 +00:00
parent 3b45f4309f
commit f7c38d88de
3 changed files with 179 additions and 81 deletions

54
Cargo.lock generated
View File

@ -87,9 +87,9 @@ dependencies = [
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
@ -156,6 +156,15 @@ dependencies = [
"libloading", "libloading",
] ]
[[package]]
name = "cmake"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b858541263efe664aead4a5209a4ae5c5d2811167d4ed4ee0944503f8d2089"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "combine" name = "combine"
version = "4.6.2" version = "4.6.2"
@ -287,6 +296,26 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "fltk"
version = "1.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9480f6742b6ba50cbfb8e016c987f8d3e2faa79c0624e22f934d80c747c62e18"
dependencies = [
"bitflags",
"fltk-sys",
"paste",
]
[[package]]
name = "fltk-sys"
version = "1.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6dc0e345a8288be36989fccd9796685b35dc63378c08fd7548100e1109a4ccb"
dependencies = [
"cmake",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -431,15 +460,6 @@ version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memoffset"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "music_player" name = "music_player"
version = "0.1.0" version = "0.1.0"
@ -448,6 +468,7 @@ dependencies = [
"byteorder", "byteorder",
"cpal", "cpal",
"ffmpeg-next", "ffmpeg-next",
"fltk",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
] ]
@ -526,15 +547,14 @@ checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d"
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.20.2" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cc", "cc",
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
"memoffset",
] ]
[[package]] [[package]]
@ -653,6 +673,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "paste"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5"
[[package]] [[package]]
name = "peeking_take_while" name = "peeking_take_while"
version = "0.1.2" version = "0.1.2"

View File

@ -10,5 +10,6 @@ anyhow = "1.0.45"
byteorder = "1.4.3" byteorder = "1.4.3"
cpal = "0.13.4" cpal = "0.13.4"
ffmpeg-next = "4.4.0" ffmpeg-next = "4.4.0"
fltk = "1.2.15"
tracing = "0.1.29" tracing = "0.1.29"
tracing-subscriber = { version = "0.3.1", features = ["env-filter"] } tracing-subscriber = { version = "0.3.1", features = ["env-filter"] }

View File

@ -22,10 +22,27 @@ use anyhow::{
use cpal::traits::{ use cpal::traits::{
DeviceTrait, DeviceTrait,
HostTrait, HostTrait,
StreamTrait,
};
use fltk::{
app,
button::Button,
enums::CallbackTrigger,
frame::Frame,
group::Flex,
prelude::*,
window::Window,
}; };
mod decoder; mod decoder;
#[derive (Clone, Copy)]
enum Message {
Play,
Pause,
}
fn main () -> Result <()> { fn main () -> Result <()> {
let args: Vec <_> = std::env::args ().collect (); let args: Vec <_> = std::env::args ().collect ();
@ -33,6 +50,7 @@ fn main () -> Result <()> {
None => bail! ("First argument must be a subcommand like `play`"), None => bail! ("First argument must be a subcommand like `play`"),
Some ("debug-dump") => cmd_debug_dump (&args [1..]), Some ("debug-dump") => cmd_debug_dump (&args [1..]),
Some ("debug-pipe") => cmd_debug_pipe (&args [1..]), Some ("debug-pipe") => cmd_debug_pipe (&args [1..]),
Some ("gui") => cmd_gui (&args [1..]),
Some ("play") => cmd_play (&args [1..]), Some ("play") => cmd_play (&args [1..]),
Some (_) => bail! ("Unrecognized subcommand"), Some (_) => bail! ("Unrecognized subcommand"),
} }
@ -84,6 +102,47 @@ pub struct SharedState {
pub quit: bool, pub quit: bool,
} }
fn cmd_gui (args: &[String]) -> Result <()> {
let (fltk_tx, fltk_rx) = app::channel::<Message> ();
let app = app::App::default ();
let window_title = "Music Player".to_string ();
let mut wind = Window::new (100, 100, 600, 600, None)
.with_label (&window_title);
wind.make_resizable (true);
let mut row = Flex::default ().row ().size_of_parent ();
let mut but_play = Button::default ().with_label ("▶️");
but_play.set_trigger (CallbackTrigger::Release);
but_play.emit (fltk_tx, Message::Play);
row.set_size (&mut but_play, 30);
let mut but_pause = Button::default ().with_label ("");
but_pause.set_trigger (CallbackTrigger::Release);
but_pause.emit (fltk_tx, Message::Pause);
row.set_size (&mut but_pause, 30);
row.end ();
wind.end ();
wind.show ();
while app.wait () {
match fltk_rx.recv () {
Some (Message::Play) => {
tracing::info! ("play");
},
Some (Message::Pause) => {
tracing::info! ("pause");
},
None => (),
}
}
Ok (())
}
fn cmd_play (args: &[String]) -> Result <()> { fn cmd_play (args: &[String]) -> Result <()> {
tracing_subscriber::fmt::init (); tracing_subscriber::fmt::init ();
@ -103,7 +162,13 @@ fn cmd_play (args: &[String]) -> Result <()> {
let mut decoder = decoder::Decoder::new (&filename)?; let mut decoder = decoder::Decoder::new (&filename)?;
'one_file: loop { 'one_file: loop {
// tracing::trace! ("decode thread parking"); let frame = match decoder.next ()? {
Some (x) => x,
None => {
tracing::debug! ("Decoder thread finished a file");
break 'one_file;
},
};
let mut decoder_state = cvar.wait_while (lock.lock ().unwrap (), |decoder_state| { let mut decoder_state = cvar.wait_while (lock.lock ().unwrap (), |decoder_state| {
decoder_state.pcm_buffers.samples_available () >= 12_000 && decoder_state.pcm_buffers.samples_available () >= 12_000 &&
@ -114,80 +179,17 @@ fn cmd_play (args: &[String]) -> Result <()> {
break 'many_files; break 'many_files;
} }
//dbg! (resampler.delay ()); decoder_state.pcm_buffers.produce_bytes (frame.data ().into ());
let pcm_buffers = &mut decoder_state.pcm_buffers;
while pcm_buffers.samples_available () < 12_000 {
// tracing::trace! ("Decoder is trying to work...");
match decoder.next ()? {
Some (frame) => pcm_buffers.produce_bytes (frame.data ().into ()),
None => {
tracing::info! ("Finished decoding file");
break 'one_file;
}
}
}
} }
} }
Ok::<_, anyhow::Error> (()) Ok::<_, anyhow::Error> (())
}); });
let host = cpal::default_host ();
let device = host.default_output_device ().ok_or_else (|| anyhow! ("can't open cpal device"))?;
let mut supported_configs_range = device.supported_output_configs ()?
.filter (|c| c.channels () == 2 && c.sample_format () == cpal::SampleFormat::F32);
let config = supported_configs_range.next ()
.ok_or_else (|| anyhow! ("can't get stereo f32 audio output"))?
.with_sample_rate (cpal::SampleRate (decoder::SAMPLE_RATE))
.config ();
let pcm_quit = Arc::new ((Mutex::new (false), Condvar::new ())); let pcm_quit = Arc::new ((Mutex::new (false), Condvar::new ()));
let pcm_quit2 = Arc::clone (&pcm_quit); let pcm_quit2 = Arc::clone (&pcm_quit);
let stream = device.build_output_stream ( let audio_output = AudioOutput::new (pcm_quit, pair)?;
&config,
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
let (lock, cvar) = &*pair;
let time_start = Instant::now ();
let mut decoder_state = match lock.lock () {
Ok (x) => x,
Err (_) => return,
};
let time_stop = Instant::now ();
let dur = time_stop - time_start;
if dur.as_micros () > 2 {
dbg! (dur.as_micros ());
}
let pcm_buffers = &mut decoder_state.pcm_buffers;
if ! pcm_buffers.consume_exact (data) {
tracing::warn! ("PCM buffer underflow");
for x in data {
*x = 0.0;
}
let (lock, cvar) = &*pcm_quit;
let mut pcm_quit = lock.lock ().unwrap ();
*pcm_quit = true;
cvar.notify_one ();
}
cvar.notify_one ();
},
move |_err| {
// react to errors here.
},
);
// sleep (std::time::Duration::from_secs (3 * 60 + 40));
tracing::debug! ("Joining decoder thread..."); tracing::debug! ("Joining decoder thread...");
@ -204,7 +206,7 @@ fn cmd_play (args: &[String]) -> Result <()> {
let (lock, cvar) = &*pcm_quit2; let (lock, cvar) = &*pcm_quit2;
let _ = cvar.wait (lock.lock ().unwrap ()).unwrap (); let _ = cvar.wait (lock.lock ().unwrap ()).unwrap ();
drop (stream); drop (audio_output);
sleep (Duration::from_secs (1)); sleep (Duration::from_secs (1));
@ -212,6 +214,75 @@ fn cmd_play (args: &[String]) -> Result <()> {
Ok (()) Ok (())
} }
struct AudioOutput {
host: cpal::Host,
device: cpal::Device,
stream: cpal::Stream,
}
impl AudioOutput {
pub fn new (
pcm_quit: Arc <(Mutex <bool>, Condvar)>,
pair: Arc <(Mutex <SharedState>, Condvar)>,
) -> Result <Self> {
let host = cpal::default_host ();
let device = host.default_output_device ().ok_or_else (|| anyhow! ("can't open cpal device"))?;
let mut supported_configs_range = device.supported_output_configs ()?
.filter (|c| c.channels () == 2 && c.sample_format () == cpal::SampleFormat::F32);
let config = supported_configs_range.next ()
.ok_or_else (|| anyhow! ("can't get stereo f32 audio output"))?
.with_sample_rate (cpal::SampleRate (decoder::SAMPLE_RATE))
.config ();
let stream = device.build_output_stream (
&config,
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
let (lock, cvar) = &*pair;
let time_start = Instant::now ();
let mut decoder_state = match lock.lock () {
Ok (x) => x,
Err (_) => return,
};
let time_stop = Instant::now ();
let dur = time_stop - time_start;
if dur.as_micros () > 100 {
tracing::warn! ("PCM thread waited {} us for a lock", dur.as_micros ());
}
let pcm_buffers = &mut decoder_state.pcm_buffers;
if ! pcm_buffers.consume_exact (data) {
tracing::warn! ("PCM buffer underflow");
for x in data {
*x = 0.0;
}
let (lock, cvar) = &*pcm_quit;
let mut pcm_quit = lock.lock ().unwrap ();
*pcm_quit = true;
cvar.notify_one ();
}
cvar.notify_one ();
},
move |_err| {
// react to errors here.
},
)?;
Ok (Self {
host,
device,
stream,
})
}
}
#[cfg (test)] #[cfg (test)]
mod test { mod test {
use super::*; use super::*;