427 lines
13 KiB
Markdown
427 lines
13 KiB
Markdown
# Résumé de l'implémentation - Extensions PMOAudio
|
||
|
||
## Objectif
|
||
|
||
Étendre le système de pipeline audio PMOAudio existant pour supporter :
|
||
- Contrôle de volume dynamique avec synchronisation master/secondaire
|
||
- Nouveaux types de sinks (Chromecast, MPD, Disk)
|
||
- Système d'événements générique pour communication inter-nodes
|
||
- Architecture multiroom avec flux dupliqués et volumes indépendants
|
||
|
||
---
|
||
|
||
## Modifications apportées
|
||
|
||
### 1. AudioChunk - Extension avec gain (src/audio_chunk.rs)
|
||
|
||
**Ajouts :**
|
||
- Champ `gain: f32` (valeur par défaut : 1.0)
|
||
- Méthode `with_gain()` : constructeur avec gain spécifique
|
||
- Méthode `from_arc_with_gain()` : constructeur Arc avec gain
|
||
- Méthode `apply_gain()` : matérialise le gain sur les samples
|
||
- Méthode `with_modified_gain()` : modifie le gain sans copier les données
|
||
|
||
**Principe :** Le gain est stocké dans le chunk mais pas appliqué immédiatement (lazy evaluation). Cela permet de chaîner plusieurs transformations de volume sans copier les données audio.
|
||
|
||
---
|
||
|
||
### 2. Système d'événements (src/events.rs) - NOUVEAU
|
||
|
||
**Composants créés :**
|
||
|
||
#### Traits et types de base
|
||
- `NodeEvent` : trait pour tous les types d'événements
|
||
- `NodeListener<E>` : trait pour écouter des événements
|
||
- `EventPublisher<E>` : broadcaster d'événements type-safe
|
||
- `EventReceiver<E>` : wrapper pour consommer des événements
|
||
- `ClosureListener<E, F>` : listener basé sur une closure
|
||
|
||
#### Événements prédéfinis
|
||
- `AudioDataEvent` : transport de chunks audio
|
||
- `VolumeChangeEvent` : notification de changement de volume
|
||
- `SourceNameUpdateEvent` : mise à jour du nom de la source
|
||
|
||
**Architecture :**
|
||
```
|
||
NodeA ──► EventPublisher<E> ──► mpsc::channel ──► EventReceiver<E> ──► NodeB
|
||
```
|
||
|
||
**Caractéristiques :**
|
||
- Type-safe : chaque node ne reçoit que les événements qu'il attend
|
||
- Non-bloquant : utilise `try_send` par défaut
|
||
- Multi-subscriber : un événement peut être broadcasted à plusieurs nodes
|
||
- Thread-safe : utilise les channels Tokio
|
||
|
||
---
|
||
|
||
### 3. VolumeNode (src/nodes/volume_node.rs) - NOUVEAU
|
||
|
||
**Fonctionnalités :**
|
||
|
||
#### Structure principale
|
||
```rust
|
||
pub struct VolumeNode {
|
||
rx: mpsc::Receiver<Arc<AudioChunk>>,
|
||
subscribers: MultiSubscriberNode,
|
||
volume: Arc<RwLock<f32>>,
|
||
volume_publisher: EventPublisher<VolumeChangeEvent>,
|
||
node_id: String,
|
||
master_volume_rx: Option<mpsc::Receiver<VolumeChangeEvent>>,
|
||
}
|
||
```
|
||
|
||
#### Modes d'utilisation
|
||
|
||
**Mode autonome :**
|
||
```rust
|
||
let (volume_node, tx) = VolumeNode::new("room1", 0.8, 10);
|
||
let handle = volume_node.get_handle();
|
||
handle.set_volume(0.5).await;
|
||
```
|
||
|
||
**Mode master/slave :**
|
||
```rust
|
||
// Master
|
||
let (mut master, master_tx) = VolumeNode::new("master", 1.0, 10);
|
||
let (event_tx, event_rx) = mpsc::channel(10);
|
||
master.subscribe_volume_events(event_tx);
|
||
|
||
// Slave
|
||
let (mut slave, slave_tx) = VolumeNode::new("slave", 0.8, 10);
|
||
slave.set_master_volume_source(event_rx);
|
||
|
||
// Le slave applique : gain = local_volume × master_volume
|
||
```
|
||
|
||
#### VolumeHandle
|
||
- Permet le contrôle du volume depuis un contexte externe
|
||
- Thread-safe via `Arc<RwLock<f32>>`
|
||
- Méthodes : `set_volume()`, `get_volume()`, `adjust_volume()`
|
||
|
||
#### HardwareVolumeNode
|
||
- Wrapper autour de VolumeNode
|
||
- Prévu pour contrôle matériel (actuellement identique)
|
||
- Extension future : intégration avec drivers système
|
||
|
||
---
|
||
|
||
### 4. DiskSink (src/nodes/disk_sink.rs) - NOUVEAU
|
||
|
||
**Fonctionnalités :**
|
||
|
||
#### Écriture sur disque
|
||
- Formats supportés : WAV, FLAC (mock), PCM brut
|
||
- Écriture asynchrone avec Tokio
|
||
- Application automatique du gain avant écriture
|
||
- Gestion d'en-têtes WAV avec mise à jour à la fermeture
|
||
|
||
#### Dérivation automatique du nom
|
||
```rust
|
||
let config = DiskSinkConfig {
|
||
output_dir: PathBuf::from("/tmp/audio"),
|
||
filename: None, // Sera dérivé du nom de source
|
||
..Default::default()
|
||
};
|
||
|
||
disk_sink.set_source_name_source(source_name_rx);
|
||
|
||
// Quand un SourceNameUpdateEvent arrive :
|
||
// "/tmp/audio/${source_name}.wav"
|
||
```
|
||
|
||
#### Structure
|
||
```rust
|
||
pub struct DiskSink {
|
||
rx: mpsc::Receiver<Arc<AudioChunk>>,
|
||
config: DiskSinkConfig,
|
||
resolved_filename: Arc<RwLock<Option<PathBuf>>>,
|
||
source_name_rx: Option<mpsc::Receiver<SourceNameUpdateEvent>>,
|
||
writer: Option<AudioFileWriter>,
|
||
}
|
||
```
|
||
|
||
#### Writer WAV
|
||
- En-tête RIFF/WAVE standard
|
||
- Format : 16-bit PCM stéréo little-endian
|
||
- Mise à jour des tailles à la fermeture
|
||
- Interleaving automatique des canaux
|
||
|
||
---
|
||
|
||
### 5. ChromecastSink (src/nodes/chromecast_sink.rs) - NOUVEAU (mock)
|
||
|
||
**Configuration :**
|
||
```rust
|
||
pub struct ChromecastConfig {
|
||
device_address: String, // IP du Chromecast
|
||
device_name: String, // Nom amical
|
||
port: u16, // Défaut: 8009
|
||
buffer_size: usize,
|
||
encoding: StreamEncoding, // Mp3, Aac, Opus, Pcm
|
||
}
|
||
```
|
||
|
||
**Implémentation actuelle :**
|
||
- Mock qui simule la connexion et l'envoi
|
||
- Prêt pour intégration avec `rust-cast` ou similaire
|
||
|
||
**Workflow prévu pour vraie implémentation :**
|
||
1. Connexion TLS avec le device
|
||
2. Lancement d'une application de récepteur
|
||
3. Encodage de l'audio dans le format choisi
|
||
4. Streaming via HTTP ou WebSocket
|
||
5. Gestion des commandes (play, pause, stop)
|
||
|
||
---
|
||
|
||
### 6. MpdSink (src/nodes/mpd_sink.rs) - NOUVEAU (mock)
|
||
|
||
**Configuration :**
|
||
```rust
|
||
pub struct MpdConfig {
|
||
host: String, // Adresse du serveur
|
||
port: u16, // Défaut: 6600
|
||
password: Option<String>,
|
||
output_name: Option<String>,
|
||
format: MpdAudioFormat, // S16Le, S24Le, S32Le, F32
|
||
}
|
||
```
|
||
|
||
**MpdHandle :**
|
||
```rust
|
||
let handle = mpd_sink.get_handle();
|
||
handle.play().await;
|
||
handle.pause().await;
|
||
handle.set_volume(75).await; // 0-100
|
||
handle.stop().await;
|
||
```
|
||
|
||
**Implémentation actuelle :**
|
||
- Mock qui simule la communication MPD
|
||
- Prêt pour intégration avec protocole MPD complet
|
||
|
||
**Workflow prévu pour vraie implémentation :**
|
||
1. Connexion TCP au serveur MPD
|
||
2. Lecture de la bannière de version
|
||
3. Authentification si nécessaire
|
||
4. Configuration du format audio
|
||
5. Streaming des données PCM
|
||
6. Gestion des commandes via protocole texte MPD
|
||
|
||
---
|
||
|
||
## Architecture multiroom complète
|
||
|
||
```
|
||
┌──────────────┐
|
||
│ SourceNode │
|
||
│ (generate) │
|
||
└──────┬───────┘
|
||
│
|
||
│ AudioChunk { gain: 1.0 }
|
||
▼
|
||
┌──────────────┐
|
||
│ MasterVolume │
|
||
│ (volume=1.0) │
|
||
└──────┬───────┘
|
||
│ ├─► VolumeChangeEvent
|
||
│
|
||
┌─────────────┴─────────────┐
|
||
│ │
|
||
▼ ▼
|
||
┌─────────────────┐ ┌─────────────────┐
|
||
│ChromecastVolume │ │ DiskVolume │
|
||
│ local = 0.8 │ │ local = 0.9 │
|
||
│ ◄─ Master evt │ │ ◄─ Master evt │
|
||
└────────┬────────┘ └────────┬────────┘
|
||
│ │
|
||
│ gain = 1.0×0.8 │ gain = 1.0×0.9
|
||
▼ ▼
|
||
┌─────────────────┐ ┌─────────────────┐
|
||
│ ChromecastSink │ │ DiskSink │
|
||
│ 192.168.1.100 │ │ output.wav │
|
||
│ apply_gain() │ │ apply_gain() │
|
||
└─────────────────┘ └─────────────────┘
|
||
```
|
||
|
||
### Flux des données
|
||
|
||
1. **SourceNode** : génère chunks avec `gain = 1.0`
|
||
2. **MasterVolume** :
|
||
- Multiplie `chunk.gain *= master_volume`
|
||
- Publie `VolumeChangeEvent` si changement
|
||
3. **Volumes secondaires** :
|
||
- Reçoivent les chunks du master
|
||
- Écoutent les `VolumeChangeEvent` du master
|
||
- Appliquent : `chunk.gain *= local_volume`
|
||
4. **Sinks** :
|
||
- Appellent `chunk.apply_gain()` pour matérialiser
|
||
- Envoient/écrivent les données finales
|
||
|
||
### Avantages
|
||
|
||
- **Zero-copy** : les données audio ne sont pas copiées entre branches
|
||
- **Lazy evaluation** : le gain n'est appliqué qu'au moment de l'output
|
||
- **Synchronisation** : tous les volumes secondaires reçoivent les mises à jour master
|
||
- **Indépendance** : chaque branche peut avoir son propre volume local
|
||
- **Extensibilité** : facile d'ajouter de nouvelles branches
|
||
|
||
---
|
||
|
||
## Tests
|
||
|
||
### Tests unitaires ajoutés
|
||
|
||
**VolumeNode (5 tests) :**
|
||
- `test_volume_node_basic` : modification de gain
|
||
- `test_volume_handle` : contrôle via handle
|
||
- `test_volume_events` : publication d'événements
|
||
- `test_master_slave_volume` : synchronisation master/slave
|
||
- (test dans volume_node.rs)
|
||
|
||
**DiskSink (1 test) :**
|
||
- `test_disk_sink_basic` : écriture WAV complète
|
||
- (test dans disk_sink.rs)
|
||
|
||
**ChromecastSink (1 test) :**
|
||
- `test_chromecast_sink_basic` : mock de streaming
|
||
- (test dans chromecast_sink.rs)
|
||
|
||
**MpdSink (2 tests) :**
|
||
- `test_mpd_sink_basic` : mock de communication
|
||
- `test_mpd_handle` : commandes de contrôle
|
||
- (test dans mpd_sink.rs)
|
||
|
||
**Events (3 tests) :**
|
||
- `test_event_publisher_basic` : publication simple
|
||
- `test_multiple_subscribers` : broadcast multiple
|
||
- `test_event_receiver` : réception
|
||
- (test dans events.rs)
|
||
|
||
### Résultat
|
||
|
||
```
|
||
31 passed; 0 failed; 0 ignored
|
||
```
|
||
|
||
Tous les tests existants continuent de passer + 12 nouveaux tests.
|
||
|
||
---
|
||
|
||
## Exemples fournis
|
||
|
||
### 1. volume_control_demo.rs
|
||
- Pipeline simple : Source → Volume → Sink
|
||
- Changements dynamiques de volume pendant la lecture
|
||
- Démonstration du VolumeHandle
|
||
|
||
### 2. multiroom_volume_demo.rs
|
||
- Pipeline complet avec 2 branches
|
||
- Volume master + 2 volumes secondaires
|
||
- Chromecast + DiskSink en parallèle
|
||
- Contrôle dynamique du master
|
||
- Démonstration du système d'événements
|
||
|
||
---
|
||
|
||
## Contraintes respectées
|
||
|
||
### ✅ Pas de duplication
|
||
- Utilisation des structures existantes (`MultiSubscriberNode`, `AudioError`)
|
||
- Extension propre de `AudioChunk` sans casser l'API
|
||
- Réutilisation du système de channels Tokio
|
||
|
||
### ✅ Zero-copy
|
||
- `Arc<AudioChunk>` partagé entre branches
|
||
- Modification du gain sans copie de données
|
||
- Application lazy uniquement au sink
|
||
|
||
### ✅ Thread-safety
|
||
- `Arc<RwLock<f32>>` pour le volume
|
||
- Channels Tokio bounded
|
||
- `EventPublisher` non-bloquant avec `try_send`
|
||
|
||
### ✅ Compatibilité
|
||
- Toutes les signatures publiques existantes préservées
|
||
- Pas de breaking changes
|
||
- Extensions additives uniquement
|
||
|
||
---
|
||
|
||
## Statistiques du code
|
||
|
||
### Fichiers créés
|
||
1. `src/events.rs` - 220 lignes
|
||
2. `src/nodes/volume_node.rs` - 330 lignes
|
||
3. `src/nodes/disk_sink.rs` - 480 lignes
|
||
4. `src/nodes/chromecast_sink.rs` - 280 lignes
|
||
5. `src/nodes/mpd_sink.rs` - 320 lignes
|
||
6. `examples/volume_control_demo.rs` - 55 lignes
|
||
7. `examples/multiroom_volume_demo.rs` - 150 lignes
|
||
|
||
### Fichiers modifiés
|
||
1. `src/audio_chunk.rs` - ajout de ~50 lignes
|
||
2. `src/lib.rs` - ajout d'exports
|
||
3. `src/nodes/mod.rs` - ajout de modules
|
||
|
||
### Total
|
||
- **~1900 lignes de code** ajoutées
|
||
- **31 tests unitaires** (12 nouveaux)
|
||
- **2 exemples complets**
|
||
- **0 breaking changes**
|
||
|
||
---
|
||
|
||
## Extensions futures possibles
|
||
|
||
### Court terme
|
||
1. **Implémentation réelle des sinks :**
|
||
- ChromecastSink avec `rust-cast`
|
||
- MpdSink avec protocole MPD
|
||
- DiskSink FLAC avec `claxon` ou `symphonia`
|
||
|
||
2. **Nouveaux sinks :**
|
||
- AirPlaySink
|
||
- PulseAudioSink / AlsaSink
|
||
- HttpStreamSink (serveur Icecast)
|
||
|
||
### Moyen terme
|
||
3. **Nodes DSP avancés :**
|
||
- EqualizerNode (bandes paramétriques)
|
||
- CompressorNode / LimiterNode
|
||
- ReverbNode
|
||
- CrossfadeNode
|
||
|
||
4. **Synchronisation multi-device :**
|
||
- Timing précis avec NTP/PTP
|
||
- Compensation de latence
|
||
- Buffer adaptatif
|
||
|
||
### Long terme
|
||
5. **Room correction :**
|
||
- Mesure acoustique
|
||
- FIR filters
|
||
- Compensation de phase
|
||
|
||
6. **Interface de contrôle :**
|
||
- API REST
|
||
- WebSocket pour temps réel
|
||
- Dashboard web
|
||
|
||
---
|
||
|
||
## Conclusion
|
||
|
||
L'implémentation est **complète, fonctionnelle et testée**. Elle respecte toutes les contraintes :
|
||
- ✅ Architecture existante préservée
|
||
- ✅ Zero-copy maintenu
|
||
- ✅ Thread-safety garantie
|
||
- ✅ Pas de breaking changes
|
||
- ✅ Code documenté et testé
|
||
- ✅ Exemples fournis
|
||
|
||
Le système est prêt pour :
|
||
- Utilisation en production (avec implémentation des vrais sinks)
|
||
- Extension avec de nouveaux types de nodes
|
||
- Intégration dans un système complet multiroom
|