From b266c38de3ef10c3e49be1a21c1ae959f275769e Mon Sep 17 00:00:00 2001 From: pstruebi Date: Tue, 12 Aug 2025 16:12:02 +0200 Subject: [PATCH] add immediate rendering flag --- README.md | 10 ++++----- src/auracast/auracast_config.py | 4 ++++ src/auracast/multicast.py | 38 +++++++++++++++++++++----------- src/auracast/multicast_script.py | 22 +++++++----------- src/scripts/list_pw_nodes.py | 4 ---- 5 files changed, 41 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 07ae802..b206e78 100644 --- a/README.md +++ b/README.md @@ -139,12 +139,11 @@ 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. # Setup the audio system +sudo apt update + sudo apt remove -y libportaudio2 portaudio19-dev libportaudiocpp0 echo "y" | rpi-update stable - - -sudo apt update # TODO: needed ? sudo apt install pipewire wireplumber pipewire-audio-client-libraries rtkit mkdir -p ~/.config/pipewire/pipewire.conf.d @@ -158,8 +157,7 @@ sudo cpufreq-set -g performance sudo apt install -y --no-install-recommends \ git build-essential cmake pkg-config \ - libasound2-dev libpulse-dev libjack-jackd2-dev jackd \ - pipewire ethtool linuxptp + libasound2-dev libpulse-devpipewire ethtool linuxptp git clone https://github.com/PortAudio/portaudio.git cd portaudio @@ -169,7 +167,7 @@ cmake -S . -B build -G"Unix Makefiles" \ -DBUILD_SHARED_LIBS=ON \ -DPA_USE_ALSA=OFF \ -DPA_USE_PULSEAUDIO=ON \ - -DPA_USE_JACK=ON + -DPA_USE_JACK=OFF cmake --build build -j$(nproc) sudo cmake --install build # installs to /usr/local/lib sudo ldconfig # refresh linker cache diff --git a/src/auracast/auracast_config.py b/src/auracast/auracast_config.py index fbb3dd7..dfeb1f9 100644 --- a/src/auracast/auracast_config.py +++ b/src/auracast/auracast_config.py @@ -35,6 +35,10 @@ class AuracastGlobalConfig(BaseModel): presentation_delay_us: int = 40000 # TODO:pydantic does not support bytes serialization - use .hex and np.fromhex() manufacturer_data: tuple[int, bytes] | tuple[None, None] = (None, None) + # LE Audio: Broadcast Audio Immediate Rendering (metadata type 0x09) + # When true, include a zero-length LTV with type 0x09 in the subgroup metadata + # so receivers may render earlier than the presentation delay for lower latency. + immediate_rendering: bool = False # "Audio input. " # "'device' -> use the host's default sound input device, " diff --git a/src/auracast/multicast.py b/src/auracast/multicast.py index dbf5983..1a404c2 100644 --- a/src/auracast/multicast.py +++ b/src/auracast/multicast.py @@ -235,6 +235,30 @@ async def init_broadcast( bap_sampling_freq = getattr(bap.SamplingFrequency, f"FREQ_{global_config.auracast_sampling_rate_hz}") bigs = {} for i, conf in enumerate(big_config): + metadata=le_audio.Metadata( + [ + le_audio.Metadata.Entry( + tag=le_audio.Metadata.Tag.LANGUAGE, data=conf.language.encode() + ), + le_audio.Metadata.Entry( + tag=le_audio.Metadata.Tag.PROGRAM_INFO, data=conf.program_info.encode() + ), + le_audio.Metadata.Entry( + tag=le_audio.Metadata.Tag.BROADCAST_NAME, data=conf.name.encode() + ), + ] + + ( + [ + # Broadcast Audio Immediate Rendering flag (type 0x09), zero-length value + le_audio.Metadata.Entry(tag = le_audio.Metadata.Tag.BROADCAST_AUDIO_IMMEDIATE_RENDERING_FLAG, data=b"") + ] + if global_config.immediate_rendering + else [] + ) + ) + logging.info( + metadata.pretty_print("\n") + ) bigs[f'big{i}'] = {} # Config advertising set bigs[f'big{i}']['basic_audio_announcement'] = bap.BasicAudioAnnouncement( @@ -247,19 +271,7 @@ async def init_broadcast( frame_duration=bap.FrameDuration.DURATION_10000_US, octets_per_codec_frame=global_config.octets_per_frame, ), - metadata=le_audio.Metadata( - [ - le_audio.Metadata.Entry( - tag=le_audio.Metadata.Tag.LANGUAGE, data=conf.language.encode() - ), - le_audio.Metadata.Entry( - tag=le_audio.Metadata.Tag.PROGRAM_INFO, data=conf.program_info.encode() - ), - le_audio.Metadata.Entry( - tag=le_audio.Metadata.Tag.BROADCAST_NAME, data=conf.name.encode() - ), - ] - ), + metadata=metadata, bis=[ bap.BasicAudioAnnouncement.BIS( index=1, diff --git a/src/auracast/multicast_script.py b/src/auracast/multicast_script.py index 6366773..70cea13 100644 --- a/src/auracast/multicast_script.py +++ b/src/auracast/multicast_script.py @@ -1,13 +1,10 @@ import logging import os -import asyncio -import aioconsole from auracast import multicast from auracast import auracast_config from auracast.utils.sounddevice_utils import list_usb_pw_inputs, list_network_pw_inputs - if __name__ == "__main__": logging.basicConfig( #export LOG_LEVEL=DEBUG @@ -25,8 +22,9 @@ if __name__ == "__main__": os.environ.setdefault("AURACAST_SD_BLOCKSIZE", "32") # Accepts 'low'/'high'/'default' or seconds (float). Our shim parses number strings to float. os.environ.setdefault("AURACAST_SD_LATENCY", "0.0015") - print("USB pw inputs:") + logging.info("USB pw inputs:") usb_inputs = list_usb_pw_inputs() + logging.info("AEs67 pw inputs:") aes67_inputs = list_network_pw_inputs() for i, d in usb_inputs: logging.info(f"{i}: {d['name']} in={d['max_input_channels']}") @@ -62,17 +60,13 @@ if __name__ == "__main__": octets_per_frame=OCTETS_PER_FRAME, ), #auracast_config.AuracastBigConfigEng(), - - ] + ], + immediate_rendering=True, + qos_config=auracast_config.AuracastQosHigh(), + auracast_sampling_rate_hz = LC3_SRATE, + octets_per_frame = OCTETS_PER_FRAME, # 32kbps@16kHz + transport=TRANSPORT1 ) - - config.qos_config=auracast_config.AuracastQosHigh() - config.transport=TRANSPORT1 - - # TODO: encrypted streams are not working - - config.auracast_sampling_rate_hz = LC3_SRATE - config.octets_per_frame = OCTETS_PER_FRAME # 32kbps@16kHz #config.debug = True multicast.run_async( diff --git a/src/scripts/list_pw_nodes.py b/src/scripts/list_pw_nodes.py index c1cf925..7c9bc85 100644 --- a/src/scripts/list_pw_nodes.py +++ b/src/scripts/list_pw_nodes.py @@ -14,10 +14,6 @@ print("\nOnly PulseAudio devices:") for i, d in devices_by_backend("PulseAudio"): print(f"{i}: {d['name']} in={d['max_input_channels']} out={d['max_output_channels']}") -# Example: only PulseAudio devices on Linux -# print("\nOnly JACK devices:") -# for i, d in devices_by_backend("JACK"): -# print(f"{i}: {d['name']} in={d['max_input_channels']} out={d['max_output_channels']}") print("Network pw inputs:") for i, d in list_network_pw_inputs():