feat(vdo): Add safe Rust bindings for VDO API#223
feat(vdo): Add safe Rust bindings for VDO API#223vsem-azamat wants to merge 3 commits intoAxisCommunications:mainfrom
Conversation
Safe wrappers over vdo-sys with builder pattern for stream creation, iterator-based frame capture, and automatic resource cleanup. Tested on device: YUV, JPEG, H.264, H.265 formats work correctly.
There was a problem hiding this comment.
Pull request overview
This PR adds safe Rust bindings for the VDO (Video Capture) API, enabling video capture from Axis cameras. The implementation provides a builder pattern for stream configuration, iterator-based frame capture, and automatic resource cleanup through RAII principles.
Key changes:
- New
vdocrate with safe abstractions overvdo-sysFFI bindings - Comprehensive error handling with detailed error code mapping
- Builder pattern for ergonomic stream configuration
- Updated
vdo_encode_clientapplication demonstrating multiple video formats
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/vdo/src/lib.rs | Core library implementation with safe wrappers for VDO API, including Stream, StreamBuffer, Frame types, and comprehensive tests |
| crates/vdo/examples/basic.rs | Simple example demonstrating video stream capture with YUV format |
| crates/vdo/Cargo.toml | Package configuration for the new vdo crate with device-tests feature flag |
| apps/vdo_encode_client/src/main.rs | Updated example application testing multiple video formats (YUV, JPEG, H.264, H.265) |
| apps/vdo_encode_client/Cargo.toml | Updated dependency from vdo-sys to the new vdo crate |
| Cargo.toml | Added vdo workspace member |
| Cargo.lock | Updated lock file with vdo crate dependencies |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
I'll wait for feedback from the maintainers before addressing these suggestions, as some may be more relevant than others for this codebase. |
|
I won't have time to look at the code before new year, but I would like to say already now that I'm happy you are interested in carrying on the work that Jonathan started 🙂 |
apljungquist
left a comment
There was a problem hiding this comment.
I skimmed the PR and I think it looks pretty good. I'll try to do a more thorough read in the before next weekend focusing on safety and I'll try to respond to comments within 24h.
There was a problem hiding this comment.
Is this example program equivalent to it's namesake from acap-native-sdk-examples?
It's OK if it is not, but then we should rename the example since the current name reflects the intention that it is a port of the C example (#183), like many other examples are ports of their namesake C example.
apps/vdo_encode_client/src/main.rs
Outdated
| use log::{error, info}; | ||
| use vdo::{Error, Stream, VdoFormat}; | ||
|
|
||
| fn test_format(name: &str, format: VdoFormat, num_frames: usize) -> Result<(), Error> { |
There was a problem hiding this comment.
That this is called test_* makes me wonder if perhaps there is a way to express it as a unit test. That way we can ensure that it doesn't break semi-automatically (I say semi because one still has to set up a compatible device etc). Is this something that you have explored?
There was a problem hiding this comment.
Some API design related questions.
API design is difficult and typically subjective. I don't want it to stall this PR, so consider this inspiration. (that's not to say that the previous review was not subjective or disputable, only that this is even more so)
crates/vdo/src/lib.rs
Outdated
| pub struct StreamBuilder { | ||
| format: VdoFormat, | ||
| buffer_count: u32, | ||
| buffer_strategy: VdoBufferStrategy, |
There was a problem hiding this comment.
Do you have a use case for the explicit buffer strategy? In the comments in the base PR, #153 (comment) using VDO_BUFFER_STRATEGY_INFINITE is recommended so I think we can omit EXPLICIT entirely which would simplify the API.
crates/vdo/src/lib.rs
Outdated
| /// Default: `VdoBufferStrategy::VDO_BUFFER_STRATEGY_INFINITE` | ||
| /// | ||
| /// - `VDO_BUFFER_STRATEGY_INFINITE`: VDO manages buffers internally (works for all formats) | ||
| /// - `VDO_BUFFER_STRATEGY_EXPLICIT`: Application manages buffers (only for YUV/RGB) |
There was a problem hiding this comment.
If EXPLICIT strategy is requested the documented invariant should be checked in the build method so we can return an appropriate error variant.
crates/vdo/src/lib.rs
Outdated
| .to_string() | ||
| }; | ||
|
|
||
| // Free the GError |
There was a problem hiding this comment.
This comment should be re-formulated as a safety comment as suggested by @apljungquist above. In it's current form it is not very useful since it just re-states what the code does.
crates/vdo/src/lib.rs
Outdated
|
|
||
| impl Map { | ||
| /// Creates a new empty map. | ||
| pub fn new() -> Result<Self> { |
There was a problem hiding this comment.
We typically prefix fallible constructors with try, see below.
| pub fn new() -> Result<Self> { | |
| pub fn try_new() -> Result<Self> { |
crates/vdo/src/lib.rs
Outdated
| } | ||
|
|
||
| /// Returns the frame data as a mutable byte slice. | ||
| pub fn as_mut_slice(&mut self) -> Result<&mut [u8]> { |
There was a problem hiding this comment.
When should the as_mut_slice method be used?
I am uncertain if writing to this memory from the application side is sound and since the buffers are a limited resource. I think a safer API would be to provide a clone implementation that copies the data which the application can then write to if needed.
crates/vdo/src/lib.rs
Outdated
| /// This can be used for zero-copy operations with other APIs. | ||
| pub fn file_descriptor(&self) -> std::os::fd::BorrowedFd<'_> { | ||
| unsafe { | ||
| let fd = vdo_frame_get_fd(self.raw); |
There was a problem hiding this comment.
vdo_frame_get_fd is deprecated, use vdo_buffer_get_fd instead.
crates/vdo/src/lib.rs
Outdated
| /// Metadata for a video frame. | ||
| /// | ||
| /// Contains information about frame timing, size, and type. | ||
| pub struct Frame<'a> { |
There was a problem hiding this comment.
Consider merging the Frame and StreamBuffer structs since their lifetimes are very much linked.
As stated in https://developer.axis.com/acap/api/src/api/vdostream/html/vdo-buffer_8h.html#a5367ff8edb99976f0b22a2809d44c097, the frame object is only valid while the buffer is so I'm not convinced it needs a separate type when we could just add relevant methods to the StreamBuffer type.
There was a problem hiding this comment.
I'm not saying we should depend on this, but no shit they are linked: typedef VdoFrame VdoBuffer; 😅
crates/vdo/src/lib.rs
Outdated
|
|
||
| /// Returns the file descriptor for the frame data. | ||
| /// | ||
| /// This can be used for zero-copy operations with other APIs. |
There was a problem hiding this comment.
I don't think using the FD is related to zero-copy; the C api documentation makes no mention of it: https://developer.axis.com/acap/api/src/api/vdostream/html/vdo-frame_8h.html#ab4bb3b7b0775ce6caee5172af24a16fd
Do you have an application in mind where you intend to use the file descriptor?
crates/vdo/src/lib.rs
Outdated
| pub struct StreamBuffer<'a> { | ||
| raw: *mut VdoBuffer, | ||
| stream: &'a Stream, | ||
| _phantom: PhantomData<&'a ()>, |
There was a problem hiding this comment.
I don't think you need the phantomdata here since you already have a borrowed reference to the stream which should prevent the buffer from outliving the stream.
|
I created this to help with my understanding of VDO, posting it here in case you find it usefule: https://github.com/apljungquist/acap-rs/blob/f778576222874210e51f0c46ab4d6ffbdd877572/crates/vdo-sys/README.md |
apljungquist
left a comment
There was a problem hiding this comment.
I have focused this review on the Map type, which looks good. Once we have decided if/how to change it I will continue reviewing other parts.
I do this to not overwhelm you with comments and to not overwhelm myself with different discussions to keep track of and large diffs that need to be reviewed with every now commit.
Address all review comments. Fix GObject leak in Stream::drop. - Consume-by-value ownership: start(self), stop(self), unref(self) - Replace Iterator with next_buffer() -> Result - Merge Frame into StreamBuffer (VdoBuffer = VdoFrame) - Extract Map to separate module, keys as &CStr - Add Resolution enum, remove buffer_strategy, rename to custom_timestamp_us - Replace as_mut_slice with data_copy(), file_descriptor() returns Result Tested: cargo clippy clean, 33/33 tests pass on Artpec-9 device.
2326701 to
70e2962
Compare
|
@apljungquist @guoxe |
|
Will try to get around to this tomorrow |
Address all review comments. Fix GObject leak in Stream::drop. - Consume-by-value ownership: start(self), stop(self), unref(self) - Replace Iterator with next_buffer() -> Result - Merge Frame into StreamBuffer (VdoBuffer = VdoFrame) - Extract Map to separate module, keys as &CStr - Add Resolution enum, remove buffer_strategy, rename to custom_timestamp_us - Replace as_mut_slice with data_copy(), file_descriptor() returns Result Tested: cargo clippy clean, 33/33 tests pass on Artpec-9 device.
70e2962 to
2326701
Compare
|
@vsem-azamat nice progress! Please refrain from force pushing in the future as it makes it difficult to review the changes and keep track of comments. I took the liberty of cherry-picking your fix onto the branch before it was force pushed and force pushing that, so now the git history looks as it should. A few of my comments seem to have gone unaddressed. You don't have to always to things the way I think is the best, but I would appreciate if you at least acknowledge on each comment that you have read it and state what, if anything, you will do to address it 🙂 I haven't seen the results of the CI yet, but if we get more revisions in on this I will make sure to start it as soon as I can instead of waiting until I have time to do human review like this time around. |
|
It looks like something changed with github actions and it now hangs after starting the dev container: I will try to look into this in the next week or so. In the meanwhile I encourage you to run the same check locally so that we have a good PR ready to go when CI gets fixed. The check that compares checksums can be a bit unreliable and is easy to fix, so if that is the only one that is failing we should be all right. |
| use std::marker::PhantomData; | ||
| use std::mem; | ||
| use std::ptr; | ||
| use vdo_sys::*; |
There was a problem hiding this comment.
Please avoid glob imports as they both make it more difficult to see where a type is coming from and risks importing unexpected traits.
| @@ -1315,45 +1245,69 @@ mod device_tests { | |||
| fn error_message_is_descriptive() { | |||
There was a problem hiding this comment.
For tests that verify that an output is subjectively good for a human, I have found that expect-test is a useful tool. You don't have to, but if you feel like looking into it I think it could be useful in this test.
Safe Rust bindings for VDO (Video Capture) API
Safe wrappers over
vdo-syswith builder pattern for stream creation, iterator-based frame capture, and automatic resource cleanup.Tested on device: YUV, JPEG, H.264, H.265 formats work correctly.
Describe your changes
This PR adds a new
vdocrate providing safe Rust bindings for the VDO API, enabling video capture from Axis cameras.Based on the work from PR #153 by @JsGjKJzi — completed the implementation, added comprehensive tests, and updated the example application.
Key features:
StreamBuilderwith builder pattern for configuring video streamsStream,RunningStream,StreamIteratorfor lifecycle managementStreamBufferandFramefor zero-copy frame accessDropimplementationsVdoErrorincluding error code namesChanges:
crates/vdo/crate with safe bindingsapps/vdo_encode_client/to use the newvdocratedevice-testsfeature)Issue ticket number and link
Checklist before requesting a review