2025-10-28 18:41:34 +01:00
|
|
|
#![cfg_attr(feature = "simd", feature(portable_simd))]
|
|
|
|
|
#![doc = r#"
|
|
|
|
|
PMOAudio - Pipeline audio stéréo async optimisé
|
|
|
|
|
|
|
|
|
|
Cette crate fournit un pipeline audio push-based async utilisant Tokio,
|
|
|
|
|
optimisé pour minimiser les clonages de données via `Arc<[[i32; 2]]>`.
|
|
|
|
|
|
|
|
|
|
# Architecture
|
|
|
|
|
|
|
|
|
|
Le pipeline est composé de nodes asynchrones qui communiquent via des channels Tokio.
|
|
|
|
|
Les données audio sont encapsulées dans des [`AudioChunk`] et partagées via `Arc` pour
|
|
|
|
|
éviter les copies inutiles.
|
|
|
|
|
|
|
|
|
|
## Pipeline type
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
SourceNode → DecoderNode → DSPNode → BufferNode → TimerNode → SinkNode(s)
|
|
|
|
|
↓
|
|
|
|
|
Multiroom Sinks
|
|
|
|
|
(avec offsets)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# Exemples
|
|
|
|
|
|
|
|
|
|
## Pipeline simple
|
|
|
|
|
|
|
|
|
|
```no_run
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Configuration multiroom
|
|
|
|
|
|
|
|
|
|
```no_run
|
|
|
|
|
use pmoaudio::{BufferNode, SinkNode};
|
|
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
|
async fn main() {
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
// Room 1 sans délai, Room 2 avec 5 chunks de retard
|
|
|
|
|
buffer.add_subscriber_with_offset(sink1_tx, 0).await;
|
|
|
|
|
buffer.add_subscriber_with_offset(sink2_tx, 5).await;
|
|
|
|
|
|
|
|
|
|
tokio::spawn(async move { buffer.run().await.unwrap() });
|
|
|
|
|
// ... spawn sinks et source
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# Optimisations
|
|
|
|
|
|
|
|
|
|
- **Zero-copy** : Les [`AudioChunk`] sont partagés via `Arc`, seul le pointeur est cloné
|
|
|
|
|
- **Copy-on-Write** : Les nodes DSP clonent les données uniquement si modification nécessaire
|
|
|
|
|
- **Backpressure** : Channels bounded avec `try_send` pour éviter les blocages
|
|
|
|
|
- **RwLock** : Pour partage concurrent du compteur [`TimerNode`]
|
|
|
|
|
"#]
|
|
|
|
|
#[cfg(feature = "simd")]
|
|
|
|
|
use std::simd::*;
|
2025-10-11 00:33:13 +02:00
|
|
|
|
|
|
|
|
mod audio_chunk;
|
2025-10-30 08:54:47 +01:00
|
|
|
mod audio_segment;
|
|
|
|
|
pub mod conversions;
|
2025-11-01 21:10:57 +01:00
|
|
|
pub mod events;
|
|
|
|
|
pub mod nodes;
|
2025-11-03 06:55:45 +01:00
|
|
|
pub mod pipeline;
|
2025-11-01 21:10:57 +01:00
|
|
|
mod sample_types;
|
|
|
|
|
mod sync_marker;
|
|
|
|
|
pub mod type_constraints;
|
2025-10-30 08:54:47 +01:00
|
|
|
#[macro_use]
|
|
|
|
|
mod macros;
|
2025-10-11 00:33:13 +02:00
|
|
|
|
2025-10-28 18:41:34 +01:00
|
|
|
pub mod bit_depth;
|
|
|
|
|
pub mod dsp;
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
pub use audio_segment::{AudioSegment, _AudioSegment};
|
2025-11-01 21:10:57 +01:00
|
|
|
pub use sync_marker::SyncMarker;
|
2025-10-30 08:54:47 +01:00
|
|
|
|
2025-11-01 21:10:57 +01:00
|
|
|
pub use audio_chunk::{
|
|
|
|
|
gain_db_from_linear, gain_linear_from_db, AudioChunk, AudioChunkData, AudioFloatChunk,
|
|
|
|
|
AudioIntegerChunk,
|
|
|
|
|
};
|
2025-10-28 18:41:34 +01:00
|
|
|
pub use bit_depth::{Bit16, Bit24, Bit32, Bit8, BitDepth};
|
2025-11-01 21:10:57 +01:00
|
|
|
pub use sample_types::{Sample, I24};
|
|
|
|
|
pub use type_constraints::{
|
|
|
|
|
check_compatibility, SampleType, TypeCategory, TypeMismatch, TypeRequirement,
|
|
|
|
|
};
|
2025-10-28 18:41:34 +01:00
|
|
|
|
2025-10-11 15:36:06 +02:00
|
|
|
pub use events::{
|
2025-10-19 13:42:29 +02:00
|
|
|
AudioDataEvent, EventPublisher, EventReceiver, NodeEvent, NodeListener, SourceNameUpdateEvent,
|
|
|
|
|
VolumeChangeEvent,
|
2025-10-11 15:36:06 +02:00
|
|
|
};
|
2025-10-30 08:54:47 +01:00
|
|
|
|
2025-11-03 06:55:45 +01:00
|
|
|
// Export du trait de pipeline
|
|
|
|
|
pub use pipeline::AudioPipelineNode;
|
|
|
|
|
|
2025-11-01 21:10:57 +01:00
|
|
|
// Exports publics des nodes
|
|
|
|
|
pub use nodes::{
|
2025-11-05 14:30:15 +00:00
|
|
|
audio_sink::AudioSink,
|
2025-11-01 21:10:57 +01:00
|
|
|
converter_nodes::{ToF32Node, ToF64Node, ToI16Node, ToI24Node, ToI32Node},
|
|
|
|
|
file_source::FileSource,
|
|
|
|
|
flac_file_sink::{FlacFileSink, FlacFileSinkStats},
|
|
|
|
|
http_source::HttpSource,
|
2025-11-05 13:44:24 +00:00
|
|
|
resampling_node::ResamplingNode,
|
2025-11-14 10:43:53 +01:00
|
|
|
timer_buffer_node::TimerBufferNode,
|
2025-11-07 13:44:25 +00:00
|
|
|
timer_node::TimerNode,
|
2026-02-26 19:58:37 +01:00
|
|
|
position_tracker_node::{PositionHandle, PositionTrackerNode},
|
2025-11-03 06:55:45 +01:00
|
|
|
AudioError, AudioNode, TypedAudioNode,
|
2025-11-01 21:10:57 +01:00
|
|
|
};
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
// Nodes temporairement désactivés
|
|
|
|
|
/*
|
2025-10-11 00:33:13 +02:00
|
|
|
pub use nodes::{
|
|
|
|
|
buffer_node::BufferNode,
|
2025-10-11 15:36:06 +02:00
|
|
|
chromecast_sink::{ChromecastConfig, ChromecastSink, ChromecastStats, StreamEncoding},
|
2025-10-11 00:33:13 +02:00
|
|
|
decoder_node::DecoderNode,
|
2025-10-11 15:36:06 +02:00
|
|
|
disk_sink::{AudioFileFormat, DiskSink, DiskSinkConfig, DiskSinkStats},
|
2025-10-11 00:33:13 +02:00
|
|
|
dsp_node::DspNode,
|
2025-10-11 15:36:06 +02:00
|
|
|
mpd_sink::{MpdAudioFormat, MpdConfig, MpdHandle, MpdSink, MpdStats},
|
2025-10-11 00:33:13 +02:00
|
|
|
sink_node::{SinkNode, SinkStats},
|
|
|
|
|
source_node::SourceNode,
|
|
|
|
|
timer_node::{TimerHandle, TimerNode},
|
2025-10-11 15:36:06 +02:00
|
|
|
volume_node::{HardwareVolumeNode, VolumeHandle, VolumeNode},
|
2025-10-11 00:33:13 +02:00
|
|
|
};
|
2025-10-30 08:54:47 +01:00
|
|
|
*/
|