Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
459 lines
13 KiB
Markdown
459 lines
13 KiB
Markdown
# 🎵 Pipeline d'Encodage Audio - Documentation Technique
|
|
|
|
**Date**: 2025-01-27
|
|
**Service**: `veza-stream-server`
|
|
**Version**: 1.0.0
|
|
|
|
---
|
|
|
|
## 📋 Table des Matières
|
|
|
|
1. [Architecture](#architecture)
|
|
2. [Format Manifest HLS](#format-manifest-hls)
|
|
3. [Mapping Qualité → Codec/Bitrate](#mapping-qualité--codecbitrate)
|
|
4. [Gestion des Erreurs](#gestion-des-erreurs)
|
|
5. [Tests](#tests)
|
|
6. [Instructions de Développement](#instructions-de-développement)
|
|
|
|
---
|
|
|
|
## 🏗️ Architecture
|
|
|
|
### Vue d'ensemble
|
|
|
|
Le pipeline d'encodage audio est composé de plusieurs couches :
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ API REST Layer │
|
|
│ POST /admin/stream/encode/:track_id/:quality │
|
|
│ GET /admin/stream/status/:track_id │
|
|
│ GET /stream/:track_id/:quality/index.m3u8 │
|
|
│ GET /stream/:track_id/:quality/segment_*.ts │
|
|
└────────────────────┬────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ Encoding Service Layer │
|
|
│ - Vérifie track.source_path en DB │
|
|
│ - Crée répertoire /data/streams/<track_id>/<quality>/ │
|
|
│ - Crée EncodeJob et l'envoie dans la queue │
|
|
└────────────────────┬────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ Encoder Pool (Worker Threads) │
|
|
│ - Queue: async_channel::Receiver<EncodeJob> │
|
|
│ - Workers: Vec<EncoderWorker> (1 thread = 1 FFmpeg) │
|
|
│ - Nombre: min(nb_cpu / 2, 8) │
|
|
└────────────────────┬────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ FFmpeg Execution │
|
|
│ - Build command avec FfmpegCommandBuilder │
|
|
│ - Spawn processus FFmpeg │
|
|
│ - Capture stderr en streaming │
|
|
│ - Parse progression (FfmpegProgress) │
|
|
│ - Détecte crashes et erreurs │
|
|
└────────────────────┬────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ Post-Processing │
|
|
│ - Parse manifest .m3u8 pour extraire segments │
|
|
│ - Calcule durée par segment │
|
|
│ - Insère segments en DB (stream_segments) │
|
|
│ - Met à jour stream_jobs.status = 'done' │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Composants Principaux
|
|
|
|
#### 1. `EncodeJob`
|
|
Structure représentant un job d'encodage :
|
|
- `track_id`: UUID du track
|
|
- `input_path`: Chemin vers le fichier source
|
|
- `output_dir`: Répertoire de sortie pour segments HLS
|
|
- `codec`: Codec audio (AAC, Opus, MP3, FLAC)
|
|
- `bitrate`: Bitrate en bps
|
|
- `quality`: Qualité (low, medium, high, hi_res)
|
|
|
|
#### 2. `EncoderPool`
|
|
Pool de workers qui traitent les jobs d'encodage :
|
|
- Queue asynchrone (`async_channel`)
|
|
- Workers threads dédiés
|
|
- Nombre de workers: `min(nb_cpu / 2, 8)`
|
|
- Chaque worker spawn un processus FFmpeg
|
|
|
|
#### 3. `EncodingService`
|
|
Service de haut niveau pour :
|
|
- Lancer des encodages
|
|
- Vérifier les statuts
|
|
- Gérer les répertoires de sortie
|
|
|
|
---
|
|
|
|
## 📄 Format Manifest HLS
|
|
|
|
### Structure d'un Manifest Media Playlist
|
|
|
|
```m3u8
|
|
#EXTM3U
|
|
#EXT-X-VERSION:3
|
|
#EXT-X-TARGETDURATION:6
|
|
#EXT-X-MEDIA-SEQUENCE:0
|
|
#EXT-X-PLAYLIST-TYPE:VOD
|
|
#EXTINF:4.000,
|
|
segment_00000.ts
|
|
#EXTINF:4.000,
|
|
segment_00001.ts
|
|
#EXTINF:4.000,
|
|
segment_00002.ts
|
|
#EXT-X-ENDLIST
|
|
```
|
|
|
|
### Tags HLS Utilisés
|
|
|
|
- `#EXTM3U`: Identifie le fichier comme playlist M3U8
|
|
- `#EXT-X-VERSION:3`: Version du protocole HLS
|
|
- `#EXT-X-TARGETDURATION:6`: Durée maximale d'un segment (secondes)
|
|
- `#EXT-X-MEDIA-SEQUENCE:0`: Numéro de séquence du premier segment
|
|
- `#EXT-X-PLAYLIST-TYPE:VOD`: Type de playlist (VOD = Video On Demand)
|
|
- `#EXTINF:4.000,`: Durée du segment suivant (secondes)
|
|
- `#EXT-X-ENDLIST`: Marque la fin de la playlist (VOD uniquement)
|
|
|
|
### Structure des Fichiers de Sortie
|
|
|
|
```
|
|
/data/streams/
|
|
<track_id>/
|
|
low/
|
|
index.m3u8
|
|
segment_00000.ts
|
|
segment_00001.ts
|
|
...
|
|
medium/
|
|
index.m3u8
|
|
segment_00000.ts
|
|
segment_00001.ts
|
|
...
|
|
high/
|
|
index.m3u8
|
|
segment_00000.ts
|
|
segment_00001.ts
|
|
...
|
|
hi_res/
|
|
index.m3u8
|
|
segment_00000.ts
|
|
segment_00001.ts
|
|
...
|
|
```
|
|
|
|
---
|
|
|
|
## 🎚️ Mapping Qualité → Codec/Bitrate
|
|
|
|
| Qualité | Codec | Bitrate | Sample Rate | Channels | HLS Segment Time |
|
|
|---------|-------|---------|-------------|----------|------------------|
|
|
| `low` | AAC | 64kbps | 22.05kHz | 1 (mono) | 4s |
|
|
| `medium`| AAC | 128kbps | 44.1kHz | 2 (stereo) | 4s |
|
|
| `high` | AAC | 192kbps | 44.1kHz | 2 (stereo) | 4s |
|
|
| `hi_res`| AAC | 320kbps | 48kHz | 2 (stereo) | 4s |
|
|
|
|
### Rationale
|
|
|
|
- **Low (64kbps, mono)**: Optimisé pour faible bande passante (mobile, 3G)
|
|
- **Medium (128kbps, stéréo)**: Qualité standard pour streaming web
|
|
- **High (192kbps, stéréo)**: Haute qualité pour audiophiles
|
|
- **Hi-Res (320kbps, stéréo)**: Qualité maximale (premium)
|
|
|
|
**Note**: Opus pourra être ajouté plus tard pour ultra-low latency (<5ms).
|
|
|
|
---
|
|
|
|
## ⚠️ Gestion des Erreurs
|
|
|
|
### Erreurs FFmpeg Courantes
|
|
|
|
| Erreur | Cause | Solution |
|
|
|--------|-------|----------|
|
|
| `FFmpeg exited with status: 1` | Fichier source invalide ou codec manquant | Vérifier le fichier source, installer codecs |
|
|
| `HLS manifest not generated` | FFmpeg a échoué silencieusement | Vérifier stderr FFmpeg, logs |
|
|
| `Encoding timed out` | Fichier trop long ou CPU saturé | Augmenter timeout ou réduire workers |
|
|
| `Failed to create output directory` | Permissions insuffisantes | Vérifier permissions `/data/streams` |
|
|
|
|
### Statuts de Job
|
|
|
|
- `pending`: Job en attente dans la queue
|
|
- `encoding`: Job en cours de traitement
|
|
- `done`: Job terminé avec succès
|
|
- `error`: Job échoué (voir `error_message`)
|
|
|
|
### Retry Logic
|
|
|
|
- **Max retries**: 3
|
|
- **Conditions**: Erreurs temporaires (timeout, IO)
|
|
- **Pas de retry**: Erreurs permanentes (fichier invalide, codec manquant)
|
|
|
|
---
|
|
|
|
## 🧪 Tests
|
|
|
|
### Tests Unitaires
|
|
|
|
#### Command Builder
|
|
```rust
|
|
#[test]
|
|
fn test_build_hls_command() {
|
|
let cmd = FfmpegCommandBuilder::new()
|
|
.input("input.wav")
|
|
.output("output/index.m3u8")
|
|
.audio_codec(AudioCodec::AAC)
|
|
.bitrate(128000)
|
|
.container(ContainerFormat::HLS)
|
|
.hls_time(4)
|
|
.build()
|
|
.unwrap();
|
|
|
|
// Vérifier que la commande contient les bonnes options
|
|
assert!(cmd.get_args().contains(&"-threads".to_string()));
|
|
assert!(cmd.get_args().contains(&"-map".to_string()));
|
|
}
|
|
```
|
|
|
|
#### Progress Parser
|
|
```rust
|
|
#[test]
|
|
fn test_parse_ffmpeg_progress() {
|
|
let line = "size=1024kB time=00:00:05.12 bitrate=128.0kbits/s speed=10.2x";
|
|
let progress = FfmpegProgress::parse(line).unwrap();
|
|
|
|
assert_eq!(progress.time, Some(Duration::from_millis(5120)));
|
|
assert_eq!(progress.speed, Some(10.2));
|
|
}
|
|
```
|
|
|
|
### Tests d'Intégration
|
|
|
|
#### Setup
|
|
```bash
|
|
# Créer un fichier audio de test
|
|
ffmpeg -f lavfi -i "sine=frequency=440:duration=10" /tmp/test_audio.wav
|
|
```
|
|
|
|
#### Test Complet
|
|
```rust
|
|
#[tokio::test]
|
|
#[ignore] // Nécessite FFmpeg installé
|
|
async fn test_encode_track() {
|
|
let db_pool = create_test_pool().await;
|
|
let encoder_pool = FfmpegEncoderPool::new(db_pool.clone(), None).await.unwrap();
|
|
let service = EncodingService::new(
|
|
encoder_pool,
|
|
db_pool,
|
|
"/tmp/streams_test"
|
|
);
|
|
|
|
// Encoder un track de test
|
|
service.encode_track(track_id, "medium").await.unwrap();
|
|
|
|
// Attendre la fin (timeout 60s)
|
|
tokio::time::sleep(Duration::from_secs(60)).await;
|
|
|
|
// Vérifier que le manifest existe
|
|
let manifest_path = PathBuf::from("/tmp/streams_test")
|
|
.join(track_id.to_string())
|
|
.join("medium")
|
|
.join("index.m3u8");
|
|
|
|
assert!(manifest_path.exists());
|
|
|
|
// Vérifier que les segments sont en DB
|
|
let segments = sqlx::query!(
|
|
"SELECT COUNT(*) FROM stream_segments WHERE track_id = $1 AND quality = 'medium'",
|
|
track_id
|
|
)
|
|
.fetch_one(&db_pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(segments.count.unwrap_or(0) > 0);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🛠️ Instructions de Développement
|
|
|
|
### Prérequis
|
|
|
|
```bash
|
|
# Installer FFmpeg
|
|
sudo apt-get update
|
|
sudo apt-get install -y ffmpeg
|
|
|
|
# Vérifier la version
|
|
ffmpeg -version
|
|
# Version minimale: 4.0+
|
|
```
|
|
|
|
### Configuration
|
|
|
|
#### Variables d'Environnement
|
|
|
|
```bash
|
|
# Répertoire de sortie pour les segments HLS
|
|
export STREAM_OUTPUT_DIR=/data/streams
|
|
|
|
# URL de la base de données
|
|
export DATABASE_URL=postgresql://veza:password@localhost:5432/veza_db
|
|
```
|
|
|
|
#### Migrations SQL
|
|
|
|
```bash
|
|
# Appliquer les migrations
|
|
psql $DATABASE_URL -f migrations/001_create_stream_jobs.sql
|
|
psql $DATABASE_URL -f migrations/002_create_stream_segments.sql
|
|
```
|
|
|
|
### Compilation
|
|
|
|
```bash
|
|
cd veza-stream-server
|
|
cargo build --release
|
|
```
|
|
|
|
### Exécution
|
|
|
|
```bash
|
|
# Démarrer le serveur
|
|
cargo run --release
|
|
|
|
# Ou avec variables d'environnement
|
|
STREAM_OUTPUT_DIR=/data/streams cargo run --release
|
|
```
|
|
|
|
### Tests
|
|
|
|
```bash
|
|
# Tests unitaires (sans FFmpeg)
|
|
cargo test
|
|
|
|
# Tests d'intégration (nécessite FFmpeg)
|
|
cargo test -- --ignored
|
|
```
|
|
|
|
### Monitoring
|
|
|
|
#### Logs
|
|
|
|
Les logs sont émis via `tracing` :
|
|
- `info!`: Événements importants (démarrage workers, jobs terminés)
|
|
- `debug!`: Progression FFmpeg (time, speed)
|
|
- `warn!`: Erreurs non-critiques (retry, timeout)
|
|
- `error!`: Erreurs critiques (échec encodage, crash FFmpeg)
|
|
|
|
#### Métriques Prometheus
|
|
|
|
```
|
|
# Nombre de jobs en cours
|
|
stream_encoding_jobs_active{quality="medium"} 2
|
|
|
|
# Nombre de jobs terminés
|
|
stream_encoding_jobs_completed_total{quality="medium"} 150
|
|
|
|
# Nombre de jobs échoués
|
|
stream_encoding_jobs_failed_total{quality="medium"} 5
|
|
|
|
# Temps moyen d'encodage (secondes)
|
|
stream_encoding_duration_seconds{quality="medium"} 45.2
|
|
```
|
|
|
|
### Dépannage
|
|
|
|
#### FFmpeg non trouvé
|
|
|
|
```bash
|
|
# Vérifier que FFmpeg est installé
|
|
which ffmpeg
|
|
|
|
# Vérifier les permissions
|
|
ls -la /data/streams
|
|
```
|
|
|
|
#### Erreurs de DB
|
|
|
|
```bash
|
|
# Vérifier la connexion
|
|
psql $DATABASE_URL -c "SELECT COUNT(*) FROM stream_jobs;"
|
|
|
|
# Vérifier les tables
|
|
psql $DATABASE_URL -c "\dt stream_*"
|
|
```
|
|
|
|
#### Workers bloqués
|
|
|
|
```bash
|
|
# Vérifier les processus FFmpeg
|
|
ps aux | grep ffmpeg
|
|
|
|
# Tuer les processus bloqués
|
|
pkill -9 ffmpeg
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 Performance
|
|
|
|
### Benchmarks
|
|
|
|
| Fichier Source | Durée | Qualité | Temps Encodage | CPU Usage |
|
|
|----------------|-------|---------|----------------|-----------|
|
|
| 3 min MP3 (128kbps) | 3:00 | medium | ~15s | 25% |
|
|
| 5 min WAV (44.1kHz) | 5:00 | high | ~30s | 40% |
|
|
| 10 min FLAC (96kHz) | 10:00 | hi_res | ~90s | 60% |
|
|
|
|
### Optimisations
|
|
|
|
1. **Pool de Workers**: Limiter à `min(nb_cpu / 2, 8)` pour éviter saturation
|
|
2. **Isolation CPU**: `-threads 1` par processus FFmpeg
|
|
3. **Cache**: Réutiliser les encodeurs si possible (future feature)
|
|
4. **Priorité**: Jobs urgents en tête de queue
|
|
|
|
---
|
|
|
|
## 🔮 Roadmap
|
|
|
|
### Phase 1 (Actuel) ✅
|
|
- [x] Pool d'encodeurs FFmpeg
|
|
- [x] Support HLS avec segments
|
|
- [x] API REST complète
|
|
- [x] Persistance en DB
|
|
|
|
### Phase 2 (Future)
|
|
- [ ] Support Opus pour ultra-low latency
|
|
- [ ] Adaptive bitrate (ABR) automatique
|
|
- [ ] Cache des encodeurs réutilisables
|
|
- [ ] Cleanup automatique des segments anciens
|
|
|
|
### Phase 3 (Future)
|
|
- [ ] Streaming live (pas seulement VOD)
|
|
- [ ] Support DASH en plus de HLS
|
|
- [ ] Hardware acceleration (GPU encoding)
|
|
- [ ] Multi-pass encoding pour meilleure qualité
|
|
|
|
---
|
|
|
|
## 📚 Références
|
|
|
|
- [HLS Specification (Apple)](https://developer.apple.com/streaming/)
|
|
- [FFmpeg Documentation](https://ffmpeg.org/documentation.html)
|
|
- [m3u8-rs Crate](https://docs.rs/m3u8-rs/)
|
|
|
|
---
|
|
|
|
**Auteur**: Veza Stream Server Team
|
|
**Dernière mise à jour**: 2025-01-27
|
|
|