refactor: formaliza formatos de áudio e alinhamento PCM
This commit is contained in:
@@ -1,58 +1,36 @@
|
||||
"""
|
||||
Canonical audio models for the Voice Assistant.
|
||||
"""PCM format contracts shared by application, transports, and providers.
|
||||
|
||||
Purpose:
|
||||
Define the provider-independent audio format used internally by
|
||||
the Voice Core.
|
||||
|
||||
Responsibilities:
|
||||
- Describe an audio format in a typed structure.
|
||||
- Define the canonical audio format used by the Voice Core.
|
||||
- Provide a shared audio contract for transports and providers.
|
||||
|
||||
Non-responsibilities:
|
||||
- Encoding or decoding audio.
|
||||
- Resampling audio.
|
||||
- Converting provider-specific audio formats.
|
||||
- Managing WebSocket audio frames.
|
||||
- Performing STT or TTS operations.
|
||||
|
||||
Architecture:
|
||||
Client / Transport
|
||||
|
|
||||
v
|
||||
Canonical Audio
|
||||
PCM S16LE / 16 kHz / mono
|
||||
|
|
||||
v
|
||||
Voice Core
|
||||
|
|
||||
+-----+-----+
|
||||
| |
|
||||
v v
|
||||
STT TTS
|
||||
Adapter Adapter
|
||||
|
||||
Provider-specific format conversion, when required, belongs
|
||||
inside the corresponding provider adapter.
|
||||
The canonical input is mono PCM S16LE at 16 kHz. Output uses the format
|
||||
explicitly declared by the TTS provider. Declaring a sample rate does not
|
||||
convert or resample audio; provider adapters own any required conversion.
|
||||
"""
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class AudioFormat(BaseModel):
|
||||
"""
|
||||
Describe one PCM audio format.
|
||||
"""An immutable PCM format; a frame contains one sample per channel."""
|
||||
|
||||
This model is independent from WebSocket, STT, TTS, or any
|
||||
concrete provider.
|
||||
"""
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
encoding: Literal["pcm_s16le"]
|
||||
sample_rate: int
|
||||
channels: int
|
||||
sample_rate: int = Field(gt=0, strict=True)
|
||||
channels: int = Field(gt=0, strict=True)
|
||||
|
||||
@property
|
||||
def frame_size_bytes(self) -> int:
|
||||
return 2 * self.channels
|
||||
|
||||
def validate_chunk(self, audio: bytes) -> None:
|
||||
if not audio:
|
||||
raise ValueError("Audio chunk cannot be empty.")
|
||||
if len(audio) % self.frame_size_bytes:
|
||||
raise ValueError(
|
||||
"PCM chunk must contain complete frames of "
|
||||
f"{self.frame_size_bytes} bytes."
|
||||
)
|
||||
|
||||
|
||||
CANONICAL_AUDIO_FORMAT = AudioFormat(
|
||||
@@ -60,4 +38,3 @@ CANONICAL_AUDIO_FORMAT = AudioFormat(
|
||||
sample_rate=16000,
|
||||
channels=1,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user