NihAV: progress report

Well, since I had no incentive to work on NihAV and recently the weather is not very encouraging for any kind of intellectual activity there was almost no progress. And yet now I have something to write about: NihAV has finally managed to decode non-trivial (i.e not fully black) RealVideo 3 I-frame properly (i.e. without any visible distortions). Loop filter is still missing but it’s a start. And it’s not a small feat considering one has to implement both coefficients decoding and intra prediction. So essentially it’s just motion vector juggling and motion compensation are all the things that are missing for P- and B-frames support. Maybe it will go faster from here (but most likely not).

And since doing that involved rewriting some C code into Rust here are some notes on how oxidising went:

  • match is a nice replacement for the cases when you have to partly remap values—in my case I had to adjust intra prediction directions in case top or left or bottom reference were missing and that means changing three or four values into other values, match looks more compact than several } else if (itype == FOO) { and does not lose readability either;
  • while in C foo = bar = 42; is a common thing, Rust does not allow this (I can understand why) and I’m surprised I ran into it only now (with intra prediction functions that assign the same calculated value to several output pixels at once);
  • loops in Rust are fine for basic use but when you need to deal with something more complex like for (i = 0; i < block_size; i += 4) or for (i = 99; i > 0; i--) you need either to write a simpler loop and remap indices inside or to remember it's Rust and permute range in less intuitive ways like for i in (0..block_size).filter(|x| x&3 == 0) and for i in (1..99+1).rev(). While this works and even somewhat conveys the meaning it's a bit unwieldy IMO;
  • and it might be a bit too esoteric but looks like I cannot easily write fn clip_u8(val: N) -> u8 that would take any primitive numeric type as input, do comparisons inside and return value either clipped to converted to u8. The best answer on how to do it I found was "you can't, it's against Rust practices". I don't need it much and I care even less, so I'll just mark it as a neutral language feature and forget about it.

And now the small but constantly irritating thing: arrays. While slices are nice and easy to use (including extracting sub-slices), in my area I often need a slice with arbitrary start and end bounds. To clarify my use case: quite often you need a piece of memory that's addressable with both positive and negative indices and those make sense on certain interval.

One of such common arrays is clipping array which essentially takes input index and returns it clipped usually to 0-255 range. So you have part [-255..-1] filled with zeroes, [0..255] filled with values in the same range and [256..511] filled with 255. I repeat, such clipping table is very common and useful thing that's currently not easy to implement in Rust.

Another less common case is the block of pixels we process may require information from its top, left and top-left neighbours—and those are addressed as src[-stride + i], src[-1 + stride*i] and src[-stride - 1]. Or a whole frame of GDI-related codec (no, not from Westwood) or even simple BMP/DIB that stores lines upside-down so after you process line 0 you have to move to line -1.

I currently deal with it by keeping an additional variable pointing to the current position in array that I use as a reference and from which I can subtract other numbers if needed, but it's a bit clunky and error-prone. Since Rust checks indices on slice access I wonder if extending it to work with e.g. negative indices is possible. IIRC FORTRAN and Pascal allowed you to define an array starting with arbitrary index, it might be possible in Rust too.

Oh well, I'll just keep using my approach meanwhile and waiting to see what rust-av does in this regard.

13 Responses to “NihAV: progress report”

  1. Luca Barbato says:

    For opus I’m doing exactly the way you are doing, e.g.:

    let previous_w = self.output[start – order..stop].windows(order);
    let iter = self.output[start..stop]
    .iter()
    .zip(residuals[start_res..stop_res].iter_mut());
    for ((&o, r), p_w) in iter.zip(previous_w) {
    let mut sum = o;

    for (&c, &p) in lpc_coeff.iter().zip(p_w.iter().rev()) {
    sum -= c * p;
    }

    *r = sum.max(-1f32).min(1f32) * scale / sf.gain;
    }

    (yes I should use variable names that have a meaning for more people than me alone)

  2. MoSal says:

    Answers to some of your concerns. Mostly obvious stuff. But the clip_u8() example might interest you:

    https://play.rust-lang.org/?gist=702bf11a9014c6d768c891d6416afdb3&version=nightly

  3. Kostya says:

    Interesting, thanks.

    Unfortunately num_traits is an external crate so I shan’t use it. But I’m looking forward to be able to use .step_by() in stable.

  4. MoSal says:

    I understand where you’re coming from regarding external crates. But with Rust, I learned not to shy away from using them, especially the major ones.

    num-traits has hundreds of published crates directly depending on it. And it is one of the crates re-exported by the meta crate num, a major crate that also has hundreds of crates depending on it. One of the owners of num-* is a member of Rust’s core and library teams.

    One can always write his own mini num_traits module. Macros make that easy. But it would really be a duplicated effort.

    A fully generic clamp to type function in case you are still interested:
    https://play.rust-lang.org/?gist=3a2cc2fdf22eef8602589679a5c91b1e

  5. Kostya says:

    Well, this project is not going to use external crates by the very definition but I’ll remember num-traits for other projects I may end up writing or if it will join std in some form.

    In any case I usually need clipping function that takes i16 or i32 and outputs u8, though if I end up doing template functions for codecs that deal with both 8- and 16-bit depth video (H.264, H.265, VPx and such) I might seriously rethink it. But maybe the situation with num-traits will change then too.

  6. Danno says:

    I don’t really understand why avoiding external crates is desired.

    PS: There is an issue for making the for loop a little easier to express. Eventually you should be able to write for i in (0..100).step_by(4) {}

    Seems like it’s maybe a few releases out from stable, I think?

  7. Kostya says:

    Because it’s a NIH project which is right there in its name.

    And yes, the previous commenter pointed out that .step_by() exists in the nightly already. No idea when it gets into the stable, that might take a year or two even. I’m in no hurry though.

  8. Havvy says:

    Even if it’s NIH, you should still consider using libraries for the stuff you don’t want to NIH. Building _everything_ from scratch means you’re spending less time on the parts you do want to reinvent, no? Also, the `num-traits` crate should basically be considered proto-core, especially with nobody putting in the effort to create a better numerics tower for the standard library.

  9. Kostya says:

    It’s a grey area then. I don’t want to reinvent anything in the language core and standard library and num-traits does not officially belong to it. And as I said in one of the previous comments I may reconsider shall the need arise (i.e. if I need something more complex than clipping function).

  10. aaa says:

    >allowed you to define an array starting with arbitrary index, it might be possible in Rust too.

    You can do this by implementing Index (or isize) trait for your type: https://doc.rust-lang.org/std/ops/trait.Index.html

    >I cannot easily write fn clip_u8(val: N) -> u

    As was written on reddit in addition to using num crates you can write your own ClipInteger trait and implement for i* and u* types manually or with macro. This will allow you to write: fn clip_u8(val: T) -> u8

    >for i in (1..99+1).rev()

    BTW alternatively now you can write `for i in (1..=100).rev()`

  11. Kostya says:

    > You can do this by implementing Index (or isize) trait for your type

    I suspected as much but that’s one of the things I’d rather not reinvent.

    > you can write your own ClipInteger trait and implement for i* and u* types manually or with macro

    True, but then it’d be either duplicated code or somewhat ugly macro containing a function definition (or trait implementation).

    > BTW alternatively now you can write `for i in (1..=100).rev()`

    Oh, nice. Thanks for the information!

  12. aaa says:

    >I suspected as much but that’s one of the things I’d rather not reinvent.

    Your use-case is quite niche, so you’ll have to express what you want to do, Rust provides tools for that. I don’t think it’s reasonable to expect that Rust and stdlib should support various niche cases out there out of the box.

    >True, but then it’d be either duplicated code or somewhat ugly macro containing a function definition (or trait implementation).

    If you take NIH to extremes and don’t want to rely on external crates, then it’s your choice to write duplicated code. The good thing that this code will be self-contained with minimal maintenance burden.

  13. Kostya says:

    Well, if I get annoyed enough I’ll add my own slice type then. And the idea is not to duplicate the same code inside my project much, if there’s the same code outside it’s not the issue at all.