diff --git a/dist/roverd b/dist/roverd index 72484550..aebc4960 100755 Binary files a/dist/roverd and b/dist/roverd differ diff --git a/dist/servoverifier b/dist/servoverifier index 1f965abb..c79a79a9 100755 Binary files a/dist/servoverifier and b/dist/servoverifier differ diff --git a/pi/bin/video-publisher.sh b/pi/bin/video-publisher.sh index 6d3f4b2e..37387b63 100644 --- a/pi/bin/video-publisher.sh +++ b/pi/bin/video-publisher.sh @@ -38,6 +38,9 @@ else AUDIO_RATE=8000 AUDIO_BITRATE=64000 fi + +# Disable audio streaming for now; only video will be published. +AUDIO_ENABLE=0 # Flip the camera 180deg (supported by rpicam-vid/libcamera-vid) FLIP_ARGS=(--rotation 180) diff --git a/pi/install_roverd.sh b/pi/install_roverd.sh index a3e3d1b2..3becfb3b 100755 --- a/pi/install_roverd.sh +++ b/pi/install_roverd.sh @@ -205,10 +205,7 @@ VIDEO_HEIGHT=720 VIDEO_FPS=30 VIDEO_BITRATE=3000000 AUDIO_ENABLE=1 -AUDIO_DEVICE=plughw:0,0 -AUDIO_RATE=48000 -AUDIO_CHANNELS=1 -AUDIO_BITRATE=24000 +AUDIO_ENABLE=0 ENV chown roverd:roverd /var/lib/roverd/video.env chmod 0640 /var/lib/roverd/video.env diff --git a/pi/roverd/config.go b/pi/roverd/config.go index 032f558f..0bfb44ff 100644 --- a/pi/roverd/config.go +++ b/pi/roverd/config.go @@ -141,7 +141,7 @@ func LoadConfig(path string) (*Config, error) { NudgeDegrees: 2, }, Audio: AudioConfig{ - CaptureEnabled: true, + CaptureEnabled: false, CaptureDevice: "plughw:0,0", SampleRate: 48000, Channels: 1, diff --git a/pi/roverd/media_env.go b/pi/roverd/media_env.go index 556a1cf0..8aef312e 100644 --- a/pi/roverd/media_env.go +++ b/pi/roverd/media_env.go @@ -25,11 +25,8 @@ func UpdatePublisherEnv(media MediaConfig, audio AudioConfig) error { fmt.Fprintf(&buf, "VIDEO_HEIGHT=%d\n", media.VideoHeight) fmt.Fprintf(&buf, "VIDEO_FPS=%d\n", media.VideoFPS) fmt.Fprintf(&buf, "VIDEO_BITRATE=%d\n", media.VideoBitrate) - fmt.Fprintf(&buf, "AUDIO_ENABLE=%d\n", boolToInt(audio.CaptureEnabled)) - fmt.Fprintf(&buf, "AUDIO_DEVICE=%s\n", audio.CaptureDevice) - fmt.Fprintf(&buf, "AUDIO_RATE=%d\n", audio.SampleRate) - fmt.Fprintf(&buf, "AUDIO_CHANNELS=%d\n", audio.Channels) - fmt.Fprintf(&buf, "AUDIO_BITRATE=%d\n", audio.Bitrate) + // Disable mic streaming; audio is handled locally for TTS only. + fmt.Fprintf(&buf, "AUDIO_ENABLE=0\n") if err := os.WriteFile(publisherEnvPath, buf.Bytes(), 0o640); err != nil { return err } diff --git a/pi/roverd/roverd.sample.yaml b/pi/roverd/roverd.sample.yaml index 151e2e0d..c5341d3f 100644 --- a/pi/roverd/roverd.sample.yaml +++ b/pi/roverd/roverd.sample.yaml @@ -38,7 +38,7 @@ cameraServo: nudgeDegrees: 2 allowRawPulse: false audio: - captureEnabled: true + captureEnabled: false captureDevice: plughw:0,0 sampleRate: 48000 channels: 1 diff --git a/server/config.example.yaml b/server/config.example.yaml index 1f9939aa..14977741 100644 --- a/server/config.example.yaml +++ b/server/config.example.yaml @@ -12,8 +12,6 @@ media: # http:////whep # Example: http://192.168.0.86:8889/video whepBaseUrl: "http://192.168.0.86:8889/video" - # Set audioBridge to false to disable server-side audio transcoding from -raw -> - # audioBridge: true homeAssistant: url: "http://homeassistant.local:8123" diff --git a/server/index.js b/server/index.js index 439902bc..f2be3274 100644 --- a/server/index.js +++ b/server/index.js @@ -26,4 +26,3 @@ require('./src/services/sessionService'); require('./src/services/batteryManager'); require('./src/services/discordBotService'); require('./src/services/httpServer'); -require('./src/services/audioBridgeService'); diff --git a/server/src/services/audioBridgeService.js b/server/src/services/audioBridgeService.js deleted file mode 100644 index 46af4720..00000000 --- a/server/src/services/audioBridgeService.js +++ /dev/null @@ -1,145 +0,0 @@ -const { spawn } = require('child_process'); -const logger = require('../globals/logger').child('audioBridge'); -const { loadConfig } = require('../helpers/configLoader'); - -const config = loadConfig(); - -const MEDIAMTX_API = - process.env.MEDIAMTX_API || 'http://127.0.0.1:9997/v3/paths/list'; -const SRT_HOST = process.env.MEDIAMTX_SRT_HOST || '127.0.0.1'; -const POLL_MS = 5000; - -// Track active ffmpeg bridges keyed by raw path name (e.g., rover-raw). -const bridges = new Map(); - -function stopBridge(rawName) { - const proc = bridges.get(rawName); - if (!proc) return; - bridges.delete(rawName); - proc.kill('SIGTERM'); - logger.info('stopped bridge', { rawName }); -} - -function startBridge(rawName, baseName) { - if (bridges.has(rawName)) return; - - // Use explicit credentials so mediamtx auth will allow the bridge to pull. - const inputUrl = `srt://${SRT_HOST}:9000?streamid=#!::r=${rawName},m=request&mode=caller&transtype=live&latency=20&u=bridge&p=bridge`; - const outputUrl = `srt://${SRT_HOST}:9000?streamid=#!::r=${baseName},m=publish&mode=caller&transtype=live&pkt_size=1316`; - - const args = [ - '-hide_banner', - '-loglevel', - 'warning', - '-fflags', - 'nobuffer', - '-thread_queue_size', - '512', - '-i', - inputUrl, - '-map', - '0:v', - '-map', - '0:a?', - '-c:v', - 'copy', - '-c:a', - 'libopus', - '-b:a', - '24000', - '-ac', - '1', - '-ar', - '16000', - '-application', - 'voip', - '-frame_duration', - '60', - '-af', - 'pan=1c|c0=c0,volume=12dB', - '-flush_packets', - '1', - '-f', - 'mpegts', - outputUrl, - ]; - - const proc = spawn('ffmpeg', args, { stdio: ['ignore', 'pipe', 'pipe'] }); - bridges.set(rawName, proc); - logger.info('started bridge', { rawName, baseName }); - - proc.stdout.on('data', (data) => { - logger.info(data.toString().trim()); - }); - proc.stderr.on('data', (data) => { - logger.info(data.toString().trim()); - }); - proc.on('exit', (code, signal) => { - bridges.delete(rawName); - logger.info('bridge exited', { rawName, code, signal }); - }); -} - -async function fetchPaths() { - try { - const res = await fetch(MEDIAMTX_API, { signal: AbortSignal.timeout(3000) }); - if (!res.ok) { - throw new Error(`mediamtx api ${res.status}`); - } - return res.json(); - } catch (err) { - logger.warn('path fetch failed: %s', err.message); - return null; - } -} - -function pickRawPairs(items) { - const names = new Set(items.map((i) => i.name)); - return items - .filter((i) => i.name.endsWith('-raw')) - .map((raw) => { - const baseName = raw.name.slice(0, -4); - return { rawName: raw.name, baseName }; - }) - .filter(({ baseName }) => !names.has(baseName)); -} - -async function reconcile() { - const data = await fetchPaths(); - if (!data?.items) { - // On failure, stop all bridges so we don't run blind. - Array.from(bridges.keys()).forEach(stopBridge); - return; - } - - const activeRaw = new Set(); - pickRawPairs(data.items).forEach(({ rawName, baseName }) => { - activeRaw.add(rawName); - startBridge(rawName, baseName); - }); - - // Stop bridges whose raw path disappeared. - Array.from(bridges.keys()).forEach((rawName) => { - if (!activeRaw.has(rawName)) { - stopBridge(rawName); - } - }); -} - -function start() { - // Only enable when explicit media config allows it, or by default. - const enabled = config.media?.audioBridge !== false; - if (!enabled) { - logger.info('audio bridge disabled by config'); - return; - } - logger.info('audio bridge enabled; watching for *-raw streams'); - reconcile(); - setInterval(reconcile, POLL_MS); -} - -start(); - -module.exports = { - start, -}; diff --git a/server/src/services/videoAuthService.js b/server/src/services/videoAuthService.js index 54a22587..ca9ca4ba 100644 --- a/server/src/services/videoAuthService.js +++ b/server/src/services/videoAuthService.js @@ -71,23 +71,9 @@ function canView(socket) { app.post('/mediamtx/auth', (req, res) => { const body = req.body || {}; const path = (body.path || '').replace(/^\//, ''); - const password = body.pass || body.password || ''; - const action = body.action || ''; const sessionId = body.user; const streamInfo = extractStreamInfo(path); - // Allow internal bridge (server-side audio transcode) to pull/push without a websocket session. - if (body.user === 'bridge' && password === 'bridge') { - return res.status(200).end(); - } - // Also allow localhost reads of *-raw paths for the bridge even if creds are missing. - if ( - streamInfo?.id?.endsWith('-raw') && - (req.ip === '127.0.0.1' || req.ip === '::1') - ) { - return res.status(200).end(); - } - logger.info('video auth request', { path: body.path, sessionId, stream: streamInfo }); if (!sessionId || !streamInfo?.id) {