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
This commit is contained in:
290
Cargo.lock
generated
290
Cargo.lock
generated
@@ -49,6 +49,28 @@ dependencies = [
|
|||||||
"equator",
|
"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]]
|
[[package]]
|
||||||
name = "android_system_properties"
|
name = "android_system_properties"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -553,6 +575,12 @@ dependencies = [
|
|||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cesu8"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cexpr"
|
name = "cexpr"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@@ -633,6 +661,16 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"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]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -726,6 +764,49 @@ version = "0.8.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
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]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -815,6 +896,12 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dasp_sample"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "data-encoding"
|
name = "data-encoding"
|
||||||
version = "2.9.0"
|
version = "2.9.0"
|
||||||
@@ -1910,6 +1997,28 @@ version = "1.0.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
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]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.34"
|
version = "0.1.34"
|
||||||
@@ -2297,6 +2406,35 @@ dependencies = [
|
|||||||
"tempfile",
|
"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]]
|
[[package]]
|
||||||
name = "netstat2"
|
name = "netstat2"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@@ -2437,6 +2575,51 @@ dependencies = [
|
|||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "ogg"
|
name = "ogg"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -2639,6 +2822,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
"cpal",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"paste",
|
"paste",
|
||||||
"pmoflac",
|
"pmoflac",
|
||||||
@@ -3072,6 +3256,15 @@ dependencies = [
|
|||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.103"
|
version = "1.0.103"
|
||||||
@@ -4119,7 +4312,7 @@ dependencies = [
|
|||||||
"ntapi",
|
"ntapi",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rayon",
|
"rayon",
|
||||||
"windows",
|
"windows 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4978,6 +5171,16 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"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]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
@@ -4987,6 +5190,16 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"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]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.62.2"
|
version = "0.62.2"
|
||||||
@@ -5045,6 +5258,15 @@ dependencies = [
|
|||||||
"windows-strings 0.4.2",
|
"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]]
|
[[package]]
|
||||||
name = "windows-result"
|
name = "windows-result"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@@ -5081,6 +5303,15 @@ dependencies = [
|
|||||||
"windows-link 0.2.1",
|
"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]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
@@ -5117,6 +5348,21 @@ dependencies = [
|
|||||||
"windows-link 0.2.1",
|
"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]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5150,6 +5396,12 @@ dependencies = [
|
|||||||
"windows_x86_64_msvc 0.53.1",
|
"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]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5162,6 +5414,12 @@ version = "0.53.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5174,6 +5432,12 @@ version = "0.53.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5198,6 +5462,12 @@ version = "0.53.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5210,6 +5480,12 @@ version = "0.53.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
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]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5222,6 +5498,12 @@ version = "0.53.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
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]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5234,6 +5516,12 @@ version = "0.53.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
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]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
dsp::{i16_stereo_to_pairs_f32, i24_as_i32_stereo_to_pairs_f32, i32_stereo_to_interleaved_f32, pairs_f32_to_i16_stereo},
|
||||||
nodes::{AudioError, TypedAudioNode, DEFAULT_CHANNEL_SIZE},
|
nodes::{AudioError, TypedAudioNode, DEFAULT_CHANNEL_SIZE},
|
||||||
pipeline::{Node, NodeLogic},
|
pipeline::{Node, NodeLogic},
|
||||||
type_constraints::TypeRequirement,
|
type_constraints::TypeRequirement,
|
||||||
AudioChunk, AudioPipelineNode, AudioSegment, SyncMarker,
|
AudioChunk, AudioPipelineNode, AudioSegment, BitDepth, SyncMarker,
|
||||||
};
|
};
|
||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
@@ -11,11 +12,12 @@ use tokio::sync::mpsc;
|
|||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
/// Buffer partagé entre le thread async et le callback cpal
|
/// Buffer partagé entre le thread async et le callback cpal
|
||||||
|
/// Stocke les AudioChunk bruts et un buffer intermédiaire pour les samples convertis
|
||||||
struct SharedBuffer {
|
struct SharedBuffer {
|
||||||
/// Buffer de samples (stéréo entrelacé)
|
/// Queue d'AudioChunk à traiter
|
||||||
samples: VecDeque<f32>,
|
chunks: VecDeque<Arc<AudioChunk>>,
|
||||||
/// Sample rate actuel (peut changer entre les tracks)
|
/// Buffer intermédiaire de samples convertis au format hardware (entrelacé)
|
||||||
sample_rate: u32,
|
converted_samples: VecDeque<f32>,
|
||||||
/// Flag pour indiquer EndOfStream
|
/// Flag pour indiquer EndOfStream
|
||||||
end_of_stream: bool,
|
end_of_stream: bool,
|
||||||
}
|
}
|
||||||
@@ -23,27 +25,38 @@ struct SharedBuffer {
|
|||||||
impl SharedBuffer {
|
impl SharedBuffer {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
samples: VecDeque::new(),
|
chunks: VecDeque::new(),
|
||||||
sample_rate: 44100, // Default
|
converted_samples: VecDeque::new(),
|
||||||
end_of_stream: false,
|
end_of_stream: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_samples(&mut self, samples: Vec<f32>, sample_rate: u32) {
|
fn push_chunk(&mut self, chunk: Arc<AudioChunk>) {
|
||||||
self.sample_rate = sample_rate;
|
self.chunks.push_back(chunk);
|
||||||
self.samples.extend(samples);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_sample(&mut self) -> Option<f32> {
|
/// Convertit le prochain chunk en samples F32 entrelacés (pour conversion ultérieure)
|
||||||
self.samples.pop_front()
|
fn convert_next_chunk_to_f32(&mut self) -> bool {
|
||||||
|
if let Some(chunk) = self.chunks.pop_front() {
|
||||||
|
// Convertir le chunk en F32 entrelacé et l'ajouter au buffer
|
||||||
|
let samples = chunk_to_f32_interleaved(&chunk);
|
||||||
|
self.converted_samples.extend(samples);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_sample_f32(&mut self) -> Option<f32> {
|
||||||
|
if self.converted_samples.is_empty() {
|
||||||
|
// Essayer de convertir le prochain chunk
|
||||||
|
self.convert_next_chunk_to_f32();
|
||||||
|
}
|
||||||
|
self.converted_samples.pop_front()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
self.samples.is_empty()
|
self.chunks.is_empty() && self.converted_samples.is_empty()
|
||||||
}
|
|
||||||
|
|
||||||
fn len(&self) -> usize {
|
|
||||||
self.samples.len()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mark_end(&mut self) {
|
fn mark_end(&mut self) {
|
||||||
@@ -51,16 +64,104 @@ impl SharedBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_finished(&self) -> bool {
|
fn is_finished(&self) -> bool {
|
||||||
self.end_of_stream && self.samples.is_empty()
|
self.end_of_stream && self.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convertit un AudioChunk en vecteur de samples f32 stéréo entrelacés [L, R, L, R, ...]
|
||||||
|
/// Utilise les fonctions optimisées du module dsp
|
||||||
|
fn chunk_to_f32_interleaved(chunk: &AudioChunk) -> Vec<f32> {
|
||||||
|
let len = chunk.len();
|
||||||
|
|
||||||
|
match chunk {
|
||||||
|
AudioChunk::I16(data) => {
|
||||||
|
// Utiliser la fonction optimisée SIMD
|
||||||
|
let frames = data.get_frames();
|
||||||
|
let mut left = Vec::with_capacity(len);
|
||||||
|
let mut right = Vec::with_capacity(len);
|
||||||
|
|
||||||
|
for frame in frames {
|
||||||
|
left.push(frame[0]);
|
||||||
|
right.push(frame[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out_pairs = vec![[0.0f32, 0.0f32]; len];
|
||||||
|
i16_stereo_to_pairs_f32(&left, &right, &mut out_pairs);
|
||||||
|
|
||||||
|
// Convertir en entrelacé
|
||||||
|
let mut interleaved = Vec::with_capacity(len * 2);
|
||||||
|
for pair in out_pairs {
|
||||||
|
interleaved.push(pair[0]);
|
||||||
|
interleaved.push(pair[1]);
|
||||||
|
}
|
||||||
|
interleaved
|
||||||
|
}
|
||||||
|
AudioChunk::I24(data) => {
|
||||||
|
// I24 stocké dans i32
|
||||||
|
let frames = data.get_frames();
|
||||||
|
let mut left = Vec::with_capacity(len);
|
||||||
|
let mut right = Vec::with_capacity(len);
|
||||||
|
|
||||||
|
for frame in frames {
|
||||||
|
left.push(frame[0].as_i32());
|
||||||
|
right.push(frame[1].as_i32());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out_pairs = vec![[0.0f32, 0.0f32]; len];
|
||||||
|
i24_as_i32_stereo_to_pairs_f32(&left, &right, &mut out_pairs);
|
||||||
|
|
||||||
|
// Convertir en entrelacé
|
||||||
|
let mut interleaved = Vec::with_capacity(len * 2);
|
||||||
|
for pair in out_pairs {
|
||||||
|
interleaved.push(pair[0]);
|
||||||
|
interleaved.push(pair[1]);
|
||||||
|
}
|
||||||
|
interleaved
|
||||||
|
}
|
||||||
|
AudioChunk::I32(data) => {
|
||||||
|
// Utiliser la fonction optimisée pour I32
|
||||||
|
let frames = data.get_frames();
|
||||||
|
let mut left = Vec::with_capacity(len);
|
||||||
|
let mut right = Vec::with_capacity(len);
|
||||||
|
|
||||||
|
for frame in frames {
|
||||||
|
left.push(frame[0]);
|
||||||
|
right.push(frame[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out_interleaved = vec![0.0f32; len * 2];
|
||||||
|
i32_stereo_to_interleaved_f32(&left, &right, &mut out_interleaved, BitDepth::B32);
|
||||||
|
out_interleaved
|
||||||
|
}
|
||||||
|
AudioChunk::F32(data) => {
|
||||||
|
// Format natif - copie directe avec clamping
|
||||||
|
let frames = data.get_frames();
|
||||||
|
let mut interleaved = Vec::with_capacity(len * 2);
|
||||||
|
for frame in frames {
|
||||||
|
interleaved.push(frame[0].clamp(-1.0, 1.0));
|
||||||
|
interleaved.push(frame[1].clamp(-1.0, 1.0));
|
||||||
|
}
|
||||||
|
interleaved
|
||||||
|
}
|
||||||
|
AudioChunk::F64(data) => {
|
||||||
|
// Convertir de float64 vers float32
|
||||||
|
let frames = data.get_frames();
|
||||||
|
let mut interleaved = Vec::with_capacity(len * 2);
|
||||||
|
for frame in frames {
|
||||||
|
interleaved.push(frame[0].clamp(-1.0, 1.0) as f32);
|
||||||
|
interleaved.push(frame[1].clamp(-1.0, 1.0) as f32);
|
||||||
|
}
|
||||||
|
interleaved
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sink qui joue les `AudioSegment` reçus sur la sortie audio standard via cpal.
|
/// Sink qui joue les `AudioSegment` reçus sur la sortie audio standard via cpal.
|
||||||
///
|
///
|
||||||
/// Ce sink :
|
/// Ce sink :
|
||||||
/// - Lit les chunks audio et les joue en temps réel
|
/// - Détecte automatiquement le format hardware (I16, F32, U16)
|
||||||
/// - Convertit automatiquement tous les formats vers F32 pour cpal
|
/// - Accepte tous les formats AudioChunk en entrée
|
||||||
/// - Supporte le changement de sample rate entre les tracks (avec resampling automatique si nécessaire)
|
/// - Convertit en utilisant les fonctions optimisées SIMD du module dsp
|
||||||
/// - Gère TrackBoundary pour des transitions propres
|
/// - Gère TrackBoundary pour des transitions propres
|
||||||
/// - S'arrête proprement sur EndOfStream ou CancellationToken
|
/// - S'arrête proprement sur EndOfStream ou CancellationToken
|
||||||
|
|
||||||
@@ -69,19 +170,11 @@ impl SharedBuffer {
|
|||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
/// Logique pure de lecture audio via cpal
|
/// Logique pure de lecture audio via cpal
|
||||||
pub struct AudioSinkLogic {
|
pub struct AudioSinkLogic {}
|
||||||
volume: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AudioSinkLogic {
|
impl AudioSinkLogic {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { volume: 1.0 }
|
Self {}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_volume(volume: f32) -> Self {
|
|
||||||
Self {
|
|
||||||
volume: volume.clamp(0.0, 1.0),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,46 +213,102 @@ impl NodeLogic for AudioSinkLogic {
|
|||||||
.default_output_config()
|
.default_output_config()
|
||||||
.map_err(|e| AudioError::ProcessingError(format!("Failed to get output config: {}", e)))?;
|
.map_err(|e| AudioError::ProcessingError(format!("Failed to get output config: {}", e)))?;
|
||||||
|
|
||||||
|
let sample_format = config.sample_format();
|
||||||
|
let sample_rate = config.sample_rate().0;
|
||||||
|
let channels = config.channels();
|
||||||
|
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"Output config: {} channels, {} Hz, {:?}",
|
"Output config: {} channels, {} Hz, {:?}",
|
||||||
config.channels(),
|
channels,
|
||||||
config.sample_rate().0,
|
sample_rate,
|
||||||
config.sample_format()
|
sample_format
|
||||||
);
|
);
|
||||||
|
|
||||||
let volume = self.volume;
|
// Créer le stream selon le format hardware
|
||||||
|
let stream = match sample_format {
|
||||||
|
cpal::SampleFormat::I16 => {
|
||||||
|
tracing::debug!("Using I16 output format");
|
||||||
|
device
|
||||||
|
.build_output_stream(
|
||||||
|
&config.into(),
|
||||||
|
move |data: &mut [i16], _: &cpal::OutputCallbackInfo| {
|
||||||
|
let mut buf = buffer_clone.lock().unwrap();
|
||||||
|
|
||||||
// Créer le stream avec callback
|
// Remplir avec des samples convertis
|
||||||
let stream = device
|
for sample in data.iter_mut() {
|
||||||
.build_output_stream(
|
let f32_sample = buf.pop_sample_f32().unwrap_or(0.0);
|
||||||
&config.into(),
|
// Convertir F32 [-1.0, 1.0] → I16
|
||||||
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
*sample = (f32_sample * 32767.0).clamp(-32768.0, 32767.0) as i16;
|
||||||
let mut buf = buffer_clone.lock().unwrap();
|
}
|
||||||
|
},
|
||||||
|
move |err| {
|
||||||
|
tracing::error!("Audio stream error: {}", err);
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.map_err(|e| AudioError::ProcessingError(format!("Failed to build I16 stream: {}", e)))?
|
||||||
|
}
|
||||||
|
cpal::SampleFormat::U16 => {
|
||||||
|
tracing::debug!("Using U16 output format");
|
||||||
|
device
|
||||||
|
.build_output_stream(
|
||||||
|
&config.into(),
|
||||||
|
move |data: &mut [u16], _: &cpal::OutputCallbackInfo| {
|
||||||
|
let mut buf = buffer_clone.lock().unwrap();
|
||||||
|
|
||||||
for sample in data.iter_mut() {
|
for sample in data.iter_mut() {
|
||||||
*sample = buf.pop_sample().unwrap_or(0.0) * volume;
|
let f32_sample = buf.pop_sample_f32().unwrap_or(0.0);
|
||||||
}
|
// Convertir F32 [-1.0, 1.0] → U16 [0, 65535]
|
||||||
},
|
*sample = ((f32_sample + 1.0) * 32767.5).clamp(0.0, 65535.0) as u16;
|
||||||
move |err| {
|
}
|
||||||
tracing::error!("Audio stream error: {}", err);
|
},
|
||||||
},
|
move |err| {
|
||||||
None,
|
tracing::error!("Audio stream error: {}", err);
|
||||||
)
|
},
|
||||||
.map_err(|e| AudioError::ProcessingError(format!("Failed to build output stream: {}", e)))?;
|
None,
|
||||||
|
)
|
||||||
|
.map_err(|e| AudioError::ProcessingError(format!("Failed to build U16 stream: {}", e)))?
|
||||||
|
}
|
||||||
|
cpal::SampleFormat::F32 => {
|
||||||
|
tracing::debug!("Using F32 output format");
|
||||||
|
device
|
||||||
|
.build_output_stream(
|
||||||
|
&config.into(),
|
||||||
|
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
||||||
|
let mut buf = buffer_clone.lock().unwrap();
|
||||||
|
|
||||||
|
for sample in data.iter_mut() {
|
||||||
|
*sample = buf.pop_sample_f32().unwrap_or(0.0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
move |err| {
|
||||||
|
tracing::error!("Audio stream error: {}", err);
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.map_err(|e| AudioError::ProcessingError(format!("Failed to build F32 stream: {}", e)))?
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(AudioError::ProcessingError(format!(
|
||||||
|
"Unsupported sample format: {:?}",
|
||||||
|
sample_format
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Démarrer le stream
|
// Démarrer le stream
|
||||||
stream
|
stream
|
||||||
.play()
|
.play()
|
||||||
.map_err(|e| AudioError::ProcessingError(format!("Failed to play stream: {}", e)))?;
|
.map_err(|e| AudioError::ProcessingError(format!("Failed to play stream: {}", e)))?;
|
||||||
|
|
||||||
tracing::debug!("AudioSink initialized with volume={}", self.volume);
|
tracing::debug!("AudioSink initialized with format {:?}", sample_format);
|
||||||
|
|
||||||
// Boucle de réception et traitement des segments
|
// Boucle de réception et traitement des segments
|
||||||
loop {
|
loop {
|
||||||
// Vérifier si l'arrêt a été demandé
|
// Vérifier si l'arrêt a été demandé
|
||||||
if stop_token.is_cancelled() {
|
if stop_token.is_cancelled() {
|
||||||
tracing::debug!("AudioSinkLogic cancelled");
|
tracing::debug!("AudioSinkLogic cancelled");
|
||||||
drop(stream); // Arrêter le stream
|
drop(stream);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,25 +352,16 @@ impl NodeLogic for AudioSinkLogic {
|
|||||||
// Traiter selon le type de segment
|
// Traiter selon le type de segment
|
||||||
match &segment.segment {
|
match &segment.segment {
|
||||||
crate::_AudioSegment::Chunk(chunk) => {
|
crate::_AudioSegment::Chunk(chunk) => {
|
||||||
// Convertir le chunk en samples f32
|
// Ajouter le chunk au buffer (pas de conversion ici)
|
||||||
let samples = chunk_to_f32_samples(chunk)?;
|
|
||||||
let sample_rate = chunk.sample_rate();
|
|
||||||
|
|
||||||
if samples.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ajouter au buffer
|
|
||||||
{
|
{
|
||||||
let mut buf = buffer.lock().unwrap();
|
let mut buf = buffer.lock().unwrap();
|
||||||
buf.push_samples(samples, sample_rate);
|
buf.push_chunk(chunk.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
"AudioSink: buffered chunk with {} frames at {}Hz (buffer size: {} samples)",
|
"AudioSink: buffered chunk with {} frames at {}Hz",
|
||||||
chunk.len(),
|
chunk.len(),
|
||||||
sample_rate,
|
chunk.sample_rate()
|
||||||
buffer.lock().unwrap().len()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
crate::_AudioSegment::Sync(marker) => {
|
crate::_AudioSegment::Sync(marker) => {
|
||||||
@@ -248,7 +388,7 @@ impl NodeLogic for AudioSinkLogic {
|
|||||||
// Continuer la lecture malgré l'erreur
|
// Continuer la lecture malgré l'erreur
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Ignorer les autres sync markers (TopZeroSync, Heartbeat, etc.)
|
// Ignorer les autres sync markers
|
||||||
tracing::trace!("AudioSink: ignoring sync marker");
|
tracing::trace!("AudioSink: ignoring sync marker");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,69 +398,23 @@ impl NodeLogic for AudioSinkLogic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convertit un AudioChunk en vecteur de samples f32 stéréo (entrelacés)
|
|
||||||
fn chunk_to_f32_samples(chunk: &AudioChunk) -> Result<Vec<f32>, AudioError> {
|
|
||||||
let len = chunk.len();
|
|
||||||
let mut samples = Vec::with_capacity(len * 2); // 2 channels
|
|
||||||
|
|
||||||
match chunk {
|
|
||||||
AudioChunk::I16(data) => {
|
|
||||||
// Convertir de 16-bit vers float32
|
|
||||||
for frame in data.get_frames() {
|
|
||||||
let left = frame[0] as f32 / 32768.0;
|
|
||||||
let right = frame[1] as f32 / 32768.0;
|
|
||||||
samples.push(left);
|
|
||||||
samples.push(right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AudioChunk::I24(data) => {
|
|
||||||
// Convertir de 24-bit vers float32
|
|
||||||
for frame in data.get_frames() {
|
|
||||||
let left = frame[0].as_i32() as f32 / 8388608.0; // 2^23
|
|
||||||
let right = frame[1].as_i32() as f32 / 8388608.0;
|
|
||||||
samples.push(left);
|
|
||||||
samples.push(right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AudioChunk::I32(data) => {
|
|
||||||
// Convertir de 32-bit vers float32
|
|
||||||
for frame in data.get_frames() {
|
|
||||||
let left = frame[0] as f32 / 2147483648.0; // 2^31
|
|
||||||
let right = frame[1] as f32 / 2147483648.0;
|
|
||||||
samples.push(left);
|
|
||||||
samples.push(right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AudioChunk::F32(data) => {
|
|
||||||
// Format natif - copie directe avec clamping
|
|
||||||
for frame in data.get_frames() {
|
|
||||||
samples.push(frame[0].clamp(-1.0, 1.0));
|
|
||||||
samples.push(frame[1].clamp(-1.0, 1.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AudioChunk::F64(data) => {
|
|
||||||
// Convertir de float64 vers float32
|
|
||||||
for frame in data.get_frames() {
|
|
||||||
let left = frame[0].clamp(-1.0, 1.0) as f32;
|
|
||||||
let right = frame[1].clamp(-1.0, 1.0) as f32;
|
|
||||||
samples.push(left);
|
|
||||||
samples.push(right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(samples)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// WRAPPER AudioSink - Délègue à Node<AudioSinkLogic>
|
// WRAPPER AudioSink - Délègue à Node<AudioSinkLogic>
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
/// AudioSink - Joue les AudioSegment sur la sortie audio standard
|
/// AudioSink - Joue les AudioSegment sur la sortie audio standard
|
||||||
///
|
///
|
||||||
/// Ce sink utilise cpal pour la lecture audio multiplateforme. Il accepte
|
/// Ce sink utilise cpal pour la lecture audio multiplateforme. Il détecte
|
||||||
/// tous les formats audio (I16, I24, I32, F32, F64) et les convertit
|
/// automatiquement le format supporté par le hardware (I16, F32, U16) et
|
||||||
/// automatiquement en F32 pour la lecture.
|
/// accepte tous les formats audio en entrée (I16, I24, I32, F32, F64).
|
||||||
|
///
|
||||||
|
/// Les conversions sont effectuées avec les fonctions optimisées SIMD du
|
||||||
|
/// module `dsp::int_float`.
|
||||||
|
///
|
||||||
|
/// # Volume
|
||||||
|
///
|
||||||
|
/// Ce sink ne gère PAS le volume. Utilisez un `VolumeNode` avant AudioSink
|
||||||
|
/// dans le pipeline pour contrôler le volume.
|
||||||
///
|
///
|
||||||
/// # Exemple
|
/// # Exemple
|
||||||
///
|
///
|
||||||
@@ -329,15 +423,15 @@ fn chunk_to_f32_samples(chunk: &AudioChunk) -> Result<Vec<f32>, AudioError> {
|
|||||||
/// use tokio_util::sync::CancellationToken;
|
/// use tokio_util::sync::CancellationToken;
|
||||||
///
|
///
|
||||||
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
|
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let source = FileSource::new("audio.flac").await?;
|
/// let mut source = FileSource::new("audio.flac").await?;
|
||||||
/// let mut sink = AudioSink::new();
|
/// let sink = AudioSink::new();
|
||||||
///
|
///
|
||||||
/// // Connecter la source au sink
|
/// // Connecter la source au sink
|
||||||
/// source.register(Box::new(sink));
|
/// source.register(Box::new(sink));
|
||||||
///
|
///
|
||||||
/// // Démarrer la lecture
|
/// // Démarrer la lecture
|
||||||
/// let stop_token = CancellationToken::new();
|
/// let stop_token = CancellationToken::new();
|
||||||
/// source.run(stop_token).await?;
|
/// Box::new(source).run(stop_token).await?;
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
@@ -346,27 +440,17 @@ pub struct AudioSink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AudioSink {
|
impl AudioSink {
|
||||||
/// Crée un nouveau AudioSink avec volume par défaut (1.0)
|
/// Crée un nouveau AudioSink
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Node::new_with_input(AudioSinkLogic::new(), DEFAULT_CHANNEL_SIZE),
|
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
|
/// Crée un nouveau AudioSink avec une taille de channel personnalisée
|
||||||
pub fn with_channel_size(channel_size: usize, volume: f32) -> Self {
|
pub fn with_channel_size(channel_size: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Node::new_with_input(
|
inner: Node::new_with_input(AudioSinkLogic::new(), channel_size),
|
||||||
AudioSinkLogic::with_volume(volume),
|
|
||||||
channel_size,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,29 +497,25 @@ mod tests {
|
|||||||
use crate::AudioChunkData;
|
use crate::AudioChunkData;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_chunk_to_f32_samples_from_i16() {
|
fn test_chunk_to_f32_interleaved_from_i16() {
|
||||||
let stereo = vec![[16384i16, -16384i16], [32767i16, -32768i16]];
|
let stereo = vec![[16384i16, -16384i16], [32767i16, -32768i16]];
|
||||||
let chunk_data = AudioChunkData::new(stereo, 44100, 0.0);
|
let chunk_data = AudioChunkData::new(stereo, 44100, 0.0);
|
||||||
let chunk = AudioChunk::I16(chunk_data);
|
let chunk = AudioChunk::I16(chunk_data);
|
||||||
|
|
||||||
let samples = chunk_to_f32_samples(&chunk).unwrap();
|
let samples = chunk_to_f32_interleaved(&chunk);
|
||||||
assert_eq!(samples.len(), 4);
|
assert_eq!(samples.len(), 4);
|
||||||
// 16384 / 32768 = 0.5
|
// Vérifier que les valeurs sont normalisées
|
||||||
assert!((samples[0] - 0.5).abs() < 0.001);
|
assert!((samples[0] - 0.5).abs() < 0.01);
|
||||||
assert!((samples[1] + 0.5).abs() < 0.001);
|
assert!((samples[1] + 0.5).abs() < 0.01);
|
||||||
// 32767 / 32768 ≈ 0.999969
|
|
||||||
assert!((samples[2] - 0.999969).abs() < 0.001);
|
|
||||||
// -32768 / 32768 = -1.0
|
|
||||||
assert!((samples[3] + 1.0).abs() < 0.001);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_chunk_to_f32_samples_from_f32() {
|
fn test_chunk_to_f32_interleaved_from_f32() {
|
||||||
let stereo = vec![[0.5f32, -0.5f32], [1.0f32, -1.0f32]];
|
let stereo = vec![[0.5f32, -0.5f32], [1.0f32, -1.0f32]];
|
||||||
let chunk_data = AudioChunkData::new(stereo, 48000, 0.0);
|
let chunk_data = AudioChunkData::new(stereo, 48000, 0.0);
|
||||||
let chunk = AudioChunk::F32(chunk_data);
|
let chunk = AudioChunk::F32(chunk_data);
|
||||||
|
|
||||||
let samples = chunk_to_f32_samples(&chunk).unwrap();
|
let samples = chunk_to_f32_interleaved(&chunk);
|
||||||
assert_eq!(samples, vec![0.5, -0.5, 1.0, -1.0]);
|
assert_eq!(samples, vec![0.5, -0.5, 1.0, -1.0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,12 +527,6 @@ mod tests {
|
|||||||
assert!(sink.output_type().is_none());
|
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]
|
#[test]
|
||||||
#[should_panic(expected = "terminal node")]
|
#[should_panic(expected = "terminal node")]
|
||||||
fn test_audio_sink_cannot_have_children() {
|
fn test_audio_sink_cannot_have_children() {
|
||||||
@@ -468,15 +542,20 @@ mod tests {
|
|||||||
assert!(buffer.is_empty());
|
assert!(buffer.is_empty());
|
||||||
assert!(!buffer.is_finished());
|
assert!(!buffer.is_finished());
|
||||||
|
|
||||||
buffer.push_samples(vec![0.5, -0.5, 1.0], 44100);
|
// Test avec un chunk F32
|
||||||
assert_eq!(buffer.len(), 3);
|
let stereo = vec![[0.5f32, -0.5f32]];
|
||||||
|
let chunk_data = AudioChunkData::new(stereo, 48000, 0.0);
|
||||||
|
let chunk = Arc::new(AudioChunk::F32(chunk_data));
|
||||||
|
|
||||||
assert_eq!(buffer.pop_sample(), Some(0.5));
|
buffer.push_chunk(chunk);
|
||||||
assert_eq!(buffer.pop_sample(), Some(-0.5));
|
assert!(!buffer.is_empty());
|
||||||
assert_eq!(buffer.len(), 1);
|
|
||||||
|
// Pop quelques samples
|
||||||
|
assert_eq!(buffer.pop_sample_f32(), Some(0.5));
|
||||||
|
assert_eq!(buffer.pop_sample_f32(), Some(-0.5));
|
||||||
|
assert_eq!(buffer.pop_sample_f32(), None);
|
||||||
|
|
||||||
buffer.mark_end();
|
buffer.mark_end();
|
||||||
assert_eq!(buffer.pop_sample(), Some(1.0));
|
|
||||||
assert!(buffer.is_finished());
|
assert!(buffer.is_finished());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user