mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Pass OPUS payload ID through VoIP (#92421)
This commit is contained in:
parent
a9d8bc989e
commit
aa78962a9a
@ -52,7 +52,11 @@ def make_protocol(
|
|||||||
or (pipeline.tts_engine is None)
|
or (pipeline.tts_engine is None)
|
||||||
):
|
):
|
||||||
# Play pre-recorded message instead of failing
|
# Play pre-recorded message instead of failing
|
||||||
return PreRecordMessageProtocol(hass, "problem.pcm")
|
return PreRecordMessageProtocol(
|
||||||
|
hass,
|
||||||
|
"problem.pcm",
|
||||||
|
opus_payload_type=call_info.opus_payload_type,
|
||||||
|
)
|
||||||
|
|
||||||
# Pipeline is properly configured
|
# Pipeline is properly configured
|
||||||
return PipelineRtpDatagramProtocol(
|
return PipelineRtpDatagramProtocol(
|
||||||
@ -60,6 +64,7 @@ def make_protocol(
|
|||||||
hass.config.language,
|
hass.config.language,
|
||||||
voip_device,
|
voip_device,
|
||||||
Context(user_id=devices.config_entry.data["user"]),
|
Context(user_id=devices.config_entry.data["user"]),
|
||||||
|
opus_payload_type=call_info.opus_payload_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -79,7 +84,9 @@ class HassVoipDatagramProtocol(VoipDatagramProtocol):
|
|||||||
hass, devices, call_info
|
hass, devices, call_info
|
||||||
),
|
),
|
||||||
invalid_protocol_factory=lambda call_info: PreRecordMessageProtocol(
|
invalid_protocol_factory=lambda call_info: PreRecordMessageProtocol(
|
||||||
hass, "not_configured.pcm"
|
hass,
|
||||||
|
"not_configured.pcm",
|
||||||
|
opus_payload_type=call_info.opus_payload_type,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
@ -109,6 +116,7 @@ class PipelineRtpDatagramProtocol(RtpDatagramProtocol):
|
|||||||
language: str,
|
language: str,
|
||||||
voip_device: VoIPDevice,
|
voip_device: VoIPDevice,
|
||||||
context: Context,
|
context: Context,
|
||||||
|
opus_payload_type: int,
|
||||||
pipeline_timeout: float = 30.0,
|
pipeline_timeout: float = 30.0,
|
||||||
audio_timeout: float = 2.0,
|
audio_timeout: float = 2.0,
|
||||||
buffered_chunks_before_speech: int = 100,
|
buffered_chunks_before_speech: int = 100,
|
||||||
@ -119,7 +127,12 @@ class PipelineRtpDatagramProtocol(RtpDatagramProtocol):
|
|||||||
tts_extra_timeout: float = 1.0,
|
tts_extra_timeout: float = 1.0,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up pipeline RTP server."""
|
"""Set up pipeline RTP server."""
|
||||||
super().__init__(rate=RATE, width=WIDTH, channels=CHANNELS)
|
super().__init__(
|
||||||
|
rate=RATE,
|
||||||
|
width=WIDTH,
|
||||||
|
channels=CHANNELS,
|
||||||
|
opus_payload_type=opus_payload_type,
|
||||||
|
)
|
||||||
|
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.language = language
|
self.language = language
|
||||||
@ -350,9 +363,7 @@ class PipelineRtpDatagramProtocol(RtpDatagramProtocol):
|
|||||||
|
|
||||||
async with async_timeout.timeout(tts_seconds + self.tts_extra_timeout):
|
async with async_timeout.timeout(tts_seconds + self.tts_extra_timeout):
|
||||||
# Assume TTS audio is 16Khz 16-bit mono
|
# Assume TTS audio is 16Khz 16-bit mono
|
||||||
await self.hass.async_add_executor_job(
|
await self._async_send_audio(audio_bytes)
|
||||||
partial(self.send_audio, audio_bytes, **RTP_AUDIO_SETTINGS)
|
|
||||||
)
|
|
||||||
except asyncio.TimeoutError as err:
|
except asyncio.TimeoutError as err:
|
||||||
_LOGGER.warning("TTS timeout")
|
_LOGGER.warning("TTS timeout")
|
||||||
raise err
|
raise err
|
||||||
@ -360,6 +371,12 @@ class PipelineRtpDatagramProtocol(RtpDatagramProtocol):
|
|||||||
# Signal pipeline to restart
|
# Signal pipeline to restart
|
||||||
self._tts_done.set()
|
self._tts_done.set()
|
||||||
|
|
||||||
|
async def _async_send_audio(self, audio_bytes: bytes, **kwargs):
|
||||||
|
"""Send audio in executor."""
|
||||||
|
await self.hass.async_add_executor_job(
|
||||||
|
partial(self.send_audio, audio_bytes, **RTP_AUDIO_SETTINGS, **kwargs)
|
||||||
|
)
|
||||||
|
|
||||||
async def _play_listening_tone(self) -> None:
|
async def _play_listening_tone(self) -> None:
|
||||||
"""Play a tone to indicate that Home Assistant is listening."""
|
"""Play a tone to indicate that Home Assistant is listening."""
|
||||||
if self._tone_bytes is None:
|
if self._tone_bytes is None:
|
||||||
@ -369,13 +386,9 @@ class PipelineRtpDatagramProtocol(RtpDatagramProtocol):
|
|||||||
"tone.pcm",
|
"tone.pcm",
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.hass.async_add_executor_job(
|
await self._async_send_audio(
|
||||||
partial(
|
self._tone_bytes,
|
||||||
self.send_audio,
|
silence_before=self.tone_delay,
|
||||||
self._tone_bytes,
|
|
||||||
silence_before=self.tone_delay,
|
|
||||||
**RTP_AUDIO_SETTINGS,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _play_processing_tone(self) -> None:
|
async def _play_processing_tone(self) -> None:
|
||||||
@ -387,13 +400,7 @@ class PipelineRtpDatagramProtocol(RtpDatagramProtocol):
|
|||||||
"processing.pcm",
|
"processing.pcm",
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.hass.async_add_executor_job(
|
await self._async_send_audio(self._processing_bytes)
|
||||||
partial(
|
|
||||||
self.send_audio,
|
|
||||||
self._processing_bytes,
|
|
||||||
**RTP_AUDIO_SETTINGS,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _play_error_tone(self) -> None:
|
async def _play_error_tone(self) -> None:
|
||||||
"""Play a tone to indicate a pipeline error occurred."""
|
"""Play a tone to indicate a pipeline error occurred."""
|
||||||
@ -404,13 +411,7 @@ class PipelineRtpDatagramProtocol(RtpDatagramProtocol):
|
|||||||
"error.pcm",
|
"error.pcm",
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.hass.async_add_executor_job(
|
await self._async_send_audio(self._error_bytes)
|
||||||
partial(
|
|
||||||
self.send_audio,
|
|
||||||
self._error_bytes,
|
|
||||||
**RTP_AUDIO_SETTINGS,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _load_pcm(self, file_name: str) -> bytes:
|
def _load_pcm(self, file_name: str) -> bytes:
|
||||||
"""Load raw audio (16Khz, 16-bit mono)."""
|
"""Load raw audio (16Khz, 16-bit mono)."""
|
||||||
@ -424,11 +425,17 @@ class PreRecordMessageProtocol(RtpDatagramProtocol):
|
|||||||
self,
|
self,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
file_name: str,
|
file_name: str,
|
||||||
|
opus_payload_type: int,
|
||||||
message_delay: float = 1.0,
|
message_delay: float = 1.0,
|
||||||
loop_delay: float = 2.0,
|
loop_delay: float = 2.0,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up RTP server."""
|
"""Set up RTP server."""
|
||||||
super().__init__(rate=RATE, width=WIDTH, channels=CHANNELS)
|
super().__init__(
|
||||||
|
rate=RATE,
|
||||||
|
width=WIDTH,
|
||||||
|
channels=CHANNELS,
|
||||||
|
opus_payload_type=opus_payload_type,
|
||||||
|
)
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.file_name = file_name
|
self.file_name = file_name
|
||||||
self.message_delay = message_delay
|
self.message_delay = message_delay
|
||||||
|
@ -4,6 +4,7 @@ import time
|
|||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
import async_timeout
|
import async_timeout
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import assist_pipeline, voip
|
from homeassistant.components import assist_pipeline, voip
|
||||||
from homeassistant.components.voip.devices import VoIPDevice
|
from homeassistant.components.voip.devices import VoIPDevice
|
||||||
@ -88,6 +89,7 @@ async def test_pipeline(
|
|||||||
hass.config.language,
|
hass.config.language,
|
||||||
voip_device,
|
voip_device,
|
||||||
Context(),
|
Context(),
|
||||||
|
opus_payload_type=123,
|
||||||
listening_tone_enabled=False,
|
listening_tone_enabled=False,
|
||||||
processing_tone_enabled=False,
|
processing_tone_enabled=False,
|
||||||
error_tone_enabled=False,
|
error_tone_enabled=False,
|
||||||
@ -138,6 +140,7 @@ async def test_pipeline_timeout(hass: HomeAssistant, voip_device: VoIPDevice) ->
|
|||||||
hass.config.language,
|
hass.config.language,
|
||||||
voip_device,
|
voip_device,
|
||||||
Context(),
|
Context(),
|
||||||
|
opus_payload_type=123,
|
||||||
pipeline_timeout=0.001,
|
pipeline_timeout=0.001,
|
||||||
listening_tone_enabled=False,
|
listening_tone_enabled=False,
|
||||||
processing_tone_enabled=False,
|
processing_tone_enabled=False,
|
||||||
@ -178,6 +181,7 @@ async def test_stt_stream_timeout(hass: HomeAssistant, voip_device: VoIPDevice)
|
|||||||
hass.config.language,
|
hass.config.language,
|
||||||
voip_device,
|
voip_device,
|
||||||
Context(),
|
Context(),
|
||||||
|
opus_payload_type=123,
|
||||||
audio_timeout=0.001,
|
audio_timeout=0.001,
|
||||||
listening_tone_enabled=False,
|
listening_tone_enabled=False,
|
||||||
processing_tone_enabled=False,
|
processing_tone_enabled=False,
|
||||||
@ -247,6 +251,14 @@ async def test_tts_timeout(
|
|||||||
# Block here to force a timeout in _send_tts
|
# Block here to force a timeout in _send_tts
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
|
async def async_send_audio(audio_bytes, **kwargs):
|
||||||
|
if audio_bytes == tone_bytes:
|
||||||
|
# Not TTS
|
||||||
|
return
|
||||||
|
|
||||||
|
# Block here to force a timeout in _send_tts
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
async def async_get_media_source_audio(
|
async def async_get_media_source_audio(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
media_source_id: str,
|
media_source_id: str,
|
||||||
@ -269,6 +281,8 @@ async def test_tts_timeout(
|
|||||||
hass.config.language,
|
hass.config.language,
|
||||||
voip_device,
|
voip_device,
|
||||||
Context(),
|
Context(),
|
||||||
|
opus_payload_type=123,
|
||||||
|
tts_extra_timeout=0.001,
|
||||||
listening_tone_enabled=True,
|
listening_tone_enabled=True,
|
||||||
processing_tone_enabled=True,
|
processing_tone_enabled=True,
|
||||||
error_tone_enabled=True,
|
error_tone_enabled=True,
|
||||||
@ -277,13 +291,18 @@ async def test_tts_timeout(
|
|||||||
rtp_protocol._processing_bytes = tone_bytes
|
rtp_protocol._processing_bytes = tone_bytes
|
||||||
rtp_protocol._error_bytes = tone_bytes
|
rtp_protocol._error_bytes = tone_bytes
|
||||||
rtp_protocol.transport = Mock()
|
rtp_protocol.transport = Mock()
|
||||||
rtp_protocol.send_audio = Mock(side_effect=send_audio)
|
rtp_protocol.send_audio = Mock()
|
||||||
|
|
||||||
|
original_send_tts = rtp_protocol._send_tts
|
||||||
|
|
||||||
async def send_tts(*args, **kwargs):
|
async def send_tts(*args, **kwargs):
|
||||||
# Call original then end test successfully
|
# Call original then end test successfully
|
||||||
rtp_protocol._send_tts(*args, **kwargs)
|
with pytest.raises(asyncio.TimeoutError):
|
||||||
|
await original_send_tts(*args, **kwargs)
|
||||||
|
|
||||||
done.set()
|
done.set()
|
||||||
|
|
||||||
|
rtp_protocol._async_send_audio = AsyncMock(side_effect=async_send_audio)
|
||||||
rtp_protocol._send_tts = AsyncMock(side_effect=send_tts)
|
rtp_protocol._send_tts = AsyncMock(side_effect=send_tts)
|
||||||
|
|
||||||
# silence
|
# silence
|
||||||
|
Loading…
x
Reference in New Issue
Block a user