From f7c38d88de0cdf379404b120d1d24866fb74eca2 Mon Sep 17 00:00:00 2001 From: _ <> Date: Sat, 13 Nov 2021 19:34:41 +0000 Subject: [PATCH] :recycle: refactor: extract AudioOutput --- Cargo.lock | 54 ++++++++++---- Cargo.toml | 1 + src/main.rs | 205 +++++++++++++++++++++++++++++++++++----------------- 3 files changed, 179 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 68a2848..2602400 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,9 +87,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bumpalo" @@ -156,6 +156,15 @@ dependencies = [ "libloading", ] +[[package]] +name = "cmake" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b858541263efe664aead4a5209a4ae5c5d2811167d4ed4ee0944503f8d2089" +dependencies = [ + "cc", +] + [[package]] name = "combine" version = "4.6.2" @@ -287,6 +296,26 @@ dependencies = [ "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]] name = "fnv" version = "1.0.7" @@ -431,15 +460,6 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" -[[package]] -name = "memoffset" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" -dependencies = [ - "autocfg", -] - [[package]] name = "music_player" version = "0.1.0" @@ -448,6 +468,7 @@ dependencies = [ "byteorder", "cpal", "ffmpeg-next", + "fltk", "tracing", "tracing-subscriber", ] @@ -526,15 +547,14 @@ checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" [[package]] name = "nix" -version = "0.20.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" +checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" dependencies = [ "bitflags", "cc", "cfg-if 1.0.0", "libc", - "memoffset", ] [[package]] @@ -653,6 +673,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "paste" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" + [[package]] name = "peeking_take_while" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index 74a153e..d6a6fcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,6 @@ anyhow = "1.0.45" byteorder = "1.4.3" cpal = "0.13.4" ffmpeg-next = "4.4.0" +fltk = "1.2.15" tracing = "0.1.29" tracing-subscriber = { version = "0.3.1", features = ["env-filter"] } diff --git a/src/main.rs b/src/main.rs index 1490748..b682ed4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,10 +22,27 @@ use anyhow::{ use cpal::traits::{ DeviceTrait, HostTrait, + StreamTrait, +}; + +use fltk::{ + app, + button::Button, + enums::CallbackTrigger, + frame::Frame, + group::Flex, + prelude::*, + window::Window, }; mod decoder; +#[derive (Clone, Copy)] +enum Message { + Play, + Pause, +} + fn main () -> Result <()> { let args: Vec <_> = std::env::args ().collect (); @@ -33,6 +50,7 @@ fn main () -> Result <()> { None => bail! ("First argument must be a subcommand like `play`"), Some ("debug-dump") => cmd_debug_dump (&args [1..]), Some ("debug-pipe") => cmd_debug_pipe (&args [1..]), + Some ("gui") => cmd_gui (&args [1..]), Some ("play") => cmd_play (&args [1..]), Some (_) => bail! ("Unrecognized subcommand"), } @@ -83,6 +101,47 @@ pub struct SharedState { pub pcm_buffers: decoder::PcmBuffers, pub quit: bool, } + +fn cmd_gui (args: &[String]) -> Result <()> { + let (fltk_tx, fltk_rx) = app::channel:: (); + + 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 <()> { tracing_subscriber::fmt::init (); @@ -103,7 +162,13 @@ fn cmd_play (args: &[String]) -> Result <()> { let mut decoder = decoder::Decoder::new (&filename)?; '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| { decoder_state.pcm_buffers.samples_available () >= 12_000 && @@ -114,80 +179,17 @@ fn cmd_play (args: &[String]) -> Result <()> { break 'many_files; } - //dbg! (resampler.delay ()); - - 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; - } - } - } + decoder_state.pcm_buffers.produce_bytes (frame.data ().into ()); } } 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_quit2 = Arc::clone (&pcm_quit); - 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 () > 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)); + let audio_output = AudioOutput::new (pcm_quit, pair)?; tracing::debug! ("Joining decoder thread..."); @@ -204,7 +206,7 @@ fn cmd_play (args: &[String]) -> Result <()> { let (lock, cvar) = &*pcm_quit2; let _ = cvar.wait (lock.lock ().unwrap ()).unwrap (); - drop (stream); + drop (audio_output); sleep (Duration::from_secs (1)); @@ -212,6 +214,75 @@ fn cmd_play (args: &[String]) -> Result <()> { Ok (()) } +struct AudioOutput { + host: cpal::Host, + device: cpal::Device, + stream: cpal::Stream, +} + +impl AudioOutput { + pub fn new ( + pcm_quit: Arc <(Mutex , Condvar)>, + pair: Arc <(Mutex , Condvar)>, + ) -> Result { + 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)] mod test { use super::*;