Archive for March, 2026

(Ir)regular NihAV news

Monday, March 30th, 2026

Here’s another portion of news about what I’ve been doing in last couple of weeks. Hopefully I’ll be able to write about fruity MVS soon.

While most of what I did is not worth mentioning, there are some bits I find funny or curious enough to share.

First of all, I’ve added VAXL and (Clari)SSA decoding support to na_eofdec (just some more and there will be enough formats for 0.1.0 release). And I actually added them mostly to test an IFF parser (derived from my AVI parser and enhanced). The idea is simple: just point it to the data needed to be parsed and provide chunk handlers and it should do the trick. The main enhancement is also providing a condition when to stop—at the end, on any unknown chunk, before some pre-defined chunk, or right after such chunk has been parsed. This way I can control parsing process without relying too much on some special callbacks or states. For example, in VAXL that means I parse header by expecting VXHD chunk and nothing else. The rest of VAXL consists of time code, palette, image data, and audio samples chunks; there I simply stop after BMAP chunk and output reconstructed frame (and audio if present). In SSA (IFF variant, I added FEE one too for completeness sake) each frame is wrapped into FORM DLTA list so there I just parse sub-chunks ignoring unknown ones.

Then I’ve finally had another look at TCA and improved my decoder, going from less than half of samples I had being supported to virtually all of them. While I presumed that video is always compressed with LZW, in reality it also has RLE and raw modes. RLE mode is rather curious at that as it codes run+mode as 00 00 xx yy zz, 00 xx yy or xx number (i.e. if first one or two bytes are zero, read one or two bytes of the number too) with low bit signalling run or skip and the rest being its length (e.g. 55 means skip 42 bytes while 00 01 00 01 means repeating 01 128 times). But since the format is next to impossible to detect statically (no magic constants in the header, so you’d better check packet structure instead), it’ll remain a useless curiosity—just like I love it.

And most of the time I spent on writing MOV muxer. Since I’ve written one for na_eofdec already (raw output only) I had some base to start off at least. Of course it’s wonky and even copying data may or may not work depending on your luck, at least now I have a format to output variable framerate content losslessly (previously the only alternative for VFR content in NihAV was to encode data with RealVideo 4 and Cook). At least now, after all possible enhancements, I can either re-mux old-style MOV into a new one (and the output may actually be playable) or use nihav-encoder -i input.mov -o output.mov --profile raw to decode some more exotic format not supported by anything else (like MoviePak in MacBinary MOV) to a raw format that (hopefully) more tools can understand. I can also use my Cinepak or Indeo 3 encoders there (and Truemotion 1 too in the future just for lulz) and I should probably write encoders for other common QT codecs (even if there are other implementations, up to SVQ1 by The Multimedia Mike). This should keep me entertained for a while. And who knows, maybe it will serve as a base for MP4 muxer if the need for one arises.

That’s it for now, hopefully I’ll have more to write about later.

Some words about RAD Audio

Saturday, March 21st, 2026

So I tried to look at RAD Audio. The main problem is that its binary specification is unclear (i.e. decompilers have problems with it) and it’s not something easily approachable without one.

Anyway, from what I managed to figure out, it’s more or less equivalent to AAC-LC except being less extensible (and thus slightly saner).

While AAC-LC gets packed into some container format with excessive features (even something like ADTS), here packet header combines all information: first byte is packet header byte (0x55) followed by a byte or two of packet flags (i.e. what features are enabled and if following number fields are 8- or 16-bit values), packet size (8- or 16-bit), two other numbers with unknown meaning (also 8- or 16-bit each, they might be related to the frame coding parameters like number of subbands in use).

Frame structure from a quick glance looks very AAC-LC as well, with active use of Huffman codebooks for coding most of the data (initially I found just two codebooks, apparently the actual number is closer to twenty). And there are tables describing coding modes for four principal sampling frequencies (24/32/44/48 kHz) and two coding modes (normal with mixed short and long frames, and short frames only mode; normal mode is remarkable for having a special frequency table with the same number of bands as for short frame but designed for a long frame—I suppose it’s for the transition frames).

Overall, it’s a rather simple audio codec in a rather simple audio format (and for that reason I’m not interested to look at it further—figuring out details is tedious and serves no real purpose for me). And for the next thing, there’s apparently some fruity codec used in proprietary VNC protocol extension that’s worth looking at…

More boring stuff in NihAV

Thursday, March 12th, 2026

Since the last time I’ve done some more boring stuff for NihAV, more precisely 10-bit H.264 decoder and extended MOV support.

The former is more or less straightforward but it made me regret Rust lack of text-level macro processing. The problem is generating almost identical code for various combinations of bitdepths that differ only by the clipping range and function name suffixes. Probably it can be solved with proc macros but that’s a hole I’m unwilling to jump into. Also supporting both 8- and 16-bit decoding in one module is annoying, so I ended up just putting them next to each other and selecting one for the current mode of operation. This also uncovered the fact that my scaler did not handle high-depth YUV in the most cases, so I had to fix it too. And the ironic thing is that I don’t have enough content to watch in that format (and a couple of sample files I have are Matroska with FLAC audio, which can’t be remuxed to MP4, so there’s that).

In other rather equally useless things, I’ve finally made a MOV muxer for na_eofdec. Of course it’s a simplified one, supporting only raw video and audio tracks, but even that was annoying to support properly (I’m still not sure if I do support it properly). At least when the bitterness passes I should be able to make a MOV muxer for NihAV (and who knows, maybe even implement some QT encoders to exercise it).

But that’s not all regarding MOV. In a recent post I mentioned the oldest known MOV flavours. Finally I’ve managed to extend my MOV demuxer to support beta version of it (it’s still MOV but with a few different details here and there) while alpha version was easier to support in a different demuxer written from scratch. If you’re curious, those files are short clips (about 15 seconds usually) compressed with QT RLE demonstrating some features of upcoming System 7 in rather symbolic form. Here’s a GIF of virtual memory feature presentation.

That’s about it for now, I hope to do more exciting things next. For instance, add some Amiga formats to na_eofdec. And most importantly, Paul told me about new RAD Audio codec which I still have to look at thoroughly. From a first glance while audio in Bink is a lot like MPEG Audio Layer II (i.e. simply quantise and write coefficients with a fixed amount of bits), this new codec is more akin to AAC LC as it apparently has long/short frames division and (at least two) Huffman tables for coefficients. Another change is that Bink audio was stored in simple container formats with fixed structure, new format looks more like an elementary stream with as few bits as possible spent on coding different fields. Let’s see how it turns out…

A word about some JPEG-based codecs

Wednesday, March 4th, 2026

As I mentioned previously, I don’t want to work on game codecs for a while, so I picked other stuff instead. For instance, I’ve fixed a bug in my Indeo 3 encoder (which nobody uses, myself included), refactored some NihAV code and added a couple of decoders.

First, there is PDQ2 decoder. It actually turned out to be slightly more interesting than I expected as there’s a version of the codec for Saturn version of the game (it’s not that different yet somewhat different nevertheless).

Then I’ve finally managed to figure out MoviePak. After locating a decompressor binary and trying to disassemble it as raw M68k code I could locate enough code and data to figure out that since it uses JPEG codebooks it’s likely to be based on JPEG. And after learning a bit more about how it works and NOPing out A-line _StripAddress calls (that was the most interesting part actually—you can’t find it without knowing terminology and even then just barely; I was lucky to try searching for information about 68881, learning terminology and finally finding a reference of Macintosh Toolbox calls) I managed to make Ghidra happy enough to produce a usable decompilation. After that it was just a question of re-using JPEG decoder with a slightly different header format, which finally made me do a refactoring of JPEG decoding.

And since I’ve factored out common JPEG decoding support for MoviePak, Radius Studio and actual motion JPEG decoder, I decided to add a video codec for effects used in proDAD Adorage video editor (its FOURCC is pDAD, quite surprisingly). This one could be reverse engineered just by looking at the frame data. Each frame consists of 20-byte header, JPEG image plus alpha channel coded as PNG or JPEG. Since I have not bothered to write a motion PNG decoder (it’s very uncommon after all and I’m not NIHing image library—at least not yet), I decode just the first part. Maybe I’ll add a PNG decoder support and re-visit this decoder for proper alpha support, but for now this seems unlikely.

I’ll keep working on some boring stuff nobody cares about but maybe I’ll have a codec or two to write about as well.