Implement a demo mode

This commit is contained in:
2025-07-08 16:20:20 +02:00
parent 8307e1cdec
commit 71d309268e
18 changed files with 124 additions and 19 deletions

View File

@@ -277,7 +277,7 @@ async def init_broadcast(
logging.debug(f'big{i} parameters are:') logging.debug(f'big{i} parameters are:')
logging.debug('%s', pprint.pformat(vars(big))) logging.debug('%s', pprint.pformat(vars(big)))
logging.debug(f'Finished setup of big{i}.') logging.info(f'Finished setup of big{i}.')
await asyncio.sleep(i+1) # Wait for advertising to set up await asyncio.sleep(i+1) # Wait for advertising to set up

View File

@@ -1,5 +1,6 @@
# frontend/app.py # frontend/app.py
import os import os
import time
import streamlit as st import streamlit as st
import requests import requests
from auracast import auracast_config from auracast import auracast_config
@@ -12,6 +13,13 @@ if 'stream_started' not in st.session_state:
# Global: desired packetization time in ms for Opus (should match backend) # Global: desired packetization time in ms for Opus (should match backend)
PTIME = 40 PTIME = 40
BACKEND_URL = "http://localhost:5000" BACKEND_URL = "http://localhost:5000"
TRANSPORT1 = "auto" #'serial:/dev/ttyAMA3,1000000,rtscts', # transport for raspberry pi gpio header
QUALITY_MAP = {
"High (48kHz)": {"rate": 48000, "octets": 120},
"Good (32kHz)": {"rate": 32000, "octets": 80},
"Medium (24kHz)": {"rate": 24000, "octets": 60},
"Fair (16kHz)": {"rate": 16000, "octets": 40},
}
# Try loading persisted settings from backend # Try loading persisted settings from backend
saved_settings = {} saved_settings = {}
@@ -25,7 +33,7 @@ except Exception:
st.title("🎙️ Auracast Audio Mode Control") st.title("🎙️ Auracast Audio Mode Control")
# Audio mode selection with persisted default # Audio mode selection with persisted default
options = ["Webapp", "USB"] options = ["Webapp", "USB", "Demo"]
saved_audio_mode = saved_settings.get("audio_mode", "Webapp") saved_audio_mode = saved_settings.get("audio_mode", "Webapp")
if saved_audio_mode not in options: if saved_audio_mode not in options:
saved_audio_mode = "Webapp" saved_audio_mode = "Webapp"
@@ -34,18 +42,108 @@ audio_mode = st.selectbox(
"Audio Mode", "Audio Mode",
options, options,
index=options.index(saved_audio_mode), index=options.index(saved_audio_mode),
help="Select the audio input source. Choose 'Webapp' for browser microphone or 'USB' for a connected hardware device." help="Select the audio input source. Choose 'Webapp' for browser microphone, 'USB' for a connected hardware device, or 'Demo' for a simulated stream."
) )
if audio_mode in ["Webapp", "USB"]: if audio_mode == "Demo":
# Stream quality selection (now enabled) demo_stream_map = {
quality_map = { "1 × 48kHz": {"quality": "High (48kHz)", "streams": 1,},
"High (48kHz)": {"rate": 48000, "octets": 120}, "2 × 24kHz": {"quality": "Medium (24kHz)", "streams": 2,},
"Good (32kHz)": {"rate": 32000, "octets": 80}, "3 × 16kHz": {"quality": "Fair (16kHz)", "streams": 3,},
"Medium (24kHz)": {"rate": 24000, "octets": 60},
"Fair (16kHz)": {"rate": 16000, "octets": 40},
} }
quality_options = list(quality_map.keys()) demo_options = list(demo_stream_map.keys())
default_demo = demo_options[0]
demo_selected = st.selectbox(
"Demo Stream Type",
demo_options,
index=0,
help="Select the demo stream configuration."
)
#st.info(f"Demo mode selected: {demo_selected} (Streams: {demo_stream_map[demo_selected]['streams']}, Rate: {demo_stream_map[demo_selected]['rate']} Hz)")
# Start/Stop buttons for demo mode
if 'demo_stream_started' not in st.session_state:
st.session_state['demo_stream_started'] = False
col1, col2 = st.columns(2)
with col1:
start_demo = st.button("Start Demo Stream")
with col2:
stop_demo = st.button("Stop Demo Stream")
if start_demo:
# Always stop any running stream for clean state
try:
requests.post(f"{BACKEND_URL}/stop_audio").json()
except Exception:
pass
time.sleep(1)
demo_cfg = demo_stream_map[demo_selected]
# Octets per frame logic matches quality_map
q = QUALITY_MAP[demo_cfg['quality']]
if demo_cfg['streams'] >= 1:
bigs = [
auracast_config.AuracastBigConfigDeu(
audio_source=f'file:../testdata/wave_particle_5min_de_{int(q["rate"]/1000)}kHz_mono.wav',
iso_que_len=32,
sampling_frequency=q['rate'],
octets_per_frame=q['octets'],
)
]
if demo_cfg['streams'] >= 2:
bigs += [
auracast_config.AuracastBigConfigEng(
audio_source=f'file:../testdata/wave_particle_5min_en_{int(q["rate"]/1000)}kHz_mono.wav',
iso_que_len=32,
sampling_frequency=q['rate'],
octets_per_frame=q['octets'],
),
]
if demo_cfg['streams'] >= 3:
bigs += [
auracast_config.AuracastBigConfigFra(
audio_source=f'file:../testdata/wave_particle_5min_fr_{int(q["rate"]/1000)}kHz_mono.wav',
iso_que_len=32,
sampling_frequency=q['rate'],
octets_per_frame=q['octets'],
),
]
config = auracast_config.AuracastConfigGroup(
auracast_sampling_rate_hz=q['rate'],
octets_per_frame=q['octets'],
transport=TRANSPORT1, # transport for raspberry pi gpio header
bigs = bigs
)
try:
r = requests.post(f"{BACKEND_URL}/init", json=config.model_dump())
if r.status_code == 200:
st.session_state['demo_stream_started'] = True
st.success(f"Demo stream started: {demo_selected}")
else:
st.session_state['demo_stream_started'] = False
st.error(f"Failed to initialize demo: {r.text}")
except Exception as e:
st.session_state['demo_stream_started'] = False
st.error(f"Error: {e}")
elif stop_demo:
try:
r = requests.post(f"{BACKEND_URL}/stop_audio").json()
st.session_state['demo_stream_started'] = False
if r.get('was_running'):
st.info("Demo stream stopped.")
else:
st.info("Demo stream was not running.")
except Exception as e:
st.error(f"Error: {e}")
elif st.session_state['demo_stream_started']:
st.success(f"Demo stream running: {demo_selected}")
else:
st.info("Demo stream not running.")
quality = None # Not used in demo mode
else:
# Stream quality selection (now enabled)
quality_options = list(QUALITY_MAP.keys())
default_quality = "Medium (24kHz)" if "Medium (24kHz)" in quality_options else quality_options[0] default_quality = "Medium (24kHz)" if "Medium (24kHz)" in quality_options else quality_options[0]
quality = st.selectbox( quality = st.selectbox(
"Stream Quality (Sampling Rate)", "Stream Quality (Sampling Rate)",
@@ -119,7 +217,7 @@ if audio_mode in ["Webapp", "USB"]:
input_device = selected_option.split(":", 1)[0] if ":" in selected_option else selected_option input_device = selected_option.split(":", 1)[0] if ":" in selected_option else selected_option
else: else:
input_device = None input_device = None
import time
start_stream = st.button("Start Auracast") start_stream = st.button("Start Auracast")
stop_stream = st.button("Stop Auracast") stop_stream = st.button("Stop Auracast")
@@ -164,13 +262,13 @@ if audio_mode in ["Webapp", "USB"]:
st.success("Stream Stopped!") st.success("Stream Stopped!")
# Small pause lets backend fully release audio devices before re-init # Small pause lets backend fully release audio devices before re-init
import time; time.sleep(1) time.sleep(1)
# Prepare config using the model (do NOT send qos_config, only relevant fields) # Prepare config using the model (do NOT send qos_config, only relevant fields)
q = quality_map[quality] q = QUALITY_MAP[quality]
config = auracast_config.AuracastConfigGroup( config = auracast_config.AuracastConfigGroup(
auracast_sampling_rate_hz=q['rate'], auracast_sampling_rate_hz=q['rate'],
octets_per_frame=q['octets'], octets_per_frame=q['octets'],
transport='serial:/dev/ttyAMA3,1000000,rtscts', # transport for raspberry pi gpio header transport=TRANSPORT1, # transport for raspberry pi gpio header
bigs = [ bigs = [
auracast_config.AuracastBigConfig( auracast_config.AuracastBigConfig(
name=stream_name, name=stream_name,
@@ -254,9 +352,9 @@ if audio_mode in ["Webapp", "USB"]:
""" """
st.components.v1.html(component, height=0) st.components.v1.html(component, height=0)
st.session_state['stream_started'] = True st.session_state['stream_started'] = True
else: #else:
st.header("Advertised Streams (Cloud Announcements)") # st.header("Advertised Streams (Cloud Announcements)")
st.info("This feature requires backend support to list advertised streams.") # st.info("This feature requires backend support to list advertised streams.")
# Placeholder for future implementation # Placeholder for future implementation
# Example: r = requests.get(f"{BACKEND_URL}/advertised_streams") # Example: r = requests.get(f"{BACKEND_URL}/advertised_streams")
# if r.status_code == 200: # if r.status_code == 200:

View File

@@ -96,6 +96,9 @@ async def initialize(conf: auracast_config.AuracastConfigGroup):
elif first_source == 'webrtc': elif first_source == 'webrtc':
audio_mode_persist = 'Webapp' audio_mode_persist = 'Webapp'
input_device = None input_device = None
elif first_source.startswith('file:'):
audio_mode_persist = 'Demo'
input_device = None
else: else:
audio_mode_persist = 'Network' audio_mode_persist = 'Network'
input_device = None input_device = None
@@ -129,7 +132,9 @@ async def initialize(conf: auracast_config.AuracastConfigGroup):
# Auto-start streaming only when using a local USB audio device. For Webapp mode the # Auto-start streaming only when using a local USB audio device. For Webapp mode the
# streamer is started by the /offer handler once the WebRTC track arrives so we know # streamer is started by the /offer handler once the WebRTC track arrives so we know
# the peer connection is established. # the peer connection is established.
if any(big.audio_source.startswith("device:") for big in conf.bigs): # TODO rather do a if not webrtc in the future
if any(big.audio_source.startswith("device:") or big.audio_source.startswith("file:") for big in conf.bigs):
log.info("Auto-starting streaming")
await multicaster.start_streaming() await multicaster.start_streaming()
except Exception as e: except Exception as e:
log.error("Exception in /init: %s", traceback.format_exc()) log.error("Exception in /init: %s", traceback.format_exc())
@@ -362,6 +367,8 @@ async def shutdown():
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
if __name__ == '__main__': if __name__ == '__main__':
import os
os.chdir(os.path.dirname(__file__))
import uvicorn import uvicorn
log.basicConfig( # for debug log level export LOG_LEVEL=DEBUG log.basicConfig( # for debug log level export LOG_LEVEL=DEBUG
level=os.environ.get('LOG_LEVEL', log.INFO), level=os.environ.get('LOG_LEVEL', log.INFO),

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.