invokerx commited on
Commit
c8ac879
·
verified ·
1 Parent(s): adba54c

Suppress Gemini mic before output audio arrives

Browse files
src/hermesbody/gemini_live.py CHANGED
@@ -16,6 +16,7 @@ import logging
16
  import math
17
  import os
18
  import secrets
 
19
  from collections.abc import AsyncIterator, Mapping
20
  from dataclasses import dataclass, field
21
  from pathlib import Path
@@ -347,6 +348,8 @@ class MacMiniGeminiAudioServer:
347
  self._audio_bytes_received = 0
348
  self._provider_events_sent = 0
349
  self._output_audio_events_sent = 0
 
 
350
 
351
  async def serve_forever(self) -> None:
352
  try:
@@ -446,6 +449,20 @@ class MacMiniGeminiAudioServer:
446
  peak,
447
  self._audio_bytes_received,
448
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
449
  try:
450
  returned = await _maybe_await(self.controller.send_audio_frame(frame))
451
  except Exception as exc:
@@ -493,13 +510,26 @@ class MacMiniGeminiAudioServer:
493
  self._provider_events_sent += 1
494
  if event.type == "output_audio" and event.frame is not None:
495
  self._output_audio_events_sent += 1
 
 
 
 
 
496
  logger.info(
497
- "Mac audio bridge provider event client #%s type=output_audio bytes=%d sr=%d",
498
  client_id,
499
  len(event.frame.pcm),
500
  event.frame.sample_rate,
 
501
  )
502
  else:
 
 
 
 
 
 
 
503
  logger.info(
504
  "Mac audio bridge provider event client #%s type=%s text=%r reason=%r data=%s",
505
  client_id,
 
16
  import math
17
  import os
18
  import secrets
19
+ import time
20
  from collections.abc import AsyncIterator, Mapping
21
  from dataclasses import dataclass, field
22
  from pathlib import Path
 
348
  self._audio_bytes_received = 0
349
  self._provider_events_sent = 0
350
  self._output_audio_events_sent = 0
351
+ self._suppress_audio_input_until = 0.0
352
+ self._suppressed_audio_input_frames = 0
353
 
354
  async def serve_forever(self) -> None:
355
  try:
 
449
  peak,
450
  self._audio_bytes_received,
451
  )
452
+ now = time.monotonic()
453
+ if now < self._suppress_audio_input_until:
454
+ self._suppressed_audio_input_frames += 1
455
+ if self._suppressed_audio_input_frames <= 5 or self._suppressed_audio_input_frames % 100 == 0:
456
+ remaining_ms = int((self._suppress_audio_input_until - now) * 1000)
457
+ logger.info(
458
+ "Mac audio bridge suppressing input during Gemini output client #%s frame=%d remaining_ms=%d rms=%.5f peak=%.5f",
459
+ client_id,
460
+ self._suppressed_audio_input_frames,
461
+ remaining_ms,
462
+ rms,
463
+ peak,
464
+ )
465
+ return
466
  try:
467
  returned = await _maybe_await(self.controller.send_audio_frame(frame))
468
  except Exception as exc:
 
510
  self._provider_events_sent += 1
511
  if event.type == "output_audio" and event.frame is not None:
512
  self._output_audio_events_sent += 1
513
+ playback_seconds = len(event.frame.pcm) / float((event.frame.sample_rate or DEFAULT_OUTPUT_SAMPLE_RATE) * max(event.frame.channels, 1) * 2)
514
+ self._suppress_audio_input_until = max(
515
+ self._suppress_audio_input_until,
516
+ time.monotonic() + playback_seconds + 0.35,
517
+ )
518
  logger.info(
519
+ "Mac audio bridge provider event client #%s type=output_audio bytes=%d sr=%d suppress_ms=%d",
520
  client_id,
521
  len(event.frame.pcm),
522
  event.frame.sample_rate,
523
+ int((self._suppress_audio_input_until - time.monotonic()) * 1000),
524
  )
525
  else:
526
+ if event.type in {"output_text", "output_transcription"}:
527
+ self._suppress_audio_input_until = max(
528
+ self._suppress_audio_input_until,
529
+ time.monotonic() + 2.0,
530
+ )
531
+ if event.type in {"turn_complete"}:
532
+ self._suppress_audio_input_until = min(self._suppress_audio_input_until, time.monotonic() + 0.25)
533
  logger.info(
534
  "Mac audio bridge provider event client #%s type=%s text=%r reason=%r data=%s",
535
  client_id,
src/hermesbody/main.py CHANGED
@@ -639,6 +639,12 @@ class HermesBodyCore:
639
  self.movement_manager.set_processing(False)
640
  if self.head_wobbler is not None:
641
  self.head_wobbler.reset()
 
 
 
 
 
 
642
  elif message.type == "brain_start":
643
  self.movement_manager.set_processing(True)
644
  logger.info("Gemini brain call started: %s", message.text or message.data)
 
639
  self.movement_manager.set_processing(False)
640
  if self.head_wobbler is not None:
641
  self.head_wobbler.reset()
642
+ elif message.type in {"output_text", "output_transcription"}:
643
+ # Gemini can emit text/transcription slightly before audio frames arrive.
644
+ # Close the mic immediately so the tail of the user's utterance or local
645
+ # speaker output doesn't interrupt the model before TTS reaches Reachy.
646
+ self._suppress_mic_until = max(self._suppress_mic_until, time.monotonic() + 2.0)
647
+ logger.info("Gemini output started; suppressing mic briefly: %s", message.text or message.data)
648
  elif message.type == "brain_start":
649
  self.movement_manager.set_processing(True)
650
  logger.info("Gemini brain call started: %s", message.text or message.data)