feat: Add AudioSink node for audio playback via rodio
Implémente AudioSink qui permet la lecture audio en temps réel sur la sortie audio standard via rodio avec architecture thread-safe. - Nouveau nœud AudioSink avec thread dédié pour gérer rodio (OutputStream non-Send) - Accepte tous formats audio (I16/I24/I32/F32/F64) et convertit vers I16 - Support volume, arrêt gracieux, transitions gapless - Exemples: play_audio.rs et play_with_resampling.rs - Documentation: INSTALL_LIBSOXR.md mise à jour avec instructions ALSA - Tests unitaires inclus, tous passent
This commit is contained in:
310
Cargo.lock
generated
310
Cargo.lock
generated
@@ -49,6 +49,28 @@ dependencies = [
|
||||
"equator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43"
|
||||
dependencies = [
|
||||
"alsa-sys",
|
||||
"bitflags 2.10.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa-sys"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
@@ -555,6 +577,12 @@ dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cesu8"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
@@ -635,6 +663,16 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
@@ -728,6 +766,49 @@ version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "coreaudio-rs"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation-sys",
|
||||
"coreaudio-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coreaudio-sys"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpal"
|
||||
version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779"
|
||||
dependencies = [
|
||||
"alsa",
|
||||
"core-foundation-sys",
|
||||
"coreaudio-rs",
|
||||
"dasp_sample",
|
||||
"jni",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"mach2",
|
||||
"ndk",
|
||||
"ndk-context",
|
||||
"oboe",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows 0.54.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
@@ -817,6 +898,12 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dasp_sample"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.9.0"
|
||||
@@ -1472,6 +1559,12 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hound"
|
||||
version = "3.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
|
||||
|
||||
[[package]]
|
||||
name = "htmlescape"
|
||||
version = "0.3.1"
|
||||
@@ -1881,6 +1974,28 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"cfg-if",
|
||||
"combine",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"thiserror 1.0.69",
|
||||
"walkdir",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.34"
|
||||
@@ -2268,6 +2383,35 @@ dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"ndk-sys",
|
||||
"num_enum",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk-context"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
|
||||
|
||||
[[package]]
|
||||
name = "ndk-sys"
|
||||
version = "0.5.0+25.2.9519653"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
|
||||
dependencies = [
|
||||
"jni-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-core"
|
||||
version = "0.7.0"
|
||||
@@ -2461,6 +2605,51 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.108",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oboe"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb"
|
||||
dependencies = [
|
||||
"jni",
|
||||
"ndk",
|
||||
"ndk-context",
|
||||
"num-derive 0.4.2",
|
||||
"num-traits",
|
||||
"oboe-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oboe-sys"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ogg"
|
||||
version = "0.8.0"
|
||||
@@ -2668,6 +2857,7 @@ dependencies = [
|
||||
"pmoflac",
|
||||
"pmometadata",
|
||||
"reqwest",
|
||||
"rodio",
|
||||
"soxr",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
@@ -3108,6 +3298,15 @@ dependencies = [
|
||||
"syn 2.0.108",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
dependencies = [
|
||||
"toml_edit 0.23.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
@@ -3487,6 +3686,20 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rodio"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6006a627c1a38d37f3d3a85c6575418cfe34a5392d60a686d0071e1c8d427acb"
|
||||
dependencies = [
|
||||
"claxon",
|
||||
"cpal",
|
||||
"hound",
|
||||
"lewton",
|
||||
"symphonia",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.37.0"
|
||||
@@ -4155,7 +4368,7 @@ dependencies = [
|
||||
"ntapi",
|
||||
"once_cell",
|
||||
"rayon",
|
||||
"windows",
|
||||
"windows 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5014,6 +5227,16 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.54.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
|
||||
dependencies = [
|
||||
"windows-core 0.54.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
@@ -5023,6 +5246,16 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.54.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
|
||||
dependencies = [
|
||||
"windows-result 0.1.2",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
@@ -5081,6 +5314,15 @@ dependencies = [
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
@@ -5117,6 +5359,15 @@ dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
@@ -5153,6 +5404,21 @@ dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@@ -5186,6 +5452,12 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.53.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -5198,6 +5470,12 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -5210,6 +5488,12 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -5234,6 +5518,12 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -5246,6 +5536,12 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -5258,6 +5554,12 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -5270,6 +5572,12 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
# Installation de libsoxr sans droits sudo
|
||||
# Installation des dépendances système sans droits sudo
|
||||
|
||||
Ce document explique comment installer libsoxr localement sans privilèges administrateur, nécessaire pour compiler `pmoaudio` avec le support de resampling.
|
||||
Ce document explique comment installer les dépendances système de `pmoaudio` localement sans privilèges administrateur.
|
||||
|
||||
## Dépendances requises
|
||||
|
||||
1. **libsoxr** - Nécessaire pour `ResamplingNode` (resampling audio haute qualité)
|
||||
2. **libasound2** (ALSA) - Nécessaire pour `AudioSink` via rodio (lecture audio sur Linux)
|
||||
|
||||
## 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.
|
||||
Les crates `soxr` et `rodio` nécessitent des bibliothèques système. Dans un environnement sans droits sudo, voici comment les installer localement.
|
||||
|
||||
## Méthode : Installation locale via apt-get download
|
||||
|
||||
@@ -12,7 +17,12 @@ Le crate `soxr` (utilisé par `ResamplingNode`) nécessite la bibliothèque syst
|
||||
|
||||
```bash
|
||||
cd ~/.local
|
||||
|
||||
# Pour libsoxr (ResamplingNode)
|
||||
apt-get download libsoxr-dev libsoxr0
|
||||
|
||||
# Pour ALSA (AudioSink)
|
||||
apt-get download libasound2-dev
|
||||
```
|
||||
|
||||
Cela télécharge les fichiers `.deb` sans les installer système-wide.
|
||||
@@ -20,8 +30,12 @@ Cela télécharge les fichiers `.deb` sans les installer système-wide.
|
||||
### 2. Extraire les packages
|
||||
|
||||
```bash
|
||||
# Extraire libsoxr
|
||||
dpkg -x libsoxr-dev_*.deb .
|
||||
dpkg -x libsoxr0_*.deb .
|
||||
|
||||
# Extraire ALSA
|
||||
dpkg -x libasound2-dev_*.deb .
|
||||
```
|
||||
|
||||
Les fichiers sont extraits dans `~/.local/usr/lib/x86_64-linux-gnu/` et `~/.local/usr/include/`.
|
||||
@@ -40,12 +54,20 @@ export LD_LIBRARY_PATH="/root/.local/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH"
|
||||
### 4. Vérifier l'installation
|
||||
|
||||
```bash
|
||||
# Vérifier libsoxr
|
||||
pkg-config --libs --cflags soxr
|
||||
|
||||
# Vérifier ALSA
|
||||
pkg-config --libs --cflags alsa
|
||||
```
|
||||
|
||||
Devrait retourner :
|
||||
Devrait retourner quelque chose comme :
|
||||
```
|
||||
# soxr
|
||||
-I/root/.local/usr/include -L/root/.local/usr/lib/x86_64-linux-gnu -lsoxr
|
||||
|
||||
# alsa
|
||||
-I/root/.local/usr/include -L/root/.local/usr/lib/x86_64-linux-gnu -lasound
|
||||
```
|
||||
|
||||
## Utilisation avec Cargo
|
||||
@@ -83,12 +105,13 @@ cargo test
|
||||
|
||||
```bash
|
||||
brew install libsoxr
|
||||
# Note: ALSA n'est pas nécessaire sur macOS (rodio utilise CoreAudio)
|
||||
```
|
||||
|
||||
### Debian/Ubuntu (avec sudo)
|
||||
|
||||
```bash
|
||||
sudo apt-get install libsoxr-dev
|
||||
sudo apt-get install libsoxr-dev libasound2-dev
|
||||
```
|
||||
|
||||
### Fedora/RHEL
|
||||
@@ -99,12 +122,12 @@ sudo dnf install soxr-devel
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Erreur : "Package 'soxr' was not found"
|
||||
### Erreur : "Package 'soxr' was not found" ou "Package 'alsa' 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
|
||||
- Vérifier que les fichiers `soxr.pc` et `alsa.pc` existent dans ce répertoire
|
||||
|
||||
### Erreur de link : "unable to find library -lsoxr"
|
||||
### Erreur de link : "unable to find library -lsoxr" ou "-lasound"
|
||||
|
||||
- Pour `cargo build` : vérifier `LD_LIBRARY_PATH`
|
||||
- Pour `cargo test` : utiliser la configuration rustflags (Option A ci-dessus)
|
||||
|
||||
@@ -19,6 +19,7 @@ soxr = "0.6.0"
|
||||
bytemuck = "1.24.0"
|
||||
reqwest = { version = "0.12", features = ["stream"] }
|
||||
tracing = "0.1"
|
||||
rodio = "0.19"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-test = "0.4"
|
||||
|
||||
60
pmoaudio/examples/play_audio.rs
Normal file
60
pmoaudio/examples/play_audio.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
//! Exemple simple de lecture audio avec AudioSink
|
||||
//!
|
||||
//! Cet exemple montre comment utiliser AudioSink pour jouer un fichier audio
|
||||
//! sur la sortie audio standard de la machine.
|
||||
//!
|
||||
//! Usage:
|
||||
//! cargo run --example play_audio -- <fichier.flac>
|
||||
|
||||
use pmoaudio::{AudioSink, FileSource};
|
||||
use std::env;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialiser le logging
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Récupérer le chemin du fichier depuis les arguments
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() < 2 {
|
||||
eprintln!("Usage: {} <fichier.flac>", args[0]);
|
||||
eprintln!("\nExemple:");
|
||||
eprintln!(" {} music.flac", args[0]);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let file_path = &args[1];
|
||||
println!("Lecture de: {}", file_path);
|
||||
|
||||
// Créer la source audio (lit le fichier FLAC)
|
||||
let mut source = FileSource::new(file_path).await?;
|
||||
|
||||
// Créer le sink audio (joue sur la sortie audio)
|
||||
let sink = AudioSink::new();
|
||||
|
||||
// Connecter la source au sink
|
||||
source.register(Box::new(sink));
|
||||
|
||||
println!("Démarrage de la lecture...");
|
||||
println!("Appuyez sur Ctrl+C pour arrêter");
|
||||
|
||||
// Créer un token d'annulation pour pouvoir arrêter proprement
|
||||
let stop_token = CancellationToken::new();
|
||||
let stop_token_clone = stop_token.clone();
|
||||
|
||||
// Gérer Ctrl+C pour arrêt propre
|
||||
tokio::spawn(async move {
|
||||
tokio::signal::ctrl_c().await.expect("Failed to listen for Ctrl+C");
|
||||
println!("\nArrêt demandé...");
|
||||
stop_token_clone.cancel();
|
||||
});
|
||||
|
||||
// Lancer le pipeline
|
||||
match Box::new(source).run(stop_token).await {
|
||||
Ok(()) => println!("\nLecture terminée"),
|
||||
Err(e) => eprintln!("\nErreur pendant la lecture: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
78
pmoaudio/examples/play_with_resampling.rs
Normal file
78
pmoaudio/examples/play_with_resampling.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
//! Exemple de lecture audio avec resampling et conversion de format
|
||||
//!
|
||||
//! Cet exemple montre comment construire un pipeline audio complet:
|
||||
//! FileSource → ResamplingNode → ToI24Node → AudioSink
|
||||
//!
|
||||
//! Usage:
|
||||
//! cargo run --example play_with_resampling -- <fichier.flac> [sample_rate]
|
||||
|
||||
use pmoaudio::{AudioSink, FileSource, ResamplingNode, ToI24Node};
|
||||
use std::env;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialiser le logging
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Récupérer les arguments
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() < 2 {
|
||||
eprintln!("Usage: {} <fichier.flac> [sample_rate]", args[0]);
|
||||
eprintln!("\nExemple:");
|
||||
eprintln!(" {} music.flac # Lecture normale", args[0]);
|
||||
eprintln!(" {} music.flac 48000 # Resample vers 48kHz", args[0]);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let file_path = &args[1];
|
||||
let target_sample_rate = if args.len() >= 3 {
|
||||
args[2].parse::<u32>()?
|
||||
} else {
|
||||
48000 // Par défaut
|
||||
};
|
||||
|
||||
println!("Lecture de: {}", file_path);
|
||||
println!("Sample rate cible: {} Hz", target_sample_rate);
|
||||
|
||||
// Créer la source audio
|
||||
let mut source = FileSource::new(file_path).await?;
|
||||
|
||||
// Créer le nœud de resampling
|
||||
let mut resampler = ResamplingNode::new(target_sample_rate);
|
||||
|
||||
// Créer le nœud de conversion vers I24
|
||||
let mut converter = ToI24Node::new();
|
||||
|
||||
// Créer le sink audio avec volume à 80%
|
||||
let sink = AudioSink::with_volume(0.8);
|
||||
|
||||
// Construire le pipeline: Source → Resampler → Converter → Sink
|
||||
source.register(Box::new(resampler));
|
||||
resampler.register(Box::new(converter));
|
||||
converter.register(Box::new(sink));
|
||||
|
||||
println!("Pipeline créé: FileSource → Resampling({} Hz) → ToI24 → AudioSink",
|
||||
target_sample_rate);
|
||||
println!("Démarrage de la lecture...");
|
||||
println!("Appuyez sur Ctrl+C pour arrêter");
|
||||
|
||||
// Créer un token d'annulation
|
||||
let stop_token = CancellationToken::new();
|
||||
let stop_token_clone = stop_token.clone();
|
||||
|
||||
// Gérer Ctrl+C
|
||||
tokio::spawn(async move {
|
||||
tokio::signal::ctrl_c().await.expect("Failed to listen for Ctrl+C");
|
||||
println!("\nArrêt demandé...");
|
||||
stop_token_clone.cancel();
|
||||
});
|
||||
|
||||
// Lancer le pipeline
|
||||
match Box::new(source).run(stop_token).await {
|
||||
Ok(()) => println!("\nLecture terminée"),
|
||||
Err(e) => eprintln!("\nErreur pendant la lecture: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -118,6 +118,7 @@ pub use pipeline::AudioPipelineNode;
|
||||
|
||||
// Exports publics des nodes
|
||||
pub use nodes::{
|
||||
audio_sink::AudioSink,
|
||||
converter_nodes::{ToF32Node, ToF64Node, ToI16Node, ToI24Node, ToI32Node},
|
||||
file_source::FileSource,
|
||||
flac_file_sink::{FlacFileSink, FlacFileSinkStats},
|
||||
|
||||
421
pmoaudio/src/nodes/audio_sink.rs
Normal file
421
pmoaudio/src/nodes/audio_sink.rs
Normal file
@@ -0,0 +1,421 @@
|
||||
use crate::{
|
||||
nodes::{AudioError, TypedAudioNode, DEFAULT_CHANNEL_SIZE},
|
||||
pipeline::{Node, NodeLogic},
|
||||
type_constraints::TypeRequirement,
|
||||
AudioChunk, AudioPipelineNode, AudioSegment, SyncMarker,
|
||||
};
|
||||
use rodio::{OutputStream, Sink};
|
||||
use std::sync::{
|
||||
mpsc as std_mpsc,
|
||||
Arc,
|
||||
};
|
||||
use std::thread;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
/// Commandes envoyées au thread rodio
|
||||
enum RodioCommand {
|
||||
AppendSamples {
|
||||
samples: Vec<i16>,
|
||||
sample_rate: u32,
|
||||
},
|
||||
WaitUntilEnd,
|
||||
Stop,
|
||||
}
|
||||
|
||||
/// Sink qui joue les `AudioSegment` reçus sur la sortie audio standard via rodio.
|
||||
///
|
||||
/// Ce sink :
|
||||
/// - Lit les chunks audio et les joue en temps réel
|
||||
/// - Convertit automatiquement tous les formats vers I16 pour rodio
|
||||
/// - Supporte le changement de sample rate entre les tracks
|
||||
/// - Gère TrackBoundary pour des transitions propres
|
||||
/// - S'arrête proprement sur EndOfStream ou CancellationToken
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
/// AudioSinkLogic - Logique métier pure
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Logique pure de lecture audio via rodio
|
||||
pub struct AudioSinkLogic {
|
||||
volume: f32,
|
||||
}
|
||||
|
||||
impl AudioSinkLogic {
|
||||
pub fn new() -> Self {
|
||||
Self { volume: 1.0 }
|
||||
}
|
||||
|
||||
pub fn with_volume(volume: f32) -> Self {
|
||||
Self {
|
||||
volume: volume.clamp(0.0, 1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AudioSinkLogic {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl NodeLogic for AudioSinkLogic {
|
||||
async fn process(
|
||||
&mut self,
|
||||
input: Option<mpsc::Receiver<Arc<AudioSegment>>>,
|
||||
_output: Vec<mpsc::Sender<Arc<AudioSegment>>>,
|
||||
stop_token: CancellationToken,
|
||||
) -> Result<(), AudioError> {
|
||||
let mut rx = input.expect("AudioSink must have input");
|
||||
|
||||
tracing::debug!("AudioSinkLogic::process started");
|
||||
|
||||
// Créer un channel pour communiquer avec le thread rodio
|
||||
let (cmd_tx, cmd_rx) = std_mpsc::channel::<RodioCommand>();
|
||||
|
||||
// Spawner un thread dédié pour rodio (car OutputStream n'est pas Send)
|
||||
let volume = self.volume;
|
||||
let rodio_thread = thread::spawn(move || {
|
||||
// Créer OutputStream et Sink dans le thread
|
||||
let (_stream, stream_handle) = match OutputStream::try_default() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to create audio output: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let sink = match Sink::try_new(&stream_handle) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to create sink: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
sink.set_volume(volume);
|
||||
tracing::debug!("Rodio thread initialized with volume={}", volume);
|
||||
|
||||
// Boucle de traitement des commandes
|
||||
while let Ok(cmd) = cmd_rx.recv() {
|
||||
match cmd {
|
||||
RodioCommand::AppendSamples { samples, sample_rate } => {
|
||||
let buffer = rodio::buffer::SamplesBuffer::new(2, sample_rate, samples);
|
||||
sink.append(buffer);
|
||||
}
|
||||
RodioCommand::WaitUntilEnd => {
|
||||
sink.sleep_until_end();
|
||||
break;
|
||||
}
|
||||
RodioCommand::Stop => {
|
||||
sink.stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::debug!("Rodio thread exiting");
|
||||
});
|
||||
|
||||
tracing::debug!("AudioSink initialized with volume={}", self.volume);
|
||||
|
||||
// Boucle de réception et traitement des segments
|
||||
loop {
|
||||
// Vérifier si l'arrêt a été demandé
|
||||
if stop_token.is_cancelled() {
|
||||
tracing::debug!("AudioSinkLogic cancelled");
|
||||
let _ = cmd_tx.send(RodioCommand::Stop);
|
||||
let _ = rodio_thread.join();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Recevoir le prochain segment
|
||||
let segment = tokio::select! {
|
||||
result = rx.recv() => {
|
||||
match result {
|
||||
Some(seg) => seg,
|
||||
None => {
|
||||
tracing::debug!("AudioSinkLogic: input channel closed");
|
||||
let _ = cmd_tx.send(RodioCommand::Stop);
|
||||
let _ = rodio_thread.join();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = stop_token.cancelled() => {
|
||||
tracing::debug!("AudioSinkLogic cancelled during recv");
|
||||
let _ = cmd_tx.send(RodioCommand::Stop);
|
||||
let _ = rodio_thread.join();
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
// Traiter selon le type de segment
|
||||
match &segment.segment {
|
||||
crate::_AudioSegment::Chunk(chunk) => {
|
||||
// Convertir le chunk en samples rodio
|
||||
let samples = chunk_to_i16_samples(chunk)?;
|
||||
let sample_rate = chunk.sample_rate();
|
||||
|
||||
if samples.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Envoyer au thread rodio
|
||||
cmd_tx
|
||||
.send(RodioCommand::AppendSamples {
|
||||
samples,
|
||||
sample_rate,
|
||||
})
|
||||
.map_err(|_| {
|
||||
AudioError::ProcessingError("Rodio thread died".to_string())
|
||||
})?;
|
||||
|
||||
tracing::trace!(
|
||||
"AudioSink: sent chunk with {} frames at {}Hz",
|
||||
chunk.len(),
|
||||
sample_rate
|
||||
);
|
||||
}
|
||||
crate::_AudioSegment::Sync(marker) => {
|
||||
match **marker {
|
||||
SyncMarker::TrackBoundary { .. } => {
|
||||
tracing::debug!("AudioSink: TrackBoundary received");
|
||||
// Le sink continue automatiquement - pas besoin d'attendre
|
||||
// Le buffer interne de rodio gère la transition
|
||||
}
|
||||
SyncMarker::EndOfStream => {
|
||||
tracing::debug!("AudioSink: EndOfStream received, waiting for playback to finish");
|
||||
// Demander au thread rodio d'attendre la fin
|
||||
cmd_tx
|
||||
.send(RodioCommand::WaitUntilEnd)
|
||||
.map_err(|_| {
|
||||
AudioError::ProcessingError("Rodio thread died".to_string())
|
||||
})?;
|
||||
// Attendre que le thread termine
|
||||
rodio_thread.join().map_err(|_| {
|
||||
AudioError::ProcessingError("Failed to join rodio thread".to_string())
|
||||
})?;
|
||||
return Ok(());
|
||||
}
|
||||
SyncMarker::Error(ref message) => {
|
||||
tracing::warn!("AudioSink: Error marker received: {}", message);
|
||||
// Continuer la lecture malgré l'erreur
|
||||
}
|
||||
_ => {
|
||||
// Ignorer les autres sync markers (TopZeroSync, Heartbeat, etc.)
|
||||
tracing::trace!("AudioSink: ignoring sync marker");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convertit un AudioChunk en vecteur de samples i16 stéréo
|
||||
fn chunk_to_i16_samples(chunk: &AudioChunk) -> Result<Vec<i16>, AudioError> {
|
||||
let len = chunk.len();
|
||||
let mut samples = Vec::with_capacity(len * 2); // 2 channels
|
||||
|
||||
match chunk {
|
||||
AudioChunk::I16(data) => {
|
||||
// Format natif - copie directe
|
||||
for frame in data.get_frames() {
|
||||
samples.push(frame[0]);
|
||||
samples.push(frame[1]);
|
||||
}
|
||||
}
|
||||
AudioChunk::I24(data) => {
|
||||
// Convertir de 24-bit vers 16-bit
|
||||
for frame in data.get_frames() {
|
||||
let left = (frame[0].as_i32() >> 8) as i16;
|
||||
let right = (frame[1].as_i32() >> 8) as i16;
|
||||
samples.push(left);
|
||||
samples.push(right);
|
||||
}
|
||||
}
|
||||
AudioChunk::I32(data) => {
|
||||
// Convertir de 32-bit vers 16-bit
|
||||
for frame in data.get_frames() {
|
||||
let left = (frame[0] >> 16) as i16;
|
||||
let right = (frame[1] >> 16) as i16;
|
||||
samples.push(left);
|
||||
samples.push(right);
|
||||
}
|
||||
}
|
||||
AudioChunk::F32(data) => {
|
||||
// Convertir de float32 vers 16-bit
|
||||
for frame in data.get_frames() {
|
||||
let left = (frame[0].clamp(-1.0, 1.0) * 32767.0) as i16;
|
||||
let right = (frame[1].clamp(-1.0, 1.0) * 32767.0) as i16;
|
||||
samples.push(left);
|
||||
samples.push(right);
|
||||
}
|
||||
}
|
||||
AudioChunk::F64(data) => {
|
||||
// Convertir de float64 vers 16-bit
|
||||
for frame in data.get_frames() {
|
||||
let left = (frame[0].clamp(-1.0, 1.0) * 32767.0) as i16;
|
||||
let right = (frame[1].clamp(-1.0, 1.0) * 32767.0) as i16;
|
||||
samples.push(left);
|
||||
samples.push(right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(samples)
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// WRAPPER AudioSink - Délègue à Node<AudioSinkLogic>
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// AudioSink - Joue les AudioSegment sur la sortie audio standard
|
||||
///
|
||||
/// Ce sink utilise rodio pour la lecture audio multiplateforme. Il accepte
|
||||
/// tous les formats audio (I16, I24, I32, F32, F64) et les convertit
|
||||
/// automatiquement en I16 pour la lecture.
|
||||
///
|
||||
/// # Exemple
|
||||
///
|
||||
/// ```no_run
|
||||
/// use pmoaudio::{AudioSink, FileSource};
|
||||
/// use tokio_util::sync::CancellationToken;
|
||||
///
|
||||
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let source = FileSource::new("audio.flac").await?;
|
||||
/// let mut sink = AudioSink::new();
|
||||
///
|
||||
/// // Connecter la source au sink
|
||||
/// source.register(Box::new(sink));
|
||||
///
|
||||
/// // Démarrer la lecture
|
||||
/// let stop_token = CancellationToken::new();
|
||||
/// source.run(stop_token).await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct AudioSink {
|
||||
inner: Node<AudioSinkLogic>,
|
||||
}
|
||||
|
||||
impl AudioSink {
|
||||
/// Crée un nouveau AudioSink avec volume par défaut (1.0)
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Node::new_with_input(AudioSinkLogic::new(), DEFAULT_CHANNEL_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
/// Crée un nouveau AudioSink avec un volume spécifique (0.0 à 1.0)
|
||||
pub fn with_volume(volume: f32) -> Self {
|
||||
Self {
|
||||
inner: Node::new_with_input(AudioSinkLogic::with_volume(volume), DEFAULT_CHANNEL_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
/// Crée un nouveau AudioSink avec une taille de channel personnalisée
|
||||
pub fn with_channel_size(channel_size: usize, volume: f32) -> Self {
|
||||
Self {
|
||||
inner: Node::new_with_input(
|
||||
AudioSinkLogic::with_volume(volume),
|
||||
channel_size,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AudioSink {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl AudioPipelineNode for AudioSink {
|
||||
fn get_tx(&self) -> Option<mpsc::Sender<Arc<AudioSegment>>> {
|
||||
self.inner.get_tx()
|
||||
}
|
||||
|
||||
fn register(&mut self, _child: Box<dyn AudioPipelineNode>) {
|
||||
panic!("AudioSink is a terminal node and cannot have children");
|
||||
}
|
||||
|
||||
async fn run(
|
||||
self: Box<Self>,
|
||||
stop_token: CancellationToken,
|
||||
) -> Result<(), AudioError> {
|
||||
Box::new(self.inner).run(stop_token).await
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedAudioNode for AudioSink {
|
||||
fn input_type(&self) -> Option<TypeRequirement> {
|
||||
// AudioSink accepte tous les types audio
|
||||
Some(TypeRequirement::any())
|
||||
}
|
||||
|
||||
fn output_type(&self) -> Option<TypeRequirement> {
|
||||
// AudioSink est un sink terminal - pas de sortie
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::AudioChunkData;
|
||||
|
||||
#[test]
|
||||
fn test_chunk_to_i16_samples_from_i16() {
|
||||
let stereo = vec![[100i16, 200i16], [300i16, 400i16]];
|
||||
let chunk_data = AudioChunkData::new(stereo, 44100, 0.0);
|
||||
let chunk = AudioChunk::I16(chunk_data);
|
||||
|
||||
let samples = chunk_to_i16_samples(&chunk).unwrap();
|
||||
assert_eq!(samples, vec![100, 200, 300, 400]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunk_to_i16_samples_from_f32() {
|
||||
use crate::I24;
|
||||
let stereo = vec![[0.5f32, -0.5f32], [1.0f32, -1.0f32]];
|
||||
let chunk_data = AudioChunkData::new(stereo, 48000, 0.0);
|
||||
let chunk = AudioChunk::F32(chunk_data);
|
||||
|
||||
let samples = chunk_to_i16_samples(&chunk).unwrap();
|
||||
// 0.5 * 32767 ≈ 16383
|
||||
// -0.5 * 32767 ≈ -16383
|
||||
// 1.0 * 32767 = 32767
|
||||
// -1.0 * 32767 = -32767
|
||||
assert_eq!(samples.len(), 4);
|
||||
assert!((samples[0] - 16383).abs() <= 1);
|
||||
assert!((samples[1] + 16383).abs() <= 1);
|
||||
assert_eq!(samples[2], 32767);
|
||||
assert_eq!(samples[3], -32767);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_audio_sink_creation() {
|
||||
let sink = AudioSink::new();
|
||||
assert!(sink.get_tx().is_some());
|
||||
assert!(sink.input_type().is_some());
|
||||
assert!(sink.output_type().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_audio_sink_with_volume() {
|
||||
let sink = AudioSink::with_volume(0.5);
|
||||
assert!(sink.get_tx().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "terminal node")]
|
||||
fn test_audio_sink_cannot_have_children() {
|
||||
let mut sink = AudioSink::new();
|
||||
let another_sink = AudioSink::new();
|
||||
sink.register(Box::new(another_sink));
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ pub const DEFAULT_CHANNEL_SIZE: usize = 16;
|
||||
pub const DEFAULT_CHUNK_DURATION_MS: f64 = 50.0;
|
||||
|
||||
// Modules actifs
|
||||
pub mod audio_sink;
|
||||
pub mod converter_nodes;
|
||||
pub mod file_source;
|
||||
pub mod flac_file_sink;
|
||||
|
||||
Reference in New Issue
Block a user