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

encoder: Add VP9 support and refactor common encoder code #64

Closed
wants to merge 7 commits into from

Conversation

bgrzesik
Copy link
Collaborator

This PR adds VP9 encoding support and refactors the common encoder code to remove code duplication between codecs.

@bgrzesik bgrzesik force-pushed the enc-vp9 branch 4 times, most recently from f5113c5 to b42c1c0 Compare February 13, 2024 15:43
Copy link
Collaborator

@Gnurou Gnurou left a comment

Choose a reason for hiding this comment

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

Thanks, this is looking very good overall. A few inline comments in the code, and it would really make my life easier if you could split the big refactoring CL into its logical parts.

But overall the end result looks really good, so I'm looking forward to merging this!

src/encoder/stateless/h264/vaapi.rs Outdated Show resolved Hide resolved
examples/ccenc.rs Show resolved Hide resolved
examples/ccenc.rs Show resolved Hide resolved
src/encoder/stateless/vp9.rs Outdated Show resolved Hide resolved
src/encoder/stateless/vp9.rs Outdated Show resolved Hide resolved
src/encoder/stateless/vp9.rs Show resolved Hide resolved
src/encoder/stateless/vp9.rs Outdated Show resolved Hide resolved
src/encoder/stateless/vp9.rs Outdated Show resolved Hide resolved
src/encoder/stateless/vp9.rs Outdated Show resolved Hide resolved
src/encoder/stateless/vp9/predictor.rs Outdated Show resolved Hide resolved
Copy link
Collaborator Author

@bgrzesik bgrzesik left a comment

Choose a reason for hiding this comment

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

Thank you soo much for the review 😄

I will continue working on the remaining comments tomorrow. Commits before a8ce22b most likely won't change.

examples/ccenc.rs Show resolved Hide resolved
src/encoder/stateless/h264/vaapi.rs Outdated Show resolved Hide resolved
src/encoder/stateless/vp9.rs Outdated Show resolved Hide resolved
src/encoder/stateless/vp9.rs Outdated Show resolved Hide resolved
examples/ccenc.rs Show resolved Hide resolved
@bgrzesik bgrzesik force-pushed the enc-vp9 branch 7 times, most recently from ac8ab58 to 9ca7e7e Compare February 22, 2024 15:41
Copy link
Collaborator Author

@bgrzesik bgrzesik left a comment

Choose a reason for hiding this comment

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

Hopefully all comments were answered 😅 Thanks again for the review!

src/encoder/stateless/vp9/predictor.rs Outdated Show resolved Hide resolved
src/encoder/stateless/vp9.rs Outdated Show resolved Hide resolved
src/encoder/stateless/vp9.rs Outdated Show resolved Hide resolved
src/encoder/stateless/vp9.rs Outdated Show resolved Hide resolved
examples/ccenc.rs Show resolved Hide resolved
src/encoder/stateless/vp9.rs Show resolved Hide resolved
@Gnurou
Copy link
Collaborator

Gnurou commented Feb 29, 2024

I have started merging up to encoder: Export codec specific types using trait (so please rebase), for which I have a comment. Once this is addressed/clarified, I think the whole series can go in! 👍

@@ -191,7 +192,30 @@ where

pub struct H264;

impl StatelessCodec for H264 {}
pub struct H264Specific<Backend>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Technically (and unless I am missing the purpose of this) you don't need a generic parameter to this struct. This allows you to remove _phantom and keep this as an empty type.

After doing so, there is also no need to have a H264Specific type since H264 can just implement StatelessCodecSpecific and refer to itself as its own Specific type.

I.e. this works just fine (patch is on top of the whole series):

diff --git a/src/encoder/stateless/h264.rs b/src/encoder/stateless/h264.rs
index bc790fb69..93d56ec8c 100644
--- a/src/encoder/stateless/h264.rs
+++ b/src/encoder/stateless/h264.rs
@@ -158,14 +158,7 @@ where

 pub struct H264;

-pub struct H264Specific<Backend>
-where
-    Backend: StatelessVideoEncoderBackend<H264>,
-{
-    _phantom: std::marker::PhantomData<Backend>,
-}
-
-impl<Backend> StatelessCodecSpecific<H264, Backend> for H264Specific<Backend>
+impl<Backend> StatelessCodecSpecific<H264, Backend> for H264
 where
     Backend: StatelessVideoEncoderBackend<H264>,
 {
@@ -179,7 +172,7 @@ where
 }

 impl StatelessCodec for H264 {
-    type Specific<Backend> = H264Specific<Backend>
+    type Specific<Backend> = Self
          where Backend : StatelessVideoEncoderBackend<Self>;
 }

diff --git a/src/encoder/stateless/vp9.rs b/src/encoder/stateless/vp9.rs
index c17bd35a2..a261ffddb 100644
--- a/src/encoder/stateless/vp9.rs
+++ b/src/encoder/stateless/vp9.rs
@@ -83,14 +83,7 @@ pub struct BackendRequest<P, R> {

 pub struct VP9;

-pub struct VP9Specific<Backend>
-where
-    Backend: StatelessVideoEncoderBackend<VP9>,
-{
-    _phantom: std::marker::PhantomData<Backend>,
-}
-
-impl<Backend> StatelessCodecSpecific<VP9, Backend> for VP9Specific<Backend>
+impl<Backend> StatelessCodecSpecific<VP9, Backend> for VP9
 where
     Backend: StatelessVideoEncoderBackend<VP9>,
 {
@@ -104,7 +97,7 @@ where
 }

 impl StatelessCodec for VP9 {
-    type Specific<Backend> = VP9Specific<Backend>
+    type Specific<Backend> = VP9
          where Backend : StatelessVideoEncoderBackend<Self>;
 }

Once you do that, StatelessCodec is just used as a way to get an associated type to itself - so you can simplify things further:

diff --git a/src/encoder/stateless.rs b/src/encoder/stateless.rs
index b2c4dacaa..9d9adcd65 100644
--- a/src/encoder/stateless.rs
+++ b/src/encoder/stateless.rs
@@ -208,10 +208,10 @@ pub trait StatelessEncoderBackendImport<Handle, Picture> {
 }

 /// Trait helping contain all codec specific and backend specific types
-pub trait StatelessCodecSpecific<Codec, Backend>
+pub trait StatelessCodecSpecific<Backend>
 where
-    Codec: StatelessCodec,
-    Backend: StatelessVideoEncoderBackend<Codec>,
+    Self: StatelessCodec,
+    Backend: StatelessVideoEncoderBackend<Self>,
 {
     /// Codec specific representation of frame reference wrapping a backend reference type
     /// containing a codec specific frame metadata
@@ -229,12 +229,7 @@ where
     type ReferencePromise: BackendPromise<Output = Self::Reference>;
 }

-pub trait StatelessCodec: Sized {
-    /// Codec and backend specific types container
-    type Specific<Backend>: StatelessCodecSpecific<Self, Backend>
-    where
-        Backend: StatelessVideoEncoderBackend<Self>;
-}
+pub trait StatelessCodec: Sized {}

 /// Stateless video encoder interface.
 pub trait StatelessVideoEncoder<Handle> {
@@ -293,23 +288,20 @@ where
 /// Helper aliases for codec and backend specific types
 type Picture<C, B> = <B as StatelessVideoEncoderBackend<C>>::Picture;

-type CodecSpecific<C, B> = <C as StatelessCodec>::Specific<B>;
-
-type Reference<C, B> = <CodecSpecific<C, B> as StatelessCodecSpecific<C, B>>::Reference;
+type Reference<C, B> = <C as StatelessCodecSpecific<B>>::Reference;

-type Request<C, B> = <CodecSpecific<C, B> as StatelessCodecSpecific<C, B>>::Request;
+type Request<C, B> = <C as StatelessCodecSpecific<B>>::Request;

-type CodedPromise<C, B> = <CodecSpecific<C, B> as StatelessCodecSpecific<C, B>>::CodedPromise;
+type CodedPromise<C, B> = <C as StatelessCodecSpecific<B>>::CodedPromise;

-type ReferencePromise<C, B> =
-    <CodecSpecific<C, B> as StatelessCodecSpecific<C, B>>::ReferencePromise;
+type ReferencePromise<C, B> = <C as StatelessCodecSpecific<B>>::ReferencePromise;

 type BoxPredictor<C, B> = Box<dyn Predictor<Picture<C, B>, Reference<C, B>, Request<C, B>>>;

 pub struct StatelessEncoder<Codec, Handle, Backend>
 where
     Backend: StatelessVideoEncoderBackend<Codec>,
-    Codec: StatelessCodec,
+    Codec: StatelessCodec + StatelessCodecSpecific<Backend>,
 {
     /// Pending frame output promise queue
     output_queue: OutputQueue<CodedPromise<Codec, Backend>>,
@@ -340,14 +332,14 @@ where
 pub trait StatelessEncoderExecute<Codec, Handle, Backend>
 where
     Backend: StatelessVideoEncoderBackend<Codec>,
-    Codec: StatelessCodec,
+    Codec: StatelessCodec + StatelessCodecSpecific<Backend>,
 {
     fn execute(&mut self, request: Request<Codec, Backend>) -> EncodeResult<()>;
 }

 impl<Codec, Handle, Backend> StatelessEncoder<Codec, Handle, Backend>
 where
-    Codec: StatelessCodec,
+    Codec: StatelessCodec + StatelessCodecSpecific<Backend>,
     Backend: StatelessVideoEncoderBackend<Codec>,
     Self: StatelessEncoderExecute<Codec, Handle, Backend>,
 {
@@ -392,7 +384,7 @@ where
 impl<Codec, Handle, Backend> StatelessVideoEncoder<Handle>
     for StatelessEncoder<Codec, Handle, Backend>
 where
-    Codec: StatelessCodec,
+    Codec: StatelessCodec + StatelessCodecSpecific<Backend>,
     Backend: StatelessVideoEncoderBackend<Codec>,
     Backend: StatelessEncoderBackendImport<Handle, Backend::Picture>,
     Self: StatelessEncoderExecute<Codec, Handle, Backend>,
diff --git a/src/encoder/stateless/h264.rs b/src/encoder/stateless/h264.rs
index 93d56ec8c..3c03a6978 100644
--- a/src/encoder/stateless/h264.rs
+++ b/src/encoder/stateless/h264.rs
@@ -158,9 +158,9 @@ where

 pub struct H264;

-impl<Backend> StatelessCodecSpecific<H264, Backend> for H264
+impl<Backend> StatelessCodecSpecific<Backend> for H264
 where
-    Backend: StatelessVideoEncoderBackend<H264>,
+    Backend: StatelessVideoEncoderBackend<Self>,
 {
     type Reference = DpbEntry<Backend::Reconstructed>;

@@ -171,10 +171,7 @@ where
     type ReferencePromise = ReferencePromise<Backend::ReconPromise>;
 }

-impl StatelessCodec for H264 {
-    type Specific<Backend> = Self
-         where Backend : StatelessVideoEncoderBackend<Self>;
-}
+impl StatelessCodec for H264 {}

 /// Trait for stateless encoder backend for H.264
 pub trait StatelessH264EncoderBackend: StatelessVideoEncoderBackend<H264> {
diff --git a/src/encoder/stateless/vp9.rs b/src/encoder/stateless/vp9.rs
index a261ffddb..4b7ed4905 100644
--- a/src/encoder/stateless/vp9.rs
+++ b/src/encoder/stateless/vp9.rs
@@ -83,9 +83,9 @@ pub struct BackendRequest<P, R> {

 pub struct VP9;

-impl<Backend> StatelessCodecSpecific<VP9, Backend> for VP9
+impl<Backend> StatelessCodecSpecific<Backend> for VP9
 where
-    Backend: StatelessVideoEncoderBackend<VP9>,
+    Backend: StatelessVideoEncoderBackend<Self>,
 {
     type Reference = Backend::Reconstructed;

@@ -96,10 +96,7 @@ where
     type ReferencePromise = Backend::ReconPromise;
 }

-impl StatelessCodec for VP9 {
-    type Specific<Backend> = VP9
-         where Backend : StatelessVideoEncoderBackend<Self>;
-}
+impl StatelessCodec for VP9 {}

 pub trait StatelessVP9EncoderBackend<H>: StatelessVideoEncoderBackend<VP9> {
     fn encode_frame(

I don't know how much this makes sense, so please take it as a suggestion. With that StatelessCodec is not really useful anymore, hinting that maybe we can simplify some more. I'll let you see what should be done as I am not completely sure of your intent for the future here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, you are right here 😄

My intent for the potential future use would be to take advantage of already existing decoder's (or similar) structures for keeping the decoder state definitely consistent instead relying on predictor for this. Second benefit of that would be that the predictor would be a single function trait which would accept a queue of pending frames and decoder state in order to make the decision of the next actions. But first of all currently I don't consider it as a deal breaking benefit now. And I think it could bring downsides of overcomplication, potentially engaging parser for no reason and breaking possible parallelism for GoP structure (WIP).

Back to the review. You are right regarding simplification of those traits. I even went ahead and merged StatelessCodec and StatelessCodecSpecific into one. And despite those simplifications I still believe the above idea continues to be possible to implement.

Also now that I think about it, shouldn't we also merge crate::decoder::stateless::h264::H264 and crate::encoder::stateless::h264::H264 (and the same for VP9)?

This is necessary for the next commits
@bgrzesik bgrzesik force-pushed the enc-vp9 branch 3 times, most recently from 06a50d0 to 8ea0e86 Compare March 4, 2024 12:54
bgrzesik added 6 commits March 4, 2024 14:02
This change adds VP9 encoding support and vaapi backend for it. The
StatelessEncoder implementation is a copy from H264 implementation. Both
will be merge in the following change.
This commit adds VP9 encoding option to ccenc
/// Accepts [`Request`] and is responsible for adding resutling [`BackendPromise`] to
/// [`StatelessEncoder`] internal queues and decrementing the internal predictor frame counter if
/// the backend moved the frame outside predictor ownership.
pub trait StatelessEncoderExecute<Codec, Handle, Backend>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since this has the same constraints has StatelessEncoder, and StatelessEncoder depends on this trait, can't we just make execute a method of StatelessEncoder?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

But in such case I'd have to add constraints for Handle and Backend as well


Ok(())
}
fn new_h264(backend: Backend, config: EncoderConfig, mode: BlockingMode) -> EncodeResult<Self> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess we could replace these codec-specific methods with a single one if we add a few more associated types (EncoderConfig and Predictor) to StatelessCodec, but let's do this as a follow-up.

..Default::default()
};

let ref_frame = self.references.pop_front().unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't see a hard guarantee that the unwrap won't crash us - it would be nice to remove it at some point.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, I should add a // SAFETY: comment. It is in the next_request(). If references is empty, this function would not be called.

@Gnurou
Copy link
Collaborator

Gnurou commented Mar 7, 2024

All merged! (not from the GUI since I had to cargo format a few commits). There are a few inline comments, we can address them as follow-ups. Thanks for the great work!

@Gnurou Gnurou closed this Mar 7, 2024
Copy link
Collaborator Author

@bgrzesik bgrzesik left a comment

Choose a reason for hiding this comment

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

Thank you!!

All merged! (not from the GUI since I had to cargo format a few commits).

I am surprised to see that, I remember running the cargo fmt before upload. Maybe we have a discrepancy in rustfmt.toml. Could you please add yours to the repo?

..Default::default()
};

let ref_frame = self.references.pop_front().unwrap();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, I should add a // SAFETY: comment. It is in the next_request(). If references is empty, this function would not be called.

/// Accepts [`Request`] and is responsible for adding resutling [`BackendPromise`] to
/// [`StatelessEncoder`] internal queues and decrementing the internal predictor frame counter if
/// the backend moved the frame outside predictor ownership.
pub trait StatelessEncoderExecute<Codec, Handle, Backend>
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

But in such case I'd have to add constraints for Handle and Backend as well

@bgrzesik bgrzesik deleted the enc-vp9 branch June 10, 2024 07:56
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.

2 participants