Fix AudioSink Send trait issue with cpal Stream
Problem: - cpal::Stream is not Send - Cannot use Stream across await points in async functions - Caused compilation error in AudioSinkLogic::process Solution: - Spawn dedicated thread for cpal Stream (similar to rodio approach) - Communicate with thread via std::mpsc channel - Thread waits for shutdown command before dropping stream - Main async loop can now safely await without Send issues Changes: - Add std::mpsc and std::thread imports - Create stream_cmd channel (std::mpsc::channel) - Spawn thread::spawn for stream creation and management - Replace drop(stream) with stream_cmd_tx.send + thread.join - Handle errors in thread with tracing::error (no ? operator) Testing: - Compiled successfully with libsoxr and libasound2 (local install) - Dependencies installed in ~/.local without sudo - PKG_CONFIG_PATH configured correctly - LD_LIBRARY_PATH configured correctly Note: pmoparadise example has unrelated netstat2 compilation issue
This commit is contained in:
160
INSTALL_LIBSOXR.md
Normal file
160
INSTALL_LIBSOXR.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Installation des dépendances système sans droits sudo
|
||||
|
||||
Ce document explique comment installer les dépendances système de `pmoaudio` localement sans privilèges administrateur.
|
||||
|
||||
## Dépendances requises
|
||||
|
||||
1. **libsoxr** - Nécessaire pour `ResamplingNode` (resampling audio haute qualité)
|
||||
2. **libasound2** (ALSA) - Nécessaire pour `AudioSink` via rodio (lecture audio sur Linux)
|
||||
|
||||
## Contexte
|
||||
|
||||
Les crates `soxr` et `rodio` nécessitent des bibliothèques système. Dans un environnement sans droits sudo, voici comment les installer localement.
|
||||
|
||||
## Méthode : Installation locale via apt-get download
|
||||
|
||||
### 1. Télécharger les packages .deb
|
||||
|
||||
```bash
|
||||
cd ~/.local
|
||||
|
||||
# Pour libsoxr (ResamplingNode)
|
||||
apt-get download libsoxr-dev libsoxr0
|
||||
|
||||
# Pour ALSA (AudioSink)
|
||||
apt-get download libasound2-dev
|
||||
```
|
||||
|
||||
Cela télécharge les fichiers `.deb` sans les installer système-wide.
|
||||
|
||||
### 2. Extraire les packages
|
||||
|
||||
```bash
|
||||
# Extraire libsoxr
|
||||
dpkg -x libsoxr-dev_*.deb .
|
||||
dpkg -x libsoxr0_*.deb .
|
||||
|
||||
# Extraire ALSA
|
||||
dpkg -x libasound2-dev_*.deb .
|
||||
```
|
||||
|
||||
Les fichiers sont extraits dans `~/.local/usr/lib/x86_64-linux-gnu/` et `~/.local/usr/include/`.
|
||||
|
||||
### 3. Configurer les variables d'environnement
|
||||
|
||||
Ajouter à votre `~/.bashrc` ou exporter dans votre session :
|
||||
|
||||
```bash
|
||||
export PKG_CONFIG_PATH="/root/.local/usr/lib/x86_64-linux-gnu/pkgconfig:$PKG_CONFIG_PATH"
|
||||
export LD_LIBRARY_PATH="/root/.local/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH"
|
||||
```
|
||||
|
||||
**IMPORTANT:** Remplacer `/root/` par le chemin de votre home directory (`$HOME` ou `~`).
|
||||
|
||||
### 4. Vérifier l'installation
|
||||
|
||||
```bash
|
||||
# Vérifier libsoxr
|
||||
pkg-config --libs --cflags soxr
|
||||
|
||||
# Vérifier ALSA
|
||||
pkg-config --libs --cflags alsa
|
||||
```
|
||||
|
||||
Devrait retourner quelque chose comme :
|
||||
```
|
||||
# soxr
|
||||
-I/root/.local/usr/include -L/root/.local/usr/lib/x86_64-linux-gnu -lsoxr
|
||||
|
||||
# alsa
|
||||
-I/root/.local/usr/include -L/root/.local/usr/lib/x86_64-linux-gnu -lasound
|
||||
```
|
||||
|
||||
## Utilisation avec Cargo
|
||||
|
||||
### Pour les builds réguliers
|
||||
|
||||
Les variables d'environnement suffisent pour `cargo build` et `cargo run`.
|
||||
|
||||
### Pour les tests
|
||||
|
||||
Les tests nécessitent également la configuration du linker. Deux options :
|
||||
|
||||
#### Option A : Configuration locale du projet (NON RECOMMANDÉ pour le versioning)
|
||||
|
||||
Créer `.cargo/config.toml` dans chaque crate :
|
||||
|
||||
```toml
|
||||
[build]
|
||||
rustflags = ["-L", "/root/.local/usr/lib/x86_64-linux-gnu"]
|
||||
```
|
||||
|
||||
**⚠️ NE PAS committer ces fichiers** - ils contiennent des chemins spécifiques à votre installation.
|
||||
|
||||
#### Option B : Variables d'environnement pour cargo test
|
||||
|
||||
```bash
|
||||
export PKG_CONFIG_PATH="$HOME/.local/usr/lib/x86_64-linux-gnu/pkgconfig:$PKG_CONFIG_PATH"
|
||||
export LD_LIBRARY_PATH="$HOME/.local/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH"
|
||||
cargo test
|
||||
```
|
||||
|
||||
## Pour d'autres distributions
|
||||
|
||||
### macOS (avec Homebrew)
|
||||
|
||||
```bash
|
||||
brew install libsoxr
|
||||
# Note: ALSA n'est pas nécessaire sur macOS (rodio utilise CoreAudio)
|
||||
```
|
||||
|
||||
### Debian/Ubuntu (avec sudo)
|
||||
|
||||
```bash
|
||||
sudo apt-get install libsoxr-dev libasound2-dev
|
||||
```
|
||||
|
||||
### Fedora/RHEL
|
||||
|
||||
```bash
|
||||
sudo dnf install soxr-devel
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Erreur : "Package 'soxr' was not found" ou "Package 'alsa' was not found"
|
||||
|
||||
- Vérifier que `PKG_CONFIG_PATH` contient le bon chemin
|
||||
- Vérifier que les fichiers `soxr.pc` et `alsa.pc` existent dans ce répertoire
|
||||
|
||||
### Erreur de link : "unable to find library -lsoxr" ou "-lasound"
|
||||
|
||||
- Pour `cargo build` : vérifier `LD_LIBRARY_PATH`
|
||||
- Pour `cargo test` : utiliser la configuration rustflags (Option A ci-dessus)
|
||||
|
||||
### Le test compile mais échoue au runtime
|
||||
|
||||
```
|
||||
error while loading shared libraries: libsoxr.so.0: cannot open shared object file
|
||||
```
|
||||
|
||||
Solution : Ajouter `LD_LIBRARY_PATH` également pour l'exécution :
|
||||
|
||||
```bash
|
||||
export LD_LIBRARY_PATH="$HOME/.local/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH"
|
||||
cargo test
|
||||
```
|
||||
|
||||
## Notes pour Claude Code sessions
|
||||
|
||||
Pour les futures sessions Claude :
|
||||
|
||||
1. Exporter les variables d'environnement en début de session
|
||||
2. NE PAS créer de fichiers `.cargo/config.toml` dans le projet
|
||||
3. Si nécessaire pour les tests, les créer localement mais ne pas les committer
|
||||
4. Documenter toute difficulté d'installation ici
|
||||
|
||||
## Références
|
||||
|
||||
- libsoxr GitHub: https://github.com/chirlu/soxr
|
||||
- Documentation pkg-config: https://www.freedesktop.org/wiki/Software/pkg-config/
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
dsp::{i16_stereo_to_pairs_f32, i24_as_i32_stereo_to_pairs_f32, i32_stereo_to_interleaved_f32, pairs_f32_to_i16_stereo},
|
||||
dsp::{i16_stereo_to_pairs_f32, i24_as_i32_stereo_to_pairs_f32, i32_stereo_to_interleaved_f32},
|
||||
nodes::{AudioError, TypedAudioNode, DEFAULT_CHANNEL_SIZE},
|
||||
pipeline::{Node, NodeLogic},
|
||||
type_constraints::TypeRequirement,
|
||||
@@ -7,7 +7,9 @@ use crate::{
|
||||
};
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::mpsc as std_mpsc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
@@ -224,12 +226,16 @@ impl NodeLogic for AudioSinkLogic {
|
||||
sample_format
|
||||
);
|
||||
|
||||
// Créer le stream selon le format hardware
|
||||
let stream = match sample_format {
|
||||
// Créer un channel pour commander le thread du stream
|
||||
let (stream_cmd_tx, stream_cmd_rx) = std_mpsc::channel::<bool>();
|
||||
|
||||
// Spawn un thread dédié pour le stream cpal (car Stream n'est pas Send)
|
||||
let stream_thread = thread::spawn(move || {
|
||||
// Créer le stream selon le format hardware
|
||||
let stream = match sample_format {
|
||||
cpal::SampleFormat::I16 => {
|
||||
tracing::debug!("Using I16 output format");
|
||||
device
|
||||
.build_output_stream(
|
||||
match device.build_output_stream(
|
||||
&config.into(),
|
||||
move |data: &mut [i16], _: &cpal::OutputCallbackInfo| {
|
||||
let mut buf = buffer_clone.lock().unwrap();
|
||||
@@ -245,13 +251,17 @@ impl NodeLogic for AudioSinkLogic {
|
||||
tracing::error!("Audio stream error: {}", err);
|
||||
},
|
||||
None,
|
||||
)
|
||||
.map_err(|e| AudioError::ProcessingError(format!("Failed to build I16 stream: {}", e)))?
|
||||
) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to build I16 stream: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
cpal::SampleFormat::U16 => {
|
||||
tracing::debug!("Using U16 output format");
|
||||
device
|
||||
.build_output_stream(
|
||||
match device.build_output_stream(
|
||||
&config.into(),
|
||||
move |data: &mut [u16], _: &cpal::OutputCallbackInfo| {
|
||||
let mut buf = buffer_clone.lock().unwrap();
|
||||
@@ -266,13 +276,17 @@ impl NodeLogic for AudioSinkLogic {
|
||||
tracing::error!("Audio stream error: {}", err);
|
||||
},
|
||||
None,
|
||||
)
|
||||
.map_err(|e| AudioError::ProcessingError(format!("Failed to build U16 stream: {}", e)))?
|
||||
) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to build U16 stream: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
cpal::SampleFormat::F32 => {
|
||||
tracing::debug!("Using F32 output format");
|
||||
device
|
||||
.build_output_stream(
|
||||
match device.build_output_stream(
|
||||
&config.into(),
|
||||
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
||||
let mut buf = buffer_clone.lock().unwrap();
|
||||
@@ -285,21 +299,34 @@ impl NodeLogic for AudioSinkLogic {
|
||||
tracing::error!("Audio stream error: {}", err);
|
||||
},
|
||||
None,
|
||||
)
|
||||
.map_err(|e| AudioError::ProcessingError(format!("Failed to build F32 stream: {}", e)))?
|
||||
) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to build F32 stream: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(AudioError::ProcessingError(format!(
|
||||
"Unsupported sample format: {:?}",
|
||||
sample_format
|
||||
)));
|
||||
}
|
||||
};
|
||||
tracing::error!("Unsupported sample format: {:?}", sample_format);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Démarrer le stream
|
||||
stream
|
||||
.play()
|
||||
.map_err(|e| AudioError::ProcessingError(format!("Failed to play stream: {}", e)))?;
|
||||
// Démarrer le stream
|
||||
if let Err(e) = stream.play() {
|
||||
tracing::error!("Failed to start stream: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
tracing::debug!("Stream thread started");
|
||||
|
||||
// Attendre la commande d'arrêt
|
||||
let _ = stream_cmd_rx.recv();
|
||||
|
||||
// Le stream se fermera automatiquement quand il sera droppé
|
||||
tracing::debug!("Stream thread exiting");
|
||||
});
|
||||
|
||||
tracing::debug!("AudioSink initialized with format {:?}", sample_format);
|
||||
|
||||
@@ -308,7 +335,8 @@ impl NodeLogic for AudioSinkLogic {
|
||||
// Vérifier si l'arrêt a été demandé
|
||||
if stop_token.is_cancelled() {
|
||||
tracing::debug!("AudioSinkLogic cancelled");
|
||||
drop(stream);
|
||||
let _ = stream_cmd_tx.send(true);
|
||||
let _ = stream_thread.join();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -317,7 +345,8 @@ impl NodeLogic for AudioSinkLogic {
|
||||
let buf = buffer.lock().unwrap();
|
||||
if buf.is_finished() {
|
||||
tracing::debug!("AudioSink: finished playing all samples");
|
||||
drop(stream);
|
||||
let _ = stream_cmd_tx.send(true);
|
||||
let _ = stream_thread.join();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -333,14 +362,16 @@ impl NodeLogic for AudioSinkLogic {
|
||||
while !buffer.lock().unwrap().is_empty() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
||||
}
|
||||
drop(stream);
|
||||
let _ = stream_cmd_tx.send(true);
|
||||
let _ = stream_thread.join();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = stop_token.cancelled() => {
|
||||
tracing::debug!("AudioSinkLogic cancelled during recv");
|
||||
drop(stream);
|
||||
let _ = stream_cmd_tx.send(true);
|
||||
let _ = stream_thread.join();
|
||||
return Ok(());
|
||||
}
|
||||
_ = tokio::time::sleep(tokio::time::Duration::from_millis(100)) => {
|
||||
@@ -380,7 +411,8 @@ impl NodeLogic for AudioSinkLogic {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
||||
}
|
||||
|
||||
drop(stream);
|
||||
let _ = stream_cmd_tx.send(true);
|
||||
let _ = stream_thread.join();
|
||||
return Ok(());
|
||||
}
|
||||
SyncMarker::Error(ref message) => {
|
||||
|
||||
Reference in New Issue
Block a user