# 🎵 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/// │ │ - Crée EncodeJob et l'envoie dans la queue │ └────────────────────┬────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ Encoder Pool (Worker Threads) │ │ - Queue: async_channel::Receiver │ │ - Workers: Vec (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/ / 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