-
Notifications
You must be signed in to change notification settings - Fork 64
Implement dav1d-rs's Rust API
#1439
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
base: main
Are you sure you want to change the base?
Conversation
kkysen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the much safer implementation! I'm still taking a closer look at the unsafes, as the few remaining are still quite critical, but I wanted to give some initial feedback in general first.
Pulled out the docs additions from #1439 into its own PR.
kkysen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@leo030303, could you rebase this on main? There are a lot of other commits in here now, so it's harder to review.
Co-authored-by: Khyber Sen <kkysen@gmail.com>
Co-authored-by: Khyber Sen <kkysen@gmail.com>
Co-authored-by: Khyber Sen <kkysen@gmail.com>
Co-authored-by: Khyber Sen <kkysen@gmail.com>
Co-authored-by: Khyber Sen <kkysen@gmail.com>
Co-authored-by: Khyber Sen <kkysen@gmail.com>
Co-authored-by: Khyber Sen <kkysen@gmail.com>
Co-authored-by: Khyber Sen <kkysen@gmail.com>
Co-authored-by: Khyber Sen <kkysen@gmail.com>
Yup, this is fine, too. I pulled out the two small changes that seem more separate (#1468, #1469). The rest seems fine to be squashed into one commit. |
Fixes `clippy` warning found in CI in #1439 (comment).
Co-authored-by: Khyber Sen <kkysen@gmail.com>
|
@kkysen Thanks for the review, resolved most of the comments, and happy to see all the outside interest in the PR! |
Shnatsel
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a safety comment and a potential soundness fix.
Are there any other outstanding work items or is this good to go?
| PlanarImageComponent::Y => self.height(), | ||
| _ => match self.pixel_layout() { | ||
| PixelLayout::I420 => self.height().div_ceil(2), | ||
| PixelLayout::I400 | PixelLayout::I422 | PixelLayout::I444 => self.height(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| PixelLayout::I400 | PixelLayout::I422 | PixelLayout::I444 => self.height(), | |
| PixelLayout::I422 | PixelLayout::I444 => self.height(), | |
| PixelLayout::I400 => return &[]; // grayscale images don't have color components |
There are checks further on that might catch this, but better safe than sorry.
I've also opened a corresponding dav1d-rs PR: rust-av/dav1d-rs#123
| if stride == 0 || raw_plane_data_pointer.is_null() { | ||
| return &[]; | ||
| } | ||
| // SAFETY: Copied checks from dav1d-rs added in this pull request https://github.com/rust-av/dav1d-rs/pull/121 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // SAFETY: Copied checks from dav1d-rs added in this pull request https://github.com/rust-av/dav1d-rs/pull/121 | |
| // SAFETY: The following invariants are upheld: | |
| // 1. Pointer validity: Checked above - if null or stride is 0, we return &[]. | |
| // 2. Pointer alignment: The allocator guarantees RAV1D_PICTURE_ALIGNMENT (64-byte) | |
| // alignment (see Rav1dPictureDataComponentInner::new), which exceeds any | |
| // primitive type's alignment requirements. | |
| // 3. Allocated size: The allocator guarantees the buffer is at least stride * height | |
| // bytes (the allocator callback contract in Dav1dPicAllocator). The checked_mul | |
| // ensures this calculation doesn't overflow. | |
| // 4. Initialization: The allocator is required to initialize the data per the | |
| // alloc_picture_callback safety requirements. | |
| // 5. Lifetime: The returned slice borrows &self, keeping the Arc<Rav1dPictureData> | |
| // alive for the duration of the borrow. | |
| // 6. No mutable aliases: The Picture is only returned after decoding is complete, | |
| // so the decoder no longer writes to this buffer. The public API only exposes | |
| // shared (&[u8]) access, and no &mut references to this data can exist once | |
| // the Picture is handed to the user. | |
| // | |
| // Past dav1d-rs PRs relevant to this line: | |
| // https://github.com/rust-av/dav1d-rs/pull/121 | |
| // https://github.com/rust-av/dav1d-rs/pull/123 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me, very thorough, thanks for the help! @kkysen thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is good, but I'd like to finish figuring out the send_data side of things so that I can go through this in more detail, considering the full lifetime of the data. It seems good from a less thorough check, though.
| let slice = &*buf; | ||
| let len = slice.len(); | ||
|
|
||
| let mut data = Rav1dData::create(len).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is a bit more complex than I thought. There are a few options here:
- Keep doing what you're doing now, with an extra boxing allocation. There are actually two here, but we can easily reduce that to just one.
- Copy
dav1d-rs's approach and unsafely callRav1dData::wrap. We'd have to manage the lifetime through thecookieand this is definitelyunsafe, but it should be sound. This does have a small overhead still, anddav1d-rshas this overhead, too. - Fix the
rav1dAPI to make everything generic over aR: AsRef<T>/R: AsRef<[u8]>, allowing API parity withdav1d-rswith no overhead and a fully safe code path. This generic parameter is quite infectious, which is annoying, but it appears to work quite well without significant changes besides fixing the generic parameters.
So what we want to do here also depends on what we'd like the API to be. If we restrict the API to just Box<[u8]>, it's simple, but allowing AsRef<[u8]>, like Vec<u8> and &[u8], is more complex.
I like approach 2 in the short term and approach 3 in the long term. Speaking of API improvements, I don't think But the real trick is getting rid of the |
Co-authored-by: Khyber Sen <kkysen@gmail.com>
Co-authored-by: Khyber Sen <kkysen@gmail.com>
|
The pub fn send_data<T: AsRef<[u8]> + Send + Sync>(
&mut self,
buf: T,
offset: Option<i64>,
timestamp: Option<i64>,
duration: Option<i64>,
) -> Result<(), Rav1dError> { |
kkysen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Box<[u8]>doesn't really gel with the APIs e.g.imageexposes. It'll work but require an extra copy of each batch of the input plus an allocation for each batch; when using this with animpl Readthis can introduce noticeable overhead.
What API is image, etc. exposing? Being able to take that into account would help designing the right API here.
I like approach 2 in the short term and approach 3 in the long term.
👍
Speaking of API improvements, I don't think
T: AsRef<[u8]>is even a good idea. It just causes more monomophisation
Hmm, you're right about the monomorphization. That would be quite annoying.
doesn't even help ergonomics that much compared to a
&[u8].
It does let use other types like [u8; N], Box<[u8]>, Vec<u8>, Arc<[u8]>, etc. I'm not sure how useful any of those are from the caller's perspective, as I don't have much familiarity with that.
But the real trick is getting rid of the
'staticrequirement without copying or infecting much of the surrounding code with lifetimes.
The tricky part here is the pending_data. If that's moved to another intermediary type wrapping Rav1dContext, we could limit the lifetime to just CBox, CArc, Rav1dData, the new wrapper type, and maybe a few others. I also experimented with adding a R: AsRef<T>/R: AsRef<[u8]> bound on these types (without the pending_data separation), but without a + 'static bound, and it seems to compile fine.
|
They aren't useful because all of them can be turned into a
This is the trait This is how it's currently wired up to |
I'm not sure what this means. Why does that make it not useful?
That seems to be passing a |
|
Yes, due to the limitations of the dav1d API. The I'm not terribly concerned about avoiding this copy in the initial version of the API. AV1/AVIF has a very high compression ratio so if we're copying the data in memory the amount is still very small and will only add a couple percent of overhead to the entire decoding, if that. I'm perfectly happy to get rid of the C dependency first and optimize later. |
I've copy pasted the PR from #1362 and updated it with some of the suggestions made on that pull request. The main changes are:
enums fromrav1dinstead of redefining new ones, and added in doc comments from the originaldav1d-rslibrary to a few items.unsafecode as I could and replaced it with the Rust methods fromrav1das much as possible.It currently works as a drop-in replacement for
dav1d-rs; adding inuse rav1d as dav1d;to my fork of image makes everything work fine.The only functional changes I made are I removed the
unsafe impls ofSendandSyncforInnerPicturesoPictureis no longerSyncorSend. I looked through the code and I don't believeDisjointMut<Rav1dPictureDataComponentInner>, which is a field of one of its children, is thread safe, though I'm open to correction there; I'm pretty unfamiliar withunsafeRust.I also don't have safety comments on the two
unsafeblocks inrust_api.rs; I'm unsure what these would look like, so open to suggestions there. These are mostly taken verbatim from the old pull request.