A tale of three formats

April 19th, 2025

Since I have nothing better to do, I keep looking at the odd formats here and there and occasionally do something about them. Here are three formats I took a look at recently and that have nothing in common beside being video codecs.

CGDI

This is a “capture” codec (aka Camcorder Video) if not for the fact that it records rather events than actual image data. I had a suspicion right from the start that it’s organised in the same way as WMF/EMF—opcodes with parameters that invoke GDI subsystem to draw actual image—and finally I’ve decided to check that.

Of course it turned out to be true. There are about 64 opcodes in total, some are for drawing things, some are for window management stuff, and some are for GDI state management (e.g. create or delete brush, pen, font and such).

Since implementing a decoder for it would mean replicating a good deal of Windows graphics subsystem (even if you can borrow code from Wine), I consider it completely impractical and merely improved codec documentation in The Wiki.

TCA

This is an animation format used on Acorn platform. Actually it has three layers of encapsulation: there’s raw TCA format that contains only the header and video frames, then there’s TCA wrapped in ACEF chunk with an optional SOUN chunk following it (no points for guessing what it contains), and finally there’s that format put inside ARMovie (as a single block).

I added its support to NihAV just for completeness sake. Of course not all of different flavours are supported (video is mostly just plain LZW but it has some alternative coding mode and an uncompressed alternative, audio is IMA ADPCM but sometimes it’s not without any reliable way to distinguish which is which). And looks like some animations may have variable frame rate (with DIR1 subchunk likely telling frame durations). All the details are there, in raw ARM binaries and semi-compiled BBC BASIC code, but I’m satisfied that it works at least for a couple of random plane samples I tried and have no desire to try supporting every known sample in existence.

Savage Warriors ANM

This one is a curious format. I’ve managed to locate decoding functions in one of the overlay files, it looked reasonable (LZ77 compression for intra frames and something a lot like FLI delta frame compression for the rest) but the decoder did not work properly. Curiously, demo version contains some of the same animations as the full game but in slightly different format (the initial magic is missing); after comparing them I found out that the release version uses a weird format with a 32-bit value inserted after each kilobyte of data. I ended up implementing my own buffered reader that loads those kilobyte blocks and skips those additional words for the release version.

Another thing is that LZ-compressed format had 17-byte header which the decoder skipped. Of course it made me suspect of being a third-party compression scheme, and after searching around it turned out to be Diet (you may remember it being used as an executable compressor but apparently it had other uses). It somewhat reminded me of MidiVid Lossless as it is yet another codec reusing third-party general compressor (with special preprocessing for executables, which was a dead giveaway).

In either case, both flavours of this ANM format are now supported by na_game_tool (and will be the part of the next release).

NihAV: even further encoder improvements

April 9th, 2025

Since the last time I wrote about it somebody actually decided to test my transcoder on a bunch of fringe formats. The main issue there was wrong audio output (since I didn’t bother to check the actual input format, it often passed e.g. floating-point audio as 16-bit PCM, resulting in complete garbage). Additionally I’ve fixed a stupid bug in Indeo IVF demuxer (so now both known samples are decoded fine) as well as improving support for some other corner cases.

And there was one fun failure only my transcoder could have: when video streams starts with skip frame(s) and ZMBV encoder was selected, it refused to encode. I’ve added a work-around so now it simply presumes that frame to be black and goes forth normally (if you encode into raw video, such initial frames would be skipped instead).

So whatever can be decoded seems to be decoded and re-encoded just fine (but there are probably more cases waiting for their turn to be discovered). And there are some more formats waiting for an open-source decoder (in case of Motion Pixels the wait is likely to be very long).

na_game_tool: new season start

April 6th, 2025

It’s been some time since na_game_tool release and I’ve finally started working on adding new formats for it again (for v0.4 the goal is to have a hundred of supported formats including a dozen of fresh ones, currently it has only about eighty). And since newly added formats are somewhat remarkable, I decided to talk about them.

Spidy Ani from Imperium Galactica. The name sounded vaguely familiar and indeed I’ve encountered it before. I did not bother to support old sprite format since they are just animations for which you need to know the background image and palette. Version 2 contains both palette and (optionally) audio, making its support much more reasonable. As for the compression, it actually keeps the old RLE method and introduces a new one (where low values mean copy length instead of a literal) plus it adds optional LZSS compression. The best part is that it’s not been reverse engineered by somebody else but it has been documented (in open-ig wiki) as well.

Shadowcaster cutscenes. This is a format used by a game published by Origin, it turned out to be RLE optionally further compressed by LZSS (I shan’t blame you if you see some similarities here). The main difference is that the format contains commands for e.g. playing an external XMI track (or stop playing it). And instead of one palette it can store several and switch between them arbitrarily.

Castles 2 (CD version) greyscale video. This one is very special: the game has colour cutscenes in Interplay MVE format (usually with castles) but beside that it apparently has some greyscale animations lifted from public domain films. The files have .m extension and start with “BYON PIZZA” string, definitely not hinting at Byon Garrabrant, who was the lead programmer of that game.

It was extremely hard to read the binary specification for it as the executable stored that code and data in executable overlay. That means that it was in a special part of executable that gets loaded on demand, thus lacking proper references to the data and even functions. Luckily it turned out that I didn’t need it at all.

One of the sequences turned out to be just one black (or white) frame. Seeing that the frame was exactly 1/4th of raw image and filled with 0x40, I decided to calculate a histogram of some other (larger) frame. It turned out that indeed if you output values in 0x00-0x3F range as is and repeat 0x40-0x7F values four times you get exactly full image. Then by looking at the reconstructed image I understood that it codes data in 2×2 blocks (and in inverse mode so 0x00 means white and 0x3F means black). After that figuring out that codes 0x80-0xFF in subsequent frames are used to signal skips was trivial.

That was fun. Hopefully I’ll encounter more interesting formats in the future.

P.S. I’ve also added PI-Video decoding support to NihAV. The codec was used at least in one multimedia encyclopaedia and did some non-conventional things with LZW coding. I’m still not sure that it was a good idea because codec developer may be russian but since I could not find any information about him (or the companies involved beside them being Western European), I’m giving it the benefit of the doubt.

My Rust experience after eight years

March 29th, 2025

Soon it will be eight years since I’ve (re)started NihAV development in Rust. And for this round date I’d like to present my impressions and thoughts on the language—from the perspective of applicability in my experimental multimedia framework.

Read the rest of this entry »

A look on Perfect Clarity Audio

March 28th, 2025

Since Paul asked me to look at it (and what’s equally important, provided the binary to look at), I did exactly that.

It turned out to be a rather simple codec, originally known as Sonic Foundry Lossless. The only really interesting detail is that it has intra- and inter-frames. Intraframes start with 0xFFFFFFFF and four last samples for each channel (for filtering purposes). Each frame stores the number of samples (I suppose), coded data size, Rice code parameter (per channel), filter method/order (ditto), LPC coefficients (same, only if global flags enable it though) and probably CRC.

Data is coded as fixed-parameter Rice codes (low bit is used for the sign)—unless it’s filter method 5 which means all zeroes, then optional LPC phase (LPC has order four and 8-bit coefficients) and then, depending on filter order, fixed prediction as in Shorten.

Finally there may be mid/stereo reconstruction (if global config signals it) and clipping for 24-bit mode (but not for 16-bit apparently).

I don’t know if they’ve added some improvements in the newer versions but this looks rather simple compared to other lossless codecs. There’s the final bit of weirdness: it’s usually stored in WAV which means it’s one of those codecs with variable-size frames in WAV. Not something I’d like to support.

P.S. I’ll document it for The Wiki (as well as DVC from the previous post) a bit later.

Revisiting QPEG

March 27th, 2025

Since I’ve done all improvements to NihAV that I wanted to do (beside vague “improve Indeo 3 encoder somehow” or “add some interesting format support”), I decided to look at the formats in my backlog and discovered that I have a QPEG player with a couple of DVC samples. Considering that I’ve REd their VfW codec over two decades ago, I had to look at this as well.

It turned out to be a straightforward format with static palette and video frames packed with moderately complex RLE (it has opcodes for run/copy/skip and literals). The most interesting thing there to me was that values without high bit set are treated as literals, or rather as indices in the remapping table (which is the first 128 bytes of a frame). Considering that low 20 colours of the palette seem to be unset, it makes some sense.

The hardest part was to read the binary specification. The executable uses Phar Lap 386 extender, so it’s actually stored in P3 format right after the loader. At least I have some experience with loading such formats when I messed with Linear eXecutable format before Ghidra plugins were available (and sometimes afterward as well, since e.g. neither of two known plugins managed to load Wing Nuts executable). Also I managed to spot that 0xE0 byte happens at the end of packed frame, so I guessed it was the end data marker and searched for the code using it as such. I’ve managed to locate four RLE decompression functions, all probably functionally identical, and after figuring out other details (like where remap table comes from) I ended up with the decoder that works on all four known samples just fine.

Overall, it’s nothing particularly complex but it was still nice to look at.

NihAV: encoder improvements

March 21st, 2025

Since I feel I’ve done enough (for a moment) on na_game_tool, I decided to work on the rather (always) neglected NihAV. And specifically on the nihav-encoder tool.

As you can guess from the name, it is a tool for re-encoding input file(s) into something else (or something the same). Since I don’t have much need in transcoding anything, I use it occasionally to test encoders (especially for collecting information by running encoding with different parameters on the selected sample set) or a muxer. Essentially if video is encoded correctly that’s enough, audio de-sync or even drop-outs (because not all encoded packets were actually muxed) is another thing. Finally I’ve decided to improve the situation and make it more usable.
Read the rest of this entry »

A bit of class theory

March 15th, 2025

There is a Ukrainian word “жлоб” (zhlob) with unknown origin, but probably coming from Polish żłób via Yiddish (and likely donating that meaning back, since in Ukrainian it means only a person while in Polish it’s not a main meaning and does not connect to it).

It is hard to give a proper translation for that word. It may mean a man large as ox, strong as ox, and with comparable intellect too. One dictionary claims it’s a synonym for a wealthy peasant who employs others. More often though it means a vulgar person with very limited intellectual needs and a miser. Kinda like an average person but without positive traits and with deficiencies magnified. I’m pretty sure that you can recognise such characters even without a strict definition.

And there’s Ukrainian living classic Les’ Poderv’iansky, often known simply as The Artist (he started his career as a painter but got much more famous as a writer and poet; he has given us many catchphrases, the best-formulated version of Ukrainian national idea and I’ve heard people quote his adaptation of Hamlet in full—people who would not normally read classics or memorise poetry).

Once he formulated the main reason why Karl Marx was wrong: people differ not by classes but rather by mindset. So poor zhlob shares interests with the rich zhlob and they can understand each other better (despite being a worker and factory-owner) than, say, a teacher.

Here’s an excerpt from his essay (published in the essay collection “Жлобологія” pp. 245-250 in 2013 if you care):

It would be nice to turn things around so zhlobs can’t get power. Unfortunately that’s utopia.

Sensible people, who are sadly too few, don’t have any influence, so zhlob ideology prospers. The prevalent class is zhlob, who is preserving his values. If this continues, this country will be completely fucked.

That’s because zhlobs are not fit for anything good but like awards nevertheless. […] They require proofs of own importance. They like various tchotchkes, posts and titles. Their hidden dream is to become counts and dukes.

Of course it had been written in Ukraine back in the day before people revolted and threw away one certain zhlob that other zhlobs voted for president, but the principles remain the same and can be applied to other countries. So if you ever wondered why rednecks vote for a hereditary billionaire believing him to be “our guy”, while hating another candidate despite her being “closer” to them—now you know the answer.

NihAV: hardware-accelerated playback revisited

March 10th, 2025

Recently I’ve made a mistake of upgrading one of my laptops to systemd 24.04. I can talk about various quality of life improvements there (like brightness control not working any longer so I have to evoke xrandr instead) but it’s so useless rant that it does not deserve to be written. What is worth talking about is hardware acceleration. Previously my player on that laptop had rather unreliable playback with VA-API decoding (i.e. it worked but some videos made it too laggy); now the situation has improved—it’s reliably unusable. Additionally it seems to consume only slightly less CPU than with software decoding. So I finally looked at the way to speed it up (spoiler alert: and failed).

Conceptually it’s simple: after you decode VA-API picture you need to obtain its internal parameters with vaExportSurfaceHandle(), create an EGL picture using eglCreateImage() (or eglCreateImageKHR()), blit it onto OpenGL texture with glEGLImageTargetTexture2DOES() and you’re done. It was not so simple in practice though.

Exporting descriptors is easy, I just modified my fork of VA-API wrapper to do that and it even seemed to produce correct output. The problems started with OpenGL integration. My player uses SDL2 so I spent a lot of time trying to make it work. First of all, it’s not clear how to obtain a proper OpenGL context for the calls, then there’s this problem of it being finicky and not liking multi-threaded execution. And of course you have to load all those functions mentioned above manually (because SDL2 offers only a small subset of all possible OpenGL functions—not surprising, considering how many of those are optionally supported extensions or missing in certain profiles).

Anyway, I threw away most of my player functionality, leaving just loading an input file, decoding it and trying to display only the first frame. It ended with a segfault.

It is probably because of (poorly documented) SDL2 wrapper doing, but it can’t provide a sane OpenGL context probably. So a call to eglGetCurrentDisplay() returns either NULL or a pointer that looks like a small negative value; the same happens with eglCreateImage() (fun thing that eglGetError() returns the same value, -424 if somebody is curious); at glEGLImageTargetTexture2DOES() call it finally segfaults.

At that point I actually tried searching for some alternative crates that would allow me to create an OpenGL window and call those functions—and found none fitting the purpose. They all are either provide rather limited OpenGL subset which is enough for drawing a triangle with shaders (and probably expect you to locate and load the library with the additional functions by yourself) or even simply provide OpenGL bindings, leaving even window creation to you.

In other words, not my kind of fun. I’ll simply disable hardware acceleration for all cases there until I find a better alternative.

P.S. Vulkan is not really an option either. My hardware is too old for ANV driver (and for HASVK driver too).

Random NihAV news

March 8th, 2025

Since I can’t do anything about the world largest countries run by over 70-year old dictators, I try to find a distraction elsewhere. It usually works only until the next time I read next. Anyway, here’s something different for a change.

With all the work on na_game_tool I’ve been mostly neglecting the original NihAV (not that it makes much difference). So I’ve tried to improve it a bit and I have something to report.

First of all, I decided to make ARMovie support more complete (after discovering that e.g. the only thing that can play Eidos Escape 122 codec is their own DOS player). So I’ve added all three video codecs with known samples (Escape 122, 124 and 130) as well as their ADPCM codec. Sadly Escape 122 description in The Wiki is somewhat unclear, so I referred to the original DOS player for it. Similarly I looked at ADPCM decoding because what libavcodec produces is not quite right (but I guess Paul can tell you more stories about all those IMA ADPCM flavours). So I guess now my project has the most complete ARMovie formats support out there. There’s only one other third-party format I’m aware of (The Complete Animation film, it may be stand-alone or encapsulated in RPL) and I might get to it eventually.

The other thing is TrueMotion S. When you think there’s nothing unknown left about it, it manages to surprise you anyway. So I was looking at Discmaster search results for potential candidates (“aviAudio” is a good format for that—sometimes it is really AVI with audio track only, sometimes it is AVI with unknown video codec) and found three .duk files that were TM1 in AVI and which could not be decoded. It turned out to use codebook 0—which is not present in the open-sourced version of the decoder. Another thing is that it does not use so-called fat deltas (i.e. larger difference values), so code zero means eight zeroes instead of an escape value. Remarkably, this is demo of The Horde game by Toys For Bob, known to employ yet another exotic version of the codec in their 3DO version of Star Control II. It makes me wonder if they had the same relationship with Duck as JVC NWC with RAD (you know, the company which released a game with Bink-b videos not found anywhere else and bundling Bink-d decoder with one of their other games—all while others started with Bink-f or later).

Hopefully I’ll be able to do more in the future, but I wanted to share these stories while they’re still fresh.