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.
|
||||
|
||||
# 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:
|
||||
- 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-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-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-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_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_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-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_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-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-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_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_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-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_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_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-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"},
|
||||
]
|
||||
|
||||
@@ -2448,17 +2464,17 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "sounddevice"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
description = "Play and Record Sound with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "sounddevice-0.5.1-py3-none-any.whl", hash = "sha256:e2017f182888c3f3c280d9fbac92e5dbddac024a7e3442f6e6116bd79dab8a9c"},
|
||||
{file = "sounddevice-0.5.1-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:d16cb23d92322526a86a9490c427bf8d49e273d9ccc0bd096feecd229cde6031"},
|
||||
{file = "sounddevice-0.5.1-py3-none-win32.whl", hash = "sha256:d84cc6231526e7a08e89beff229c37f762baefe5e0cc2747cbe8e3a565470055"},
|
||||
{file = "sounddevice-0.5.1-py3-none-win_amd64.whl", hash = "sha256:4313b63f2076552b23ac3e0abd3bcfc0c1c6a696fc356759a13bd113c9df90f1"},
|
||||
{file = "sounddevice-0.5.1.tar.gz", hash = "sha256:09ca991daeda8ce4be9ac91e15a9a81c8f81efa6b695a348c9171ea0c16cb041"},
|
||||
{file = "sounddevice-0.5.2-py3-none-any.whl", hash = "sha256:82375859fac2e73295a4ab3fc60bd4782743157adc339561c1f1142af472f505"},
|
||||
{file = "sounddevice-0.5.2-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:943f27e66037d41435bdd0293454072cdf657b594c9cde63cd01ee3daaac7ab3"},
|
||||
{file = "sounddevice-0.5.2-py3-none-win32.whl", hash = "sha256:3a113ce614a2c557f14737cb20123ae6298c91fc9301eb014ada0cba6d248c5f"},
|
||||
{file = "sounddevice-0.5.2-py3-none-win_amd64.whl", hash = "sha256:e18944b767d2dac3771a7771bdd7ff7d3acd7d334e72c4bedab17d1aed5dbc22"},
|
||||
{file = "sounddevice-0.5.2.tar.gz", hash = "sha256:c634d51bd4e922d6f0fa5e1a975cc897c947f61d31da9f79ba7ea34dff448b49"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2903,4 +2919,4 @@ test = ["pytest", "pytest-asyncio"]
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.11"
|
||||
content-hash = "ad107b21cc3f0f5e78c4bb2dccd12cf320d37b2d3f262618796d848b05d06086"
|
||||
content-hash = "ee5d8e347947d5b3651aa1b9bcfb8b952e706d0a4a7d28bc0a9d3e5526d82c16"
|
||||
|
||||
@@ -6,16 +6,15 @@ requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"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",
|
||||
"sounddevice",
|
||||
"aioconsole",
|
||||
"fastapi==0.115.11",
|
||||
"uvicorn==0.34.0",
|
||||
"aiohttp==3.9.3",
|
||||
"sounddevice (>=0.5.1,<0.6.0)",
|
||||
"aioconsole (>=0.8.1,<0.9.0)",
|
||||
"numpy (>=2.2.6,<3.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]
|
||||
|
||||
@@ -203,8 +203,21 @@ else:
|
||||
# Input device selection for USB mode
|
||||
if audio_mode == "USB/Network":
|
||||
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:
|
||||
st.warning("No hardware audio input devices found. Plug in a USB input device and click Refresh.")
|
||||
if st.button("Refresh"):
|
||||
@@ -215,11 +228,13 @@ else:
|
||||
st.rerun()
|
||||
input_device = None
|
||||
else:
|
||||
if default_input not in input_options:
|
||||
default_input = input_options[0]
|
||||
col1, col2 = st.columns([3, 1], vertical_alignment="bottom")
|
||||
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:
|
||||
if st.button("Refresh"):
|
||||
try:
|
||||
@@ -227,8 +242,8 @@ else:
|
||||
except Exception as e:
|
||||
st.error(f"Failed to refresh devices: {e}")
|
||||
st.rerun()
|
||||
# We send only the numeric/card identifier (before :) or 'default'
|
||||
input_device = selected_option.split(":", 1)[0] if ":" in selected_option else selected_option
|
||||
# Send only the device name to backend
|
||||
input_device = option_name_map[selected_option] if selected_option in option_name_map else None
|
||||
else:
|
||||
input_device = None
|
||||
|
||||
|
||||
@@ -27,6 +27,13 @@ class Offer(BaseModel):
|
||||
sdp: 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
|
||||
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)
|
||||
|
||||
|
||||
# TODO: select this dynamically from pw-cli ls Node determine id
|
||||
os.environ["PIPEWIRE_NODE"] = "88"
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# Allow CORS for frontend on localhost
|
||||
@@ -83,26 +93,36 @@ async def initialize(conf: auracast_config.AuracastConfigGroup):
|
||||
conf.transport = f'serial:{device},115200,rtscts'
|
||||
break
|
||||
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
|
||||
first_source = conf.bigs[0].audio_source if conf.bigs else ''
|
||||
if first_source.startswith('device:'):
|
||||
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':
|
||||
audio_mode_persist = 'Webapp'
|
||||
input_device = None
|
||||
input_device_name = None
|
||||
elif first_source.startswith('file:'):
|
||||
audio_mode_persist = 'Demo'
|
||||
input_device = None
|
||||
input_device_name = None
|
||||
else:
|
||||
audio_mode_persist = 'Network'
|
||||
input_device = None
|
||||
input_device_name = None
|
||||
save_stream_settings({
|
||||
'channel_names': [big.name for big in conf.bigs],
|
||||
'languages': [big.language for big in conf.bigs],
|
||||
'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],
|
||||
'gain': [getattr(big, 'input_gain', 1.0) for big in conf.bigs],
|
||||
'timestamp': datetime.utcnow().isoformat()
|
||||
@@ -137,12 +157,17 @@ async def initialize2(conf: auracast_config.AuracastConfigGroup):
|
||||
conf.transport = f'serial:{device},115200,rtscts'
|
||||
break
|
||||
if conf.transport == 'auto':
|
||||
HTTPException(status_code=500, detail='No suitable transport found.')
|
||||
if multicaster2 is not None:
|
||||
try:
|
||||
await multicaster2.shutdown()
|
||||
except Exception:
|
||||
log.warning("Failed to shutdown previous multicaster2", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail='No suitable transport found.')
|
||||
# Patch device name to index for sounddevice
|
||||
for big in conf.bigs:
|
||||
if big.audio_source.startswith('device:'):
|
||||
device_name = big.audio_source.split(':', 1)[1]
|
||||
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))
|
||||
multicaster2 = multicast_control.Multicaster(conf, conf.bigs)
|
||||
await multicaster2.init_broadcast()
|
||||
@@ -220,13 +245,11 @@ async def scan_audio_devices():
|
||||
sd._terminate()
|
||||
sd._initialize()
|
||||
|
||||
# TODO: select this dynamically from pw-cli ls Node determine id
|
||||
#os.environ["PIPEWIRE_NODE"] = "76"
|
||||
devs = sd.query_devices()
|
||||
inputs = [
|
||||
{"id": idx, "name": d["name"]}
|
||||
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)
|
||||
AUDIO_INPUT_DEVICES_CACHE = inputs
|
||||
@@ -243,8 +266,9 @@ async def startup_event():
|
||||
|
||||
@app.get("/audio_inputs")
|
||||
async def list_audio_inputs():
|
||||
"""Return available hardware audio input devices from cache."""
|
||||
return {"inputs": AUDIO_INPUT_DEVICES_CACHE}
|
||||
"""Return available hardware audio input devices from cache (by name, for selection)."""
|
||||
# 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")
|
||||
|
||||
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"
|
||||
device.api = aes67
|
||||
# You can adjust the latency buffering here. Use integer values only
|
||||
sess.latency.msec = 50
|
||||
sess.latency.msec = 5
|
||||
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