improve selection of audio source
This commit is contained in:
24
README.md
24
README.md
@@ -138,5 +138,27 @@ sudo systemctl status auracast-frontend
|
|||||||
|
|
||||||
If you want to run the services as a specific user, edit the `User=` line in the service files accordingly.
|
If you want to run the services as a specific user, edit the `User=` line in the service files accordingly.
|
||||||
|
|
||||||
|
# install port audio so it can see pipewire devices on raspian
|
||||||
|
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install --no-install-recommends \
|
||||||
|
git build-essential cmake pkg-config \
|
||||||
|
libasound2-dev libpulse-dev libjack-jackd2-dev
|
||||||
|
git clone https://github.com/PortAudio/portaudio.git
|
||||||
|
cd portaudio # branch = master (19.7‑devel)
|
||||||
|
rm -rf build
|
||||||
|
cmake -S . -B build -G"Unix Makefiles" \
|
||||||
|
-DBUILD_SHARED_LIBS=ON \
|
||||||
|
-DPA_USE_ALSA=ON \
|
||||||
|
-DPA_USE_OSS=ON \
|
||||||
|
-DPA_USE_PULSEAUDIO=ON \
|
||||||
|
-DPA_USE_JACK=OFF
|
||||||
|
cmake --build build -j$(nproc)
|
||||||
|
sudo apt remove -y libportaudio2 portaudio19-dev libportaudiocpp0
|
||||||
|
sudo cmake --install build # installs to /usr/local/lib
|
||||||
|
sudo ldconfig # refresh linker cache
|
||||||
|
|
||||||
|
|
||||||
# Known issues:
|
# Known issues:
|
||||||
- When running on a laptop there might be issues switching between usb and browser audio input since they use the same audio device
|
- When running on a laptop there might be issues switching between usb and browser audio input since they use the same audio device
|
||||||
|
|
||||||
|
|||||||
30
poetry.lock
generated
30
poetry.lock
generated
@@ -1476,27 +1476,43 @@ files = [
|
|||||||
{file = "pandas-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6872d695c896f00df46b71648eea332279ef4077a409e2fe94220208b6bb675"},
|
{file = "pandas-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6872d695c896f00df46b71648eea332279ef4077a409e2fe94220208b6bb675"},
|
||||||
{file = "pandas-2.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4dd97c19bd06bc557ad787a15b6489d2614ddaab5d104a0310eb314c724b2d2"},
|
{file = "pandas-2.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4dd97c19bd06bc557ad787a15b6489d2614ddaab5d104a0310eb314c724b2d2"},
|
||||||
{file = "pandas-2.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:034abd6f3db8b9880aaee98f4f5d4dbec7c4829938463ec046517220b2f8574e"},
|
{file = "pandas-2.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:034abd6f3db8b9880aaee98f4f5d4dbec7c4829938463ec046517220b2f8574e"},
|
||||||
|
{file = "pandas-2.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23c2b2dc5213810208ca0b80b8666670eb4660bbfd9d45f58592cc4ddcfd62e1"},
|
||||||
{file = "pandas-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:39ff73ec07be5e90330cc6ff5705c651ace83374189dcdcb46e6ff54b4a72cd6"},
|
{file = "pandas-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:39ff73ec07be5e90330cc6ff5705c651ace83374189dcdcb46e6ff54b4a72cd6"},
|
||||||
{file = "pandas-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:40cecc4ea5abd2921682b57532baea5588cc5f80f0231c624056b146887274d2"},
|
{file = "pandas-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:40cecc4ea5abd2921682b57532baea5588cc5f80f0231c624056b146887274d2"},
|
||||||
{file = "pandas-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8adff9f138fc614347ff33812046787f7d43b3cef7c0f0171b3340cae333f6ca"},
|
{file = "pandas-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8adff9f138fc614347ff33812046787f7d43b3cef7c0f0171b3340cae333f6ca"},
|
||||||
|
{file = "pandas-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e5f08eb9a445d07720776df6e641975665c9ea12c9d8a331e0f6890f2dcd76ef"},
|
||||||
{file = "pandas-2.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa35c266c8cd1a67d75971a1912b185b492d257092bdd2709bbdebe574ed228d"},
|
{file = "pandas-2.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa35c266c8cd1a67d75971a1912b185b492d257092bdd2709bbdebe574ed228d"},
|
||||||
{file = "pandas-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a0cc77b0f089d2d2ffe3007db58f170dae9b9f54e569b299db871a3ab5bf46"},
|
{file = "pandas-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a0cc77b0f089d2d2ffe3007db58f170dae9b9f54e569b299db871a3ab5bf46"},
|
||||||
|
{file = "pandas-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c06f6f144ad0a1bf84699aeea7eff6068ca5c63ceb404798198af7eb86082e33"},
|
||||||
{file = "pandas-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed16339bc354a73e0a609df36d256672c7d296f3f767ac07257801aa064ff73c"},
|
{file = "pandas-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed16339bc354a73e0a609df36d256672c7d296f3f767ac07257801aa064ff73c"},
|
||||||
{file = "pandas-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:fa07e138b3f6c04addfeaf56cc7fdb96c3b68a3fe5e5401251f231fce40a0d7a"},
|
{file = "pandas-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:fa07e138b3f6c04addfeaf56cc7fdb96c3b68a3fe5e5401251f231fce40a0d7a"},
|
||||||
{file = "pandas-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2eb4728a18dcd2908c7fccf74a982e241b467d178724545a48d0caf534b38ebf"},
|
{file = "pandas-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2eb4728a18dcd2908c7fccf74a982e241b467d178724545a48d0caf534b38ebf"},
|
||||||
|
{file = "pandas-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9d8c3187be7479ea5c3d30c32a5d73d62a621166675063b2edd21bc47614027"},
|
||||||
|
{file = "pandas-2.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ff730713d4c4f2f1c860e36c005c7cefc1c7c80c21c0688fd605aa43c9fcf09"},
|
||||||
{file = "pandas-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba24af48643b12ffe49b27065d3babd52702d95ab70f50e1b34f71ca703e2c0d"},
|
{file = "pandas-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba24af48643b12ffe49b27065d3babd52702d95ab70f50e1b34f71ca703e2c0d"},
|
||||||
|
{file = "pandas-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:404d681c698e3c8a40a61d0cd9412cc7364ab9a9cc6e144ae2992e11a2e77a20"},
|
||||||
{file = "pandas-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6021910b086b3ca756755e86ddc64e0ddafd5e58e076c72cb1585162e5ad259b"},
|
{file = "pandas-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6021910b086b3ca756755e86ddc64e0ddafd5e58e076c72cb1585162e5ad259b"},
|
||||||
{file = "pandas-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:094e271a15b579650ebf4c5155c05dcd2a14fd4fdd72cf4854b2f7ad31ea30be"},
|
{file = "pandas-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:094e271a15b579650ebf4c5155c05dcd2a14fd4fdd72cf4854b2f7ad31ea30be"},
|
||||||
{file = "pandas-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c7e2fc25f89a49a11599ec1e76821322439d90820108309bf42130d2f36c983"},
|
{file = "pandas-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c7e2fc25f89a49a11599ec1e76821322439d90820108309bf42130d2f36c983"},
|
||||||
|
{file = "pandas-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6da97aeb6a6d233fb6b17986234cc723b396b50a3c6804776351994f2a658fd"},
|
||||||
{file = "pandas-2.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb32dc743b52467d488e7a7c8039b821da2826a9ba4f85b89ea95274f863280f"},
|
{file = "pandas-2.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb32dc743b52467d488e7a7c8039b821da2826a9ba4f85b89ea95274f863280f"},
|
||||||
{file = "pandas-2.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:213cd63c43263dbb522c1f8a7c9d072e25900f6975596f883f4bebd77295d4f3"},
|
{file = "pandas-2.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:213cd63c43263dbb522c1f8a7c9d072e25900f6975596f883f4bebd77295d4f3"},
|
||||||
|
{file = "pandas-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1d2b33e68d0ce64e26a4acc2e72d747292084f4e8db4c847c6f5f6cbe56ed6d8"},
|
||||||
{file = "pandas-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:430a63bae10b5086995db1b02694996336e5a8ac9a96b4200572b413dfdfccb9"},
|
{file = "pandas-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:430a63bae10b5086995db1b02694996336e5a8ac9a96b4200572b413dfdfccb9"},
|
||||||
{file = "pandas-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4930255e28ff5545e2ca404637bcc56f031893142773b3468dc021c6c32a1390"},
|
{file = "pandas-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4930255e28ff5545e2ca404637bcc56f031893142773b3468dc021c6c32a1390"},
|
||||||
{file = "pandas-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f925f1ef673b4bd0271b1809b72b3270384f2b7d9d14a189b12b7fc02574d575"},
|
{file = "pandas-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f925f1ef673b4bd0271b1809b72b3270384f2b7d9d14a189b12b7fc02574d575"},
|
||||||
{file = "pandas-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78ad363ddb873a631e92a3c063ade1ecfb34cae71e9a2be6ad100f875ac1042"},
|
{file = "pandas-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78ad363ddb873a631e92a3c063ade1ecfb34cae71e9a2be6ad100f875ac1042"},
|
||||||
{file = "pandas-2.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951805d146922aed8357e4cc5671b8b0b9be1027f0619cea132a9f3f65f2f09c"},
|
{file = "pandas-2.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951805d146922aed8357e4cc5671b8b0b9be1027f0619cea132a9f3f65f2f09c"},
|
||||||
{file = "pandas-2.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a881bc1309f3fce34696d07b00f13335c41f5f5a8770a33b09ebe23261cfc67"},
|
{file = "pandas-2.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a881bc1309f3fce34696d07b00f13335c41f5f5a8770a33b09ebe23261cfc67"},
|
||||||
|
{file = "pandas-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e1991bbb96f4050b09b5f811253c4f3cf05ee89a589379aa36cd623f21a31d6f"},
|
||||||
{file = "pandas-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bb3be958022198531eb7ec2008cfc78c5b1eed51af8600c6c5d9160d89d8d249"},
|
{file = "pandas-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bb3be958022198531eb7ec2008cfc78c5b1eed51af8600c6c5d9160d89d8d249"},
|
||||||
|
{file = "pandas-2.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9efc0acbbffb5236fbdf0409c04edce96bec4bdaa649d49985427bd1ec73e085"},
|
||||||
|
{file = "pandas-2.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:75651c14fde635e680496148a8526b328e09fe0572d9ae9b638648c46a544ba3"},
|
||||||
|
{file = "pandas-2.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5be867a0541a9fb47a4be0c5790a4bccd5b77b92f0a59eeec9375fafc2aa14"},
|
||||||
|
{file = "pandas-2.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84141f722d45d0c2a89544dd29d35b3abfc13d2250ed7e68394eda7564bd6324"},
|
||||||
|
{file = "pandas-2.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f95a2aef32614ed86216d3c450ab12a4e82084e8102e355707a1d96e33d51c34"},
|
||||||
|
{file = "pandas-2.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e0f51973ba93a9f97185049326d75b942b9aeb472bec616a129806facb129ebb"},
|
||||||
|
{file = "pandas-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b198687ca9c8529662213538a9bb1e60fa0bf0f6af89292eb68fea28743fcd5a"},
|
||||||
{file = "pandas-2.3.0.tar.gz", hash = "sha256:34600ab34ebf1131a7613a260a61dbe8b62c188ec0ea4c296da7c9a06b004133"},
|
{file = "pandas-2.3.0.tar.gz", hash = "sha256:34600ab34ebf1131a7613a260a61dbe8b62c188ec0ea4c296da7c9a06b004133"},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2448,17 +2464,17 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sounddevice"
|
name = "sounddevice"
|
||||||
version = "0.5.1"
|
version = "0.5.2"
|
||||||
description = "Play and Record Sound with Python"
|
description = "Play and Record Sound with Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "sounddevice-0.5.1-py3-none-any.whl", hash = "sha256:e2017f182888c3f3c280d9fbac92e5dbddac024a7e3442f6e6116bd79dab8a9c"},
|
{file = "sounddevice-0.5.2-py3-none-any.whl", hash = "sha256:82375859fac2e73295a4ab3fc60bd4782743157adc339561c1f1142af472f505"},
|
||||||
{file = "sounddevice-0.5.1-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:d16cb23d92322526a86a9490c427bf8d49e273d9ccc0bd096feecd229cde6031"},
|
{file = "sounddevice-0.5.2-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:943f27e66037d41435bdd0293454072cdf657b594c9cde63cd01ee3daaac7ab3"},
|
||||||
{file = "sounddevice-0.5.1-py3-none-win32.whl", hash = "sha256:d84cc6231526e7a08e89beff229c37f762baefe5e0cc2747cbe8e3a565470055"},
|
{file = "sounddevice-0.5.2-py3-none-win32.whl", hash = "sha256:3a113ce614a2c557f14737cb20123ae6298c91fc9301eb014ada0cba6d248c5f"},
|
||||||
{file = "sounddevice-0.5.1-py3-none-win_amd64.whl", hash = "sha256:4313b63f2076552b23ac3e0abd3bcfc0c1c6a696fc356759a13bd113c9df90f1"},
|
{file = "sounddevice-0.5.2-py3-none-win_amd64.whl", hash = "sha256:e18944b767d2dac3771a7771bdd7ff7d3acd7d334e72c4bedab17d1aed5dbc22"},
|
||||||
{file = "sounddevice-0.5.1.tar.gz", hash = "sha256:09ca991daeda8ce4be9ac91e15a9a81c8f81efa6b695a348c9171ea0c16cb041"},
|
{file = "sounddevice-0.5.2.tar.gz", hash = "sha256:c634d51bd4e922d6f0fa5e1a975cc897c947f61d31da9f79ba7ea34dff448b49"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -2903,4 +2919,4 @@ test = ["pytest", "pytest-asyncio"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = ">=3.11"
|
python-versions = ">=3.11"
|
||||||
content-hash = "ad107b21cc3f0f5e78c4bb2dccd12cf320d37b2d3f262618796d848b05d06086"
|
content-hash = "ee5d8e347947d5b3651aa1b9bcfb8b952e706d0a4a7d28bc0a9d3e5526d82c16"
|
||||||
|
|||||||
@@ -6,16 +6,15 @@ requires-python = ">=3.11"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bumble @ git+ssh://git@ssh.pstruebi.xyz:222/auracaster/bumble_mirror.git@12bcdb7770c0d57a094bc0a96cd52e701f97fece",
|
"bumble @ git+ssh://git@ssh.pstruebi.xyz:222/auracaster/bumble_mirror.git@12bcdb7770c0d57a094bc0a96cd52e701f97fece",
|
||||||
"lc3 @ git+ssh://git@ssh.pstruebi.xyz:222/auracaster/liblc3.git@7558637303106c7ea971e7bb8cedf379d3e08bcc",
|
"lc3 @ git+ssh://git@ssh.pstruebi.xyz:222/auracaster/liblc3.git@7558637303106c7ea971e7bb8cedf379d3e08bcc",
|
||||||
"sounddevice",
|
|
||||||
"aioconsole",
|
"aioconsole",
|
||||||
"fastapi==0.115.11",
|
"fastapi==0.115.11",
|
||||||
"uvicorn==0.34.0",
|
"uvicorn==0.34.0",
|
||||||
"aiohttp==3.9.3",
|
"aiohttp==3.9.3",
|
||||||
"sounddevice (>=0.5.1,<0.6.0)",
|
|
||||||
"aioconsole (>=0.8.1,<0.9.0)",
|
"aioconsole (>=0.8.1,<0.9.0)",
|
||||||
"numpy (>=2.2.6,<3.0.0)",
|
"numpy (>=2.2.6,<3.0.0)",
|
||||||
"streamlit (>=1.45.1,<2.0.0)",
|
"streamlit (>=1.45.1,<2.0.0)",
|
||||||
"aiortc (>=1.13.0,<2.0.0)"
|
"aiortc (>=1.13.0,<2.0.0)",
|
||||||
|
"sounddevice (>=0.5.2,<0.6.0)"
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|||||||
@@ -203,8 +203,21 @@ else:
|
|||||||
# Input device selection for USB mode
|
# Input device selection for USB mode
|
||||||
if audio_mode == "USB/Network":
|
if audio_mode == "USB/Network":
|
||||||
resp = requests.get(f"{BACKEND_URL}/audio_inputs")
|
resp = requests.get(f"{BACKEND_URL}/audio_inputs")
|
||||||
input_options = [f"{d['id']}:{d['name']}" for d in resp.json().get('inputs', [])]
|
device_list = resp.json().get('inputs', [])
|
||||||
|
# Display "name [id]" but use name as value
|
||||||
|
input_options = [f"{d['name']} [{d['id']}]" for d in device_list]
|
||||||
|
option_name_map = {f"{d['name']} [{d['id']}]": d['name'] for d in device_list}
|
||||||
|
device_names = [d['name'] for d in device_list]
|
||||||
|
|
||||||
|
# Determine default input by name
|
||||||
|
default_input_name = saved_settings.get('input_device')
|
||||||
|
if default_input_name not in device_names and device_names:
|
||||||
|
default_input_name = device_names[0]
|
||||||
|
default_input_label = None
|
||||||
|
for label, name in option_name_map.items():
|
||||||
|
if name == default_input_name:
|
||||||
|
default_input_label = label
|
||||||
|
break
|
||||||
if not input_options:
|
if not input_options:
|
||||||
st.warning("No hardware audio input devices found. Plug in a USB input device and click Refresh.")
|
st.warning("No hardware audio input devices found. Plug in a USB input device and click Refresh.")
|
||||||
if st.button("Refresh"):
|
if st.button("Refresh"):
|
||||||
@@ -215,11 +228,13 @@ else:
|
|||||||
st.rerun()
|
st.rerun()
|
||||||
input_device = None
|
input_device = None
|
||||||
else:
|
else:
|
||||||
if default_input not in input_options:
|
|
||||||
default_input = input_options[0]
|
|
||||||
col1, col2 = st.columns([3, 1], vertical_alignment="bottom")
|
col1, col2 = st.columns([3, 1], vertical_alignment="bottom")
|
||||||
with col1:
|
with col1:
|
||||||
selected_option = st.selectbox("Input Device", input_options, index=input_options.index(default_input))
|
selected_option = st.selectbox(
|
||||||
|
"Input Device",
|
||||||
|
input_options,
|
||||||
|
index=input_options.index(default_input_label) if default_input_label in input_options else 0
|
||||||
|
)
|
||||||
with col2:
|
with col2:
|
||||||
if st.button("Refresh"):
|
if st.button("Refresh"):
|
||||||
try:
|
try:
|
||||||
@@ -227,8 +242,8 @@ else:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"Failed to refresh devices: {e}")
|
st.error(f"Failed to refresh devices: {e}")
|
||||||
st.rerun()
|
st.rerun()
|
||||||
# We send only the numeric/card identifier (before :) or 'default'
|
# Send only the device name to backend
|
||||||
input_device = selected_option.split(":", 1)[0] if ":" in selected_option else selected_option
|
input_device = option_name_map[selected_option] if selected_option in option_name_map else None
|
||||||
else:
|
else:
|
||||||
input_device = None
|
input_device = None
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ class Offer(BaseModel):
|
|||||||
sdp: str
|
sdp: str
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
|
def get_device_index_by_name(name: str):
|
||||||
|
"""Return the device index for a given device name, or None if not found."""
|
||||||
|
for d in AUDIO_INPUT_DEVICES_CACHE:
|
||||||
|
if d["name"] == name:
|
||||||
|
return d["id"]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Path to persist stream settings
|
# Path to persist stream settings
|
||||||
STREAM_SETTINGS_FILE = os.path.join(os.path.dirname(__file__), 'stream_settings.json')
|
STREAM_SETTINGS_FILE = os.path.join(os.path.dirname(__file__), 'stream_settings.json')
|
||||||
@@ -50,6 +57,9 @@ def save_stream_settings(settings: dict):
|
|||||||
log.error('Unable to persist stream settings: %s', e)
|
log.error('Unable to persist stream settings: %s', e)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: select this dynamically from pw-cli ls Node determine id
|
||||||
|
os.environ["PIPEWIRE_NODE"] = "88"
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
# Allow CORS for frontend on localhost
|
# Allow CORS for frontend on localhost
|
||||||
@@ -83,26 +93,36 @@ async def initialize(conf: auracast_config.AuracastConfigGroup):
|
|||||||
conf.transport = f'serial:{device},115200,rtscts'
|
conf.transport = f'serial:{device},115200,rtscts'
|
||||||
break
|
break
|
||||||
if conf.transport == 'auto':
|
if conf.transport == 'auto':
|
||||||
HTTPException(status_code=500, detail='No suitable transport found.')
|
raise HTTPException(status_code=500, detail='No suitable transport found.')
|
||||||
# Derive audio_mode and input_device from first BIG audio_source
|
# Derive audio_mode and input_device from first BIG audio_source
|
||||||
first_source = conf.bigs[0].audio_source if conf.bigs else ''
|
first_source = conf.bigs[0].audio_source if conf.bigs else ''
|
||||||
if first_source.startswith('device:'):
|
if first_source.startswith('device:'):
|
||||||
audio_mode_persist = 'USB'
|
audio_mode_persist = 'USB'
|
||||||
input_device = first_source.split(':', 1)[1] if ':' in first_source else 'default'
|
input_device_name = first_source.split(':', 1)[1] if ':' in first_source else None
|
||||||
|
# Map device name to current index for use with sounddevice
|
||||||
|
device_index = get_device_index_by_name(input_device_name) if input_device_name else None
|
||||||
|
# Patch config to use index for sounddevice (but persist name)
|
||||||
|
if device_index is not None:
|
||||||
|
for big in conf.bigs:
|
||||||
|
if big.audio_source.startswith('device:'):
|
||||||
|
big.audio_source = f'device:{device_index}'
|
||||||
|
else:
|
||||||
|
log.error(f"Device name '{input_device_name}' not found in current device list.")
|
||||||
|
raise HTTPException(status_code=400, detail=f"Audio device '{input_device_name}' not found.")
|
||||||
elif first_source == 'webrtc':
|
elif first_source == 'webrtc':
|
||||||
audio_mode_persist = 'Webapp'
|
audio_mode_persist = 'Webapp'
|
||||||
input_device = None
|
input_device_name = None
|
||||||
elif first_source.startswith('file:'):
|
elif first_source.startswith('file:'):
|
||||||
audio_mode_persist = 'Demo'
|
audio_mode_persist = 'Demo'
|
||||||
input_device = None
|
input_device_name = None
|
||||||
else:
|
else:
|
||||||
audio_mode_persist = 'Network'
|
audio_mode_persist = 'Network'
|
||||||
input_device = None
|
input_device_name = None
|
||||||
save_stream_settings({
|
save_stream_settings({
|
||||||
'channel_names': [big.name for big in conf.bigs],
|
'channel_names': [big.name for big in conf.bigs],
|
||||||
'languages': [big.language for big in conf.bigs],
|
'languages': [big.language for big in conf.bigs],
|
||||||
'audio_mode': audio_mode_persist,
|
'audio_mode': audio_mode_persist,
|
||||||
'input_device': input_device,
|
'input_device': input_device_name,
|
||||||
'program_info': [getattr(big, 'program_info', None) for big in conf.bigs],
|
'program_info': [getattr(big, 'program_info', None) for big in conf.bigs],
|
||||||
'gain': [getattr(big, 'input_gain', 1.0) for big in conf.bigs],
|
'gain': [getattr(big, 'input_gain', 1.0) for big in conf.bigs],
|
||||||
'timestamp': datetime.utcnow().isoformat()
|
'timestamp': datetime.utcnow().isoformat()
|
||||||
@@ -137,12 +157,17 @@ async def initialize2(conf: auracast_config.AuracastConfigGroup):
|
|||||||
conf.transport = f'serial:{device},115200,rtscts'
|
conf.transport = f'serial:{device},115200,rtscts'
|
||||||
break
|
break
|
||||||
if conf.transport == 'auto':
|
if conf.transport == 'auto':
|
||||||
HTTPException(status_code=500, detail='No suitable transport found.')
|
raise HTTPException(status_code=500, detail='No suitable transport found.')
|
||||||
if multicaster2 is not None:
|
# Patch device name to index for sounddevice
|
||||||
try:
|
for big in conf.bigs:
|
||||||
await multicaster2.shutdown()
|
if big.audio_source.startswith('device:'):
|
||||||
except Exception:
|
device_name = big.audio_source.split(':', 1)[1]
|
||||||
log.warning("Failed to shutdown previous multicaster2", exc_info=True)
|
device_index = get_device_index_by_name(device_name)
|
||||||
|
if device_index is not None:
|
||||||
|
big.audio_source = f'device:{device_index}'
|
||||||
|
else:
|
||||||
|
log.error(f"Device name '{device_name}' not found in current device list.")
|
||||||
|
raise HTTPException(status_code=400, detail=f"Audio device '{device_name}' not found.")
|
||||||
log.info('Initializing multicaster2 with config:\n %s', conf.model_dump_json(indent=2))
|
log.info('Initializing multicaster2 with config:\n %s', conf.model_dump_json(indent=2))
|
||||||
multicaster2 = multicast_control.Multicaster(conf, conf.bigs)
|
multicaster2 = multicast_control.Multicaster(conf, conf.bigs)
|
||||||
await multicaster2.init_broadcast()
|
await multicaster2.init_broadcast()
|
||||||
@@ -220,13 +245,11 @@ async def scan_audio_devices():
|
|||||||
sd._terminate()
|
sd._terminate()
|
||||||
sd._initialize()
|
sd._initialize()
|
||||||
|
|
||||||
# TODO: select this dynamically from pw-cli ls Node determine id
|
|
||||||
#os.environ["PIPEWIRE_NODE"] = "76"
|
|
||||||
devs = sd.query_devices()
|
devs = sd.query_devices()
|
||||||
inputs = [
|
inputs = [
|
||||||
{"id": idx, "name": d["name"]}
|
{"id": idx, "name": d["name"]}
|
||||||
for idx, d in enumerate(devs)
|
for idx, d in enumerate(devs)
|
||||||
# if d.get("max_input_channels", 0) > 0 and ("(hw:" in d["name"].lower() or "usb" in d["name"].lower())
|
if d.get("max_input_channels", 0) > 0
|
||||||
]
|
]
|
||||||
log.info('Found %d audio input devices: %s', len(inputs), inputs)
|
log.info('Found %d audio input devices: %s', len(inputs), inputs)
|
||||||
AUDIO_INPUT_DEVICES_CACHE = inputs
|
AUDIO_INPUT_DEVICES_CACHE = inputs
|
||||||
@@ -243,8 +266,9 @@ async def startup_event():
|
|||||||
|
|
||||||
@app.get("/audio_inputs")
|
@app.get("/audio_inputs")
|
||||||
async def list_audio_inputs():
|
async def list_audio_inputs():
|
||||||
"""Return available hardware audio input devices from cache."""
|
"""Return available hardware audio input devices from cache (by name, for selection)."""
|
||||||
return {"inputs": AUDIO_INPUT_DEVICES_CACHE}
|
# Only expose name and id for frontend
|
||||||
|
return {"inputs": [{"name": d["name"], "id": d["id"]} for d in AUDIO_INPUT_DEVICES_CACHE]}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/refresh_audio_inputs")
|
@app.post("/refresh_audio_inputs")
|
||||||
|
|||||||
4
src/scripts/list_pw_nodes.py
Normal file
4
src/scripts/list_pw_nodes.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import sounddevice as sd, pprint
|
||||||
|
print(sd._libname) # → /usr/local/lib/libportaudio.so.2
|
||||||
|
print(sd.get_portaudio_version()) # 19.7.0‑devel (or newer)
|
||||||
|
pprint.pprint(sd.query_devices()) # every PipeWire sink/source appears
|
||||||
@@ -87,7 +87,7 @@ context.modules = [
|
|||||||
media.class = "Audio/Source"
|
media.class = "Audio/Source"
|
||||||
device.api = aes67
|
device.api = aes67
|
||||||
# You can adjust the latency buffering here. Use integer values only
|
# You can adjust the latency buffering here. Use integer values only
|
||||||
sess.latency.msec = 50
|
sess.latency.msec = 5
|
||||||
node.group = pipewire.ptp0
|
node.group = pipewire.ptp0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,49 +105,4 @@ context.modules = [
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ name = libpipewire-module-rtp-sink
|
|
||||||
args = {
|
|
||||||
### Please select the interface here
|
|
||||||
local.ifname = eth0
|
|
||||||
### If you want to create multiple output streams, please copy the whole
|
|
||||||
### module-rtp-sink block, but change this multicast IP to another unused
|
|
||||||
### one keeping 239.69.x.x range unless you know you need another one
|
|
||||||
destination.ip = 239.69.150.243
|
|
||||||
destination.port = 5004
|
|
||||||
net.mtu = 1280
|
|
||||||
net.ttl = 32
|
|
||||||
net.loop = false
|
|
||||||
# These should typically be equal
|
|
||||||
# You can customize packet length, but 1 ms should work for every device
|
|
||||||
# Consult receiver documentation to ensure it supports the value you set
|
|
||||||
sess.min-ptime = 1
|
|
||||||
sess.max-ptime = 1
|
|
||||||
### Please change this, especially if you create multiple sinks
|
|
||||||
sess.name = "PipeWire RTP stream"
|
|
||||||
sess.media = "audio"
|
|
||||||
# This property is used if you aren't using ptp4l 4
|
|
||||||
sess.ts-refclk = "ptp=traceable"
|
|
||||||
sess.ts-offset = 0
|
|
||||||
# You can adjust the latency buffering here. Use integer values only
|
|
||||||
sess.latency.msec = 3
|
|
||||||
audio.format = "S24BE"
|
|
||||||
audio.rate = 48000
|
|
||||||
audio.channels = 2
|
|
||||||
# These channel names will be visible both to applications and AES67 receivers
|
|
||||||
node.channel-names = ["CH1", "CH2"]
|
|
||||||
|
|
||||||
stream.props = {
|
|
||||||
### Please change the sink name, this is necessary when you create multiple sinks
|
|
||||||
node.name = "rtp-sink"
|
|
||||||
media.class = "Audio/Sink"
|
|
||||||
node.virtual = false
|
|
||||||
device.api = aes67
|
|
||||||
sess.sap.announce = true
|
|
||||||
node.always-process = true
|
|
||||||
node.group = pipewire.ptp0
|
|
||||||
rtp.ntp = 0
|
|
||||||
rtp.fetch-ts-refclk = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user