# 🎵 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

