113 lines
4.8 KiB
Markdown
113 lines
4.8 KiB
Markdown
# ox_speak – Architecture Audio Temps Réel en Rust
|
||
|
||
## 📦 Structure actuelle du projet
|
||
|
||
```text
|
||
src/
|
||
├── main.rs # Point d'entrée, instancie App et démarre l'orchestration
|
||
├── lib.rs # Déclarations des modules principaux
|
||
│
|
||
├── app/ # Initialisation, gestion de haut niveau de l'application
|
||
│ ├── mod.rs
|
||
│ └── app.rs # Struct App : contient les stacks (audio, net) et le EventBus
|
||
│
|
||
├── core/ # Traitements audio bas-niveau (CPAL, Opus, stats...)
|
||
│ ├── mod.rs
|
||
│ ├── capture.rs # Capture micro avec CPAL
|
||
│ ├── playback.rs # Playback via CPAL
|
||
│ ├── mixer.rs # Mixage audio
|
||
│ ├── opus.rs # Encodage/Décodage Opus
|
||
│ ├── stats.rs # Statistiques audio
|
||
│ └── rms.rs # Détection de silence via RMS
|
||
│
|
||
├── domain/ # Types globaux et événements partagés
|
||
│ ├── mod.rs
|
||
│ └── event.rs # EventBus (struct + enum Event) pour routing interne
|
||
│
|
||
├── network/ # Communication réseau (UDP, protocole)
|
||
│ ├── mod.rs
|
||
│ ├── udp.rs # Client UDP (envoi/réception)
|
||
│ └── protocol.rs # Types et parsing de messages réseau
|
||
│
|
||
├── runtime/ # Logique d'exécution centrale (orchestration)
|
||
│ ├── mod.rs
|
||
│ └── dispatcher.rs # Dispatcher central (consomme les Event)
|
||
```
|
||
|
||
---
|
||
|
||
## 🛠️ Tâches à faire dans l’ordre
|
||
|
||
1. **Compléter `domain/event.rs`**
|
||
- Définir les `enum Event` utilisés dans ton application (ex : `AudioIn`, `EncodedFrame`, etc.)
|
||
- Structurer `EventBus` avec `crossbeam_channel`
|
||
|
||
2. **Écrire un `dispatcher` simple dans `runtime/dispatcher.rs`**
|
||
- Boucle bloquante `while let Ok(event) = rx.recv()`
|
||
- Match sur chaque type d’événement (→ faire une "god function" au début)
|
||
- Appeler directement les fonctions concernées (ex : `udp.send()`, `playback.play()`…)
|
||
|
||
3. **Initialiser `App` dans `app/app.rs`**
|
||
- Créer une struct `App` contenant :
|
||
- le `EventBus`
|
||
- les instances des modules nécessaires (`capture`, `udp`, etc.)
|
||
- Méthode `run()` qui spawn les threads d’entrée (`audio`, `network`), et lance le `dispatcher`
|
||
|
||
4. **Compléter les modules `core/` et `network/`**
|
||
- Rendre chaque module autonome, avec une méthode `start()` ou `run()` prenant un `Sender<Event>`
|
||
- Exemple : `capture.run(tx)` capture le micro et envoie des `AudioIn(Vec<f32>)`
|
||
|
||
5. **Dans `main.rs`**
|
||
- Créer `App`, puis appeler `app.run()`.
|
||
|
||
6. (optionnel) **Plus tard : découpler le dispatcher en handlers**
|
||
- Créer un `trait EventHandler` pour déléguer proprement la logique métier
|
||
- Injecter les handlers dans le dispatcher pour garder `dispatcher.rs` léger
|
||
|
||
---
|
||
|
||
## 🔄 Orchestration globale (comment tout communique)
|
||
|
||
```text
|
||
THREADS :
|
||
- capture_thread : génère AudioIn
|
||
- opus_encode_thread: encode AudioIn → EncodedFrame
|
||
- udp_send_thread : reçoit EncodedFrame → envoie réseau
|
||
- udp_recv_thread : reçoit NetIn → decode → AudioDecoded
|
||
- playback_thread : lit AudioDecoded
|
||
|
||
TOUS utilisent :
|
||
Sender<Event> (cloné)
|
||
↑
|
||
EventBus central (domain/event.rs)
|
||
|
||
RECEIVER :
|
||
dispatcher (runtime/dispatcher.rs)
|
||
→ lit les events
|
||
→ décide quoi faire : encode, envoyer, lire...
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 Remarques
|
||
|
||
- Tu peux rester **mono-thread** dans un premier temps pour simplifier le flow.
|
||
- L’approche actuelle (dispatcher = "god function") est très bien pour commencer **et lisible**.
|
||
- Tu peux logguer chaque événement dans le dispatcher pour debugger le flux.
|
||
- `EventBus` est cloné et injecté dans chaque module. **Aucun module ne s'appelle entre eux.**
|
||
|
||
---
|
||
|
||
## 🧠 Notes de conception
|
||
|
||
- Tu as choisi de garder la génération de la trame encodée **dans le thread de capture**, ce qui est parfaitement logique ici : tu as 20 ms de temps CPU garanti dans le callback CPAL pour encoder et publier un `Event::EncodedFrame`.
|
||
- Cette approche évite la surcharge d’un thread supplémentaire et permet une architecture **performante et simple** tant que tu restes dans les contraintes temps réel.
|
||
|
||
---
|
||
|
||
## ❓Question
|
||
|
||
> Pour le moment, elle ne se pose pas, car en réalité je vais garder l'ancienne logique du "c'est capture qui va générer la frame encodée", vu que le thread a 20 ms pour bosser entre chaque callback cpal, il a largement le temps de le faire.
|
||
|
||
Donc aucun doute à ce stade. Si tu changes d’avis ou que la logique devient trop lourde dans `capture`, tu pourras refactorer vers un modèle où `AudioIn` est un event brut et l'encodage est fait côté dispatcher ou dans un worker dédié.
|