introduce input/output audio and image frames
We now distinguish between input and output audio and image frames. We introduce `InputAudioRawFrame`, `OutputAudioRawFrame`, `InputImageRawFrame` and `OutputImageRawFrame` (and other subclasses of those). The input frames usually come from an input transport and are meant to be processed inside the pipeline to generate new frames. However, the input frames will not be sent through an output transport. The output frames can also be processed by any frame processor in the pipeline and they are allowed to be sent by the output transport.
This commit is contained in:
@@ -15,17 +15,17 @@ from typing import List
|
||||
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
from pipecat.frames.frames import (
|
||||
AudioRawFrame,
|
||||
BotSpeakingFrame,
|
||||
BotStartedSpeakingFrame,
|
||||
BotStoppedSpeakingFrame,
|
||||
CancelFrame,
|
||||
MetricsFrame,
|
||||
OutputAudioRawFrame,
|
||||
OutputImageRawFrame,
|
||||
SpriteFrame,
|
||||
StartFrame,
|
||||
EndFrame,
|
||||
Frame,
|
||||
ImageRawFrame,
|
||||
StartInterruptionFrame,
|
||||
StopInterruptionFrame,
|
||||
SystemFrame,
|
||||
@@ -122,7 +122,7 @@ class BaseOutputTransport(FrameProcessor):
|
||||
async def send_metrics(self, frame: MetricsFrame):
|
||||
pass
|
||||
|
||||
async def write_frame_to_camera(self, frame: ImageRawFrame):
|
||||
async def write_frame_to_camera(self, frame: OutputImageRawFrame):
|
||||
pass
|
||||
|
||||
async def write_raw_audio_frames(self, frames: bytes):
|
||||
@@ -162,9 +162,9 @@ class BaseOutputTransport(FrameProcessor):
|
||||
await self._sink_queue.put(frame)
|
||||
await self.stop(frame)
|
||||
# Other frames.
|
||||
elif isinstance(frame, AudioRawFrame):
|
||||
elif isinstance(frame, OutputAudioRawFrame):
|
||||
await self._handle_audio(frame)
|
||||
elif isinstance(frame, ImageRawFrame) or isinstance(frame, SpriteFrame):
|
||||
elif isinstance(frame, OutputImageRawFrame) or isinstance(frame, SpriteFrame):
|
||||
await self._handle_image(frame)
|
||||
elif isinstance(frame, TransportMessageFrame) and frame.urgent:
|
||||
await self.send_message(frame)
|
||||
@@ -191,7 +191,7 @@ class BaseOutputTransport(FrameProcessor):
|
||||
if self._bot_speaking:
|
||||
await self._bot_stopped_speaking()
|
||||
|
||||
async def _handle_audio(self, frame: AudioRawFrame):
|
||||
async def _handle_audio(self, frame: OutputAudioRawFrame):
|
||||
if not self._params.audio_out_enabled:
|
||||
return
|
||||
|
||||
@@ -200,12 +200,14 @@ class BaseOutputTransport(FrameProcessor):
|
||||
else:
|
||||
self._audio_buffer.extend(frame.audio)
|
||||
while len(self._audio_buffer) >= self._audio_chunk_size:
|
||||
chunk = AudioRawFrame(bytes(self._audio_buffer[:self._audio_chunk_size]),
|
||||
sample_rate=frame.sample_rate, num_channels=frame.num_channels)
|
||||
chunk = OutputAudioRawFrame(
|
||||
bytes(self._audio_buffer[:self._audio_chunk_size]),
|
||||
sample_rate=frame.sample_rate, num_channels=frame.num_channels
|
||||
)
|
||||
await self._sink_queue.put(chunk)
|
||||
self._audio_buffer = self._audio_buffer[self._audio_chunk_size:]
|
||||
|
||||
async def _handle_image(self, frame: ImageRawFrame | SpriteFrame):
|
||||
async def _handle_image(self, frame: OutputImageRawFrame | SpriteFrame):
|
||||
if not self._params.camera_out_enabled:
|
||||
return
|
||||
|
||||
@@ -226,11 +228,11 @@ class BaseOutputTransport(FrameProcessor):
|
||||
self._sink_clock_task = loop.create_task(self._sink_clock_task_handler())
|
||||
|
||||
async def _sink_frame_handler(self, frame: Frame):
|
||||
if isinstance(frame, AudioRawFrame):
|
||||
if isinstance(frame, OutputAudioRawFrame):
|
||||
await self.write_raw_audio_frames(frame.audio)
|
||||
await self.push_frame(frame)
|
||||
await self.push_frame(BotSpeakingFrame(), FrameDirection.UPSTREAM)
|
||||
elif isinstance(frame, ImageRawFrame):
|
||||
elif isinstance(frame, OutputImageRawFrame):
|
||||
await self._set_camera_image(frame)
|
||||
elif isinstance(frame, SpriteFrame):
|
||||
await self._set_camera_images(frame.images)
|
||||
@@ -305,10 +307,10 @@ class BaseOutputTransport(FrameProcessor):
|
||||
# Camera out
|
||||
#
|
||||
|
||||
async def send_image(self, frame: ImageRawFrame | SpriteFrame):
|
||||
async def send_image(self, frame: OutputImageRawFrame | SpriteFrame):
|
||||
await self.process_frame(frame, FrameDirection.DOWNSTREAM)
|
||||
|
||||
async def _draw_image(self, frame: ImageRawFrame):
|
||||
async def _draw_image(self, frame: OutputImageRawFrame):
|
||||
desired_size = (self._params.camera_out_width, self._params.camera_out_height)
|
||||
|
||||
if frame.size != desired_size:
|
||||
@@ -316,14 +318,17 @@ class BaseOutputTransport(FrameProcessor):
|
||||
resized_image = image.resize(desired_size)
|
||||
logger.warning(
|
||||
f"{frame} does not have the expected size {desired_size}, resizing")
|
||||
frame = ImageRawFrame(resized_image.tobytes(), resized_image.size, resized_image.format)
|
||||
frame = OutputImageRawFrame(
|
||||
resized_image.tobytes(),
|
||||
resized_image.size,
|
||||
resized_image.format)
|
||||
|
||||
await self.write_frame_to_camera(frame)
|
||||
|
||||
async def _set_camera_image(self, image: ImageRawFrame):
|
||||
async def _set_camera_image(self, image: OutputImageRawFrame):
|
||||
self._camera_images = itertools.cycle([image])
|
||||
|
||||
async def _set_camera_images(self, images: List[ImageRawFrame]):
|
||||
async def _set_camera_images(self, images: List[OutputImageRawFrame]):
|
||||
self._camera_images = itertools.cycle(images)
|
||||
|
||||
async def _camera_out_task_handler(self):
|
||||
@@ -375,7 +380,7 @@ class BaseOutputTransport(FrameProcessor):
|
||||
# Audio out
|
||||
#
|
||||
|
||||
async def send_audio(self, frame: AudioRawFrame):
|
||||
async def send_audio(self, frame: OutputAudioRawFrame):
|
||||
await self.process_frame(frame, FrameDirection.DOWNSTREAM)
|
||||
|
||||
async def _audio_out_task_handler(self):
|
||||
|
||||
Reference in New Issue
Block a user