Bug fix
This commit is contained in:
+126
-158
@@ -46,8 +46,8 @@ pub struct VoiceStateInner {
|
||||
}
|
||||
|
||||
pub struct VoiceSession {
|
||||
pub input_stream: cpal::Stream,
|
||||
pub output_stream: Arc<Mutex<cpal::Stream>>,
|
||||
pub input_stream: Mutex<Option<cpal::Stream>>,
|
||||
pub output_stream: Arc<Mutex<Option<cpal::Stream>>>,
|
||||
pub socket: Arc<UdpSocket>,
|
||||
pub pin: u64,
|
||||
pub shutdown: Arc<AtomicBool>,
|
||||
@@ -59,6 +59,44 @@ pub struct VoiceSession {
|
||||
pub consumer_out: AudioConsumer,
|
||||
}
|
||||
|
||||
fn resolve_input_device(device_name: &Option<String>) -> Result<cpal::Device, String> {
|
||||
let host = cpal::default_host();
|
||||
match device_name {
|
||||
Some(name) => host
|
||||
.input_devices()
|
||||
.map_err(|e| format!("Failed to enumerate input devices: {e}"))?
|
||||
.find(|d| {
|
||||
d.description()
|
||||
.ok()
|
||||
.map(|x| x.name() == *name)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.ok_or_else(|| format!("Input device not found: {name}")),
|
||||
None => host.default_input_device().ok_or_else(|| {
|
||||
format!("No default input device")
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_output_device(device_name: &Option<String>) -> Result<cpal::Device, String> {
|
||||
let host = cpal::default_host();
|
||||
match device_name {
|
||||
Some(name) => host
|
||||
.output_devices()
|
||||
.map_err(|e| format!("Failed to enumerate output devices: {e}"))?
|
||||
.find(|d| {
|
||||
d.description()
|
||||
.ok()
|
||||
.map(|x| x.name() == *name)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.ok_or_else(|| format!("Output device not found: {name}")),
|
||||
None => host.default_output_device().ok_or_else(|| {
|
||||
format!("No default output device")
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Simple Linear Resampler for real-time audio conversion
|
||||
struct LinearResampler {
|
||||
phase: f64,
|
||||
@@ -93,82 +131,70 @@ impl LinearResampler {
|
||||
}
|
||||
|
||||
impl VoiceSession {
|
||||
pub fn update_input_device(
|
||||
// Single code path for (re)initializing the input stream. Used both for the
|
||||
// initial connect and for changing/recovering the device.
|
||||
pub fn setup_input(
|
||||
&mut self,
|
||||
device_name: Option<String>,
|
||||
state_inner: Arc<VoiceStateInner>,
|
||||
) -> Result<(), String> {
|
||||
let host = cpal::default_host();
|
||||
let device = match &device_name {
|
||||
Some(name) => host
|
||||
.input_devices()
|
||||
.map_err(|e| e.to_string())?
|
||||
.find(|d| {
|
||||
d.description()
|
||||
.ok()
|
||||
.map(|x| x.name() == *name)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.ok_or_else(|| format!("Input device '{name}' not found"))?,
|
||||
None => host
|
||||
.default_input_device()
|
||||
.ok_or("No default input device")?,
|
||||
};
|
||||
let device = resolve_input_device(&device_name)?;
|
||||
|
||||
let input_config = device
|
||||
.default_input_config()
|
||||
.map_err(|e| format!("Failed to get default input config: {e}"))?
|
||||
.map_err(|e| format!("Failed to get input config: {e}"))?
|
||||
.config();
|
||||
|
||||
eprintln!(
|
||||
"[vc] Input device {:?} config: {} Hz, {} ch",
|
||||
device.description().map(|d| d.name().to_string()),
|
||||
input_config.sample_rate,
|
||||
input_config.channels
|
||||
);
|
||||
|
||||
let producer = Arc::clone(&self.producer_in);
|
||||
let new_stream = build_input_stream(&device, input_config, producer, state_inner)?;
|
||||
|
||||
new_stream
|
||||
.play()
|
||||
.map_err(|e| format!("Failed to play input stream: {e}"))?;
|
||||
|
||||
self.input_stream = new_stream;
|
||||
if let Ok(mut slot) = self.input_stream.lock() {
|
||||
*slot = Some(new_stream);
|
||||
}
|
||||
self.current_input_device = device_name;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_output_device(
|
||||
// Single code path for (re)initializing the output stream. Used both for the
|
||||
// initial connect and for changing/recovering the device.
|
||||
pub fn setup_output(
|
||||
&mut self,
|
||||
device_name: Option<String>,
|
||||
state_inner: Arc<VoiceStateInner>,
|
||||
) -> Result<(), String> {
|
||||
let host = cpal::default_host();
|
||||
let device = match &device_name {
|
||||
Some(name) => host
|
||||
.output_devices()
|
||||
.map_err(|e| e.to_string())?
|
||||
.find(|d| {
|
||||
d.description()
|
||||
.ok()
|
||||
.map(|x| x.name() == *name)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.ok_or_else(|| format!("Output device '{name}' not found"))?,
|
||||
None => host
|
||||
.default_output_device()
|
||||
.ok_or("No default output device")?,
|
||||
};
|
||||
let device = resolve_output_device(&device_name)?;
|
||||
|
||||
let output_config = device
|
||||
.default_output_config()
|
||||
.map_err(|e| format!("Failed to get default output config: {e}"))?
|
||||
.map_err(|e| format!("Failed to get output config: {e}"))?
|
||||
.config();
|
||||
|
||||
eprintln!(
|
||||
"[vc] Output device {:?} config: {} Hz, {} ch",
|
||||
device.description().map(|d| d.name().to_string()),
|
||||
output_config.sample_rate,
|
||||
output_config.channels
|
||||
);
|
||||
|
||||
let consumer = Arc::clone(&self.consumer_out);
|
||||
let new_stream = build_output_stream(&device, output_config, consumer, state_inner)?;
|
||||
new_stream
|
||||
.play()
|
||||
.map_err(|e| format!("Failed to play output stream: {e}"))?;
|
||||
|
||||
if let Ok(mut active_stream) = self.output_stream.lock() {
|
||||
*active_stream = new_stream;
|
||||
if let Ok(mut slot) = self.output_stream.lock() {
|
||||
*slot = Some(new_stream);
|
||||
}
|
||||
|
||||
self.current_output_device = device_name;
|
||||
Ok(())
|
||||
}
|
||||
@@ -271,7 +297,7 @@ where
|
||||
return;
|
||||
}
|
||||
let target_device = session.current_input_device.clone();
|
||||
let _ = session.update_input_device(target_device, Arc::clone(&rec));
|
||||
let _ = session.setup_input(target_device, Arc::clone(&rec));
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -402,7 +428,7 @@ where
|
||||
return;
|
||||
}
|
||||
let target_device = session.current_output_device.clone();
|
||||
let _ = session.update_output_device(target_device, Arc::clone(&rec));
|
||||
let _ = session.setup_output(target_device, Arc::clone(&rec));
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -444,9 +470,15 @@ pub fn disconnect_from_vc(voice_state: State<'_, VoiceState>) -> Result<(), Stri
|
||||
|
||||
if let Some(session) = session {
|
||||
session.shutdown.store(true, Ordering::SeqCst);
|
||||
let _ = session.input_stream.pause();
|
||||
if let Ok(input) = session.input_stream.lock() {
|
||||
if let Some(stream) = input.as_ref() {
|
||||
let _ = stream.pause();
|
||||
}
|
||||
}
|
||||
if let Ok(output) = session.output_stream.lock() {
|
||||
let _ = output.pause();
|
||||
if let Some(stream) = output.as_ref() {
|
||||
let _ = stream.pause();
|
||||
}
|
||||
}
|
||||
eprintln!("[vc] disconnected and paused streams");
|
||||
}
|
||||
@@ -506,111 +538,33 @@ pub fn connect_to_vc(
|
||||
return Err(msg);
|
||||
}
|
||||
|
||||
let host = cpal::default_host();
|
||||
|
||||
let input_device = match &config.input_device_name {
|
||||
Some(name) => host
|
||||
.input_devices()
|
||||
.map_err(|e| {
|
||||
let msg = e.to_string();
|
||||
eprintln!("[vc] failed to enumerate input devices: {msg}");
|
||||
msg
|
||||
})?
|
||||
.find(|d| {
|
||||
d.description()
|
||||
.ok()
|
||||
.map(|x| x.name() == *name)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
let msg = format!("Input device not found: {name}");
|
||||
eprintln!("[vc] {msg}");
|
||||
msg
|
||||
})?,
|
||||
None => host.default_input_device().ok_or_else(|| {
|
||||
let msg = "No default input device".to_string();
|
||||
eprintln!("[vc] {msg}");
|
||||
msg
|
||||
})?,
|
||||
};
|
||||
|
||||
let output_device = match &config.output_device_name {
|
||||
Some(name) => host
|
||||
.output_devices()
|
||||
.map_err(|e| {
|
||||
let msg = e.to_string();
|
||||
eprintln!("[vc] failed to enumerate output devices: {msg}");
|
||||
msg
|
||||
})?
|
||||
.find(|d| {
|
||||
d.description()
|
||||
.ok()
|
||||
.map(|x| x.name() == *name)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
let msg = format!("Output device not found: {name}");
|
||||
eprintln!("[vc] {msg}");
|
||||
msg
|
||||
})?,
|
||||
None => host.default_output_device().ok_or_else(|| {
|
||||
let msg = "No default output device".to_string();
|
||||
eprintln!("[vc] {msg}");
|
||||
msg
|
||||
})?,
|
||||
};
|
||||
|
||||
let shutdown = Arc::new(AtomicBool::new(false));
|
||||
|
||||
// Ring Buffer Setup
|
||||
// Ring Buffers (device-agnostic; shared between initial setup and any rebuild)
|
||||
let rb_out = HeapRb::<f32>::new(19200);
|
||||
let (producer_out, consumer_out) = rb_out.split();
|
||||
let mut producer_out = producer_out;
|
||||
let (mut producer_out, consumer_out) = rb_out.split();
|
||||
let shared_consumer_out = Arc::new(Mutex::new(consumer_out));
|
||||
|
||||
let output_config = output_device
|
||||
.default_output_config()
|
||||
.map_err(|e| format!("Failed to get default output config: {e}"))?
|
||||
.config();
|
||||
|
||||
eprintln!(
|
||||
"[vc] Output device {:?} config: {} Hz, {} ch",
|
||||
output_device.description().map(|d| d.name().to_string()),
|
||||
output_config.sample_rate,
|
||||
output_config.channels
|
||||
);
|
||||
let output_stream = build_output_stream(
|
||||
&output_device,
|
||||
output_config,
|
||||
Arc::clone(&shared_consumer_out),
|
||||
Arc::clone(&state_inner),
|
||||
)?;
|
||||
output_stream
|
||||
.play()
|
||||
.map_err(|e| format!("Failed to start output: {e}"))?;
|
||||
let output_stream = Arc::new(Mutex::new(output_stream));
|
||||
|
||||
let rb_in = HeapRb::<f32>::new(19200);
|
||||
let (producer_in, mut consumer_in) = rb_in.split();
|
||||
let shared_producer_in = Arc::new(Mutex::new(producer_in));
|
||||
|
||||
let input_config = input_device
|
||||
.default_input_config()
|
||||
.map_err(|e| format!("Failed to get default input config: {e}"))?
|
||||
.config();
|
||||
// Build the session with empty stream slots, then (re)initialize the actual
|
||||
// device streams through the same setup path used by device changes.
|
||||
let mut session = VoiceSession {
|
||||
input_stream: Mutex::new(None),
|
||||
output_stream: Arc::new(Mutex::new(None)),
|
||||
socket: socket.clone(),
|
||||
pin,
|
||||
shutdown: shutdown.clone(),
|
||||
current_input_device: config.input_device_name.clone(),
|
||||
current_output_device: config.output_device_name.clone(),
|
||||
producer_in: Arc::clone(&shared_producer_in),
|
||||
consumer_out: Arc::clone(&shared_consumer_out),
|
||||
};
|
||||
|
||||
eprintln!(
|
||||
"[vc] Input device {:?} config: {} Hz, {} ch",
|
||||
input_device.description().map(|d| d.name().to_string()),
|
||||
input_config.sample_rate,
|
||||
input_config.channels
|
||||
);
|
||||
let input_stream = build_input_stream(
|
||||
&input_device,
|
||||
input_config,
|
||||
Arc::clone(&shared_producer_in),
|
||||
Arc::clone(&state_inner),
|
||||
)?;
|
||||
session.setup_input(config.input_device_name.clone(), Arc::clone(&state_inner))?;
|
||||
session.setup_output(config.output_device_name.clone(), Arc::clone(&state_inner))?;
|
||||
|
||||
// Sender Thread
|
||||
{
|
||||
@@ -789,21 +743,29 @@ pub fn connect_to_vc(
|
||||
});
|
||||
}
|
||||
|
||||
input_stream
|
||||
.play()
|
||||
.map_err(|e| format!("Failed to start input: {e}"))?;
|
||||
*state_inner.session.lock().unwrap() = Some(session);
|
||||
|
||||
*state_inner.session.lock().unwrap() = Some(VoiceSession {
|
||||
input_stream,
|
||||
output_stream,
|
||||
socket,
|
||||
pin,
|
||||
shutdown,
|
||||
current_input_device: config.input_device_name,
|
||||
current_output_device: config.output_device_name,
|
||||
producer_in: shared_producer_in,
|
||||
consumer_out: shared_consumer_out,
|
||||
});
|
||||
// Workaround for Linux devices that don't start capturing/playing until the
|
||||
// stream is (re)initialized. Rebuild both directions a moment after connect.
|
||||
{
|
||||
let state_for_rebuild = Arc::clone(&state_inner);
|
||||
thread::spawn(move || {
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
let Ok(mut lock) = state_for_rebuild.session.lock() else {
|
||||
return;
|
||||
};
|
||||
if let Some(session) = lock.as_mut() {
|
||||
if session.shutdown.load(Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
let out = session.current_output_device.clone();
|
||||
let _ = session.setup_output(out, Arc::clone(&state_for_rebuild));
|
||||
let inp = session.current_input_device.clone();
|
||||
let _ = session.setup_input(inp, Arc::clone(&state_for_rebuild));
|
||||
eprintln!("[vc] Reinitialized input/output streams after connect");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -812,9 +774,15 @@ impl Drop for VoiceSession {
|
||||
fn drop(&mut self) {
|
||||
self.shutdown.store(true, Ordering::SeqCst);
|
||||
|
||||
let _ = self.input_stream.pause();
|
||||
if let Ok(input) = self.input_stream.lock() {
|
||||
if let Some(stream) = input.as_ref() {
|
||||
let _ = stream.pause();
|
||||
}
|
||||
}
|
||||
if let Ok(output) = self.output_stream.lock() {
|
||||
let _ = output.pause();
|
||||
if let Some(stream) = output.as_ref() {
|
||||
let _ = stream.pause();
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("[vc] VoiceSession dropped and audio streams paused.");
|
||||
|
||||
Reference in New Issue
Block a user