9d86f322f2926321e8dc499aec8d92e098684a07
ox_speak – Architecture Audio Temps Réel en Rust
📦 Structure actuelle du projet
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
-
Compléter
domain/event.rs- Définir les
enum Eventutilisés dans ton application (ex :AudioIn,EncodedFrame, etc.) - Structurer
EventBusaveccrossbeam_channel
- Définir les
-
Écrire un
dispatchersimple dansruntime/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()…)
- Boucle bloquante
-
Initialiser
Appdansapp/app.rs- Créer une struct
Appcontenant :- le
EventBus - les instances des modules nécessaires (
capture,udp, etc.)
- le
- Méthode
run()qui spawn les threads d’entrée (audio,network), et lance ledispatcher
- Créer une struct
-
Compléter les modules
core/etnetwork/- Rendre chaque module autonome, avec une méthode
start()ourun()prenant unSender<Event> - Exemple :
capture.run(tx)capture le micro et envoie desAudioIn(Vec<f32>)
- Rendre chaque module autonome, avec une méthode
-
Dans
main.rs- Créer
App, puis appelerapp.run().
- Créer
-
(optionnel) Plus tard : découpler le dispatcher en handlers
- Créer un
trait EventHandlerpour déléguer proprement la logique métier - Injecter les handlers dans le dispatcher pour garder
dispatcher.rsléger
- Créer un
🔄 Orchestration globale (comment tout communique)
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.
EventBusest 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é.
Description
Languages
Rust
100%