Skip to content

feat: support Grundig/Philips (GR/PH) DSS/DS2 containers#2

Merged
Guillain-RDCDE merged 1 commit into
mainfrom
feat/grundig-philips-container
Jun 6, 2026
Merged

feat: support Grundig/Philips (GR/PH) DSS/DS2 containers#2
Guillain-RDCDE merged 1 commit into
mainfrom
feat/grundig-philips-container

Conversation

@Guillain-RDCDE
Copy link
Copy Markdown
Owner

@Guillain-RDCDE Guillain-RDCDE commented Jun 6, 2026

What

Adds support for Grundig/Philips ("GR/PH") DSS/DS2 containers to the
conversion chain. These recorders (header tag GR/PH9607, e.g. the Grundig
Digta — reported in hirparak/dss-codec#11)
write ordinary DS2-QP / DSS-SP audio but frame it differently, so the decoder
rejected real files with unsupported DS2 format type: 6/7.

The insight

The first byte is the header size in 512-byte blocks. Olympus .ds2 = 3
(0x600); this recorder = 7 (0xe00), with the extra blocks holding
0xFF-padded GR___ device-id records before the audio. The .dss side already
worked this way upstream (header = version * 512); DS2 had simply hard-coded
version 3. Same recorder's .dss = 6 (0xc00) → that's exactly issue #11.

The change

No codec change — the CELP frames are standard. A container normalizer
(src/lib/grph.mjs) keeps the 0x600 metadata header,
resets the version byte to 3, drops the GR___ records and concatenates the
audio. Wired into all three decode paths:

  • src/lib/core.mjs (native chain) — normalized temp file before dss-decode-native
  • src/lib/core-wasm.mjs (WASM chain) — normalized buffer before decode()
  • src/bin/conv-dss-ds2-to-mp3 (bash CLI) — maybe_normalize + get_encryption now reports plain (not unknown)

Proof

Native decode of the raw GR/PH .ds2, sample-for-sample vs the licensed
Olympus decoder (NCH Switch):

metric value
samples 1 958 144 / 1 958 144
correlation 1.000000
SNR vs Switch 68.8 dB

The JS normalizer output is byte-identical (cmp) to a hand-built transcode.
WASM path: corr 1.0. Bash CLI → 64 kbps MP3: corr 0.998 (lossy). All existing
behavior on Olympus files is unchanged (normalizer returns null for version 2/3).

Docs / ffmpeg

Note: correct decoding of paused GR/PH QP recordings also relies on the
empty-block / byte1 re-anchoring fix (docs/07), same as paused Olympus QP files.

Grundig/Philips recorders (header tag GR/PH9607, e.g. the Grundig Digta --
hirparak/dss-codec#11) write standard DS2-QP / DSS-SP audio but with a
larger header: the first byte is the header size in 512-byte blocks (7 for
.ds2, 6 for .dss, vs Olympus 2/3), and the extra blocks hold GR___-tagged
device-id records before the audio. The decoder rejected these as
"unsupported DS2 format type: 6/7" (the value is the unexpected first byte).

Normalize the container to the standard Olympus layout in front of the
decoder (new src/lib/grph.mjs), wired into all three paths: native
(core.mjs), WASM (core-wasm.mjs) and the bash CLI. --inspect / get_encryption
now report these as plain instead of unknown/encrypted.

Verified bit-exact vs the licensed Olympus decoder (NCH Switch) on a real
DS2-QP dictation: corr 1.000000, 68.8 dB, identical sample count. The JS
normalizer output is byte-identical to a hand-built transcode.

docs/11 documents the variant; ffmpeg-upstream/05 carries the matching
libavformat/ds2.c change (header_size = first_byte * 512) for the upstream
demuxer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Guillain-RDCDE Guillain-RDCDE merged commit de0d77c into main Jun 6, 2026
2 checks passed
@Guillain-RDCDE Guillain-RDCDE deleted the feat/grundig-philips-container branch June 6, 2026 12:13
Guillain-RDCDE added a commit that referenced this pull request Jun 6, 2026
Grundig/Philips recorders (header tag GR/PH9607, e.g. the Grundig Digta --
hirparak/dss-codec#11) write standard DS2-QP / DSS-SP audio but with a
larger header: the first byte is the header size in 512-byte blocks (7 for
.ds2, 6 for .dss, vs Olympus 2/3), and the extra blocks hold GR___-tagged
device-id records before the audio. The decoder rejected these as
"unsupported DS2 format type: 6/7" (the value is the unexpected first byte).

Normalize the container to the standard Olympus layout in front of the
decoder (new src/lib/grph.mjs), wired into all three paths: native
(core.mjs), WASM (core-wasm.mjs) and the bash CLI. --inspect / get_encryption
now report these as plain instead of unknown/encrypted.

Verified bit-exact vs the licensed Olympus decoder (NCH Switch) on a real
DS2-QP dictation: corr 1.000000, 68.8 dB, identical sample count. The JS
normalizer output is byte-identical to a hand-built transcode.

docs/11 documents the variant; ffmpeg-upstream/05 carries the matching
libavformat/ds2.c change (header_size = first_byte * 512) for the upstream
demuxer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant