Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support arbitrary SAR anamorphic video #2505

Merged
merged 2 commits into from Aug 29, 2020
Merged

Conversation

rzumer
Copy link
Collaborator

@rzumer rzumer commented Aug 20, 2020

This adds an aspect ratio input parameter
to the Rust and C APIs, computes the render
size by upscaling one dimension, and codes
it in-bitstream, as well as in Y4M output.

The default aspect ratio is parsed from
Y4M input, or set to 1:1 if absent.

Note that neither aomdec nor dav1d will propagate the bitstream render size to the Y4M files they output.
It doesn't seem to be honored by ffmpeg with aomdec either, but it might be with dav1d (haven't tested it myself, but the code seems present). Reconstruction output is affected as well.
I think we can close #2504 with this, as it implements everything that should be needed for decoders/players to correctly output/display the encoded video.

@rzumer rzumer requested a review from tmatth August 20, 2020 18:22
src/api/config/encoder.rs Outdated Show resolved Hide resolved
@rzumer rzumer force-pushed the aspect-ratio branch 2 times, most recently from d4f02d4 to 45ce98c Compare August 20, 2020 18:58
@coveralls
Copy link
Collaborator

coveralls commented Aug 20, 2020

Coverage Status

Coverage increased (+0.1%) to 83.096% when pulling 1d3eda3 on rzumer:aspect-ratio into fe87644 on xiph:master.

@rzumer rzumer force-pushed the aspect-ratio branch 2 times, most recently from 5d5641f to c7f399b Compare August 20, 2020 23:34
@rzumer rzumer requested a review from dwbuiten August 21, 2020 00:58
Cargo.toml Outdated Show resolved Hide resolved
Copy link
Collaborator

@dwbuiten dwbuiten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be a bug that causes only the first frame to have its render width/height changed.

@rzumer rzumer force-pushed the aspect-ratio branch 2 times, most recently from b418aeb to 1f86153 Compare August 21, 2020 15:31
@rzumer rzumer changed the title Support arbitrary DAR anamorphic video Support arbitrary SAR anamorphic video Aug 21, 2020
This adds an aspect ratio input parameter
to the Rust and C APIs, computes the render
size by upscaling one dimension, and codes
it in-bitstream, as well as in Y4M output.

The default aspect ratio is parsed from
Y4M input, or set to 1:1 if absent.
@rzumer
Copy link
Collaborator Author

rzumer commented Aug 22, 2020

I'm now aware that 0:0 is a "valid" aspect ratio that should map to 1:1, so such samples are to-test and might be rejected with the current state of the branch.

@dwbuiten
Copy link
Collaborator

Thinking more about this, shouldn't it be per-frame?

@rzumer
Copy link
Collaborator Author

rzumer commented Aug 24, 2020

I think we can leave that as future work.

@rzumer
Copy link
Collaborator Author

rzumer commented Aug 24, 2020

@dwbuiten I moved the parameters to FrameInvariants where they should be. I first thought you were suggesting to make aspect ratio variable per-frame via the API.

@dwbuiten
Copy link
Collaborator

@rzumer I was indeed saying it should be per-frame in the API. Aspect ratio may change midstream, and should be supported either via frame properties, or via encoder-reconfig, I believe.

@rzumer
Copy link
Collaborator Author

rzumer commented Aug 25, 2020

Then as mentioned earlier I prefer it to be left as future work considering how rare of a use case it is, unless there is already a framework for setting parameters frame by frame (I can't see anything in the api/config module).

@jamrial
Copy link
Contributor

jamrial commented Aug 25, 2020

Maybe this should instead set frame_size_override_flag to 1 on inter frames and take the frame and render dimension values from one of the frames they reference in the ref_frame_idx[] array (The frame_size_with_refs() function defined in Section 5.9.7).

That way you'll save 4 bytes per frame header used to code the same render dimension values.

@rzumer
Copy link
Collaborator Author

rzumer commented Aug 25, 2020

@jamrial That works as long as frame size is static. We can probably assume it is for now though.

@jamrial
Copy link
Contributor

jamrial commented Aug 25, 2020

Afailk, it can also work for non static frame sizes by ensuring a given inter frame references a frame with the exact same values it would otherwise write. If none of the seven potential reference frames share the exact same values, then they are explicitly written instead.

@rzumer
Copy link
Collaborator Author

rzumer commented Aug 25, 2020

I see, I'll look into adding that.

@jamrial
Copy link
Contributor

jamrial commented Aug 25, 2020

If you'd rather not bother looking at the values for each of the seven reference frame since for now the sizes are fixed, then it's a matter of setting frame_size_override_flag to 1 and implementing frame_size_with_refs() as a single bit found_ref to signal that the first reference frame should be used by the decoder to infer this frame's dimensions.

@rzumer
Copy link
Collaborator Author

rzumer commented Aug 26, 2020

After looking into it I don't think this frame_size_override has the same effect as overriding the render_size. Seems to me that frame size is related to the number of luma samples, while render size signals players to rescale the frame when displaying it. Unless this part of the spec is inconsistently written and poorly defined, and the aomdec behaviour matches what you're implying, I'd keep the current implementation.

@jamrial
Copy link
Contributor

jamrial commented Aug 26, 2020

No, it doesn't have the same effect. frame_size_override_flag means that either frame dimensions, render dimensions, or both, may be coded in the frame or should be inferred from a reference frame. For this specific case, frame dimensions and render dimensions will be constant for the entire video sequence, so setting both frame_size_override_flag and render_and_frame_size_different to 1 and then taking the first reference frame's dimensions is the same as signaling only render_and_frame_size_different and coding the render dimensions in the frame, except it doesn't use four bytes for it.

See the code below

diff --git a/src/header.rs b/src/header.rs
index 194c0510..7c66fe98 100644
--- a/src/header.rs
+++ b/src/header.rs
@@ -538,7 +538,14 @@ impl<W: io::Write> UncompressedHeader for BitWriter<W, BigEndian> {
       //self.write(frame_id_len, fi.current_frame_id);
     }

-    let mut frame_size_override_flag = false;
+    let render_and_frame_size_different = fi.render_width != fi.width as u32
+      || fi.render_height != fi.height as u32;
+
+    // This assumes frame dimensions are the same as sequence header dimensions,
+    // and that only render dimensions may be different but nonetheless constant
+    // for the entire video sequence
+    let mut frame_size_override_flag = render_and_frame_size_different && !fi.intra_only
+      && !fi.error_resilient;
     if fi.frame_type == FrameType::SWITCH {
       frame_size_override_flag = true;
     } else if fi.sequence.reduced_still_picture_hdr {
@@ -606,8 +613,6 @@ impl<W: io::Write> UncompressedHeader for BitWriter<W, BigEndian> {
       if fi.sequence.enable_superres {
         unimplemented!();
       }
-      let render_and_frame_size_different = fi.render_width != fi.width as u32
-        || fi.render_height != fi.height as u32;
       self.write_bit(render_and_frame_size_different)?;
       if render_and_frame_size_different {
         self.write(16, fi.render_width - 1);
@@ -640,7 +645,11 @@ impl<W: io::Write> UncompressedHeader for BitWriter<W, BigEndian> {
       }

       if !fi.error_resilient && frame_size_override_flag {
-        unimplemented!();
+        //TODO: check reference frames to compare frame dimensions
+        self.write_bit(true)?; // found_ref[0]
+        if fi.sequence.enable_superres {
+          unimplemented!();
+        }
       } else {
         if frame_size_override_flag {
           self.write_frame_size_override(fi);
@@ -648,9 +657,6 @@ impl<W: io::Write> UncompressedHeader for BitWriter<W, BigEndian> {
         if fi.sequence.enable_superres {
           unimplemented!();
         }
-        let render_and_frame_size_different = fi.render_width
-          != fi.width as u32
-          || fi.render_height != fi.height as u32;
         self.write_bit(render_and_frame_size_different)?;
         if render_and_frame_size_different {
           self.write(16, fi.render_width - 1);

@rzumer
Copy link
Collaborator Author

rzumer commented Aug 26, 2020

I mean, I'm fine with this change but it seems like it should be separate from this PR.

Isn't it applicable regardless of render size anyway since we currently assume a static frame size?

@jamrial
Copy link
Contributor

jamrial commented Aug 26, 2020

Sure, i can send a PR for it after this one is merged.

Also, here's a complex sample where frame dimensions vary between frames (But render dimensions are constant), and uses the frame_size_with_refs() logic to avoid coding the values in the bitstream when a reference frame shares the same values, if you're curious.

https://code.videolan.org/videolan/dav1d/uploads/e41cf2423c6ae93e2a0a4fc9ae543730/resize.ivf

@dwbuiten
Copy link
Collaborator

Then as mentioned earlier I prefer it to be left as future work considering how rare of a use case it is, unless there is already a framework for setting parameters frame by frame (I can't see anything in the api/config module).

I think it should be doable with an encoder reconfig API, so doing it eoncig level should be OK, I suppose. Color properties are done this way too.

@rzumer rzumer merged commit 6bf1ad6 into xiph:master Aug 29, 2020
@rzumer rzumer mentioned this pull request Aug 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Output of encoding small_input.y4m is stretched by players
8 participants