feat: support Grundig/Philips (GR/PH) DSS/DS2 containers#2
Merged
Conversation
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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds support for Grundig/Philips ("GR/PH") DSS/DS2 containers to the
conversion chain. These recorders (header tag
GR/PH9607, e.g. the GrundigDigta — 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 holding0xFF-paddedGR___device-id records before the audio. The.dssside alreadyworked this way upstream (
header = version * 512); DS2 had simply hard-codedversion 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 the0x600metadata header,resets the version byte to 3, drops the
GR___records and concatenates theaudio. Wired into all three decode paths:
src/lib/core.mjs(native chain) — normalized temp file beforedss-decode-nativesrc/lib/core-wasm.mjs(WASM chain) — normalized buffer beforedecode()src/bin/conv-dss-ds2-to-mp3(bash CLI) —maybe_normalize+get_encryptionnow reports plain (notunknown)Proof
Native decode of the raw GR/PH
.ds2, sample-for-sample vs the licensedOlympus decoder (NCH Switch):
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
docs/11-the-grundig-philips-variant.md— the discovery write-up.ffmpeg-upstream/05-grundig-philips-container.md— the matchinglibavformat/ds2.cchange (header_size = first_byte * 512) for the upstream demuxer, pending a FATE sample.