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

[RSDK-6469] Set x264 bitrate based on resolution and fps #4769

Merged
merged 14 commits into from
Feb 27, 2025

Conversation

seanavery
Copy link
Member

@seanavery seanavery commented Feb 3, 2025

Description

This PR gives a more accurate bitrate estimation to our libx264 pipeline based on resolution and framerate. The idea here is that smaller streams will use less bandwidth and we will get better quality out of larger streams.

  • Removes hardcoded 3.2 Mbps bitrate.
  • Adds look up for framerate from Properties and if it is not found default to 30.
  • Calculates bitrate using a reasonable 0.15 bits per pixel ratio.
  • Adds minimum bitrate to avoid buffering issues with small frame sizes.
  • Adds a maximum bitrate that is safely over 20fps 4k stream size.

Examples bitrates:

640p at 30 fps: 640*480*30*0.15 - > 1.382400 Mbps
1080p at 30fps : 1920*1080*30*0.15 -> 9.331200 Mbps
4k at 20fps: 3840*2160*20*0.15 -> 24.883200 Mbps

Tests

  • add unit test for bitrate calculation ✅

  • webcam

    • picks up framerate from properties ✅
    • suitable quality across multiple resolutions ✅
Screen.Recording.2025-02-25.at.2.41.22.PM.mov
  • viamrtsp

    • suitable quality at 2k and 4k resolutions ✅
      rtsp-cam-1-2025-02-25_14_53_53
  • dyanmic resolution

    • suitable quality at extremely small frame sizes ✅

@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Feb 3, 2025
@@ -622,6 +622,11 @@ func (c *webcam) Properties(ctx context.Context) (camera.Properties, error) {
if err != nil {
return camera.Properties{}, err
}

if c.conf.FrameRate > 0 {
props.FrameRate = c.conf.FrameRate
Copy link
Member Author

@seanavery seanavery Feb 3, 2025

Choose a reason for hiding this comment

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

Moved framerate props setter outside of conditional IntrinsicParams == nil block.
!!! Need to rebase here with latest webcam for same fix. Fixed in latest. ✅

@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Feb 3, 2025
@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Feb 3, 2025
@seanavery seanavery requested review from hexbabe and randhid February 3, 2025 18:54
// calcBitrateFromResolution calculates the bitrate based on the given resolution and framerate.
func calcBitrateFromResolution(width, height int, framerate float32) int {
bitrate := int(float32(width) * float32(height) * framerate * encodeCompressionRatio)
if bitrate < minBitrate {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if bitrate < minBitrate {
// this accounts for zero bitrates too
if bitrate < minBitrate {

Copy link
Member Author

Choose a reason for hiding this comment

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

Sounds good, added comment.

// We walk the updated set of `videoSources` and ensure all of the sources are "created" and
// "started".
config := gostream.StreamConfig{
Name: name,
VideoEncoderFactory: server.streamConfig.VideoEncoderFactory,
TargetFrameRate: framerate,
Copy link
Member

Choose a reason for hiding this comment

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

What happens if this is 0? Can we add a test?

Copy link
Member Author

Choose a reason for hiding this comment

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

encoder pipeline will default to 30fps in bitrate calculation (the same as the key-frame interval) if it cant pick up framerate.

Will see about adding a test for this.


// calcBitrateFromResolution calculates the bitrate based on the given resolution and framerate.
func calcBitrateFromResolution(width, height int, framerate float32) int {
bitrate := int(float32(width) * float32(height) * framerate * encodeCompressionRatio)
Copy link
Member

Choose a reason for hiding this comment

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

Does bitrate scale linearly with width and height? Apparently the answer is complicated https://www.reddit.com/r/explainlikeimfive/comments/hlrwcu/eli5_what_is_the_relationship_between_bit_rate/

Copy link
Member Author

@seanavery seanavery Feb 3, 2025

Choose a reason for hiding this comment

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

I am currently scaling the bitrate linearly with the number of pixels.

Interesting graphs in the thread that show logarithmic quality gains with increasing bitrate for a given size/fps. Will try to verify that we are hitting these sweet-spots. The amount of scene change also comes into play here so it gets quite complicated.

Copy link
Member Author

Choose a reason for hiding this comment

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

If you look at the example bitrates in the description we seem to be pretty bang on to the recommended settings linked in the jira ticket: https://support.google.com/youtube/answer/2853702?hl=en

@@ -507,11 +507,16 @@ func (server *Server) AddNewStreams(ctx context.Context) error {
server.logger.Warn("video streaming not supported on Windows yet")
break
}
framerate, err := server.getFramerateFromCamera(name)
Copy link
Member

Choose a reason for hiding this comment

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

will this new logic break streaming for cameras that don't provide framerate in getProperties?

Copy link
Member Author

@seanavery seanavery Feb 3, 2025

Choose a reason for hiding this comment

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

Will add comment here -- encoder pipeline will default to 30fps in bitrate calculation (the same as the key-frame interval) if it cant pick up framerate.

@hexbabe hexbabe self-requested a review February 3, 2025 19:18
@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Feb 3, 2025
@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Feb 3, 2025
@seanavery seanavery requested a review from randhid February 19, 2025 18:51
@seanavery
Copy link
Member Author

@randhid Do you think we are ok to merge this?

Copy link
Member

@randhid randhid left a comment

Choose a reason for hiding this comment

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

Can we :

  1. add tests for this logic
  2. Verify that adding and removing streams from multiple sources works still and report the results of the manual testing? I'd like a video.


// calcBitrateFromResolution calculates the bitrate based on the given resolution and framerate.
func calcBitrateFromResolution(width, height int, framerate float32) int {
bitrate := int(float32(width) * float32(height) * framerate * encodeCompressionRatio)
Copy link
Member

Choose a reason for hiding this comment

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

This could result in a float? Does that break the stream sever?

Copy link
Member Author

Choose a reason for hiding this comment

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

The int cast will remove decimal portion effectively rounding down to nearest int. Changed to round up instead which seems to make more sense.

@@ -27,3 +38,16 @@ func (f *factory) New(width, height, keyFrameInterval int, logger logging.Logger
func (f *factory) MIMEType() string {
return "video/H264"
}

Copy link
Member

Choose a reason for hiding this comment

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

Can't comment above - but I just realized - if the initial resolution is odd - x264 could break could we add a check for the initial resolution not being odd as well?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good call -- added dimension check to NewEncoder.

@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Feb 25, 2025
@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Feb 25, 2025
Comment on lines 27 to 29
if width%2 != 0 || height%2 != 0 {
return nil, errors.New("x264 encoder does not support odd dimensions")
}
Copy link
Member

Choose a reason for hiding this comment

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

If an initial camera resoution is odd - could we change the resolution the same way that we do when we're setting stream options?

Or more explicitly tell the user in this error to return an image that is even dimensions? I think I prefer this option.

Copy link
Member

Choose a reason for hiding this comment

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

Because where this is coming from is someone badly encoding a random resolution image.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, improved error message here to inform user to provide even dim frames.

If an initial camera resoution is odd - could we change the resolution the same way that we do when we're setting stream options?

We could do something fancy like this but not sure if it is worth it given that realistically all cameras should be providing even dimension frames. The only time I have seen this is when fake camera allowed odd dimensions.

Copy link
Member

Choose a reason for hiding this comment

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

So pete made a camera where he cropped, re-saturated and transformed a bunch of things from an ffmpeg camera and ended up with odd resolutions in the new camera. Remember the return of bytes is really up to the implementer and you can do wild things if you're doing really custom things.

Copy link
Member Author

@seanavery seanavery Feb 26, 2025

Choose a reason for hiding this comment

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

Sounsd good. This check should still work in such cases because if there is a size change we re-initialize the encoder in the stream loop and the new resolution (if odd) should hit this check.

@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Feb 25, 2025
@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Feb 25, 2025
@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Feb 25, 2025
@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Feb 25, 2025
Copy link
Member Author

@seanavery seanavery left a comment

Choose a reason for hiding this comment

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

@randhid Added unit tests for bitrate calculation and put up manual test videos in description.

@randhid
Copy link
Member

randhid commented Feb 25, 2025

Can you test with viamrtsp?

@seanavery
Copy link
Member Author

@randhid

Can you test with viamrtsp?

Yep, tested quite a bit with viamrtsp -- see description.

@seanavery seanavery requested a review from randhid February 26, 2025 18:50
@seanavery seanavery merged commit 1d76eb7 into viamrobotics:main Feb 27, 2025
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
safe to test This pull request is marked safe to test from a trusted zone
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants