allow multiple StartFrames
This commit is contained in:
@@ -25,10 +25,8 @@ from pipecat.frames.frames import (
|
|||||||
InputAudioRawFrame,
|
InputAudioRawFrame,
|
||||||
LLMFullResponseEndFrame,
|
LLMFullResponseEndFrame,
|
||||||
LLMFullResponseStartFrame,
|
LLMFullResponseStartFrame,
|
||||||
LLMMessagesFrame,
|
|
||||||
StartFrame,
|
StartFrame,
|
||||||
StartInterruptionFrame,
|
StartInterruptionFrame,
|
||||||
StopInterruptionFrame,
|
|
||||||
SystemFrame,
|
SystemFrame,
|
||||||
TextFrame,
|
TextFrame,
|
||||||
TranscriptionFrame,
|
TranscriptionFrame,
|
||||||
|
|||||||
@@ -246,11 +246,15 @@ class LLMUserContextAggregator(LLMContextResponseAggregator):
|
|||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
if isinstance(frame, StartFrame):
|
if isinstance(frame, StartFrame):
|
||||||
|
# Push StartFrame before start(), because we want StartFrame to be
|
||||||
|
# processed by every processor before any other frame is processed.
|
||||||
|
await self.push_frame(frame, direction)
|
||||||
await self._start(frame)
|
await self._start(frame)
|
||||||
await self.push_frame(frame, direction)
|
|
||||||
elif isinstance(frame, EndFrame):
|
elif isinstance(frame, EndFrame):
|
||||||
await self._stop(frame)
|
# Push EndFrame before stop(), because stop() waits on the task to
|
||||||
|
# finish and the task finishes when EndFrame is processed.
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
await self._stop(frame)
|
||||||
elif isinstance(frame, CancelFrame):
|
elif isinstance(frame, CancelFrame):
|
||||||
await self._cancel(frame)
|
await self._cancel(frame)
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|||||||
@@ -840,7 +840,8 @@ class SegmentedSTTService(STTService):
|
|||||||
|
|
||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
(self._content, self._wave) = self._new_wave()
|
if not self._wave:
|
||||||
|
(self._content, self._wave) = self._new_wave()
|
||||||
|
|
||||||
async def stop(self, frame: EndFrame):
|
async def stop(self, frame: EndFrame):
|
||||||
await super().stop(frame)
|
await super().stop(frame)
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ class AssemblyAISTTService(STTService):
|
|||||||
AssemblyAI transcriber.
|
AssemblyAI transcriber.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if self._transcriber:
|
||||||
|
return
|
||||||
|
|
||||||
def on_open(session_opened: aai.RealtimeSessionOpened):
|
def on_open(session_opened: aai.RealtimeSessionOpened):
|
||||||
"""Callback for when the connection to AssemblyAI is opened."""
|
"""Callback for when the connection to AssemblyAI is opened."""
|
||||||
logger.info(f"{self}: Connected to AssemblyAI")
|
logger.info(f"{self}: Connected to AssemblyAI")
|
||||||
|
|||||||
@@ -535,6 +535,10 @@ class AzureTTSService(AzureBaseTTSService):
|
|||||||
|
|
||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
|
|
||||||
|
if self._speech_config:
|
||||||
|
return
|
||||||
|
|
||||||
# Now self.sample_rate is properly initialized
|
# Now self.sample_rate is properly initialized
|
||||||
self._speech_config = SpeechConfig(
|
self._speech_config = SpeechConfig(
|
||||||
subscription=self._api_key,
|
subscription=self._api_key,
|
||||||
@@ -624,6 +628,10 @@ class AzureHttpTTSService(AzureBaseTTSService):
|
|||||||
|
|
||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
|
|
||||||
|
if self._speech_config:
|
||||||
|
return
|
||||||
|
|
||||||
self._speech_config = SpeechConfig(
|
self._speech_config = SpeechConfig(
|
||||||
subscription=self._api_key,
|
subscription=self._api_key,
|
||||||
region=self._region,
|
region=self._region,
|
||||||
@@ -678,15 +686,22 @@ class AzureSTTService(STTService):
|
|||||||
self._speech_config = SpeechConfig(subscription=api_key, region=region)
|
self._speech_config = SpeechConfig(subscription=api_key, region=region)
|
||||||
self._speech_config.speech_recognition_language = language
|
self._speech_config.speech_recognition_language = language
|
||||||
|
|
||||||
|
self._audio_stream = None
|
||||||
|
self._speech_recognizer = None
|
||||||
|
|
||||||
async def run_stt(self, audio: bytes) -> AsyncGenerator[Frame, None]:
|
async def run_stt(self, audio: bytes) -> AsyncGenerator[Frame, None]:
|
||||||
await self.start_processing_metrics()
|
await self.start_processing_metrics()
|
||||||
self._audio_stream.write(audio)
|
if self._audio_stream:
|
||||||
|
self._audio_stream.write(audio)
|
||||||
await self.stop_processing_metrics()
|
await self.stop_processing_metrics()
|
||||||
yield None
|
yield None
|
||||||
|
|
||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
|
|
||||||
|
if self._audio_stream:
|
||||||
|
return
|
||||||
|
|
||||||
stream_format = AudioStreamFormat(samples_per_second=self.sample_rate, channels=1)
|
stream_format = AudioStreamFormat(samples_per_second=self.sample_rate, channels=1)
|
||||||
self._audio_stream = PushAudioInputStream(stream_format)
|
self._audio_stream = PushAudioInputStream(stream_format)
|
||||||
|
|
||||||
@@ -700,13 +715,21 @@ class AzureSTTService(STTService):
|
|||||||
|
|
||||||
async def stop(self, frame: EndFrame):
|
async def stop(self, frame: EndFrame):
|
||||||
await super().stop(frame)
|
await super().stop(frame)
|
||||||
self._speech_recognizer.stop_continuous_recognition_async()
|
|
||||||
self._audio_stream.close()
|
if self._speech_recognizer:
|
||||||
|
self._speech_recognizer.stop_continuous_recognition_async()
|
||||||
|
|
||||||
|
if self._audio_stream:
|
||||||
|
self._audio_stream.close()
|
||||||
|
|
||||||
async def cancel(self, frame: CancelFrame):
|
async def cancel(self, frame: CancelFrame):
|
||||||
await super().cancel(frame)
|
await super().cancel(frame)
|
||||||
self._speech_recognizer.stop_continuous_recognition_async()
|
|
||||||
self._audio_stream.close()
|
if self._speech_recognizer:
|
||||||
|
self._speech_recognizer.stop_continuous_recognition_async()
|
||||||
|
|
||||||
|
if self._audio_stream:
|
||||||
|
self._audio_stream.close()
|
||||||
|
|
||||||
def _on_handle_recognized(self, event):
|
def _on_handle_recognized(self, event):
|
||||||
if event.result.reason == ResultReason.RecognizedSpeech and len(event.result.text) > 0:
|
if event.result.reason == ResultReason.RecognizedSpeech and len(event.result.text) > 0:
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ class GladiaSTTService(STTService):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
self._confidence = confidence
|
self._confidence = confidence
|
||||||
|
self._websocket = None
|
||||||
self._receive_task = None
|
self._receive_task = None
|
||||||
|
|
||||||
def language_to_service_language(self, language: Language) -> Optional[str]:
|
def language_to_service_language(self, language: Language) -> Optional[str]:
|
||||||
@@ -179,6 +180,8 @@ class GladiaSTTService(STTService):
|
|||||||
|
|
||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
|
if self._websocket:
|
||||||
|
return
|
||||||
self._settings["sample_rate"] = self.sample_rate
|
self._settings["sample_rate"] = self.sample_rate
|
||||||
response = await self._setup_gladia()
|
response = await self._setup_gladia()
|
||||||
self._websocket = await websockets.connect(response["url"])
|
self._websocket = await websockets.connect(response["url"])
|
||||||
@@ -188,7 +191,9 @@ class GladiaSTTService(STTService):
|
|||||||
async def stop(self, frame: EndFrame):
|
async def stop(self, frame: EndFrame):
|
||||||
await super().stop(frame)
|
await super().stop(frame)
|
||||||
await self._send_stop_recording()
|
await self._send_stop_recording()
|
||||||
await self._websocket.close()
|
if self._websocket:
|
||||||
|
await self._websocket.close()
|
||||||
|
self._websocket = None
|
||||||
if self._receive_task:
|
if self._receive_task:
|
||||||
await self.wait_for_task(self._receive_task)
|
await self.wait_for_task(self._receive_task)
|
||||||
self._receive_task = None
|
self._receive_task = None
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ class ParakeetSTTService(STTService):
|
|||||||
self._asr_service = riva.client.ASRService(auth)
|
self._asr_service = riva.client.ASRService(auth)
|
||||||
|
|
||||||
self._queue = asyncio.Queue()
|
self._queue = asyncio.Queue()
|
||||||
|
self._config = None
|
||||||
self._thread_task = None
|
self._thread_task = None
|
||||||
self._response_task = None
|
self._response_task = None
|
||||||
|
|
||||||
@@ -175,6 +176,9 @@ class ParakeetSTTService(STTService):
|
|||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
|
|
||||||
|
if self._config:
|
||||||
|
return
|
||||||
|
|
||||||
config = riva.client.StreamingRecognitionConfig(
|
config = riva.client.StreamingRecognitionConfig(
|
||||||
config=riva.client.RecognitionConfig(
|
config=riva.client.RecognitionConfig(
|
||||||
encoding=riva.client.AudioEncoding.LINEAR_PCM,
|
encoding=riva.client.AudioEncoding.LINEAR_PCM,
|
||||||
|
|||||||
@@ -43,11 +43,15 @@ class SimliVideoService(FrameProcessor):
|
|||||||
self._pipecat_resampler: AudioResampler = None
|
self._pipecat_resampler: AudioResampler = None
|
||||||
self._simli_resampler = AudioResampler("s16", "mono", 16000)
|
self._simli_resampler = AudioResampler("s16", "mono", 16000)
|
||||||
|
|
||||||
|
self._initialized = False
|
||||||
self._audio_task: asyncio.Task = None
|
self._audio_task: asyncio.Task = None
|
||||||
self._video_task: asyncio.Task = None
|
self._video_task: asyncio.Task = None
|
||||||
|
|
||||||
async def _start_connection(self):
|
async def _start_connection(self):
|
||||||
await self._simli_client.Initialize()
|
if not self._initialized:
|
||||||
|
await self._simli_client.Initialize()
|
||||||
|
self._initialized = True
|
||||||
|
|
||||||
# Create task to consume and process audio and video
|
# Create task to consume and process audio and video
|
||||||
if not self._audio_task:
|
if not self._audio_task:
|
||||||
self._audio_task = self.create_task(self._consume_and_process_audio())
|
self._audio_task = self.create_task(self._consume_and_process_audio())
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ class XTTSService(TTSService):
|
|||||||
|
|
||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
|
|
||||||
|
if self._studio_speakers:
|
||||||
|
return
|
||||||
|
|
||||||
async with self._aiohttp_session.get(self._settings["base_url"] + "/studio_speakers") as r:
|
async with self._aiohttp_session.get(self._settings["base_url"] + "/studio_speakers") as r:
|
||||||
if r.status != 200:
|
if r.status != 200:
|
||||||
text = await r.text()
|
text = await r.text()
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ class LocalAudioInputTransport(BaseInputTransport):
|
|||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
|
|
||||||
|
if self._in_stream:
|
||||||
|
return
|
||||||
|
|
||||||
self._sample_rate = self._params.audio_in_sample_rate or frame.audio_in_sample_rate
|
self._sample_rate = self._params.audio_in_sample_rate or frame.audio_in_sample_rate
|
||||||
num_frames = int(self._sample_rate / 100) * 2 # 20ms of audio
|
num_frames = int(self._sample_rate / 100) * 2 # 20ms of audio
|
||||||
|
|
||||||
@@ -94,6 +97,9 @@ class LocalAudioOutputTransport(BaseOutputTransport):
|
|||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
|
|
||||||
|
if self._out_stream:
|
||||||
|
return
|
||||||
|
|
||||||
self._sample_rate = self._params.audio_out_sample_rate or frame.audio_out_sample_rate
|
self._sample_rate = self._params.audio_out_sample_rate or frame.audio_out_sample_rate
|
||||||
|
|
||||||
self._out_stream = self._py_audio.open(
|
self._out_stream = self._py_audio.open(
|
||||||
@@ -110,6 +116,7 @@ class LocalAudioOutputTransport(BaseOutputTransport):
|
|||||||
if self._out_stream:
|
if self._out_stream:
|
||||||
self._out_stream.stop_stream()
|
self._out_stream.stop_stream()
|
||||||
self._out_stream.close()
|
self._out_stream.close()
|
||||||
|
self._out_stream = None
|
||||||
|
|
||||||
async def write_raw_audio_frames(self, frames: bytes):
|
async def write_raw_audio_frames(self, frames: bytes):
|
||||||
if self._out_stream:
|
if self._out_stream:
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ class TkInputTransport(BaseInputTransport):
|
|||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
|
|
||||||
|
if self._in_stream:
|
||||||
|
return
|
||||||
|
|
||||||
self._sample_rate = self._params.audio_in_sample_rate or frame.audio_in_sample_rate
|
self._sample_rate = self._params.audio_in_sample_rate or frame.audio_in_sample_rate
|
||||||
num_frames = int(self._sample_rate / 100) * 2 # 20ms of audio
|
num_frames = int(self._sample_rate / 100) * 2 # 20ms of audio
|
||||||
|
|
||||||
@@ -70,6 +73,7 @@ class TkInputTransport(BaseInputTransport):
|
|||||||
if self._in_stream:
|
if self._in_stream:
|
||||||
self._in_stream.stop_stream()
|
self._in_stream.stop_stream()
|
||||||
self._in_stream.close()
|
self._in_stream.close()
|
||||||
|
self._in_stream = None
|
||||||
|
|
||||||
def _audio_in_callback(self, in_data, frame_count, time_info, status):
|
def _audio_in_callback(self, in_data, frame_count, time_info, status):
|
||||||
frame = InputAudioRawFrame(
|
frame = InputAudioRawFrame(
|
||||||
@@ -106,6 +110,9 @@ class TkOutputTransport(BaseOutputTransport):
|
|||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
|
|
||||||
|
if self._out_stream:
|
||||||
|
return
|
||||||
|
|
||||||
self._sample_rate = self._params.audio_out_sample_rate or frame.audio_out_sample_rate
|
self._sample_rate = self._params.audio_out_sample_rate or frame.audio_out_sample_rate
|
||||||
|
|
||||||
self._out_stream = self._py_audio.open(
|
self._out_stream = self._py_audio.open(
|
||||||
@@ -122,6 +129,7 @@ class TkOutputTransport(BaseOutputTransport):
|
|||||||
if self._out_stream:
|
if self._out_stream:
|
||||||
self._out_stream.stop_stream()
|
self._out_stream.stop_stream()
|
||||||
self._out_stream.close()
|
self._out_stream.close()
|
||||||
|
self._out_stream = None
|
||||||
|
|
||||||
async def write_raw_audio_frames(self, frames: bytes):
|
async def write_raw_audio_frames(self, frames: bytes):
|
||||||
if self._out_stream:
|
if self._out_stream:
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ from pipecat.frames.frames import (
|
|||||||
OutputAudioRawFrame,
|
OutputAudioRawFrame,
|
||||||
StartFrame,
|
StartFrame,
|
||||||
StartInterruptionFrame,
|
StartInterruptionFrame,
|
||||||
TextFrame,
|
|
||||||
TransportMessageFrame,
|
TransportMessageFrame,
|
||||||
TransportMessageUrgentFrame,
|
TransportMessageUrgentFrame,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -832,6 +832,9 @@ class DailyInputTransport(BaseInputTransport):
|
|||||||
|
|
||||||
self._video_renderers = {}
|
self._video_renderers = {}
|
||||||
|
|
||||||
|
# Whether we have seen a StartFrame already.
|
||||||
|
self._initialized = False
|
||||||
|
|
||||||
# Task that gets audio data from a device or the network and queues it
|
# Task that gets audio data from a device or the network and queues it
|
||||||
# internally to be processed.
|
# internally to be processed.
|
||||||
self._audio_in_task = None
|
self._audio_in_task = None
|
||||||
@@ -852,6 +855,12 @@ class DailyInputTransport(BaseInputTransport):
|
|||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
# Parent start.
|
# Parent start.
|
||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
|
|
||||||
|
if self._initialized:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._initialized = True
|
||||||
|
|
||||||
# Setup client.
|
# Setup client.
|
||||||
await self._client.setup(frame)
|
await self._client.setup(frame)
|
||||||
# Join the room.
|
# Join the room.
|
||||||
@@ -980,9 +989,18 @@ class DailyOutputTransport(BaseOutputTransport):
|
|||||||
|
|
||||||
self._client = client
|
self._client = client
|
||||||
|
|
||||||
|
# Whether we have seen a StartFrame already.
|
||||||
|
self._initialized = False
|
||||||
|
|
||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
# Parent start.
|
# Parent start.
|
||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
|
|
||||||
|
if self._initialized:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._initialized = True
|
||||||
|
|
||||||
# Setup client.
|
# Setup client.
|
||||||
await self._client.setup(frame)
|
await self._client.setup(frame)
|
||||||
# Join the room.
|
# Join the room.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from openai.types.chat import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from pipecat.frames.frames import LLMFullResponseEndFrame, LLMFullResponseStartFrame, TextFrame
|
from pipecat.frames.frames import LLMFullResponseEndFrame, LLMFullResponseStartFrame, TextFrame
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
from pipecat.services.openai import OpenAILLMContext, OpenAILLMContextFrame, OpenAILLMService
|
from pipecat.services.openai import OpenAILLMContext, OpenAILLMContextFrame, OpenAILLMService
|
||||||
from pipecat.utils.test_frame_processor import TestFrameProcessor
|
from pipecat.utils.test_frame_processor import TestFrameProcessor
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user