Having trouble playing simple WAV file using rodio crate

I am including a wav file in my application binary using
include_bytes!, and storing it in my main app data struct as:

sound: &'static [u8]

my play_sound function looks like this:

pub fn play_audio(sound_file: &'static [u8]) {
    let (_stream, stream_handle) = OutputStream::try_default().expect("Failed to open default audio device");
    let file = Cursor::new(sound_file);
    let source = Decoder::new_wav(file).expect("Failed to decode WAV file");
    stream_handle.play_raw(source.convert_samples()).expect("Failed to play file");
}

i pass in my sound file and i get nothing on the speakers, and no errors. I also tried sleeping on the thread as i saw in some example code. Not sure how to proceed to troubleshoot this. Volume is up on my computer, and i can play the same wav file from audacity

Output::try_default returns both a resource guard and a handle to it. at the end of the play_audio function, the RAII guard is dropped, so the underlying device is closed, which invalidate the handle, and stops the playback.

you need to keep the guard object live to keep the playback going. it is recommended to use the Sink API, which manages the device for you. you can use play_once instead of play_raw, since play_once will return an Sink object that you can use to control the playback. you can also manually create Sink objects and add Sources (e.g. your wav decoder) to your Sinks.

1 Like

ok, got it, i added a thread sleep in my function and can hear the audio. So if i want to use audio sink, as per docs:

let (_stream, stream_handle) = OutputStream::try_default().unwrap();
let sink = Sink::try_new(&stream_handle).unwrap();

// Add a dummy source of the sake of the example.
let source = SineWave::new(440.0).take_duration(Duration::from_secs_f32(0.25)).amplify(0.20);
sink.append(source);

can i just store the sink, or do i need to keep the streamhandle alive as well?

I tried saving the steam_handle and the sink, like this:

fn load_sounds(&mut self) {
        let (_stream, stream_handle) = OutputStream::try_default().unwrap();
        self.audio.sink = Some(Sink::try_new(&stream_handle).unwrap());
        self.audio.stream_handle = Some(stream_handle);
    }

and then i have my play function like this:

fn play_air_horn(&self) {
        println!("Attempting to play sound");
        if let Some(sink) = &self.sink {
            println!("Sink is valid, appending");
            sink.append(Decoder::new_wav(Cursor::new(self.sound)).unwrap());
        }
    }

but not getting any sound

I don't know anything about these APIs, but you may have misread the above because you're saving the handle and not the other item in the tuple, presumably the guard. That's how I read it, anyway.

sorry, I was not clear. you need to store the OutputStream in any case, dropping it will close the device and invalidate the handle. the sink is recommended because it allows more advanced playback control, but it is an handle after all, see docs for OutpuStream and Sink, to quote:

OutputStream:
cpal::Stream container. Also see the more useful OutputStreamHandle.
If this is dropped playback will end & attached OutputStreamHandles will no longer work.

Sink:
Handle to an device that outputs sounds.
Dropping the Sink stops all sounds. You can use detach if you want the sounds to continue playing.

Got it, thanks for your help, this is what i have now, all working..

pub struct Audio {
    sound: &'static [u8],
    stream_handle: Option<OutputStreamHandle>,
    stream: Option<OutputStream>,
    sink: Option<Sink>,
}

impl Audio {
    pub fn init(&mut self) {
        let (stream, stream_handle) = OutputStream::try_default().unwrap();
        self.sink = Some(Sink::try_new(&stream_handle).unwrap());
        self.stream = Some(stream);
        self.stream_handle = Some(stream_handle);
    }
    pub fn play_air_horn(&self) {
        if let Some(sink) = &self.sink {
            let source = Decoder::new_wav(Cursor::new(self.sound)).expect("Unable to decode WAV file");
            sink.append(source);
        }
    }
}
impl Default for Audio {
    fn default() -> Self {
        Self {
            sound: include_bytes!("../../assets/sounds/air-horn.wav"),
            stream_handle: None,
            stream: None,
            sink: None,
        }
    }
}

glad you make it working.

just some minor comment on the code:

  1. you don't need both OutStreamHandle and Sink, you only need one of them to play audio samples. again, I recommend Sink because it has more functionality, but you can choose whichever suits your use case (it's not wrong to use both, just unnecessary for common uses).

  2. I suggest you avoid "two stage construction" (other terms like "two phase initialization" are also common used referring the same concept), instead of impl Default + fn init(&mut self), just let your constructor return a fully constructed object, and you can get rid of those Options (which need to be checked at runtime)

  3. you should not conflate the playback device and the audio sample data into one type. it will work if you have only one audio file to play, but you what if you want to play multiple audios at the same time?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.