Files
pmomusic/pmoflac/src/aac.rs
Eric Coissac 18b9194530 Add AAC support and update version
This commit adds support for AAC audio decoding via the fdk-aac library, including:
- New AAC decoder module (ADTS streaming)
- Integration into the autodetection system
- Support for AAC in transcoding
- Updated version to 0.3.23
- Added necessary dependencies in Cargo.toml and Cargo.lock
- Added tests for AAC decoding
2026-02-27 00:00:56 +01:00

178 lines
6.8 KiB
Rust

//! AAC Decoder Module (ADTS streaming)
//!
//! Provides asynchronous streaming AAC/ADTS decoding via libfdk-aac (statically linked).
//! Decodes AAC audio streams into PCM data (16-bit little-endian interleaved).
//!
//! Designed for live radio streams (e.g. Radio France icecast AAC 192kbps).
//! No seek required — pure linear streaming.
//!
//! ## Architecture
//!
//! ```text
//! AAC Input → [Ingest Task] → [Decode Task (blocking)] → [Writer Task] → PCM Output (AsyncRead)
//! ```
use fdk_aac::dec::{Decoder, DecoderError, Transport};
use tokio::{
io::AsyncRead,
sync::{mpsc, oneshot},
};
use crate::{
common::ChannelReader,
decoder_common::{
spawn_ingest_task, spawn_writer_task, DecodedStream, DecoderError as PmoDecoderError,
CHANNEL_CAPACITY, DUPLEX_BUFFER_SIZE,
},
pcm::StreamInfo,
stream::ManagedAsyncReader,
};
/// Errors that can occur while decoding AAC data.
pub type AacError = PmoDecoderError;
/// Async decoded AAC stream.
pub type AacDecodedStream = DecodedStream<AacError>;
/// Decodes an AAC/ADTS stream into PCM audio data (16-bit little-endian interleaved).
///
/// The input must be a raw ADTS stream (as produced by Radio France icecast).
/// No seek is required — the decoder processes frames linearly.
pub async fn decode_aac_stream<R>(reader: R) -> Result<AacDecodedStream, AacError>
where
R: AsyncRead + Unpin + Send + 'static,
{
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);
let mut decoder = Decoder::new(Transport::Adts);
let mut info_tx = Some(info_tx);
// Buffer de lecture — on lit des chunks et on les pousse au décodeur
let mut read_buf = vec![0u8; 8192];
// Buffer de sortie PCM — fdk-aac écrit des frames entières
let mut pcm_out = vec![0i16; 8192];
use std::io::Read;
loop {
// Lire des bytes depuis le stream ADTS
let n = match channel_reader.read(&mut read_buf) {
Ok(0) => break, // EOF
Ok(n) => n,
Err(e) => {
let err = AacError::Io {
kind: e.kind(),
message: e.to_string(),
};
if let Some(tx) = info_tx.take() {
let _ = tx.send(Err(err.clone()));
}
return Err(err);
}
};
// Pousser les bytes au décodeur fdk-aac
let filled = match decoder.fill(&read_buf[..n]) {
Ok(filled) => filled,
Err(e) => {
let err = AacError::Decode(format!("fdk-aac fill error: {:?}", e));
if let Some(tx) = info_tx.take() {
let _ = tx.send(Err(err.clone()));
}
return Err(err);
}
};
// Si fill n'a pas consommé tous les bytes, on les remet devant
// (fdk-aac peut ne pas consommer tout en une passe)
// Note: fdk-aac retourne le nombre de bytes non-consommés — on les ignore
// car le décodeur conserve son état interne entre appels.
let _ = filled;
// Décoder les frames disponibles
loop {
// Adapter la taille du buffer PCM si nécessaire
let stream_info = decoder.stream_info();
let frame_size = if stream_info.frameSize > 0 && stream_info.numChannels > 0 {
(stream_info.frameSize * stream_info.numChannels) as usize
} else {
2048 // taille par défaut avant la première frame
};
if pcm_out.len() < frame_size {
pcm_out.resize(frame_size, 0i16);
}
match decoder.decode_frame(&mut pcm_out) {
Ok(()) => {
let info_ref = decoder.stream_info();
// Envoyer les infos au premier décodage réussi
if let Some(tx) = info_tx.take() {
let info = StreamInfo {
sample_rate: info_ref.sampleRate as u32,
channels: info_ref.numChannels as u8,
bits_per_sample: 16,
total_samples: None,
max_block_size: 0,
min_block_size: 0,
};
if tx.send(Ok(info)).is_err() {
return Ok(());
}
}
// Convertir i16 → bytes little-endian
let samples = &pcm_out[..frame_size];
let mut bytes = Vec::with_capacity(samples.len() * 2);
for s in samples {
bytes.extend_from_slice(&s.to_le_bytes());
}
if pcm_tx.blocking_send(Ok(bytes)).is_err() {
return Ok(());
}
}
Err(DecoderError::NOT_ENOUGH_BITS) => {
// Pas assez de données — besoin de plus d'input
break;
}
Err(DecoderError::TRANSPORT_SYNC_ERROR) => {
// Erreur de sync ADTS transitoire — continuer
break;
}
Err(e) => {
let err = AacError::Decode(format!("fdk-aac decode error: {:?}", e));
if let Some(tx) = info_tx.take() {
let _ = tx.send(Err(err.clone()));
}
return Err(err);
}
}
}
}
if let Some(tx) = info_tx.take() {
let err = AacError::Decode("AAC stream contained no decodable frames".into());
let _ = tx.send(Err(err.clone()));
return Err(err);
}
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))
}