test: Add comprehensive test coverage for PlaylistSource and ResamplingNode
This commit adds extensive unit and integration tests for the audio pipeline components that were previously untested. ## ResamplingNode Tests (pmoaudio/src/nodes/resampling_node.rs) - Added 7 test functions covering: - Helper function tests: extract_channels_i16/i24 - Reconstruction tests: reconstruct_chunk_i16/i24 - Logic tests: passthrough when sample rate matches - Async tests: verify sync markers pass through unchanged - Integration test: actual 44.1kHz → 48kHz resampling ## PlaylistSource Tests (pmoaudio-ext/src/sources/playlist_source.rs) - Added 10 test functions covering: - Stream validation: valid/invalid channel counts and bit depths - PCM conversion: bytes_to_segment for I16/I24/I32 formats - Mono/stereo handling: verify channel duplication - Error handling: unsupported bit depth rejection - Type safety: compilation verification ## Bug Fixes - Fixed imports: Node and NodeLogic moved from nodes to pipeline module - Fixed AudioCache import: use pmoaudiocache::Cache with alias - Fixed API calls in ResamplingNode: - BitDepth::from_audio_chunk() → match pattern - .stereo() → .get_frames() - .to_i32() → .as_i32() for I24 - .sample_rate() → .get_sample_rate() ## Documentation - Added INSTALL_LIBSOXR.md with detailed installation instructions - Documents local libsoxr installation without sudo privileges - Provides troubleshooting guide for build and test environments All tests pass successfully (17 tests total: 7 ResamplingNode + 10 PlaylistSource).
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -36,4 +36,5 @@ all.txt
|
||||
pmo_src.txt
|
||||
upmpdcli/
|
||||
/*.xml
|
||||
test_upnp
|
||||
test_upnp*.cargo/
|
||||
.cargo/
|
||||
|
||||
137
INSTALL_LIBSOXR.md
Normal file
137
INSTALL_LIBSOXR.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# Installation de libsoxr sans droits sudo
|
||||
|
||||
Ce document explique comment installer libsoxr localement sans privilèges administrateur, nécessaire pour compiler `pmoaudio` avec le support de resampling.
|
||||
|
||||
## Contexte
|
||||
|
||||
Le crate `soxr` (utilisé par `ResamplingNode`) nécessite la bibliothèque système `libsoxr`. Dans un environnement sans droits sudo, voici comment l'installer localement.
|
||||
|
||||
## Méthode : Installation locale via apt-get download
|
||||
|
||||
### 1. Télécharger les packages .deb
|
||||
|
||||
```bash
|
||||
cd ~/.local
|
||||
apt-get download libsoxr-dev libsoxr0
|
||||
```
|
||||
|
||||
Cela télécharge les fichiers `.deb` sans les installer système-wide.
|
||||
|
||||
### 2. Extraire les packages
|
||||
|
||||
```bash
|
||||
dpkg -x libsoxr-dev_*.deb .
|
||||
dpkg -x libsoxr0_*.deb .
|
||||
```
|
||||
|
||||
Les fichiers sont extraits dans `~/.local/usr/lib/x86_64-linux-gnu/` et `~/.local/usr/include/`.
|
||||
|
||||
### 3. Configurer les variables d'environnement
|
||||
|
||||
Ajouter à votre `~/.bashrc` ou exporter dans votre session :
|
||||
|
||||
```bash
|
||||
export PKG_CONFIG_PATH="/root/.local/usr/lib/x86_64-linux-gnu/pkgconfig:$PKG_CONFIG_PATH"
|
||||
export LD_LIBRARY_PATH="/root/.local/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH"
|
||||
```
|
||||
|
||||
**IMPORTANT:** Remplacer `/root/` par le chemin de votre home directory (`$HOME` ou `~`).
|
||||
|
||||
### 4. Vérifier l'installation
|
||||
|
||||
```bash
|
||||
pkg-config --libs --cflags soxr
|
||||
```
|
||||
|
||||
Devrait retourner :
|
||||
```
|
||||
-I/root/.local/usr/include -L/root/.local/usr/lib/x86_64-linux-gnu -lsoxr
|
||||
```
|
||||
|
||||
## Utilisation avec Cargo
|
||||
|
||||
### Pour les builds réguliers
|
||||
|
||||
Les variables d'environnement suffisent pour `cargo build` et `cargo run`.
|
||||
|
||||
### Pour les tests
|
||||
|
||||
Les tests nécessitent également la configuration du linker. Deux options :
|
||||
|
||||
#### Option A : Configuration locale du projet (NON RECOMMANDÉ pour le versioning)
|
||||
|
||||
Créer `.cargo/config.toml` dans chaque crate :
|
||||
|
||||
```toml
|
||||
[build]
|
||||
rustflags = ["-L", "/root/.local/usr/lib/x86_64-linux-gnu"]
|
||||
```
|
||||
|
||||
**⚠️ NE PAS committer ces fichiers** - ils contiennent des chemins spécifiques à votre installation.
|
||||
|
||||
#### Option B : Variables d'environnement pour cargo test
|
||||
|
||||
```bash
|
||||
export PKG_CONFIG_PATH="$HOME/.local/usr/lib/x86_64-linux-gnu/pkgconfig:$PKG_CONFIG_PATH"
|
||||
export LD_LIBRARY_PATH="$HOME/.local/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH"
|
||||
cargo test
|
||||
```
|
||||
|
||||
## Pour d'autres distributions
|
||||
|
||||
### macOS (avec Homebrew)
|
||||
|
||||
```bash
|
||||
brew install libsoxr
|
||||
```
|
||||
|
||||
### Debian/Ubuntu (avec sudo)
|
||||
|
||||
```bash
|
||||
sudo apt-get install libsoxr-dev
|
||||
```
|
||||
|
||||
### Fedora/RHEL
|
||||
|
||||
```bash
|
||||
sudo dnf install soxr-devel
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Erreur : "Package 'soxr' was not found"
|
||||
|
||||
- Vérifier que `PKG_CONFIG_PATH` contient le bon chemin
|
||||
- Vérifier que le fichier `soxr.pc` existe dans ce répertoire
|
||||
|
||||
### Erreur de link : "unable to find library -lsoxr"
|
||||
|
||||
- Pour `cargo build` : vérifier `LD_LIBRARY_PATH`
|
||||
- Pour `cargo test` : utiliser la configuration rustflags (Option A ci-dessus)
|
||||
|
||||
### Le test compile mais échoue au runtime
|
||||
|
||||
```
|
||||
error while loading shared libraries: libsoxr.so.0: cannot open shared object file
|
||||
```
|
||||
|
||||
Solution : Ajouter `LD_LIBRARY_PATH` également pour l'exécution :
|
||||
|
||||
```bash
|
||||
export LD_LIBRARY_PATH="$HOME/.local/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH"
|
||||
cargo test
|
||||
```
|
||||
|
||||
## Notes pour Claude Code sessions
|
||||
|
||||
Pour les futures sessions Claude :
|
||||
|
||||
1. Exporter les variables d'environnement en début de session
|
||||
2. NE PAS créer de fichiers `.cargo/config.toml` dans le projet
|
||||
3. Si nécessaire pour les tests, les créer localement mais ne pas les committer
|
||||
4. Documenter toute difficulté d'installation ici
|
||||
|
||||
## Références
|
||||
|
||||
- libsoxr GitHub: https://github.com/chirlu/soxr
|
||||
- Documentation pkg-config: https://www.freedesktop.org/wiki/Software/pkg-config/
|
||||
@@ -72,12 +72,12 @@
|
||||
//! - `EndOfStream` final uniquement lors de l'arrêt
|
||||
|
||||
use pmoaudio::{
|
||||
nodes::{AudioError, Node, NodeLogic, TypedAudioNode, DEFAULT_CHUNK_DURATION_MS},
|
||||
pipeline::AudioPipelineNode,
|
||||
nodes::{AudioError, TypedAudioNode, DEFAULT_CHUNK_DURATION_MS},
|
||||
pipeline::{AudioPipelineNode, Node, NodeLogic},
|
||||
type_constraints::TypeRequirement,
|
||||
AudioChunk, AudioChunkData, AudioSegment, I24,
|
||||
};
|
||||
use pmoaudiocache::AudioCache;
|
||||
use pmoaudiocache::Cache as AudioCache;
|
||||
use pmoflac::{decode_audio_stream, StreamInfo};
|
||||
use pmoplaylist::ReadHandle;
|
||||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||
@@ -597,3 +597,256 @@ impl TypedAudioNode for PlaylistSource {
|
||||
Some(TypeRequirement::any())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Tests unitaires pour les fonctions helper
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn test_validate_stream_valid_stereo_16bit() {
|
||||
let info = StreamInfo {
|
||||
sample_rate: 44100,
|
||||
channels: 2,
|
||||
bits_per_sample: 16,
|
||||
total_samples: Some(1000),
|
||||
max_block_size: 4096,
|
||||
min_block_size: 256,
|
||||
};
|
||||
assert!(validate_stream(&info).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_stream_valid_mono_24bit() {
|
||||
let info = StreamInfo {
|
||||
sample_rate: 48000,
|
||||
channels: 1,
|
||||
bits_per_sample: 24,
|
||||
total_samples: Some(1000),
|
||||
max_block_size: 4096,
|
||||
min_block_size: 256,
|
||||
};
|
||||
assert!(validate_stream(&info).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_stream_invalid_channel_count() {
|
||||
let info = StreamInfo {
|
||||
sample_rate: 44100,
|
||||
channels: 5, // Invalid
|
||||
bits_per_sample: 16,
|
||||
total_samples: Some(1000),
|
||||
max_block_size: 4096,
|
||||
min_block_size: 256,
|
||||
};
|
||||
assert!(validate_stream(&info).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_stream_invalid_bit_depth() {
|
||||
let info = StreamInfo {
|
||||
sample_rate: 44100,
|
||||
channels: 2,
|
||||
bits_per_sample: 12, // Invalid
|
||||
total_samples: Some(1000),
|
||||
max_block_size: 4096,
|
||||
min_block_size: 256,
|
||||
};
|
||||
assert!(validate_stream(&info).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes_to_segment_i16_stereo() {
|
||||
// Create mock PCM data (2 frames, stereo, 16-bit)
|
||||
// Frame 1: L=100, R=200
|
||||
// Frame 2: L=300, R=400
|
||||
let chunk_bytes = vec![
|
||||
100u8, 0, // L1
|
||||
200, 0, // R1
|
||||
44, 1, // L2 (300 = 0x012C)
|
||||
144, 1, // R2 (400 = 0x0190)
|
||||
];
|
||||
|
||||
let info = StreamInfo {
|
||||
sample_rate: 44100,
|
||||
channels: 2,
|
||||
bits_per_sample: 16,
|
||||
total_samples: Some(2),
|
||||
max_block_size: 4096,
|
||||
min_block_size: 256,
|
||||
};
|
||||
|
||||
let segment = bytes_to_segment(&chunk_bytes, &info, 2, 0, 0.0).unwrap();
|
||||
|
||||
assert_eq!(segment.order, 0);
|
||||
assert_eq!(segment.timestamp_sec, 0.0);
|
||||
|
||||
match &segment.segment {
|
||||
pmoaudio::_AudioSegment::Chunk(chunk) => {
|
||||
match chunk.as_ref() {
|
||||
AudioChunk::I16(data) => {
|
||||
let frames = data.get_frames();
|
||||
assert_eq!(frames.len(), 2);
|
||||
assert_eq!(frames[0], [100, 200]);
|
||||
assert_eq!(frames[1], [300, 400]);
|
||||
assert_eq!(data.get_sample_rate(), 44100);
|
||||
}
|
||||
_ => panic!("Expected I16 chunk"),
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected audio chunk"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes_to_segment_i16_mono() {
|
||||
// Create mock PCM data (2 frames, mono, 16-bit)
|
||||
let chunk_bytes = vec![
|
||||
100u8, 0, // Frame 1
|
||||
200, 0, // Frame 2
|
||||
];
|
||||
|
||||
let info = StreamInfo {
|
||||
sample_rate: 48000,
|
||||
channels: 1,
|
||||
bits_per_sample: 16,
|
||||
total_samples: Some(2),
|
||||
max_block_size: 4096,
|
||||
min_block_size: 256,
|
||||
};
|
||||
|
||||
let segment = bytes_to_segment(&chunk_bytes, &info, 2, 5, 1.5).unwrap();
|
||||
|
||||
assert_eq!(segment.order, 5);
|
||||
assert_eq!(segment.timestamp_sec, 1.5);
|
||||
|
||||
match &segment.segment {
|
||||
pmoaudio::_AudioSegment::Chunk(chunk) => {
|
||||
match chunk.as_ref() {
|
||||
AudioChunk::I16(data) => {
|
||||
let frames = data.get_frames();
|
||||
assert_eq!(frames.len(), 2);
|
||||
// Mono is duplicated to both channels
|
||||
assert_eq!(frames[0], [100, 100]);
|
||||
assert_eq!(frames[1], [200, 200]);
|
||||
}
|
||||
_ => panic!("Expected I16 chunk"),
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected audio chunk"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes_to_segment_i24_stereo() {
|
||||
// Create mock PCM data (1 frame, stereo, 24-bit)
|
||||
// Frame 1: L=1000 (0x0003E8), R=-1000 (0xFFFC18)
|
||||
let chunk_bytes = vec![
|
||||
0xE8, 0x03, 0x00, // L (1000)
|
||||
0x18, 0xFC, 0xFF, // R (-1000, sign-extended)
|
||||
];
|
||||
|
||||
let info = StreamInfo {
|
||||
sample_rate: 96000,
|
||||
channels: 2,
|
||||
bits_per_sample: 24,
|
||||
total_samples: Some(1),
|
||||
max_block_size: 4096,
|
||||
min_block_size: 256,
|
||||
};
|
||||
|
||||
let segment = bytes_to_segment(&chunk_bytes, &info, 1, 0, 0.0).unwrap();
|
||||
|
||||
match &segment.segment {
|
||||
pmoaudio::_AudioSegment::Chunk(chunk) => {
|
||||
match chunk.as_ref() {
|
||||
AudioChunk::I24(data) => {
|
||||
let frames = data.get_frames();
|
||||
assert_eq!(frames.len(), 1);
|
||||
assert_eq!(frames[0][0].as_i32(), 1000);
|
||||
assert_eq!(frames[0][1].as_i32(), -1000);
|
||||
}
|
||||
_ => panic!("Expected I24 chunk"),
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected audio chunk"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes_to_segment_i32_stereo() {
|
||||
// Create mock PCM data (1 frame, stereo, 32-bit)
|
||||
let chunk_bytes = vec![
|
||||
0x00, 0x10, 0x00, 0x00, // L (4096)
|
||||
0x00, 0x20, 0x00, 0x00, // R (8192)
|
||||
];
|
||||
|
||||
let info = StreamInfo {
|
||||
sample_rate: 44100,
|
||||
channels: 2,
|
||||
bits_per_sample: 32,
|
||||
total_samples: Some(1),
|
||||
max_block_size: 4096,
|
||||
min_block_size: 256,
|
||||
};
|
||||
|
||||
let segment = bytes_to_segment(&chunk_bytes, &info, 1, 0, 0.0).unwrap();
|
||||
|
||||
match &segment.segment {
|
||||
pmoaudio::_AudioSegment::Chunk(chunk) => {
|
||||
match chunk.as_ref() {
|
||||
AudioChunk::I32(data) => {
|
||||
let frames = data.get_frames();
|
||||
assert_eq!(frames.len(), 1);
|
||||
assert_eq!(frames[0], [4096, 8192]);
|
||||
}
|
||||
_ => panic!("Expected I32 chunk"),
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected audio chunk"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes_to_segment_unsupported_bit_depth() {
|
||||
let chunk_bytes = vec![0u8; 8];
|
||||
|
||||
let info = StreamInfo {
|
||||
sample_rate: 44100,
|
||||
channels: 2,
|
||||
bits_per_sample: 8, // Currently unsupported by bytes_to_segment
|
||||
total_samples: Some(1),
|
||||
max_block_size: 4096,
|
||||
min_block_size: 256,
|
||||
};
|
||||
|
||||
let result = bytes_to_segment(&chunk_bytes, &info, 1, 0, 0.0);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Tests d'intégration pour PlaylistSource
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Note: Les tests d'intégration complets nécessitent une vraie playlist et un cache.
|
||||
// Ces tests peuvent être ajoutés dans un module d'intégration séparé avec des
|
||||
// fixtures FLAC de test.
|
||||
|
||||
#[test]
|
||||
fn test_playlist_source_type_check() {
|
||||
// Test de création basique - vérifie que le code compile
|
||||
// Ce test ne peut pas être exécuté sans mock ou fixture réelles
|
||||
// car ReadHandle n'implémente pas Clone
|
||||
use std::sync::Arc;
|
||||
|
||||
// Vérification de type - ces lignes ne sont jamais exécutées
|
||||
if false {
|
||||
let _handle: ReadHandle = unreachable!();
|
||||
let _cache: Arc<AudioCache> = unreachable!();
|
||||
let _source = PlaylistSource::new(_handle, _cache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,3 +379,199 @@ impl TypedAudioNode for ResamplingNode {
|
||||
Some(TypeRequirement::any())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{AudioChunk, AudioChunkData, SyncMarker};
|
||||
|
||||
#[test]
|
||||
fn test_extract_channels_i16() {
|
||||
let chunk = AudioChunk::I16(AudioChunkData::new(
|
||||
vec![[100, 200], [300, 400]],
|
||||
48000,
|
||||
0.0,
|
||||
));
|
||||
|
||||
let (left, right) = extract_channels_i32(&chunk).unwrap();
|
||||
|
||||
assert_eq!(left, vec![100i32, 300i32]);
|
||||
assert_eq!(right, vec![200i32, 400i32]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_channels_i24() {
|
||||
let chunk = AudioChunk::I24(AudioChunkData::new(
|
||||
vec![
|
||||
[I24::new(1_000_000).unwrap(), I24::new(2_000_000).unwrap()],
|
||||
[I24::new(3_000_000).unwrap(), I24::new(4_000_000).unwrap()],
|
||||
],
|
||||
48000,
|
||||
0.0,
|
||||
));
|
||||
|
||||
let (left, right) = extract_channels_i32(&chunk).unwrap();
|
||||
|
||||
assert_eq!(left, vec![1_000_000i32, 3_000_000i32]);
|
||||
assert_eq!(right, vec![2_000_000i32, 4_000_000i32]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reconstruct_chunk_i16() {
|
||||
let original = AudioChunk::I16(AudioChunkData::new(
|
||||
vec![[100, 200]],
|
||||
44100,
|
||||
0.0,
|
||||
));
|
||||
|
||||
let left = vec![100i32, 300i32];
|
||||
let right = vec![200i32, 400i32];
|
||||
|
||||
let result = reconstruct_chunk(&original, left, right, 48000).unwrap();
|
||||
|
||||
if let AudioChunk::I16(data) = result {
|
||||
assert_eq!(data.get_sample_rate(), 48000);
|
||||
let frames = data.get_frames();
|
||||
assert_eq!(frames.len(), 2);
|
||||
assert_eq!(frames[0], [100i16, 200i16]);
|
||||
assert_eq!(frames[1], [300i16, 400i16]);
|
||||
} else {
|
||||
panic!("Expected I16 chunk");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reconstruct_chunk_i24() {
|
||||
let original = AudioChunk::I24(AudioChunkData::new(
|
||||
vec![[I24::new(1_000_000).unwrap(), I24::new(2_000_000).unwrap()]],
|
||||
44100,
|
||||
0.0,
|
||||
));
|
||||
|
||||
let left = vec![1_000_000i32, 3_000_000i32];
|
||||
let right = vec![2_000_000i32, 4_000_000i32];
|
||||
|
||||
let result = reconstruct_chunk(&original, left, right, 48000).unwrap();
|
||||
|
||||
if let AudioChunk::I24(data) = result {
|
||||
assert_eq!(data.get_sample_rate(), 48000);
|
||||
let frames = data.get_frames();
|
||||
assert_eq!(frames.len(), 2);
|
||||
assert_eq!(frames[0][0].as_i32(), 1_000_000);
|
||||
assert_eq!(frames[0][1].as_i32(), 2_000_000);
|
||||
} else {
|
||||
panic!("Expected I24 chunk");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resample_chunk_no_change_if_same_rate() {
|
||||
let mut logic = ResamplingLogic::new(48000);
|
||||
|
||||
let chunk = AudioChunk::I16(AudioChunkData::new(
|
||||
vec![[100, 200], [300, 400]],
|
||||
48000, // Déjà à 48kHz
|
||||
0.0,
|
||||
));
|
||||
|
||||
let result = logic.resample_chunk(&chunk).unwrap();
|
||||
|
||||
// Doit retourner le même chunk sans resampling
|
||||
if let AudioChunk::I16(data) = result {
|
||||
assert_eq!(data.get_sample_rate(), 48000);
|
||||
assert_eq!(data.get_frames().len(), 2);
|
||||
} else {
|
||||
panic!("Expected I16 chunk");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resampling_logic_passes_sync_markers() {
|
||||
let mut logic = ResamplingLogic::new(48000);
|
||||
|
||||
let (input_tx, input_rx) = mpsc::channel(10);
|
||||
let (output_tx, mut output_rx) = mpsc::channel(10);
|
||||
let stop_token = CancellationToken::new();
|
||||
|
||||
// Créer un TrackBoundary
|
||||
let metadata = Arc::new(tokio::sync::RwLock::new(
|
||||
pmometadata::MemoryTrackMetadata::new()
|
||||
));
|
||||
let boundary = AudioSegment::new_track_boundary(0, 0.0, metadata);
|
||||
|
||||
// Envoyer le boundary
|
||||
input_tx.send(boundary.clone()).await.unwrap();
|
||||
drop(input_tx);
|
||||
|
||||
// Lancer le traitement
|
||||
tokio::spawn(async move {
|
||||
logic
|
||||
.process(Some(input_rx), vec![output_tx], stop_token)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
// Vérifier que le boundary passe tel quel
|
||||
let result = output_rx.recv().await.unwrap();
|
||||
assert!(result.as_sync_marker().is_some());
|
||||
|
||||
if let Some(marker) = result.as_sync_marker() {
|
||||
assert!(matches!(**marker, SyncMarker::TrackBoundary { .. }));
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resampling_node_integration() {
|
||||
// Test d'intégration complet avec ResamplingNode
|
||||
let (input_tx, input_rx) = mpsc::channel(10);
|
||||
let (output_tx, mut output_rx) = mpsc::channel(10);
|
||||
let stop_token = CancellationToken::new();
|
||||
|
||||
let mut logic = ResamplingLogic::new(48000);
|
||||
|
||||
// Créer un chunk à 44.1kHz
|
||||
let chunk_44k = AudioChunk::I16(AudioChunkData::new(
|
||||
vec![[1000, 2000]; 100], // 100 frames
|
||||
44100,
|
||||
0.0,
|
||||
));
|
||||
|
||||
let segment = Arc::new(AudioSegment {
|
||||
order: 0,
|
||||
timestamp_sec: 0.0,
|
||||
segment: crate::_AudioSegment::Chunk(Arc::new(chunk_44k)),
|
||||
});
|
||||
|
||||
input_tx.send(segment).await.unwrap();
|
||||
drop(input_tx);
|
||||
|
||||
// Lancer le traitement
|
||||
tokio::spawn(async move {
|
||||
logic
|
||||
.process(Some(input_rx), vec![output_tx], stop_token)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
// Vérifier le résultat
|
||||
let result = output_rx.recv().await.unwrap();
|
||||
assert!(result.is_audio_chunk());
|
||||
|
||||
if let Some(chunk) = result.as_chunk() {
|
||||
// Le chunk doit être I16 (même type)
|
||||
assert!(matches!(chunk.as_ref(), AudioChunk::I16(_)));
|
||||
|
||||
// Le sample rate doit être 48000
|
||||
assert_eq!(chunk.sample_rate(), 48000);
|
||||
|
||||
// Le nombre de frames doit avoir changé (ratio ~1.088)
|
||||
// 100 frames @ 44.1kHz ≈ 109 frames @ 48kHz
|
||||
if let AudioChunk::I16(data) = chunk.as_ref() {
|
||||
let frames = data.get_frames().len();
|
||||
assert!(frames >= 105 && frames <= 115, "Expected ~109 frames, got {}", frames);
|
||||
}
|
||||
} else {
|
||||
panic!("Expected audio chunk");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user