Enhanced logging to INFO level for key TimerNode operations: - TopZeroSync reception and timer reset - Sleep operations when lead_time exceeds max_lead_time - Warning when no timer is set (missing TopZeroSync) This diagnostic logging confirmed that: 1. TopZeroSync is properly received from RadioParadiseStreamSource 2. TimerNode correctly calculates lead_time and sleeps ~48ms per 50ms chunk 3. Real-time pacing is working as expected (3.0s max lead time) The backpressure mechanism is functioning correctly - chunks flow at real-time speed (~50ms per chunk) rather than downloading at maximum speed.
PMOAudio
Pipeline audio stéréo async optimisé pour Rust, utilisant Tokio.
Caractéristiques
- Pipeline push-based async : Tous les nodes utilisent Tokio pour un traitement non-bloquant
- Zero-copy optimisé : Les données audio sont partagées via
Arc<Vec<f32>>pour éviter les clonages inutiles - Support multiroom : BufferNode avec buffer circulaire et offsets indépendants par abonné
- TimerNode : Calcul de position temporelle en temps réel
- Backpressure : Channels bounded avec
try_sendpour éviter les blocages
Architecture
AudioChunk
Structure de données pour un chunk audio stéréo :
pub struct AudioChunk {
pub order: u64, // Numéro d'ordre
pub left: Arc<Vec<f32>>, // Canal gauche (partagé)
pub right: Arc<Vec<f32>>, // Canal droit (partagé)
pub sample_rate: u32, // Taux d'échantillonnage
}
Les données sont wrappées dans Arc pour permettre le partage sans copie entre plusieurs abonnés.
Nodes
SingleSubscriberNode
- Un seul abonné
- Pas de clone inutile du Arc
MultiSubscriberNode
- Plusieurs abonnés
- Partage le même
Arc<AudioChunk>avec tous
SourceNode
- Génère ou lit des chunks audio
- Version mock avec génération de sinusoïdes pour tests
DecoderNode
- Décode les chunks audio
- Supporte le passthrough et le resampling (mock)
DspNode
- Applique des transformations DSP
- Clone les données uniquement si modification nécessaire
- Exemple : gain, filtrage
BufferNode
- Buffer circulaire (
VecDeque<Arc<AudioChunk>>) - Support multiroom avec offsets indépendants
try_sendnon-bloquant pour éviter de bloquer la source
TimerNode
- Node passthrough qui ne modifie pas les données
- Incrémente un compteur de samples
- Calcule la position :
position_sec = elapsed_samples / sample_rate - Fournit un
TimerHandlepour monitoring
SinkNode
- Node terminal qui consomme les chunks
- Versions : silent, logging, stats, mock file writer
Pipeline type
SourceNode → DecoderNode → DSPNode → BufferNode → TimerNode → SinkNode(s)
↓
Multiroom Sinks
(avec offsets)
Exemples
Pipeline simple
use pmoaudio::{SinkNode, SourceNode, TimerNode};
#[tokio::main]
async fn main() {
let (mut timer, timer_tx) = TimerNode::new(10);
let (sink, sink_tx) = SinkNode::new("Output".to_string(), 10);
timer.add_subscriber(sink_tx);
let timer_handle = timer.get_position_handle();
tokio::spawn(async move { timer.run().await.unwrap() });
let sink_handle = tokio::spawn(async move {
sink.run_with_stats().await.unwrap()
});
tokio::spawn(async move {
let mut source = SourceNode::new();
source.add_subscriber(timer_tx);
source.generate_chunks(30, 4800, 48000, 440.0).await.unwrap();
});
sink_handle.await.unwrap();
}
Multiroom
let (buffer, buffer_tx) = BufferNode::new(50, 10);
let (sink1, sink1_tx) = SinkNode::new("Room 1".to_string(), 10);
let (sink2, sink2_tx) = SinkNode::new("Room 2".to_string(), 10);
buffer.add_subscriber_with_offset(sink1_tx, 0).await; // Pas de délai
buffer.add_subscriber_with_offset(sink2_tx, 5).await; // 5 chunks de retard
Lancer les exemples
# Pipeline simple
cargo run --example simple_pipeline
# Pipeline complet avec tous les nodes
cargo run --example pipeline_demo
# Configuration multiroom
cargo run --example multiroom_demo
# Streaming avec timing réel
cargo run --example streaming_demo
Tests
cargo test
20 tests unitaires couvrant :
- Propagation des chunks
- Calcul de position par TimerNode
- BufferNode multi-abonné avec offsets
- Arc sharing et zero-copy
- DSP avec gain et filtrage
- Resampling
Optimisations
- Arc sharing : Les
AudioChunksont clonés viaArc::clone()qui ne clone que le pointeur - Copy-on-Write : Les DSP nodes clonent les données uniquement si modification nécessaire
- Bounded channels : Backpressure automatique
- try_send : Non-bloquant pour BufferNode, permet de sauter des chunks si un abonné est saturé
- RwLock : Pour partage concurrent du compteur TimerNode
Dépendances
tokio: Runtime async et channelsasync-trait: Traits async
License
CeCill-2.0