docs
tessl install tessl/pypi-pipecat-ai@0.0.0An open source framework for building real-time voice and multimodal conversational AI agents with support for speech-to-text, text-to-speech, LLMs, and multiple transport protocols
Frame serializers convert Pipecat frames to and from wire formats for telephony providers. Each serializer implements provider-specific protocols for audio streaming, DTMF handling, and call control over WebSocket connections.
{ .api }
from pipecat.serializers.base_serializer import FrameSerializer
class FrameSerializer(ABC):
"""Abstract base class for frame serialization implementations.
Defines the interface for converting frames to/from serialized formats
for transmission or storage. Subclasses must implement the core
serialize/deserialize methods.
Methods:
setup(frame): Initialize serializer with startup configuration
serialize(frame): Convert frame to serialized representation
deserialize(data): Convert serialized data back to frame object
Usage:
class CustomSerializer(FrameSerializer):
async def serialize(self, frame: Frame) -> str | bytes | None:
# Convert frame to wire format
pass
async def deserialize(self, data: str | bytes) -> Frame | None:
# Convert wire format to frame
pass
"""
async def setup(self, frame: StartFrame):
"""Initialize the serializer with startup configuration.
Args:
frame: StartFrame containing initialization parameters
"""
pass
@abstractmethod
async def serialize(self, frame: Frame) -> str | bytes | None:
"""Convert a frame to its serialized representation.
Args:
frame: The frame to serialize
Returns:
Serialized frame data as string, bytes, or None if serialization fails
"""
pass
@abstractmethod
async def deserialize(self, data: str | bytes) -> Frame | None:
"""Convert serialized data back to a frame object.
Args:
data: Serialized frame data as string or bytes
Returns:
Reconstructed Frame object, or None if deserialization fails
"""
pass{ .api }
from pipecat.serializers.protobuf import ProtobufFrameSerializer
class ProtobufFrameSerializer(FrameSerializer):
"""Serializer for converting Pipecat frames to/from Protocol Buffer format.
Provides efficient binary serialization for frame transport over network
connections. Supports text, audio, transcription, and message frames with
automatic conversion between transport message types.
Serializable Types:
- TextFrame: Text content
- OutputAudioRawFrame: Audio output
- TranscriptionFrame: Speech transcriptions
- OutputTransportMessageFrame/OutputTransportMessageUrgentFrame: Transport messages
Deserializable Types:
- TextFrame: Text content
- InputAudioRawFrame: Audio input
- TranscriptionFrame: Speech transcriptions
- InputTransportMessageFrame: Transport messages
Example:
serializer = ProtobufFrameSerializer()
# Serialize frame to binary
binary_data = await serializer.serialize(text_frame)
# Deserialize binary to frame
frame = await serializer.deserialize(binary_data)
"""
SERIALIZABLE_TYPES = {
TextFrame: "text",
OutputAudioRawFrame: "audio",
TranscriptionFrame: "transcription",
MessageFrame: "message",
}
DESERIALIZABLE_TYPES = {
TextFrame: "text",
InputAudioRawFrame: "audio",
TranscriptionFrame: "transcription",
MessageFrame: "message",
}
def __init__(self):
"""Initialize the Protobuf frame serializer."""
pass
async def serialize(self, frame: Frame) -> str | bytes | None:
"""Serialize a frame to Protocol Buffer binary format.
Args:
frame: The frame to serialize
Returns:
Serialized frame as bytes, or None if frame type is not serializable
"""
pass
async def deserialize(self, data: str | bytes) -> Frame | None:
"""Deserialize Protocol Buffer binary data to a frame.
Args:
data: Binary protobuf data to deserialize
Returns:
Deserialized frame instance, or None if deserialization fails
"""
pass{ .api }
from pipecat.serializers.protobuf import MessageFrame
@dataclasses.dataclass
class MessageFrame:
"""Data class for converting transport messages into Protobuf format.
Parameters:
data: JSON-encoded message data for transport
"""
data: str{ .api }
from pipecat.serializers.twilio import TwilioFrameSerializer
class TwilioFrameSerializer(FrameSerializer):
"""Serializer for Twilio Media Streams WebSocket protocol.
This serializer handles converting between Pipecat frames and Twilio's WebSocket
media streams protocol. It supports audio conversion (PCM to/from μ-law), DTMF events,
and automatic call termination.
Audio Handling:
- Input: Converts Twilio's 8kHz μ-law to PCM at pipeline sample rate
- Output: Converts PCM to 8kHz μ-law for Twilio
- Automatic resampling between rates
Features:
- Audio streaming with μ-law encoding
- DTMF tone detection
- Automatic call hangup on EndFrame/CancelFrame
- Interruption support (clear buffer)
- Regional edge support (e.g., au1, ie1)
When auto_hang_up is enabled (default), the serializer will automatically terminate
the Twilio call when an EndFrame or CancelFrame is processed, but requires Twilio
credentials to be provided.
Example:
serializer = TwilioFrameSerializer(
stream_sid="MZ123...",
call_sid="CA123...",
account_sid="AC123...",
auth_token="your-auth-token",
params=TwilioFrameSerializer.InputParams(
twilio_sample_rate=8000,
auto_hang_up=True
)
)
# Serialize audio for Twilio
data = await serializer.serialize(audio_frame)
# Deserialize Twilio media
frame = await serializer.deserialize(websocket_data)
"""
def __init__(
self,
stream_sid: str,
call_sid: Optional[str] = None,
account_sid: Optional[str] = None,
auth_token: Optional[str] = None,
region: Optional[str] = None,
edge: Optional[str] = None,
params: Optional[InputParams] = None,
):
"""Initialize the TwilioFrameSerializer.
Args:
stream_sid: The Twilio Media Stream SID
call_sid: The associated Twilio Call SID (optional, but required for auto hang-up)
account_sid: Twilio account SID (required for auto hang-up)
auth_token: Twilio auth token (required for auto hang-up)
region: Twilio region (e.g., "au1", "ie1"). Must be specified with edge
edge: Twilio edge location (e.g., "sydney", "dublin"). Must be specified with region
params: Configuration parameters
"""
pass
async def setup(self, frame: StartFrame):
"""Sets up the serializer with pipeline configuration.
Args:
frame: The StartFrame containing pipeline configuration
"""
pass
async def serialize(self, frame: Frame) -> str | bytes | None:
"""Serializes a Pipecat frame to Twilio WebSocket format.
Handles conversion of various frame types to Twilio WebSocket messages.
For EndFrames, initiates call termination if auto_hang_up is enabled.
Supported Frames:
- AudioRawFrame: Converts to Twilio media event (μ-law encoded)
- InterruptionFrame: Sends clear event to stop playback
- EndFrame/CancelFrame: Triggers call hangup (if auto_hang_up enabled)
- OutputTransportMessageFrame: Sends custom messages
Args:
frame: The Pipecat frame to serialize
Returns:
Serialized data as string or bytes, or None if the frame isn't handled
"""
pass
async def deserialize(self, data: str | bytes) -> Frame | None:
"""Deserializes Twilio WebSocket data to Pipecat frames.
Handles conversion of Twilio media events to appropriate Pipecat frames.
Twilio Events:
- media: Audio data (μ-law encoded) -> InputAudioRawFrame
- dtmf: DTMF tone -> InputDTMFFrame
Args:
data: The raw WebSocket data from Twilio
Returns:
A Pipecat frame corresponding to the Twilio event, or None if unhandled
"""
pass{ .api }
from pipecat.serializers.twilio import TwilioFrameSerializer
class TwilioFrameSerializer.InputParams(BaseModel):
"""Configuration parameters for TwilioFrameSerializer.
Parameters:
twilio_sample_rate: Sample rate used by Twilio, defaults to 8000 Hz
sample_rate: Optional override for pipeline input sample rate
auto_hang_up: Whether to automatically terminate call on EndFrame
"""
twilio_sample_rate: int = 8000
sample_rate: Optional[int] = None
auto_hang_up: bool = True{ .api }
from pipecat.serializers.telnyx import TelnyxFrameSerializer
class TelnyxFrameSerializer(FrameSerializer):
"""Serializer for Telnyx WebSocket protocol.
This serializer handles converting between Pipecat frames and Telnyx's WebSocket
media streams protocol. It supports audio conversion (PCM to/from μ-law or A-law),
DTMF events, and automatic call termination.
Audio Encoding Support:
- PCMU (μ-law): G.711 μ-law encoding
- PCMA (A-law): G.711 A-law encoding
- Automatic resampling to/from 8kHz
Features:
- Flexible audio encoding (PCMU/PCMA)
- DTMF tone detection
- Automatic call hangup on EndFrame/CancelFrame
- Interruption support (clear buffer)
Example:
serializer = TelnyxFrameSerializer(
stream_id="stream_123",
outbound_encoding="PCMU",
inbound_encoding="PCMU",
call_control_id="call_123",
api_key="your-api-key",
params=TelnyxFrameSerializer.InputParams(
telnyx_sample_rate=8000,
auto_hang_up=True
)
)
"""
def __init__(
self,
stream_id: str,
outbound_encoding: str,
inbound_encoding: str,
call_control_id: Optional[str] = None,
api_key: Optional[str] = None,
params: Optional[InputParams] = None,
):
"""Initialize the TelnyxFrameSerializer.
Args:
stream_id: The Stream ID for Telnyx
outbound_encoding: The encoding type for outbound audio (e.g., "PCMU", "PCMA")
inbound_encoding: The encoding type for inbound audio (e.g., "PCMU", "PCMA")
call_control_id: The Call Control ID for the Telnyx call (optional, but required for auto hang-up)
api_key: Your Telnyx API key (required for auto hang-up)
params: Configuration parameters
"""
pass
async def setup(self, frame: StartFrame):
"""Sets up the serializer with pipeline configuration.
Args:
frame: The StartFrame containing pipeline configuration
"""
pass
async def serialize(self, frame: Frame) -> str | bytes | None:
"""Serializes a Pipecat frame to Telnyx WebSocket format.
Handles conversion of various frame types to Telnyx WebSocket messages.
For EndFrames and CancelFrames, initiates call termination if auto_hang_up is enabled.
Supported Frames:
- AudioRawFrame: Converts to Telnyx media event (PCMU/PCMA encoded)
- InterruptionFrame: Sends clear event to stop playback
- EndFrame/CancelFrame: Triggers call hangup (if auto_hang_up enabled)
Args:
frame: The Pipecat frame to serialize
Returns:
Serialized data as string or bytes, or None if the frame isn't handled
Raises:
ValueError: If an unsupported encoding is specified
"""
pass
async def deserialize(self, data: str | bytes) -> Frame | None:
"""Deserializes Telnyx WebSocket data to Pipecat frames.
Handles conversion of Telnyx media events to appropriate Pipecat frames,
including audio data and DTMF keypresses.
Telnyx Events:
- media: Audio data (PCMU/PCMA encoded) -> InputAudioRawFrame
- dtmf: DTMF tone -> InputDTMFFrame
Args:
data: The raw WebSocket data from Telnyx
Returns:
A Pipecat frame corresponding to the Telnyx event, or None if unhandled
Raises:
ValueError: If an unsupported encoding is specified
"""
pass{ .api }
from pipecat.serializers.telnyx import TelnyxFrameSerializer
class TelnyxFrameSerializer.InputParams(BaseModel):
"""Configuration parameters for TelnyxFrameSerializer.
Parameters:
telnyx_sample_rate: Sample rate used by Telnyx, defaults to 8000 Hz
sample_rate: Optional override for pipeline input sample rate
inbound_encoding: Audio encoding for data sent to Telnyx (e.g., "PCMU", "PCMA")
outbound_encoding: Audio encoding for data received from Telnyx (e.g., "PCMU", "PCMA")
auto_hang_up: Whether to automatically terminate call on EndFrame
"""
telnyx_sample_rate: int = 8000
sample_rate: Optional[int] = None
inbound_encoding: str = "PCMU"
outbound_encoding: str = "PCMU"
auto_hang_up: bool = True{ .api }
from pipecat.serializers.vonage import VonageFrameSerializer
class VonageFrameSerializer(FrameSerializer):
"""Serializer for Vonage Video API Audio Connector WebSocket protocol.
This serializer converts between Pipecat frames and the Vonage Audio Connector
WebSocket streaming protocol. Uses 16-bit linear PCM audio format.
Audio Format:
- Default sample rate: 16kHz (configurable: 8kHz, 16kHz, 24kHz)
- Encoding: 16-bit linear PCM
- Channels: Mono
- Transport: Binary WebSocket frames for audio, JSON for events
Features:
- PCM audio streaming
- DTMF tone detection
- Interruption support (clear buffer)
- Custom message passing
Reference:
https://developer.vonage.com/en/video/guides/audio-connector
Example:
serializer = VonageFrameSerializer(
params=VonageFrameSerializer.InputParams(
vonage_sample_rate=16000,
sample_rate=16000
)
)
# Serialize audio for Vonage (returns binary PCM)
pcm_data = await serializer.serialize(audio_frame)
# Deserialize Vonage data
frame = await serializer.deserialize(websocket_data)
"""
def __init__(self, params: Optional[InputParams] = None):
"""Initialize the VonageFrameSerializer.
Args:
params: Configuration parameters
"""
pass
async def setup(self, frame: StartFrame):
"""Sets up the serializer with pipeline configuration.
Args:
frame: The StartFrame containing pipeline configuration
"""
pass
async def serialize(self, frame: Frame) -> str | bytes | None:
"""Serializes a Pipecat frame to Vonage WebSocket format.
Handles conversion of various frame types to Vonage WebSocket messages.
Supported Frames:
- AudioRawFrame: Converts to binary PCM data
- InterruptionFrame: Sends clear action (JSON)
- OutputTransportMessageFrame: Sends custom JSON messages
Args:
frame: The Pipecat frame to serialize
Returns:
Serialized data as string (JSON commands) or bytes (audio), or None if the frame isn't handled
"""
pass
async def deserialize(self, data: str | bytes) -> Frame | None:
"""Deserializes Vonage WebSocket data to Pipecat frames.
Handles conversion of Vonage events to appropriate Pipecat frames.
- Binary messages contain audio data (16-bit linear PCM)
- Text messages contain JSON events (websocket:connected, websocket:cleared, dtmf, etc.)
Vonage Events:
- Binary data: Audio (16-bit PCM) -> InputAudioRawFrame
- websocket:connected: Connection established
- websocket:cleared: Audio buffer cleared
- websocket:notify: Notification event
- websocket:dtmf: DTMF tone -> InputDTMFFrame
Args:
data: The raw WebSocket data from Vonage
Returns:
A Pipecat frame corresponding to the Vonage event, or None if unhandled
"""
pass{ .api }
from pipecat.serializers.vonage import VonageFrameSerializer
class VonageFrameSerializer.InputParams(BaseModel):
"""Configuration parameters for VonageFrameSerializer.
Parameters:
vonage_sample_rate: Sample rate used by Vonage, defaults to 16000 Hz.
Common values: 8000, 16000, 24000 Hz
sample_rate: Optional override for pipeline input sample rate
"""
vonage_sample_rate: int = 16000
sample_rate: Optional[int] = None{ .api }
from pipecat.serializers.plivo import PlivoFrameSerializer
class PlivoFrameSerializer(FrameSerializer):
"""Serializer for Plivo Audio Streaming WebSocket protocol.
This serializer handles converting between Pipecat frames and Plivo's WebSocket
audio streaming protocol. It supports audio conversion (PCM to/from μ-law), DTMF events,
and automatic call termination.
Audio Handling:
- Input: Converts Plivo's 8kHz μ-law to PCM at pipeline sample rate
- Output: Converts PCM to 8kHz μ-law for Plivo
- Automatic resampling between rates
Features:
- Audio streaming with μ-law encoding
- DTMF tone detection
- Automatic call hangup on EndFrame/CancelFrame
- Interruption support (clear audio buffer)
Example:
serializer = PlivoFrameSerializer(
stream_id="stream_123",
call_id="call_123",
auth_id="PLIVO_AUTH_ID",
auth_token="your-auth-token",
params=PlivoFrameSerializer.InputParams(
plivo_sample_rate=8000,
auto_hang_up=True
)
)
"""
def __init__(
self,
stream_id: str,
call_id: Optional[str] = None,
auth_id: Optional[str] = None,
auth_token: Optional[str] = None,
params: Optional[InputParams] = None,
):
"""Initialize the PlivoFrameSerializer.
Args:
stream_id: The Plivo Stream ID
call_id: The associated Plivo Call ID (optional, but required for auto hang-up)
auth_id: Plivo auth ID (required for auto hang-up)
auth_token: Plivo auth token (required for auto hang-up)
params: Configuration parameters
"""
pass
async def setup(self, frame: StartFrame):
"""Sets up the serializer with pipeline configuration.
Args:
frame: The StartFrame containing pipeline configuration
"""
pass
async def serialize(self, frame: Frame) -> str | bytes | None:
"""Serializes a Pipecat frame to Plivo WebSocket format.
Handles conversion of various frame types to Plivo WebSocket messages.
For EndFrames, initiates call termination if auto_hang_up is enabled.
Supported Frames:
- AudioRawFrame: Converts to Plivo playAudio event (μ-law encoded)
- InterruptionFrame: Sends clearAudio event to stop playback
- EndFrame/CancelFrame: Triggers call hangup (if auto_hang_up enabled)
- OutputTransportMessageFrame: Sends custom messages
Args:
frame: The Pipecat frame to serialize
Returns:
Serialized data as string or bytes, or None if the frame isn't handled
"""
pass
async def deserialize(self, data: str | bytes) -> Frame | None:
"""Deserializes Plivo WebSocket data to Pipecat frames.
Handles conversion of Plivo media events to appropriate Pipecat frames.
Plivo Events:
- media: Audio data (μ-law encoded) -> InputAudioRawFrame
- dtmf: DTMF tone -> InputDTMFFrame
Args:
data: The raw WebSocket data from Plivo
Returns:
A Pipecat frame corresponding to the Plivo event, or None if unhandled
"""
pass{ .api }
from pipecat.serializers.plivo import PlivoFrameSerializer
class PlivoFrameSerializer.InputParams(BaseModel):
"""Configuration parameters for PlivoFrameSerializer.
Parameters:
plivo_sample_rate: Sample rate used by Plivo, defaults to 8000 Hz
sample_rate: Optional override for pipeline input sample rate
auto_hang_up: Whether to automatically terminate call on EndFrame
"""
plivo_sample_rate: int = 8000
sample_rate: Optional[int] = None
auto_hang_up: bool = True{ .api }
from pipecat.serializers.exotel import ExotelFrameSerializer
class ExotelFrameSerializer(FrameSerializer):
"""Serializer for Exotel Media Streams WebSocket protocol.
This serializer handles converting between Pipecat frames and Exotel's WebSocket
media streams protocol. It supports PCM audio conversion, DTMF events, and automatic
call termination.
Audio Format:
- Sample rate: 8kHz (default)
- Encoding: 16-bit linear PCM (base64 encoded)
- Channels: Mono
Features:
- PCM audio streaming
- DTMF tone detection
- Interruption support (clear buffer)
- Automatic resampling
Reference:
https://support.exotel.com/support/solutions/articles/3000108630-working-with-the-stream-and-voicebot-applet
Example:
serializer = ExotelFrameSerializer(
stream_sid="stream_sid_123",
call_sid="call_sid_123",
params=ExotelFrameSerializer.InputParams(
exotel_sample_rate=8000
)
)
"""
def __init__(
self,
stream_sid: str,
call_sid: Optional[str] = None,
params: Optional[InputParams] = None
):
"""Initialize the ExotelFrameSerializer.
Args:
stream_sid: The Exotel Media Stream SID
call_sid: The associated Exotel Call SID (optional, not used in this implementation)
params: Configuration parameters
"""
pass
async def setup(self, frame: StartFrame):
"""Sets up the serializer with pipeline configuration.
Args:
frame: The StartFrame containing pipeline configuration
"""
pass
async def serialize(self, frame: Frame) -> str | bytes | None:
"""Serializes a Pipecat frame to Exotel WebSocket format.
Handles conversion of various frame types to Exotel WebSocket messages.
Supported Frames:
- AudioRawFrame: Converts to Exotel media event (base64 PCM)
- InterruptionFrame: Sends clear event to stop playback
- OutputTransportMessageFrame: Sends custom messages
Args:
frame: The Pipecat frame to serialize
Returns:
Serialized data as string or bytes, or None if the frame isn't handled
"""
pass
async def deserialize(self, data: str | bytes) -> Frame | None:
"""Deserializes Exotel WebSocket data to Pipecat frames.
Handles conversion of Exotel media events to appropriate Pipecat frames.
Exotel Events:
- media: Audio data (base64 PCM) -> InputAudioRawFrame
- dtmf: DTMF tone -> InputDTMFFrame
Args:
data: The raw WebSocket data from Exotel
Returns:
A Pipecat frame corresponding to the Exotel event, or None if unhandled
"""
pass{ .api }
from pipecat.serializers.exotel import ExotelFrameSerializer
class ExotelFrameSerializer.InputParams(BaseModel):
"""Configuration parameters for ExotelFrameSerializer.
Parameters:
exotel_sample_rate: Sample rate used by Exotel, defaults to 8000 Hz
sample_rate: Optional override for pipeline input sample rate
"""
exotel_sample_rate: int = 8000
sample_rate: Optional[int] = None{ .api }
from pipecat.serializers.twilio import TwilioFrameSerializer
from pipecat.frames.frames import AudioRawFrame
# Initialize serializer
serializer = TwilioFrameSerializer(
stream_sid="MZ123...",
call_sid="CA123...",
account_sid="AC123...",
auth_token="your-token"
)
# Setup with StartFrame
await serializer.setup(start_frame)
# Serialize audio for transmission
audio_frame = AudioRawFrame(audio=pcm_data, sample_rate=16000, num_channels=1)
websocket_data = await serializer.serialize(audio_frame)
# Deserialize incoming data
incoming_frame = await serializer.deserialize(websocket_data){ .api }
import asyncio
import websockets
from pipecat.serializers.telnyx import TelnyxFrameSerializer
async def handle_telephony_stream(websocket):
serializer = TelnyxFrameSerializer(
stream_id="stream_123",
outbound_encoding="PCMU",
inbound_encoding="PCMU"
)
# Receive and deserialize
async for message in websocket:
frame = await serializer.deserialize(message)
if frame:
await process_frame(frame)
# Send serialized frames
async def send_audio(audio_frame):
data = await serializer.serialize(audio_frame)
if data:
await websocket.send(data){ .api }
from pipecat.serializers.twilio import TwilioFrameSerializer
from pipecat.serializers.vonage import VonageFrameSerializer
from pipecat.serializers.plivo import PlivoFrameSerializer
def create_serializer(provider: str, **kwargs):
"""Factory function for creating provider-specific serializers."""
serializers = {
"twilio": TwilioFrameSerializer,
"vonage": VonageFrameSerializer,
"plivo": PlivoFrameSerializer,
}
serializer_class = serializers.get(provider)
if not serializer_class:
raise ValueError(f"Unknown provider: {provider}")
return serializer_class(**kwargs)
# Use factory
serializer = create_serializer("twilio",
stream_sid="...",
call_sid="...",
account_sid="...",
auth_token="..."
){ .api }
# Good: Let serializer handle resampling
serializer = TwilioFrameSerializer(stream_sid="...")
audio_frame = AudioRawFrame(audio=data, sample_rate=16000, num_channels=1)
await serializer.serialize(audio_frame) # Automatically resamples to 8kHz μ-law
# Bad: Manual conversion (error-prone)
# Don't manually resample or encode{ .api }
# Good: Handle serialization failures
try:
data = await serializer.serialize(frame)
if data:
await websocket.send(data)
else:
logger.warning(f"Frame type {type(frame)} not serializable")
except Exception as e:
logger.error(f"Serialization error: {e}")
# Good: Handle deserialization failures
try:
frame = await serializer.deserialize(websocket_data)
if frame:
await pipeline.queue_frame(frame)
except Exception as e:
logger.error(f"Deserialization error: {e}"){ .api }
# Good: Configure for provider requirements
# Twilio: μ-law, 8kHz
twilio_serializer = TwilioFrameSerializer(
stream_sid="...",
params=TwilioFrameSerializer.InputParams(
twilio_sample_rate=8000,
auto_hang_up=True
)
)
# Vonage: PCM, 16kHz
vonage_serializer = VonageFrameSerializer(
params=VonageFrameSerializer.InputParams(
vonage_sample_rate=16000
)
)
# Telnyx: Configurable encoding
telnyx_serializer = TelnyxFrameSerializer(
stream_id="...",
outbound_encoding="PCMU", # or "PCMA"
inbound_encoding="PCMU"
){ .api }
# Good: Enable auto-hangup for clean termination
serializer = TwilioFrameSerializer(
stream_sid="...",
call_sid="...",
account_sid="...",
auth_token="...",
params=TwilioFrameSerializer.InputParams(auto_hang_up=True)
)
# Pipeline will automatically hang up call when EndFrame is processed
await pipeline.queue_frame(EndFrame())
# Bad: Forgetting to hang up (leaves call active)
serializer = TwilioFrameSerializer(
stream_sid="...",
params=TwilioFrameSerializer.InputParams(auto_hang_up=False)
)
# Call will remain active even after pipeline ends