feat: Implémentation complète de la source Radio France avec intégration UPnP et serveur HTTP
Ajout de la source Radio France avec : - Implémentation du trait MusicSource pour l'intégration UPnP - Routes HTTP API REST pour l'accès aux stations et flux AAC - Proxy streaming avec tracking des connexions - Génération dynamique de l'arborescence UPnP - Cache multi-niveaux (stations, métadonnées, covers) - Support des ~70 stations (standalone, groupes, radios ICI) Fichiers créés : - pmoradiofrance/src/source.rs (implémentation MusicSource) - pmoradiofrance/src/server_ext.rs (routes HTTP et proxy streaming) - pmoradiofrance/assets/radiofrance-logo.webp (logo placeholder) Fichiers modifiés : - pmoradiofrance/src/lib.rs (re-exports et modules) - pmoradiofrance/Cargo.toml (dépendances server) - Cargo.toml (workspace) - pmomediaserver/Cargo.toml (feature radiofrance) - PMOMusic/Cargo.toml (feature radiofrance) - PMOMusic/src/main.rs (enregistrement automatique) Tests et validation : compilation OK, pattern respecté, feature-gating cohérent
This commit is contained in:
586
Blackboard/ToDiscuss/Support_AAC_streaming_pmoflac.md
Normal file
586
Blackboard/ToDiscuss/Support_AAC_streaming_pmoflac.md
Normal file
@@ -0,0 +1,586 @@
|
||||
** Tu dois suivre scrupuleusement les règles définies dans le fichier [@Rules.md](file:///Users/coissac/Sync/maison/Petite_maisons/src/pmomusic/Blackboard/Rules.md) **
|
||||
|
||||
** Cette tâche est une tâche de recherche et développement. Elle doit conduire à un prototype fonctionnel et/ou un rapport technique sur la faisabilité. **
|
||||
|
||||
# Support du streaming AAC dans pmoflac
|
||||
|
||||
## Contexte
|
||||
|
||||
Actuellement, `pmoflac` supporte le décodage streaming pour :
|
||||
- ✅ MP3 (via `minimp3`)
|
||||
- ✅ FLAC (via `claxon`)
|
||||
- ✅ Ogg Vorbis (via `lewton`)
|
||||
- ✅ Ogg Opus (via `opus`)
|
||||
- ✅ WAV (parsing manuel)
|
||||
- ✅ AIFF (parsing manuel)
|
||||
|
||||
**Manque critique** : Pas de support AAC, pourtant très utilisé pour :
|
||||
- Streams radio live (Radio France, etc.)
|
||||
- Podcasts
|
||||
- Services de streaming musicaux
|
||||
- Fichiers M4A/MP4
|
||||
|
||||
## Problématique
|
||||
|
||||
Le décodage AAC en **streaming infini** (radio live) est actuellement impossible dans `pmoflac`, ce qui force à :
|
||||
- Soit faire un proxy passthrough (pas de transcodage FLAC)
|
||||
- Soit utiliser une redirection 302 (pas de tracking)
|
||||
|
||||
Cela empêche d'avoir une expérience uniforme où toutes les sources servent du FLAC.
|
||||
|
||||
## Objectif
|
||||
|
||||
**Investiguer et prototyper** le support du décodage AAC streaming dans `pmoflac`, en s'inspirant de l'architecture existante (MP3, Ogg, etc.).
|
||||
|
||||
## Recherches préliminaires
|
||||
|
||||
### 1. Symphonia avec ReadOnlySource
|
||||
|
||||
[Symphonia](https://github.com/pdeljanov/Symphonia) est la bibliothèque Rust la plus complète pour le décodage audio. Elle fournit :
|
||||
|
||||
- **`ReadOnlySource`** : Wrapper pour sources non-seekable (streams infinis)
|
||||
- **`AdtsReader`** : Format reader spécifique pour ADTS (AAC streaming)
|
||||
- **`symphonia-codec-aac`** : Décodeur AAC-LC (Low Complexity)
|
||||
|
||||
**Points d'attention** :
|
||||
- [Issue connue](https://github.com/RustAudio/rodio/issues/580) : Certains formats peuvent quand même réclamer le seek
|
||||
- Nécessite de tester avec un vrai stream ADTS
|
||||
|
||||
### 2. Format ADTS
|
||||
|
||||
[ADTS](https://wiki.multimedia.cx/index.php/ADTS) (Audio Data Transport Stream) est le format AAC conçu pour le streaming :
|
||||
|
||||
- Auto-synchronisant : chaque frame a un header (12 bits `0xFFF`)
|
||||
- Pas de container nécessaire (MP4, M4A)
|
||||
- Utilisé par les radios en streaming
|
||||
- Chaque frame contient ses métadonnées (sample rate, channels, etc.)
|
||||
|
||||
**Structure** :
|
||||
```
|
||||
Frame 1: [ADTS Header 7-9 bytes][AAC Data]
|
||||
Frame 2: [ADTS Header 7-9 bytes][AAC Data]
|
||||
...
|
||||
```
|
||||
|
||||
### 3. Alternative : fdk-aac
|
||||
|
||||
[Bindings Rust pour fdk-aac](https://github.com/haileys/fdk-aac-rs) (bibliothèque Fraunhofer) :
|
||||
|
||||
**Avantages** :
|
||||
- ✅ Décodeur de référence (qualité maximale)
|
||||
- ✅ Support explicite du streaming chunk-by-chunk
|
||||
- ✅ Buffer interne géré automatiquement
|
||||
- ✅ Pas besoin de seek
|
||||
|
||||
**Inconvénients** :
|
||||
- ❌ Dépendance C (libfdk-aac)
|
||||
- ❌ Licence restrictive (non-commerciale pour certaines versions)
|
||||
- ❌ Compilation plus complexe
|
||||
|
||||
## Plan d'investigation
|
||||
|
||||
### Round 1 : Prototype Symphonia ADTS
|
||||
|
||||
**Objectif** : Tester si Symphonia peut décoder un stream AAC infini avec `ReadOnlySource` + `AdtsReader`.
|
||||
|
||||
#### Étapes
|
||||
|
||||
1. **Créer un module de test** : `pmoflac/tests/aac_streaming_test.rs`
|
||||
|
||||
2. **Implémenter un décodeur basique** :
|
||||
```rust
|
||||
use symphonia::core::io::{MediaSourceStream, ReadOnlySource};
|
||||
use symphonia::default::get_probe;
|
||||
use symphonia_codec_aac::AdtsReader;
|
||||
|
||||
async fn decode_aac_stream_test<R: AsyncRead + Unpin>(
|
||||
reader: R
|
||||
) -> Result<Vec<u8>> {
|
||||
// Wrapper AsyncRead → Read synchrone (pattern pmoflac)
|
||||
let sync_reader = blocking_reader_from_async(reader);
|
||||
|
||||
// ReadOnlySource pour stream infini
|
||||
let source = ReadOnlySource::new(sync_reader);
|
||||
let mss = MediaSourceStream::new(Box::new(source), Default::default());
|
||||
|
||||
// Probe avec hint AAC/ADTS
|
||||
let mut hint = Hint::new();
|
||||
hint.with_extension("aac");
|
||||
|
||||
let mut format = get_probe()
|
||||
.format(&hint, mss, &Default::default(), &Default::default())?;
|
||||
|
||||
// Récupérer le track audio
|
||||
let track = format.default_track().unwrap();
|
||||
let mut decoder = symphonia::default::get_codecs()
|
||||
.make(&track.codec_params, &Default::default())?;
|
||||
|
||||
let mut pcm_output = Vec::new();
|
||||
|
||||
// Décoder frame par frame (boucle infinie jusqu'à disconnect)
|
||||
loop {
|
||||
match format.next_packet() {
|
||||
Ok(packet) => {
|
||||
let decoded = decoder.decode(&packet)?;
|
||||
// Convertir en PCM et accumuler
|
||||
let samples = convert_to_pcm_bytes(decoded);
|
||||
pcm_output.extend_from_slice(&samples);
|
||||
}
|
||||
Err(symphonia::core::errors::Error::IoError(e))
|
||||
if e.kind() == std::io::ErrorKind::UnexpectedEof => {
|
||||
break; // Stream fermé
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(pcm_output)
|
||||
}
|
||||
```
|
||||
|
||||
3. **Tester avec un fichier AAC ADTS statique** :
|
||||
- Télécharger un échantillon AAC ADTS
|
||||
- Vérifier que le décodage fonctionne
|
||||
- Comparer PCM output avec ffmpeg
|
||||
|
||||
4. **Tester avec un stream Radio France live** :
|
||||
```rust
|
||||
#[tokio::test]
|
||||
#[ignore = "Requires network"]
|
||||
async fn test_decode_radiofrance_stream() {
|
||||
let stream_url = "https://icecast.radiofrance.fr/fip-hifi.aac";
|
||||
let response = reqwest::get(stream_url).await.unwrap();
|
||||
let reader = response.bytes_stream();
|
||||
|
||||
// Lire 10 secondes de stream
|
||||
let pcm = decode_aac_stream_test(reader).await.unwrap();
|
||||
|
||||
assert!(!pcm.is_empty());
|
||||
// Vérifier format PCM (44.1kHz ou 48kHz, stéréo, 16-bit)
|
||||
}
|
||||
```
|
||||
|
||||
#### Critères de succès Round 1
|
||||
|
||||
- ✅ Le décodeur accepte un `ReadOnlySource` sans erreur de seek
|
||||
- ✅ Les frames ADTS sont correctement parsées
|
||||
- ✅ Le décodage AAC → PCM fonctionne
|
||||
- ✅ Un stream live (infini) peut être décodé sans plantage
|
||||
- ✅ Le PCM output est valide (vérifiable avec `ffplay`)
|
||||
|
||||
#### Livrables Round 1
|
||||
|
||||
1. **Module de test** : `pmoflac/tests/aac_streaming_test.rs`
|
||||
2. **Rapport technique** : `Blackboard/Report/Support_AAC_streaming_pmoflac.md`
|
||||
- Résultats des tests
|
||||
- Problèmes rencontrés (seek, parsing, etc.)
|
||||
- Métriques de performance (CPU, latence)
|
||||
- Comparaison qualité avec ffmpeg
|
||||
|
||||
---
|
||||
|
||||
### Round 2 : Intégration dans pmoflac (si Round 1 réussit)
|
||||
|
||||
**Objectif** : Intégrer le décodeur AAC dans l'architecture streaming de `pmoflac`.
|
||||
|
||||
#### Fichiers à créer/modifier
|
||||
|
||||
**1. `pmoflac/src/aac.rs`** (nouveau)
|
||||
|
||||
```rust
|
||||
use symphonia::core::io::{MediaSourceStream, ReadOnlySource};
|
||||
use tokio::sync::mpsc;
|
||||
use crate::{
|
||||
common::ChannelReader,
|
||||
decoder_common::{spawn_ingest_task, spawn_writer_task, DecodedStream},
|
||||
pcm::StreamInfo,
|
||||
};
|
||||
|
||||
pub type AacDecodedStream = DecodedStream<AacError>;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum AacError {
|
||||
#[error("AAC decode error: {0}")]
|
||||
Decode(String),
|
||||
#[error("I/O error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Channel closed")]
|
||||
ChannelClosed,
|
||||
}
|
||||
|
||||
/// Décoder un stream AAC/ADTS en PCM
|
||||
pub async fn decode_aac_stream<R>(reader: R) -> Result<AacDecodedStream, AacError>
|
||||
where
|
||||
R: AsyncRead + Unpin + Send + 'static,
|
||||
{
|
||||
// Suivre le pattern existant (MP3, FLAC, etc.)
|
||||
let (ingest_tx, ingest_rx) = mpsc::channel(CHANNEL_CAPACITY);
|
||||
spawn_ingest_task(reader, ingest_tx);
|
||||
|
||||
let (pcm_tx, pcm_rx) = mpsc::channel(CHANNEL_CAPACITY);
|
||||
let (pcm_reader, pcm_writer) = tokio::io::duplex(DUPLEX_BUFFER_SIZE);
|
||||
let (info_tx, info_rx) = oneshot::channel::<Result<StreamInfo, AacError>>();
|
||||
|
||||
let blocking_handle = tokio::task::spawn_blocking(move || -> Result<(), AacError> {
|
||||
let mut channel_reader = ChannelReader::<AacError>::new(ingest_rx);
|
||||
|
||||
// ReadOnlySource pour stream infini
|
||||
let source = ReadOnlySource::new(&mut channel_reader);
|
||||
let mss = MediaSourceStream::new(Box::new(source), Default::default());
|
||||
|
||||
// Probe AAC/ADTS
|
||||
let mut hint = Hint::new();
|
||||
hint.with_extension("aac");
|
||||
|
||||
let mut format = get_probe()
|
||||
.format(&hint, mss, &Default::default(), &Default::default())
|
||||
.map_err(|e| AacError::Decode(e.to_string()))?;
|
||||
|
||||
let track = format.default_track()
|
||||
.ok_or_else(|| AacError::Decode("No audio track found".into()))?;
|
||||
|
||||
let mut decoder = get_codecs()
|
||||
.make(&track.codec_params, &Default::default())
|
||||
.map_err(|e| AacError::Decode(e.to_string()))?;
|
||||
|
||||
// Extraire StreamInfo
|
||||
let codec_params = &track.codec_params;
|
||||
let info = StreamInfo {
|
||||
sample_rate: codec_params.sample_rate.unwrap_or(48000),
|
||||
channels: codec_params.channels.unwrap().count() as u8,
|
||||
bits_per_sample: 16, // AAC decode to 16-bit PCM
|
||||
total_samples: None, // Stream infini
|
||||
max_block_size: 0,
|
||||
min_block_size: 0,
|
||||
};
|
||||
|
||||
if info_tx.send(Ok(info.clone())).is_err() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Boucle de décodage
|
||||
loop {
|
||||
match format.next_packet() {
|
||||
Ok(packet) => {
|
||||
let decoded = decoder.decode(&packet)
|
||||
.map_err(|e| AacError::Decode(e.to_string()))?;
|
||||
|
||||
// Convertir AudioBufferRef → bytes PCM
|
||||
let pcm_bytes = convert_audio_buffer_to_bytes(decoded, &info);
|
||||
|
||||
if pcm_tx.blocking_send(Ok(pcm_bytes)).is_err() {
|
||||
break; // Reader fermé
|
||||
}
|
||||
}
|
||||
Err(symphonia::core::errors::Error::IoError(e))
|
||||
if e.kind() == std::io::ErrorKind::UnexpectedEof => {
|
||||
break; // Stream terminé normalement
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = e.to_string();
|
||||
let _ = pcm_tx.blocking_send(Err(AacError::Decode(msg.clone())));
|
||||
return Err(AacError::Decode(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let writer_handle = spawn_writer_task(pcm_rx, pcm_writer, blocking_handle, "aac-decode");
|
||||
let info = info_rx.await.map_err(|_| AacError::ChannelClosed)??;
|
||||
let reader = ManagedAsyncReader::new("aac-decode-writer", pcm_reader, writer_handle);
|
||||
|
||||
Ok(DecodedStream::new(info, reader))
|
||||
}
|
||||
|
||||
/// Convertir AudioBufferRef Symphonia → bytes PCM little-endian
|
||||
fn convert_audio_buffer_to_bytes(
|
||||
audio_buffer: AudioBufferRef,
|
||||
info: &StreamInfo,
|
||||
) -> Vec<u8> {
|
||||
// Implémenter conversion selon le type de buffer
|
||||
// (S16, S24, S32, F32, etc.) → i16 little-endian interleaved
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**2. `pmoflac/src/lib.rs`** (modifier)
|
||||
|
||||
```rust
|
||||
pub mod aac;
|
||||
|
||||
pub use aac::{decode_aac_stream, AacDecodedStream, AacError};
|
||||
```
|
||||
|
||||
**3. `pmoflac/src/autodetect.rs`** (modifier)
|
||||
|
||||
Ajouter la détection AAC/ADTS :
|
||||
|
||||
```rust
|
||||
fn detect_format(bytes: &[u8]) -> Option<DetectedFormat> {
|
||||
// ... détections existantes ...
|
||||
|
||||
// Détecter ADTS AAC (syncword 0xFFF)
|
||||
if is_adts(bytes) {
|
||||
return Some(DetectedFormat::Aac);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn is_adts(bytes: &[u8]) -> bool {
|
||||
if bytes.len() < 2 {
|
||||
return false;
|
||||
}
|
||||
// ADTS syncword: 12 bits à 1 (0xFFF)
|
||||
bytes[0] == 0xFF && (bytes[1] & 0xF0) == 0xF0
|
||||
}
|
||||
|
||||
pub enum DecodedAudioStream {
|
||||
// ... variants existants ...
|
||||
Aac(AacDecodedStream),
|
||||
}
|
||||
```
|
||||
|
||||
**4. `pmoflac/src/transcode.rs`** (modifier)
|
||||
|
||||
Ajouter AAC au transcodeur :
|
||||
|
||||
```rust
|
||||
pub enum AudioCodec {
|
||||
// ... codecs existants ...
|
||||
Aac,
|
||||
}
|
||||
|
||||
pub async fn transcode_to_flac_stream<R>(
|
||||
reader: R,
|
||||
options: TranscodeOptions,
|
||||
) -> Result<TranscodeToFlac, TranscodeError>
|
||||
where
|
||||
R: AsyncRead + Unpin + Send + 'static,
|
||||
{
|
||||
// ... détection auto ...
|
||||
|
||||
match decoded {
|
||||
// ... cas existants ...
|
||||
DecodedAudioStream::Aac(stream) => {
|
||||
transcode_from_decoded(AudioCodec::Aac, stream, options.encoder_options).await
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**5. `pmoflac/Cargo.toml`** (modifier)
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
# ... dépendances existantes ...
|
||||
|
||||
# AAC support
|
||||
symphonia = { version = "0.5", features = ["aac", "isomp4"], optional = true }
|
||||
symphonia-core = { version = "0.5", optional = true }
|
||||
symphonia-codec-aac = { version = "0.5", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["mp3", "ogg", "opus", "wav", "aiff"]
|
||||
aac = ["dep:symphonia", "dep:symphonia-core", "dep:symphonia-codec-aac"]
|
||||
all = ["mp3", "ogg", "opus", "wav", "aiff", "aac"]
|
||||
```
|
||||
|
||||
#### Tests Round 2
|
||||
|
||||
**Tests unitaires** :
|
||||
```rust
|
||||
#[tokio::test]
|
||||
async fn test_decode_aac_to_pcm() {
|
||||
let aac_data = include_bytes!("../test-data/sample.aac");
|
||||
let stream = decode_aac_stream(&aac_data[..]).await.unwrap();
|
||||
|
||||
let info = stream.info();
|
||||
assert_eq!(info.sample_rate, 48000);
|
||||
assert_eq!(info.channels, 2);
|
||||
|
||||
// Lire quelques samples
|
||||
let mut buffer = vec![0u8; 4096];
|
||||
let mut reader = stream;
|
||||
let n = reader.read(&mut buffer).await.unwrap();
|
||||
assert!(n > 0);
|
||||
}
|
||||
```
|
||||
|
||||
**Tests intégration** :
|
||||
```rust
|
||||
#[tokio::test]
|
||||
#[ignore = "Integration test - network required"]
|
||||
async fn test_transcode_radiofrance_to_flac() {
|
||||
let stream_url = "https://icecast.radiofrance.fr/fip-hifi.aac";
|
||||
let response = reqwest::get(stream_url).await.unwrap();
|
||||
let reader = response.bytes_stream();
|
||||
|
||||
let transcoded = transcode_to_flac_stream(
|
||||
reader,
|
||||
TranscodeOptions::default()
|
||||
).await.unwrap();
|
||||
|
||||
assert_eq!(transcoded.input_codec(), AudioCodec::Aac);
|
||||
assert_eq!(transcoded.input_stream_info().sample_rate, 48000);
|
||||
|
||||
// Lire 5 secondes de FLAC
|
||||
let mut output = Vec::new();
|
||||
let mut stream = transcoded.into_stream();
|
||||
|
||||
for _ in 0..50 {
|
||||
let mut chunk = vec![0u8; 8192];
|
||||
stream.read(&mut chunk).await.unwrap();
|
||||
output.extend_from_slice(&chunk);
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
assert!(output.len() > 100_000); // Au moins 100 KB de FLAC
|
||||
}
|
||||
```
|
||||
|
||||
#### Critères de succès Round 2
|
||||
|
||||
- ✅ `decode_aac_stream()` suit le pattern existant (MP3, Ogg, etc.)
|
||||
- ✅ Auto-détection AAC/ADTS fonctionne
|
||||
- ✅ Transcodage AAC → FLAC streaming opérationnel
|
||||
- ✅ Tests unitaires et intégration passent
|
||||
- ✅ Documentation complète (doctests, exemples)
|
||||
- ✅ Feature flag `aac` pour compilation optionnelle
|
||||
|
||||
---
|
||||
|
||||
### Round 3 : Intégration dans pmoradiofrance (si Round 2 réussit)
|
||||
|
||||
**Objectif** : Remplacer le proxy AAC passthrough par un transcodage FLAC.
|
||||
|
||||
#### Modifications
|
||||
|
||||
**1. `pmoradiofrance/src/server_ext.rs`**
|
||||
|
||||
Remplacer le proxy passthrough par un transcodage :
|
||||
|
||||
```rust
|
||||
async fn proxy_stream(
|
||||
Path(slug): Path<String>,
|
||||
State(state): State<Arc<RadioFranceServerState>>
|
||||
) -> Result<Response, (StatusCode, String)> {
|
||||
let stream_url = state.client.get_stream_url(&slug).await
|
||||
.map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
|
||||
|
||||
let response = reqwest::get(&stream_url).await
|
||||
.map_err(|e| (StatusCode::BAD_GATEWAY, e.to_string()))?;
|
||||
|
||||
// Transcoder AAC → FLAC avec pmoflac
|
||||
let transcoded = pmoflac::transcode_to_flac_stream(
|
||||
response.bytes_stream(),
|
||||
pmoflac::TranscodeOptions::default()
|
||||
).await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
// Enregistrer connexion active et démarrer metadata refresh
|
||||
// ...
|
||||
|
||||
// Stream FLAC au lieu d'AAC
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("Content-Type", "audio/flac".parse().unwrap());
|
||||
headers.insert("Cache-Control", "no-cache".parse().unwrap());
|
||||
|
||||
Ok((headers, Body::from_stream(transcoded.into_stream())).into_response())
|
||||
}
|
||||
```
|
||||
|
||||
**2. `pmoradiofrance/src/playlist.rs`**
|
||||
|
||||
Changer le protocol_info pour FLAC :
|
||||
|
||||
```rust
|
||||
// Avant (AAC)
|
||||
protocol_info: "http-get:*:audio/aac:*"
|
||||
|
||||
// Après (FLAC)
|
||||
protocol_info: "http-get:*:audio/flac:*"
|
||||
sample_frequency: Some(info.sample_rate.to_string())
|
||||
bits_per_sample: Some("16".to_string())
|
||||
```
|
||||
|
||||
#### Critères de succès Round 3
|
||||
|
||||
- ✅ Radio France sert du FLAC au lieu d'AAC
|
||||
- ✅ Uniformité : toutes les sources PMOMusic servent du FLAC
|
||||
- ✅ Latence acceptable (<2s) pour le streaming live
|
||||
- ✅ CPU raisonnable pour 2-3 streams simultanés sur LAN
|
||||
- ✅ Métadonnées volatiles toujours mises à jour
|
||||
|
||||
---
|
||||
|
||||
## Alternative : fdk-aac (si Symphonia échoue)
|
||||
|
||||
Si Symphonia ne fonctionne pas en streaming infini, explorer `fdk-aac` :
|
||||
|
||||
### Avantages
|
||||
- ✅ Décodeur de référence (meilleure qualité)
|
||||
- ✅ Conçu pour le streaming
|
||||
- ✅ Utilisé en production (Android, etc.)
|
||||
|
||||
### Inconvénients
|
||||
- ❌ Dépendance C (compilation complexe)
|
||||
- ❌ Licence restrictive (vérifier compatibilité projet)
|
||||
|
||||
### Prototype minimal
|
||||
|
||||
```rust
|
||||
use fdk_aac::dec::{Decoder, DecoderParams};
|
||||
|
||||
pub async fn decode_aac_with_fdk<R>(reader: R) -> Result<AacDecodedStream>
|
||||
where
|
||||
R: AsyncRead + Unpin + Send + 'static,
|
||||
{
|
||||
// Similar pattern to pmoflac MP3 decoder
|
||||
// spawn_blocking pour le décodeur C
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Résultats attendus
|
||||
|
||||
### Minimum viable (Round 1)
|
||||
|
||||
- ✅ Rapport technique sur la faisabilité du streaming AAC avec Symphonia
|
||||
- ✅ Prototype fonctionnel (même basique)
|
||||
- ✅ Identification des limitations et solutions de contournement
|
||||
|
||||
### Objectif complet (Round 1-3)
|
||||
|
||||
- ✅ Support AAC/ADTS dans `pmoflac` (feature flag optionnelle)
|
||||
- ✅ Transcodage AAC → FLAC streaming opérationnel
|
||||
- ✅ Radio France servant du FLAC uniforme
|
||||
- ✅ Documentation et tests complets
|
||||
|
||||
### En cas d'échec
|
||||
|
||||
- ✅ Rapport détaillé des blocages techniques
|
||||
- ✅ Recommandations alternatives (fdk-aac, attendre évolution Symphonia, etc.)
|
||||
- ✅ Garder le proxy AAC passthrough actuel
|
||||
|
||||
---
|
||||
|
||||
## Références
|
||||
|
||||
### Documentation
|
||||
- [Symphonia Getting Started](https://github.com/pdeljanov/Symphonia/blob/master/GETTING_STARTED.md)
|
||||
- [AdtsReader API](https://docs.rs/symphonia-codec-aac/latest/symphonia_codec_aac/struct.AdtsReader.html)
|
||||
- [ADTS Format Specification](https://wiki.multimedia.cx/index.php/ADTS)
|
||||
- [fdk-aac Rust Bindings](https://github.com/haileys/fdk-aac-rs)
|
||||
|
||||
### Issues et discussions
|
||||
- [Symphonia ReadOnlySource Issue #580](https://github.com/RustAudio/rodio/issues/580)
|
||||
- [Symphonia MediaSource Trait](https://docs.rs/symphonia-core/latest/symphonia_core/io/index.html)
|
||||
|
||||
### Contexte PMOMusic
|
||||
- [Task Radio France](file:///Users/coissac/Sync/maison/Petite_maisons/src/pmomusic/Blackboard/ToDiscuss/Construire_pmoradiofrance.md)
|
||||
- [Architecture pmoflac](file:///Users/coissac/Sync/maison/Petite_maisons/src/pmomusic/pmoflac/src/lib.rs)
|
||||
Reference in New Issue
Block a user