460 lines
13 KiB
Markdown
460 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
|
||
|
|
|