Files
DS5Dongle-OLED-Edition-stea…/scripts/decode_opus_dump.py
MarcelineVPQandClaude Opus 4.8 84393c00e8 wip: park audio-crackle investigation + haptic/OLED defaults + debug tooling
Preserves in-progress work that had been sitting uncommitted in the working
tree (predates this session) so it is protected in git history. This is NOT a
verified/shipped fix — it is parked. Audio path is unverified on hardware and
still carries debug instrumentation.

- src/audio.cpp: drop the 512->480 resampler and retime the 0x36 frame to a
  true 10ms/100Hz grid (SAMPLE_SIZE 64->60, 480-sample buffer) to chase the
  ~45kHz-vs-48kHz speaker underrun theory behind the crackle; dynamic speaker
  sub-report offset; debug printf + Opus-frame-dump instrumentation.
- src/config.cpp: default audio_buffer_length 64->16; auto_haptics_enable
  default 1 (Fallback) -> 0 (Off).
- src/oled.cpp: chunked non-blocking SPI flush (issue #7 OLED-stall angle) and
  a battery-% midpoint display tweak.
- scripts/: audio debug helpers (opus-dump decode, pi audio test, sine ch1/2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 19:11:39 -06:00

74 lines
2.2 KiB
Python

#!/usr/bin/env python3
"""Decode raw Opus frames captured from firmware serial output.
Reads [OPUS_FRAME_N] hex lines from stdin or a file, decodes each frame
with libopus, and writes the result as a WAV file + prints a summary
(peak amplitude, zero-crossing rate) to diagnose encoder output quality.
"""
import ctypes
import struct
import sys
import wave
import re
lib = ctypes.cdll.LoadLibrary("libopus.so.0")
SAMPLE_RATE = 48000
CHANNELS = 2
FRAME_SIZE = 480 # 10ms at 48kHz
# Create decoder
err = ctypes.c_int(0)
decoder = lib.opus_decoder_create(SAMPLE_RATE, CHANNELS, ctypes.byref(err))
if err.value != 0:
print(f"opus_decoder_create failed: {err.value}", file=sys.stderr)
sys.exit(1)
infile = sys.argv[1] if len(sys.argv) > 1 else "/tmp/ds5_opus.log"
with open(infile) as f:
lines = f.readlines()
frames = []
for line in lines:
m = re.match(r'\[OPUS_FRAME_\d+\]\s+([0-9a-fA-F]+)', line.strip())
if m:
frames.append(bytes.fromhex(m.group(1)))
if not frames:
print("No [OPUS_FRAME_N] lines found in input.", file=sys.stderr)
sys.exit(1)
print(f"Found {len(frames)} Opus frames, decoding...")
all_pcm = b""
for i, frame_data in enumerate(frames):
pcm = (ctypes.c_int16 * (FRAME_SIZE * CHANNELS))()
ret = lib.opus_decode(
decoder,
frame_data, len(frame_data),
pcm, FRAME_SIZE,
0 # no FEC
)
if ret < 0:
errstr = lib.opus_strerror(ret)
print(f" Frame {i+1}: DECODE ERROR {ret} ({ctypes.string_at(errstr).decode()})")
continue
samples = list(pcm)
peak = max(abs(s) for s in samples)
nonzero = sum(1 for s in samples if s != 0)
print(f" Frame {i+1}: {ret} samples decoded, peak={peak}, nonzero={nonzero}/{len(samples)}")
print(f" TOC=0x{frame_data[0]:02x} first 8 bytes: {frame_data[:8].hex()}")
all_pcm += struct.pack(f"<{ret * CHANNELS}h", *samples[:ret * CHANNELS])
outpath = "/tmp/ds5_opus_decoded.wav"
with wave.open(outpath, "w") as wf:
wf.setnchannels(CHANNELS)
wf.setsampwidth(2)
wf.setframerate(SAMPLE_RATE)
wf.writeframes(all_pcm)
print(f"\nDecoded audio written to {outpath}")
print(f"Play with: aplay {outpath}")
print(f"View spectrogram: sox {outpath} -n spectrogram -o /tmp/ds5_opus_spectrogram.png")