Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Wasapi capture doesn't work: Stream is spawned but callback not called

オープン
#516 コメント 4 件 リアクション 2 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
35/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
停滞
技術スタック
rust

調査の方向性

Start with the linked swyh-rs loopback capture implementation and compare it with the supplied capture_output_audio flow, especially how it selects the device and builds the input stream on Windows WASAPI. Check the CPAL WASAPI stream and callback behavior; the report says Linux calls the callback while Windows does not. Done means identifying why the callback is not called and confirming a working loopback capture path.

索引モデルが issue の本文から書いたものです。

説明

host - wasapi platform - windows

I'm trying to do Wasapi loopback capture but it's not working:
Even though my input stream is spawned, the stream callback never gets called. Why could that be?

It prints this:

[DEBUG] Try capturing system audio
[INFO ] Capturing audio from: Line (Steinberg UR22mkII )
[INFO ] Default audio cfg: SupportedStreamConfig { channels: 2, sample_rate: SampleRate(44100), buffer_size: Unknown, sample_format: F32 }
build_stream F32
stream is playing
still playing
still playing
still playing

But it should ALSO print wave_reader called (from inside the wave_reader stream callback), but it never prints this and so I'm not receiving any audio frames through the channel in my application.

I based my code on the implementation of loopback capture in https://github.com/dheijl/swyh-rs:
https://github.com/dheijl/swyh-rs/blob/e6709b2f546af6cf272ded9529f6dc5d95145453/src/main.rs#L849

In swyh it apparently works (but I can't test that application because I don't have a streaming server/receiver).
I just refactored the code a little bit, but apart from that it's the same.
Any idea why the stream callback (wave_reader) doesn't get called?

(Note: On linux, wave_reader does get called and I receive audio frames over the channel. They are all 0.0, but that's a different issue, probably a linux issue rather than cpal issue?)

use super::*;
use anyhow::{Context, Result};
use cpal::{Device, Sample, SampleFormat, Stream, StreamError, SupportedStreamConfig};
use device::*;
use std::{
    sync::{
        atomic::{AtomicBool, Ordering},
        mpsc::channel,
        Arc,
    },
    thread,
};

pub fn capture_live_audio_input(
    m_audio_device: Option<String>,
    terminate_capture: Arc<AtomicBool>,
) -> Result<(BidirAudioChannel, AudioFormat)> {
    let (audio_to_vis, vis_to_audio) = BidirAudioChannel::duplex_unbounded();

    // spawn new thread as workaround: https://github.com/RustAudio/rodio/issues/214
    let (tx, rx) = channel();
    let _thread_capture = thread::Builder::new()
        .name("capture".to_string())
        .spawn(move || -> Result<_> {
            let output_device = match m_audio_device {
                Some(name) => unwropt!(
                    get_audio_output_devices()
                        .into_iter()
                        .find_map(|device| (device.name().ok().as_deref() == Some(&name)).then_some(device)),
                    "audio device not found"
                ),
                None => unwropt!(get_default_audio_output_device(), "no default audio device"),
            };

            // capture system audio
            debug!("Try capturing system audio");
            let (stream, audio_format) =
                capture_output_audio(&output_device, audio_to_vis).context("Could not capture audio")?;
            tx.send(audio_format).unwrap();
            stream.play().context("stream.play()")?;
            println!("stream is playing");
            // stream shouldn't be dropped, but it's not Send, so we need to keep this thread alive
            while !terminate_capture.load(Ordering::SeqCst) {
                std::thread::sleep(std::time::Duration::from_millis(100));
                println!("still playing");
            }
            Ok(())
        })
        .expect("spawn");
    let audio_format = rx.recv()?;

    Ok((vis_to_audio, audio_format))
}

/// capture the audio stream from the default audio output device
/// sets up an input stream for the wave_reader in the appropriate format (f32/i16/u16)
fn capture_output_audio(
    output_device: &Device,
    audio_to_vis: BidirAudioChannel,
) -> Result<(Stream, AudioFormat)> {
    info!(
        "Capturing audio from: {}",
        output_device.name().context("Could not get default audio device name")?
    );

    let audio_cfg = output_device.default_output_config().context("No default output config found")?;
    let audio_format =
        AudioFormat { sample_rate: audio_cfg.sample_rate().0, channel_count: audio_cfg.channels() as usize };
    info!("Default audio cfg: {:?}", audio_cfg);

    fn build_stream<T: Sample>(
        output_device: &Device,
        audio_cfg: SupportedStreamConfig,
        audio_format: AudioFormat,
        audio_to_vis: BidirAudioChannel,
    ) -> Result<(Stream, AudioFormat)> {
        println!("build_stream {:?}", T::FORMAT);
        fn wave_reader<T: Sample>(samples: &[T], audio_to_vis: &BidirAudioChannel) {
			println!("wave_reader called");
            let mut f32_samples =
                audio_to_vis.rx.try_recv().unwrap_or_else(|_| Vec::with_capacity(samples.len()));
            f32_samples.clear();
            f32_samples.extend(samples.iter().map(|x| x.to_f32()));
            println!("wave_reader {}", f32_samples.len());
            let _ = audio_to_vis.tx.send(f32_samples);
        }

        match output_device.build_input_stream(
            &audio_cfg.config(),
            move |data, _: &_| wave_reader::<T>(data, &audio_to_vis),
            |e| error!("Error on audio input stream: {}", e),
        ) {
            Ok(stream) => Ok((stream, audio_format)),
            Err(e) => {
                bail!("Error capturing {:?} audio stream: {}", T::FORMAT, e);
            }
        }
    }

    match audio_cfg.sample_format() {
        SampleFormat::F32 => build_stream::<f32>(output_device, audio_cfg, audio_format, audio_to_vis),
        SampleFormat::I16 => build_stream::<i16>(output_device, audio_cfg, audio_format, audio_to_vis),
        SampleFormat::U16 => build_stream::<u16>(output_device, audio_cfg, audio_format, audio_to_vis),
    }
}

Btw, if you're wondering why I'm spawning a new thread (compared to swyh):
If I don't create the stream in the separate thread, I get this:

Os { code: -2147417850, kind: Other, message: "Cannot change thread mode after it is set." }', C:\Users\me.cargo\registry\src\github.com-1ecc6299db9ec823\cpal-0.13.1\src\host\wasapi\com.rs:13:77

It's this issue: https://github.com/RustAudio/rodio/issues/214
My application uses winit, and apparently this combination causes this issue:
https://github.com/RustAudio/rodio/issues/214#issuecomment-640979469
https://github.com/rust-windowing/winit/issues/1185
That's why I'm spawning a new thread for the stream (which I'm then keeping alive to prevent the stream from getting dropped).
But that's the only difference to swyh that I see. So I'm wondering why the stream callback is not being called.

主要言語
Rust
スター
4k
フォーク
554
平均マージ
3日 3時間
マージ済み PR(30日)
4

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

RustAudio/cpal のほかの issue

RustAudio/cpal の issue をすべて見る

似ている issue

Rust の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。