106 lines
3.3 KiB
Markdown
106 lines
3.3 KiB
Markdown
# Audio Capture & Processing — Architecture Notes
|
||
|
||
Ce document résume les décisions techniques et bonnes pratiques concernant la capture, l'encodage, et la transmission de la voix en temps réel dans un logiciel de type Discord/TeamSpeak, basé sur `cpal` (Rust) et `Opus`.
|
||
|
||
---
|
||
|
||
## 🎤 Capture audio avec `cpal`
|
||
|
||
### ✅ Bonnes pratiques
|
||
|
||
- Utiliser `cpal` en **mode callback** pour une capture audio fiable et efficace.
|
||
- **Ne jamais traiter** l’audio directement dans le callback.
|
||
- Le callback doit être **minimaliste** : uniquement pousser les échantillons dans un buffer partagé/thread-safe.
|
||
|
||
### ❌ Mauvaises pratiques
|
||
|
||
- Encoder dans le callback → risque de **décrochage audio**.
|
||
- Loguer ou dormir dans le callback → **clics, perte de données**.
|
||
|
||
---
|
||
|
||
## 🎯 Taille des frames : 960 *samples*, pas 960 *ms*
|
||
|
||
- Pour un encodage **Opus en 20 ms à 48 kHz mono**, il faut **960 samples par frame**.
|
||
- Les chunks reçus de CPAL ne garantissent **jamais** d’être exactement de cette taille.
|
||
|
||
---
|
||
|
||
## 🧱 Bufferisation : découpage rigide
|
||
|
||
- Mettre en place un **buffer circulaire** (ex : `VecDeque<i16>`) pour accumuler les samples reçus.
|
||
- Dès que `buffer.len() >= 960` :
|
||
- Extraire les 960 premiers samples,
|
||
- Encoder via Opus,
|
||
- Transmettre immédiatement.
|
||
|
||
### Pourquoi ne **pas** compléter avec des zéros ?
|
||
- Cela introduit des **artefacts audio**,
|
||
- L'encodeur Opus s’attend à des données audio **continues et naturelles**.
|
||
|
||
---
|
||
|
||
## ⏱️ Pas de timer à 20ms
|
||
|
||
- Ne jamais baser l’envoi des paquets audio sur une **horloge ou un cycle fixe**.
|
||
- L’encodage doit être déclenché **uniquement** par la disponibilité d’une frame complète.
|
||
|
||
---
|
||
|
||
## 🧵 Architecture multithread recommandée
|
||
|
||
```
|
||
[ CPAL Callback ]
|
||
↓
|
||
[ Channel / Ring Buffer ]
|
||
↓
|
||
[ Encode Thread (Opus) ]
|
||
↓
|
||
[ Network Sender (UDP) ]
|
||
```
|
||
|
||
- Le callback ne fait que `send(samples)` dans un channel rapide.
|
||
- L'encodage Opus est fait dans un thread séparé dès qu'une frame de 960 samples est disponible.
|
||
|
||
---
|
||
|
||
## 🔄 Communication entre threads
|
||
|
||
### Option 1 – Crossbeam channel (recommandé au départ)
|
||
```rust
|
||
use crossbeam_channel::{bounded, Receiver, Sender};
|
||
```
|
||
- Solide, rapide, facile à intégrer.
|
||
- Peut être remplacé plus tard si besoin de plus de performance.
|
||
|
||
### Option 2 – `ringbuf` (optimisé audio)
|
||
- Ultra rapide, pas d’allocation après init,
|
||
- Idéal pour de l'audio pro mais limité à 1 producer / 1 consumer.
|
||
|
||
---
|
||
|
||
## 📊 Latence et performance
|
||
|
||
- Encodage Opus d’une frame 960 mono ≈ **0.1 à 0.5 ms** sur PC moderne.
|
||
- Aucun besoin d'ajouter de délai artificiel.
|
||
- **Pas de risque de “fractionner la voix”** si le buffer est bien géré.
|
||
|
||
---
|
||
|
||
## ✅ Récapitulatif des règles d’or
|
||
|
||
- 🎧 Ne pas encoder dans le callback CPAL.
|
||
- 🔁 Accumuler les samples dans un buffer jusqu'à 960.
|
||
- ✂️ Ne jamais compléter une frame incomplète avec des zéros.
|
||
- 🚫 Ne pas timer les envois → laisser le flux audio dicter le tempo.
|
||
- 🧵 Utiliser des channels ou buffers inter-threads pour le découplage.
|
||
|
||
---
|
||
|
||
## 📌 TODO futur
|
||
|
||
- [ ] Comparer performance `crossbeam_channel` vs `ringbuf` dans ton usage réel.
|
||
- [ ] Implémenter détection de silence (VAD) pour ne pas encoder à vide.
|
||
- [ ] Ajouter sequence number + timestamp (RTP-like) dans les paquets réseau.
|
||
- [ ] Implémenter jitter buffer côté client (récepteur).
|