Merge listen-talkover: non-blocking ready tone (talk over the dial)
This commit is contained in:
commit
08f40ae2dc
@ -238,6 +238,27 @@ async def _terminate_quietly(proc) -> None:
|
|||||||
await proc.wait()
|
await proc.wait()
|
||||||
|
|
||||||
|
|
||||||
|
def _fire_ready_tone(ready_tone: Path | None) -> "asyncio.Task | None":
|
||||||
|
"""Play the "go" cue WITHOUT blocking capture, so the person can talk over it.
|
||||||
|
|
||||||
|
The old behaviour awaited the tone before the read loop began, adding ~1s
|
||||||
|
of dead time. Firing it as a background task lets capture start immediately;
|
||||||
|
the tone bleeds into the recorded head but VAD skips it (pure tone !=
|
||||||
|
speech) and the min_speech_ms guard keeps it from ending the recording.
|
||||||
|
Returns the task (or None) so the caller can cancel it on cleanup.
|
||||||
|
"""
|
||||||
|
if ready_tone is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _play() -> None:
|
||||||
|
try:
|
||||||
|
await play_audio(ready_tone, expected_seconds=0.3)
|
||||||
|
except Exception:
|
||||||
|
pass # the cue is optional — never fail a recording over it
|
||||||
|
|
||||||
|
return asyncio.create_task(_play())
|
||||||
|
|
||||||
|
|
||||||
async def record_audio_until_silence(
|
async def record_audio_until_silence(
|
||||||
out_path: Path,
|
out_path: Path,
|
||||||
silence_threshold_ms: int = 1500,
|
silence_threshold_ms: int = 1500,
|
||||||
@ -291,16 +312,13 @@ async def record_audio_until_silence(
|
|||||||
stderr=asyncio.subprocess.DEVNULL,
|
stderr=asyncio.subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Cue the person only AFTER the mic is live (see warmup rationale above), so
|
# Warm up (mic live before the cue so the first word isn't clipped), then
|
||||||
# the first word isn't clipped by pw-record's stream startup. The beep is
|
# fire the "go" tone WITHOUT awaiting it and drop straight into the VAD
|
||||||
# captured as leading audio but VAD skips it (pure tone != speech) and the
|
# loop — so the person can talk right over the tone instead of waiting ~1s
|
||||||
# min_speech_ms guard below keeps it from ending the recording.
|
# for it to finish.
|
||||||
if ready_tone is not None:
|
if ready_tone is not None:
|
||||||
await asyncio.sleep(warmup_ms / 1000)
|
await asyncio.sleep(warmup_ms / 1000)
|
||||||
try:
|
tone_task = _fire_ready_tone(ready_tone)
|
||||||
await play_audio(ready_tone, expected_seconds=0.3)
|
|
||||||
except Exception:
|
|
||||||
pass # tone failure is non-fatal — keep recording
|
|
||||||
|
|
||||||
pcm_buf = bytearray()
|
pcm_buf = bytearray()
|
||||||
leftover = b""
|
leftover = b""
|
||||||
@ -343,6 +361,8 @@ async def record_audio_until_silence(
|
|||||||
# The inner read returned no data — break outer loop
|
# The inner read returned no data — break outer loop
|
||||||
break
|
break
|
||||||
finally:
|
finally:
|
||||||
|
if tone_task is not None and not tone_task.done():
|
||||||
|
tone_task.cancel() # don't let a short recording outlive by the tone
|
||||||
await _terminate_quietly(proc)
|
await _terminate_quietly(proc)
|
||||||
|
|
||||||
if not pcm_buf:
|
if not pcm_buf:
|
||||||
@ -415,15 +435,12 @@ async def record_audio(
|
|||||||
stdout=asyncio.subprocess.DEVNULL,
|
stdout=asyncio.subprocess.DEVNULL,
|
||||||
stderr=asyncio.subprocess.PIPE,
|
stderr=asyncio.subprocess.PIPE,
|
||||||
)
|
)
|
||||||
# Cue the person only AFTER the mic is live so the first word isn't clipped
|
# Warm up (mic live before the cue), then fire the "go" tone WITHOUT
|
||||||
# by pw-record's stream startup (the beep bleeds harmlessly into the head of
|
# awaiting it so recording is underway immediately and the person can talk
|
||||||
# the WAV; Parakeet ignores it). The duration timeout starts after the cue.
|
# over it. The beep bleeds harmlessly into the head of the WAV.
|
||||||
if ready_tone is not None:
|
if ready_tone is not None:
|
||||||
await asyncio.sleep(warmup_ms / 1000)
|
await asyncio.sleep(warmup_ms / 1000)
|
||||||
try:
|
tone_task = _fire_ready_tone(ready_tone)
|
||||||
await play_audio(ready_tone, expected_seconds=0.3)
|
|
||||||
except Exception:
|
|
||||||
pass # tone failure is non-fatal — keep recording
|
|
||||||
try:
|
try:
|
||||||
# pw-record never exits on its own — wait the desired duration then term.
|
# pw-record never exits on its own — wait the desired duration then term.
|
||||||
await asyncio.wait_for(proc.wait(), timeout=duration_seconds)
|
await asyncio.wait_for(proc.wait(), timeout=duration_seconds)
|
||||||
@ -438,6 +455,9 @@ async def record_audio(
|
|||||||
pass
|
pass
|
||||||
await proc.wait()
|
await proc.wait()
|
||||||
raise
|
raise
|
||||||
|
finally:
|
||||||
|
if tone_task is not None and not tone_task.done():
|
||||||
|
tone_task.cancel()
|
||||||
|
|
||||||
# pw-record's exit code on SIGTERM is non-deterministic: some versions
|
# pw-record's exit code on SIGTERM is non-deterministic: some versions
|
||||||
# return 0, some 1, some -15. The reliable signal is whether the WAV
|
# return 0, some 1, some -15. The reliable signal is whether the WAV
|
||||||
@ -477,12 +497,11 @@ async def _record_fixed_stream(
|
|||||||
stderr=asyncio.subprocess.DEVNULL,
|
stderr=asyncio.subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Warm up, then fire the "go" tone WITHOUT awaiting so capture is underway
|
||||||
|
# immediately and the person can talk over it.
|
||||||
if ready_tone is not None:
|
if ready_tone is not None:
|
||||||
await asyncio.sleep(warmup_ms / 1000)
|
await asyncio.sleep(warmup_ms / 1000)
|
||||||
try:
|
tone_task = _fire_ready_tone(ready_tone)
|
||||||
await play_audio(ready_tone, expected_seconds=0.3)
|
|
||||||
except Exception:
|
|
||||||
pass # tone failure is non-fatal — keep recording
|
|
||||||
|
|
||||||
pcm = bytearray()
|
pcm = bytearray()
|
||||||
|
|
||||||
@ -504,6 +523,9 @@ async def _record_fixed_stream(
|
|||||||
pass
|
pass
|
||||||
await proc.wait()
|
await proc.wait()
|
||||||
raise
|
raise
|
||||||
|
finally:
|
||||||
|
if tone_task is not None and not tone_task.done():
|
||||||
|
tone_task.cancel()
|
||||||
|
|
||||||
if len(pcm) < 100:
|
if len(pcm) < 100:
|
||||||
raise PlaybackError(
|
raise PlaybackError(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user