Wires the SSTV decoder into the stack, from the audio the server already
has to a panel in the browser that shows the picture arriving.
Server: a decoder task alongside the WEFAX one, running whenever the
decoder is enabled and the rig is in a mode SSTV is sent in. A finished
picture is written to the cache as a PNG and sent on as a message; the
rows are sent as they decode, so a client can watch two minutes of
Martin M1 fill in rather than waiting for it. Pictures join the decode
history, are replayed to a client that connects later, and survive a
restart.
Protocol: SetSstvDecodeEnabled and ResetSstvDecoder, a sstv_decode
_enabled flag in the rig state, two audio message types, and Sstv and
SstvProgress on DecodedMessage. The history stores the message without
its base64 payload -- the picture is already on disk, and a megabyte per
entry is not what a history is for.
Client: pictures land in their own history, and the PNG the server sent
is written to the local cache so /sstv-images/ can serve it back. That
endpoint and the WEFAX one now share their filename checks rather than
each carrying a copy: no separators, no parent references, .png only.
Web UI: an SSTV sub-tab beside WEFAX, with a live canvas the rows paint
into at the line number they carry, a card for the last picture, and a
filterable history with links to the files. Rows below the one arriving
are grey rather than black -- not yet received is a different thing from
received as black. A picture is not a spot, so neither pictures nor
their progress updates reach the decode statistics; that exclusion list
had grown by hand for LRPT and WEFAX and is now one named set.
The decoder crate gains what the server needed to hand a picture on:
to_png, to_png_base64 and save_png, with file names stamped in UTC so
they sort.
Panel behaviour is tested with the plugin runtime: rows painting at
their own line numbers rather than in arrival order, a completed picture
linked by file name alone with no server path in the page, a cut-off
picture reported as partial, clearing, and the toggle following the rig
state.
Signed-off-by: Stan Grams <sjg@haxx.space>
A new decoder crate covering the modes SSTV is actually sent in: Martin
M1/M2, Scottie S1/S2/DX, Robot 36/72, PD50 through PD290, and Wraase
SC2-180. The mode comes from the VIS header every transmission opens
with, so nothing has to be told what is arriving.
Modes are a table rather than code: a list of segments -- sync, gaps,
and one scan per colour channel -- plus a colour model and a geometry.
The decoder reads the offset of each scan straight off that list, which
is what makes fifteen modes cost about as much as one, and a new mode a
table entry. The segment lists are checked against the published line
durations in a test, because both are transcribed by hand from the same
specification and a digit wrong in one is unlikely to be wrong
identically in the other.
Signal path: band-pass over the SSTV band, Hilbert FIR, instantaneous
frequency by phase difference, then a state machine that walks the
transmission a line at a time. Each line is looked for where the mode
says it should be and nudged into place by the sync pulse found near
it -- two sound cards never agree exactly, and over the two minutes of a
Martin M1 frame an uncorrected error of a few parts per million shears
the picture visibly. Rows are emitted as they decode, so a picture can
be watched arriving, which is most of the appeal of the mode.
Four things this cost, each now the reason a piece of it is shaped the
way it is:
The per-sample frequency estimate ripples by ±95 Hz at 1200 Hz, where
the Hilbert approximation is weakest, though its mean is exact. Pixels
average over their own window and were always right; the VIS bits and
the sync detector classify individual samples and were reading the
ripple. Both now read short means. Pixels deliberately still do not,
so edges stay where they are.
Broadband noise cost the whole picture, not part of it: a
phase-difference detector answers whatever is loudest, and there was no
input filter. Hence the band-pass, which is what every real decoder
does first.
A sync search window shorter than a sync pulse rejected every pulse
arriving late in it, for being short.
The first line's sync search locked onto the VIS stop bit -- 30 ms at
exactly the sync frequency, immediately before the picture starts. The
header already says where the picture begins, so the first line no
longer searches.
Tests: nine modes are encoded from a test card and decoded back,
compared pixel by pixel, alongside silence around the signal, a
transmission cut off part way, two transmissions back to back, 20 dB of
noise, and a transmitter clock 0.1% fast. The encoder that produces
those signals reads the same table as the decoder, so a round trip
tests the decoder and not the timings; the timings are held to the
published line durations separately.
Nothing is wired into the server or the web UI yet: this is the decoder
alone.
Signed-off-by: Stan Grams <sjg@haxx.space>