Add VADUserStartedSpeakingFrame and VADUserStoppedSpeakingFrame
This commit is contained in:
@@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Added `VADUserStartedSpeakingFrame` and `VADUserStoppedSpeakingFrame`,
|
||||||
|
indicating when the VAD detected the user to start and stop speaking. These
|
||||||
|
events are helpful when using smart turn detection, as the user's stop time
|
||||||
|
can differ from when their turn ends (signified by UserStoppedSpeakingFrame).
|
||||||
|
|
||||||
- Added `TranslationFrame`, a new frame type that contains a translated
|
- Added `TranslationFrame`, a new frame type that contains a translated
|
||||||
transcription.
|
transcription.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@@ -88,12 +88,24 @@ class VADAnalyzer(ABC):
|
|||||||
volume = calculate_audio_volume(audio, self.sample_rate)
|
volume = calculate_audio_volume(audio, self.sample_rate)
|
||||||
return exp_smoothing(volume, self._prev_volume, self._smoothing_factor)
|
return exp_smoothing(volume, self._prev_volume, self._smoothing_factor)
|
||||||
|
|
||||||
def analyze_audio(self, buffer) -> VADState:
|
def analyze_audio(self, buffer) -> Tuple[VADState, Optional[str]]:
|
||||||
|
"""Analyze audio for voice activity.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
buffer: Audio buffer to analyze
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple containing:
|
||||||
|
- VADState: Current VAD state
|
||||||
|
- Optional[str]: Event type if a speech event occurred ("speech_started",
|
||||||
|
"speech_stopped"), or None if no event occurred
|
||||||
|
"""
|
||||||
self._vad_buffer += buffer
|
self._vad_buffer += buffer
|
||||||
|
event_type = None
|
||||||
|
|
||||||
num_required_bytes = self._vad_frames_num_bytes
|
num_required_bytes = self._vad_frames_num_bytes
|
||||||
if len(self._vad_buffer) < num_required_bytes:
|
if len(self._vad_buffer) < num_required_bytes:
|
||||||
return self._vad_state
|
return self._vad_state, event_type
|
||||||
|
|
||||||
audio_frames = self._vad_buffer[:num_required_bytes]
|
audio_frames = self._vad_buffer[:num_required_bytes]
|
||||||
self._vad_buffer = self._vad_buffer[num_required_bytes:]
|
self._vad_buffer = self._vad_buffer[num_required_bytes:]
|
||||||
@@ -132,6 +144,7 @@ class VADAnalyzer(ABC):
|
|||||||
):
|
):
|
||||||
self._vad_state = VADState.SPEAKING
|
self._vad_state = VADState.SPEAKING
|
||||||
self._vad_starting_count = 0
|
self._vad_starting_count = 0
|
||||||
|
event_type = "speech_started"
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self._vad_state == VADState.STOPPING
|
self._vad_state == VADState.STOPPING
|
||||||
@@ -139,5 +152,6 @@ class VADAnalyzer(ABC):
|
|||||||
):
|
):
|
||||||
self._vad_state = VADState.QUIET
|
self._vad_state = VADState.QUIET
|
||||||
self._vad_stopping_count = 0
|
self._vad_stopping_count = 0
|
||||||
|
event_type = "speech_stopped"
|
||||||
|
|
||||||
return self._vad_state
|
return self._vad_state, event_type
|
||||||
|
|||||||
@@ -587,6 +587,20 @@ class EmulateUserStoppedSpeakingFrame(SystemFrame):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VADUserStartedSpeakingFrame(SystemFrame):
|
||||||
|
"""Frame emitted when VAD detects the user has definitively started speaking."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VADUserStoppedSpeakingFrame(SystemFrame):
|
||||||
|
"""Frame emitted when VAD detects the user has definitively stopped speaking."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BotInterruptionFrame(SystemFrame):
|
class BotInterruptionFrame(SystemFrame):
|
||||||
"""Emitted by when the bot should be interrupted. This will mainly cause the
|
"""Emitted by when the bot should be interrupted. This will mainly cause the
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ from pipecat.frames.frames import (
|
|||||||
UserStartedSpeakingFrame,
|
UserStartedSpeakingFrame,
|
||||||
UserStoppedSpeakingFrame,
|
UserStoppedSpeakingFrame,
|
||||||
VADParamsUpdateFrame,
|
VADParamsUpdateFrame,
|
||||||
|
VADUserStartedSpeakingFrame,
|
||||||
|
VADUserStoppedSpeakingFrame,
|
||||||
)
|
)
|
||||||
from pipecat.metrics.metrics import MetricsData
|
from pipecat.metrics.metrics import MetricsData
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
@@ -228,9 +230,13 @@ class BaseInputTransport(FrameProcessor):
|
|||||||
async def _vad_analyze(self, audio_frame: InputAudioRawFrame) -> VADState:
|
async def _vad_analyze(self, audio_frame: InputAudioRawFrame) -> VADState:
|
||||||
state = VADState.QUIET
|
state = VADState.QUIET
|
||||||
if self.vad_analyzer:
|
if self.vad_analyzer:
|
||||||
state = await self.get_event_loop().run_in_executor(
|
state, event_type = await self.get_event_loop().run_in_executor(
|
||||||
self._executor, self.vad_analyzer.analyze_audio, audio_frame.audio
|
self._executor, self.vad_analyzer.analyze_audio, audio_frame.audio
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if event_type:
|
||||||
|
await self._handle_vad_event(event_type)
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
async def _handle_vad(self, audio_frame: InputAudioRawFrame, vad_state: VADState):
|
async def _handle_vad(self, audio_frame: InputAudioRawFrame, vad_state: VADState):
|
||||||
@@ -260,6 +266,16 @@ class BaseInputTransport(FrameProcessor):
|
|||||||
vad_state = new_vad_state
|
vad_state = new_vad_state
|
||||||
return vad_state
|
return vad_state
|
||||||
|
|
||||||
|
async def _handle_vad_event(self, event_type: str):
|
||||||
|
"""Handle VAD speech events by creating and pushing appropriate frames."""
|
||||||
|
if event_type == "speech_started":
|
||||||
|
logger.debug("VAD detected definitive speech start")
|
||||||
|
await self.push_frame(VADUserStartedSpeakingFrame())
|
||||||
|
|
||||||
|
elif event_type == "speech_stopped":
|
||||||
|
logger.debug("VAD detected definitive speech stop")
|
||||||
|
await self.push_frame(VADUserStoppedSpeakingFrame())
|
||||||
|
|
||||||
async def _handle_end_of_turn(self):
|
async def _handle_end_of_turn(self):
|
||||||
if self.turn_analyzer:
|
if self.turn_analyzer:
|
||||||
state, prediction = await self.turn_analyzer.analyze_end_of_turn()
|
state, prediction = await self.turn_analyzer.analyze_end_of_turn()
|
||||||
|
|||||||
Reference in New Issue
Block a user