.
This commit is contained in:
@@ -17,7 +17,22 @@ LOGGER = logging.getLogger("uvicorn.error")
|
||||
AUDIO_SOCKET_PACKET_HANGUP = 0x00
|
||||
AUDIO_SOCKET_PACKET_UUID = 0x01
|
||||
AUDIO_SOCKET_PACKET_DTMF = 0x03
|
||||
AUDIO_SOCKET_PACKET_PCM16 = 0x10
|
||||
_AUDIO_SOCKET_PCM16_PACKET_TYPES_BY_SAMPLE_RATE_HZ: dict[int, int] = {
|
||||
8000: 0x10,
|
||||
12000: 0x11,
|
||||
16000: 0x12,
|
||||
24000: 0x13,
|
||||
32000: 0x14,
|
||||
44100: 0x15,
|
||||
48000: 0x16,
|
||||
96000: 0x17,
|
||||
192000: 0x18,
|
||||
}
|
||||
_AUDIO_SOCKET_SAMPLE_RATE_HZ_BY_PCM16_PACKET_TYPE: dict[int, int] = {
|
||||
packet_type: sample_rate_hz
|
||||
for sample_rate_hz, packet_type in _AUDIO_SOCKET_PCM16_PACKET_TYPES_BY_SAMPLE_RATE_HZ.items()
|
||||
}
|
||||
SUPPORTED_AUDIO_SOCKET_SAMPLE_RATES_HZ = frozenset(_AUDIO_SOCKET_PCM16_PACKET_TYPES_BY_SAMPLE_RATE_HZ)
|
||||
|
||||
|
||||
def _float_env(name: str, default: float) -> float:
|
||||
@@ -43,8 +58,20 @@ def encode_packet(packet_type: int, payload: bytes = b"") -> bytes:
|
||||
return struct.pack("!BH", packet_type & 0xFF, len(payload)) + payload
|
||||
|
||||
|
||||
def encode_audio_packet(pcm_bytes: bytes) -> bytes:
|
||||
return encode_packet(AUDIO_SOCKET_PACKET_PCM16, pcm_bytes)
|
||||
def pcm16_packet_type_for_sample_rate(sample_rate_hz: int) -> int:
|
||||
try:
|
||||
return _AUDIO_SOCKET_PCM16_PACKET_TYPES_BY_SAMPLE_RATE_HZ[int(sample_rate_hz)]
|
||||
except KeyError as exc:
|
||||
supported = ", ".join(str(rate) for rate in sorted(SUPPORTED_AUDIO_SOCKET_SAMPLE_RATES_HZ))
|
||||
raise ValueError(f"unsupported AudioSocket PCM16 sample rate {sample_rate_hz}; supported: {supported}") from exc
|
||||
|
||||
|
||||
def sample_rate_for_pcm16_packet_type(packet_type: int) -> int | None:
|
||||
return _AUDIO_SOCKET_SAMPLE_RATE_HZ_BY_PCM16_PACKET_TYPE.get(packet_type)
|
||||
|
||||
|
||||
def encode_audio_packet(pcm_bytes: bytes, *, sample_rate_hz: int = 16000) -> bytes:
|
||||
return encode_packet(pcm16_packet_type_for_sample_rate(sample_rate_hz), pcm_bytes)
|
||||
|
||||
|
||||
async def read_packet(
|
||||
@@ -78,6 +105,7 @@ class AudioSocketTransport(BaseMediaTransport):
|
||||
)
|
||||
self._reader = reader
|
||||
self._writer = writer
|
||||
self._pcm_packet_type = pcm16_packet_type_for_sample_rate(self.sample_rate_hz)
|
||||
self._read_timeout_seconds = max(read_timeout_seconds, 1.0)
|
||||
self._closed = False
|
||||
self._rx_audio_packet_count = 0
|
||||
@@ -166,7 +194,20 @@ class AudioSocketTransport(BaseMediaTransport):
|
||||
self._rx_audio_bytes,
|
||||
)
|
||||
return None
|
||||
if packet_type == AUDIO_SOCKET_PACKET_PCM16:
|
||||
packet_sample_rate_hz = sample_rate_for_pcm16_packet_type(packet_type)
|
||||
if packet_sample_rate_hz is not None:
|
||||
if packet_sample_rate_hz != self.sample_rate_hz:
|
||||
LOGGER.error(
|
||||
"AudioSocket sample rate mismatch: session=%s expected_sample_rate=%s expected_packet_type=0x%02x "
|
||||
"received_sample_rate=%s received_packet_type=0x%02x payload_bytes=%s",
|
||||
self.transport_id,
|
||||
self.sample_rate_hz,
|
||||
self._pcm_packet_type,
|
||||
packet_sample_rate_hz,
|
||||
packet_type,
|
||||
len(payload),
|
||||
)
|
||||
return None
|
||||
self._rx_audio_packet_count += 1
|
||||
self._rx_audio_bytes += len(payload)
|
||||
self._track_rx_audio_level(payload)
|
||||
@@ -186,7 +227,7 @@ class AudioSocketTransport(BaseMediaTransport):
|
||||
continue
|
||||
self._rx_ignored_packet_count += 1
|
||||
LOGGER.warning(
|
||||
"AudioSocket unknown packet ignored: session=%s packet_type=%s payload_bytes=%s",
|
||||
"AudioSocket unknown packet ignored: session=%s packet_type=0x%02x payload_bytes=%s",
|
||||
self.transport_id,
|
||||
packet_type,
|
||||
len(payload),
|
||||
@@ -196,7 +237,7 @@ class AudioSocketTransport(BaseMediaTransport):
|
||||
async def _send_frame(self, frame: bytes) -> None:
|
||||
if self._closed or self._writer.is_closing():
|
||||
return
|
||||
self._writer.write(encode_audio_packet(frame))
|
||||
self._writer.write(encode_audio_packet(frame, sample_rate_hz=self.sample_rate_hz))
|
||||
await self._writer.drain()
|
||||
|
||||
async def close(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user