-
Notifications
You must be signed in to change notification settings - Fork 25
fix: opus codec issue #318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
4e0ea7b
bdaee7a
d8275db
0f281e9
fdef111
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -31,6 +31,7 @@ | |||||
| from advanced_omi_backend.services.audio_stream.producer import ( | ||||||
| get_audio_stream_producer, | ||||||
| ) | ||||||
| from advanced_omi_backend.utils.omi_codec_utils import is_opus_header_stripped | ||||||
|
|
||||||
| # Thread pool executors for audio decoding | ||||||
| _DEC_IO_EXECUTOR = concurrent.futures.ThreadPoolExecutor( | ||||||
|
|
@@ -667,6 +668,7 @@ async def _handle_omi_audio_chunk( | |||||
| audio_stream_producer, | ||||||
| opus_payload: bytes, | ||||||
| decode_packet_fn, | ||||||
| strip_header: bool, | ||||||
| user_id: str, | ||||||
| client_id: str, | ||||||
| packet_count: int, | ||||||
|
|
@@ -679,6 +681,7 @@ async def _handle_omi_audio_chunk( | |||||
| audio_stream_producer: Audio stream producer instance | ||||||
| opus_payload: Opus-encoded audio bytes | ||||||
| decode_packet_fn: Opus decoder function | ||||||
| strip_header: Whether to strip 3-byte BLE header before decoding | ||||||
| user_id: User ID | ||||||
| client_id: Client ID | ||||||
| packet_count: Current packet number for logging | ||||||
|
|
@@ -687,7 +690,7 @@ async def _handle_omi_audio_chunk( | |||||
| start_time = time.time() | ||||||
| loop = asyncio.get_running_loop() | ||||||
| pcm_data = await loop.run_in_executor( | ||||||
| _DEC_IO_EXECUTOR, decode_packet_fn, opus_payload | ||||||
| _DEC_IO_EXECUTOR, decode_packet_fn, opus_payload, strip_header | ||||||
| ) | ||||||
| decode_time = time.time() - start_time | ||||||
|
|
||||||
|
|
@@ -1031,8 +1034,7 @@ async def _handle_button_event( | |||||
| audio_uuid = client_state.current_audio_uuid | ||||||
|
|
||||||
| application_logger.info( | ||||||
| f"🔘 Button event from {client_id}: {button_state} " | ||||||
| f"(audio_uuid={audio_uuid})" | ||||||
| f"🔘 Button event from {client_id}: {button_state} (audio_uuid={audio_uuid})" | ||||||
| ) | ||||||
|
|
||||||
| # Store marker on client state for later persistence to conversation | ||||||
|
|
@@ -1353,7 +1355,7 @@ async def handle_omi_websocket( | |||||
|
|
||||||
| # OMI-specific: Setup Opus decoder | ||||||
| decoder = OmiOpusDecoder() | ||||||
| _decode_packet = partial(decoder.decode_packet, strip_header=False) | ||||||
| _decode_packet = decoder.decode_packet | ||||||
|
|
||||||
| packet_count = 0 | ||||||
| total_bytes = 0 | ||||||
|
|
@@ -1368,20 +1370,26 @@ async def handle_omi_websocket( | |||||
| ) | ||||||
| application_logger.info(f"🎙️ OMI audio session started for {client_id}") | ||||||
|
|
||||||
| audio_start_data = header.get("data", {}) | ||||||
| # Most current clients (mobile app, local wearable relay) send Opus | ||||||
| # payloads with BLE header already removed. | ||||||
| # Allow explicit override for raw BLE packet sources. | ||||||
| client_state.opus_header_stripped = is_opus_header_stripped( | ||||||
| audio_start_data | ||||||
| ) | ||||||
|
|
||||||
| interim_holder[0] = await _initialize_streaming_session( | ||||||
| client_state, | ||||||
| audio_stream_producer, | ||||||
| user.user_id, | ||||||
| user.email, | ||||||
| client_id, | ||||||
| header.get( | ||||||
| "data", | ||||||
| { | ||||||
| "rate": OMI_SAMPLE_RATE, | ||||||
| "width": OMI_SAMPLE_WIDTH, | ||||||
| "channels": OMI_CHANNELS, | ||||||
| }, | ||||||
| ), | ||||||
| audio_start_data | ||||||
| or { | ||||||
| "rate": OMI_SAMPLE_RATE, | ||||||
| "width": OMI_SAMPLE_WIDTH, | ||||||
| "channels": OMI_CHANNELS, | ||||||
| }, | ||||||
| websocket=ws, | ||||||
| ) | ||||||
|
|
||||||
|
|
@@ -1399,6 +1407,7 @@ async def handle_omi_websocket( | |||||
| audio_stream_producer, | ||||||
| payload, | ||||||
| _decode_packet, | ||||||
| not getattr(client_state, "opus_header_stripped", False), | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Default value inconsistency with The fallback here is While the protocol should guarantee 🛡️ Proposed fix to align defaults await _handle_omi_audio_chunk(
client_state,
audio_stream_producer,
payload,
_decode_packet,
- not getattr(client_state, "opus_header_stripped", False),
+ not getattr(client_state, "opus_header_stripped", True),
user.user_id,
client_id,
packet_count,
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| user.user_id, | ||||||
| client_id, | ||||||
| packet_count, | ||||||
|
|
@@ -1476,13 +1485,14 @@ async def handle_pcm_websocket( | |||||
| ) | ||||||
|
|
||||||
| # Handle audio session start (pass websocket for error handling) | ||||||
| audio_streaming, recording_mode = ( | ||||||
| await _handle_audio_session_start( | ||||||
| client_state, | ||||||
| header.get("data", {}), | ||||||
| client_id, | ||||||
| websocket=ws, | ||||||
| ) | ||||||
| ( | ||||||
| audio_streaming, | ||||||
| recording_mode, | ||||||
| ) = await _handle_audio_session_start( | ||||||
| client_state, | ||||||
| header.get("data", {}), | ||||||
| client_id, | ||||||
| websocket=ws, | ||||||
| ) | ||||||
|
|
||||||
| # Initialize streaming session | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| """Helpers for OMI Opus payload metadata handling.""" | ||
|
|
||
| from typing import Any | ||
|
|
||
|
|
||
| def is_opus_header_stripped(audio_start_data: dict[str, Any] | None) -> bool: | ||
| """ | ||
| Determine whether incoming OMI Opus payloads already have BLE header removed. | ||
|
|
||
| Defaults to True because current mobile and relay clients send header-stripped | ||
| payload bytes. Raw BLE packet sources can override with | ||
| ``opus_header_stripped: false``. | ||
| """ | ||
| if not audio_start_data: | ||
| return True | ||
|
|
||
| value = audio_start_data.get("opus_header_stripped", True) | ||
|
|
||
| if isinstance(value, str): | ||
| normalized = value.strip().lower() | ||
| if normalized in {"false", "0", "no", "off"}: | ||
| return False | ||
| if normalized in {"true", "1", "yes", "on"}: | ||
| return True | ||
|
|
||
| return bool(value) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import pytest | ||
|
|
||
| from advanced_omi_backend.utils.omi_codec_utils import is_opus_header_stripped | ||
|
|
||
|
|
||
| @pytest.mark.unit | ||
| def test_defaults_to_header_stripped_when_metadata_missing(): | ||
| assert is_opus_header_stripped(None) is True | ||
| assert is_opus_header_stripped({}) is True | ||
|
|
||
|
|
||
| @pytest.mark.unit | ||
| def test_respects_explicit_boolean_flag(): | ||
| assert is_opus_header_stripped({"opus_header_stripped": True}) is True | ||
| assert is_opus_header_stripped({"opus_header_stripped": False}) is False | ||
|
|
||
|
|
||
| @pytest.mark.unit | ||
| @pytest.mark.parametrize( | ||
| ("value", "expected"), | ||
| [ | ||
| ("true", True), | ||
| ("TRUE", True), | ||
| ("1", True), | ||
| ("yes", True), | ||
| ("on", True), | ||
| ("false", False), | ||
| ("FALSE", False), | ||
| ("0", False), | ||
| ("no", False), | ||
| ("off", False), | ||
| ], | ||
| ) | ||
| def test_handles_string_flags(value, expected): | ||
| assert is_opus_header_stripped({"opus_header_stripped": value}) is expected |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate variable declaration will cause compilation error.
Lines 66 and 68 both declare
const isAdvanced. This is a syntax error in TypeScript/JavaScript — you cannot redeclare aconstvariable in the same scope.🐛 Proposed fix: Remove the duplicate declaration
if (/[?&]codec=/i.test(url)) { url = url.replace(/([?&])codec=[^&]*/i, '$1codec=opus'); } else { const sep = url.includes('?') ? '&' : '?'; url = url + sep + 'codec=opus'; } - const isAdvanced = settings.jwtToken && settings.isAuthenticated; - const isAdvanced = settings.jwtToken && settings.isAuthenticated; if (isAdvanced) {📝 Committable suggestion
🧰 Tools
🪛 Biome (2.4.4)
[error] 68-68: Shouldn't redeclare 'isAdvanced'. Consider to delete it or rename it.
(lint/suspicious/noRedeclare)
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks legit? @0xrushi