-
Notifications
You must be signed in to change notification settings - Fork 116
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
Conversation
@@ -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 |
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.
Moved framerate props setter outside of conditional IntrinsicParams == nil
block.
!!! Need to rebase here with latest webcam for same fix. Fixed in latest. ✅
// 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 { |
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.
if bitrate < minBitrate { | |
// this accounts for zero bitrates too | |
if bitrate < minBitrate { |
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.
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, |
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.
What happens if this is 0? Can we add a test?
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.
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.
gostream/codec/x264/utils.go
Outdated
|
||
// 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) |
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.
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/
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 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.
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.
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) |
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.
will this new logic break streaming for cameras that don't provide framerate in getProperties?
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.
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.
@randhid Do you think we are ok to merge this? |
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.
Can we :
- add tests for this logic
- Verify that adding and removing streams from multiple sources works still and report the results of the manual testing? I'd like a video.
gostream/codec/x264/utils.go
Outdated
|
||
// 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) |
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 could result in a float? Does that break the stream sever?
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.
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" | |||
} | |||
|
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.
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?
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.
Good call -- added dimension check to NewEncoder
.
gostream/codec/x264/encoder.go
Outdated
if width%2 != 0 || height%2 != 0 { | ||
return nil, errors.New("x264 encoder does not support odd dimensions") | ||
} |
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.
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.
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.
Because where this is coming from is someone badly encoding a random resolution image.
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.
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.
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 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.
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.
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.
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.
@randhid Added unit tests for bitrate calculation and put up manual test videos in description.
Can you test with viamrtsp? |
Yep, tested quite a bit with viamrtsp -- see description. |
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.
Examples bitrates:
Tests
add unit test for bitrate calculation ✅
webcam
Screen.Recording.2025-02-25.at.2.41.22.PM.mov
viamrtsp
dyanmic resolution