Files
pmomusic/pmoaudio
Claude 30d30739bd Refactor AudioSink: remove volume, use dsp optimized conversions
Changes:
- Remove all volume management (use VolumeNode in pipeline instead)
- Detect hardware format (I16/U16/F32) at startup
- Accept all AudioChunk formats (I16/I24/I32/F32/F64) as input
- Use optimized SIMD functions from dsp::int_float module
- SharedBuffer stores raw AudioChunk + intermediate F32 buffer
- Callbacks adapted to hardware format with proper conversion

Architecture:
1. AudioChunk pushed to SharedBuffer
2. Lazy conversion to F32 interleaved using dsp functions
3. Callback converts F32 → hardware format (I16/U16) if needed

Benefits:
- SIMD optimized conversions (dsp module)
- Clean separation of concerns (volume in VolumeNode)
- Hardware format detection (use native format when possible)
- Flexible input (accepts any AudioChunk type)

Note: Requires ALSA (libasound2-dev) on Linux for compilation
2025-11-05 18:25:08 +00:00
..

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_send pour é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_send non-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 TimerHandle pour 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

  1. Arc sharing : Les AudioChunk sont clonés via Arc::clone() qui ne clone que le pointeur
  2. Copy-on-Write : Les DSP nodes clonent les données uniquement si modification nécessaire
  3. Bounded channels : Backpressure automatique
  4. try_send : Non-bloquant pour BufferNode, permet de sauter des chunks si un abonné est saturé
  5. RwLock : Pour partage concurrent du compteur TimerNode

Dépendances

  • tokio : Runtime async et channels
  • async-trait : Traits async

License

CeCill-2.0