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

@@ -1,5 +1,6 @@
# frontend/app.py
import os
import time
import streamlit as st
import requests
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)
PTIME = 40
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
saved_settings = {}
@@ -25,7 +33,7 @@ except Exception:
st.title("🎙️ Auracast Audio Mode Control")
# Audio mode selection with persisted default
options = ["Webapp", "USB"]
options = ["Webapp", "USB", "Demo"]
saved_audio_mode = saved_settings.get("audio_mode", "Webapp")
if saved_audio_mode not in options:
saved_audio_mode = "Webapp"
@@ -34,18 +42,108 @@ audio_mode = st.selectbox(
"Audio Mode",
options,
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"]:
# Stream quality selection (now enabled)
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},
if audio_mode == "Demo":
demo_stream_map = {
"1 × 48kHz": {"quality": "High (48kHz)", "streams": 1,},
"2 × 24kHz": {"quality": "Medium (24kHz)", "streams": 2,},
"3 × 16kHz": {"quality": "Fair (16kHz)", "streams": 3,},
}
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]
quality = st.selectbox(
"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
else:
input_device = None
import time
start_stream = st.button("Start Auracast")
stop_stream = st.button("Stop Auracast")
@@ -164,13 +262,13 @@ if audio_mode in ["Webapp", "USB"]:
st.success("Stream Stopped!")
# 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)
q = quality_map[quality]
q = QUALITY_MAP[quality]
config = auracast_config.AuracastConfigGroup(
auracast_sampling_rate_hz=q['rate'],
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 = [
auracast_config.AuracastBigConfig(
name=stream_name,
@@ -254,9 +352,9 @@ if audio_mode in ["Webapp", "USB"]:
"""
st.components.v1.html(component, height=0)
st.session_state['stream_started'] = True
else:
st.header("Advertised Streams (Cloud Announcements)")
st.info("This feature requires backend support to list advertised streams.")
#else:
# st.header("Advertised Streams (Cloud Announcements)")
# st.info("This feature requires backend support to list advertised streams.")
# Placeholder for future implementation
# Example: r = requests.get(f"{BACKEND_URL}/advertised_streams")
# if r.status_code == 200: