Files
ox_speak/README.md
2025-07-04 17:02:28 +02:00

113 lines
4.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 lordre
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 dentré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.
- Lapproche 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 dun 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 davis 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é.