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-8982, RSDK-9167, RSDK-8979] - Add SetStreamOptions to stream server #4530

Open
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

seanavery
Copy link
Member

@seanavery seanavery commented Nov 5, 2024

Description

RSDK-8982, RSDK-9167 RSDK-8979

This PR adds the SetStreamOptions endpoint to the stream server.

  • Validates the requests and performs a hot swap on the video source with a NewResizeVideoSource.
  • Dispatches a Resize msg to the streamState handler.
    • If in Passthrough mode, the state transitions to gostream.
    • Prevents attempting to use passthrough during Increment and Decrement calls.

Tests

  • CI Tests:
    • Request Validation ✅
    • GetImage Fake camera resize swap ✅
    • RTPPassthrough fake camera downgrade state transition ✅
    • Video tracks stay alive through resize transition ✅
    • Verifies prevention of RTPPassthrough when resized and peers Increment/Decrement ✅
  • Manual tests (using NewStreamServiceClient script):
	conn, err := rgrpc.Dial(ctx, addr, logger, rpc.WithEntityCredentials(
               // ... 
               rpc.Credentials{
                       // ...
               }))
        livestreamClient := streampb.NewStreamServiceClient(conn)
        // ...
  • GetImage livestreams still work ✅
    • webcam ✅
    • fake cam ✅
    • viamrtsp ✅
  • Passthrough livestreams still work ✅
    • fake cam ✅
    • viamrtsp ✅
  • GetImage resize livestreams ✅
    • webcam ✅
    • fake cam ✅
    • viamrtsp ✅
  • Passthrough->GetImage resize livestream ✅
    • fake cam ✅
    • viamrtsp ✅

Demo

  • viamrtsp passthrough -> resize getimage stream via SetStreamOptions
Screen.Recording.2024-11-12.at.2.05.18.PM.mov

TODO:

  • Test that video tracks survive SetSteamOptions swap.
  • Make sure we are safe wrt locking strategy.
  • Resize state transition from passthrough -> get_image.
  • Figure out how to test stream state transition for passthrough -> get_image.
  • Figure out how to prevent passthrough attempts when in resized mode.
  • Make sure resizes are working with viamrtsp camera source.

@seanavery seanavery marked this pull request as draft November 5, 2024 21:12
@viambot viambot added the safe to test This pull request is marked safe to test from a trusted zone label Nov 5, 2024
@seanavery seanavery changed the title RSDK-8982 - Add basic set stream options RSDK-8982 - Add SetStreamOptions to stream server Nov 5, 2024
@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 Nov 6, 2024
@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 Nov 7, 2024
@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 Nov 7, 2024
@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 Nov 7, 2024
@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 Nov 8, 2024
@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 Nov 8, 2024
@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 Nov 8, 2024
@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 Nov 11, 2024
@viambot viambot removed the safe to test This pull request is marked safe to test from a trusted zone label Nov 11, 2024
@seanavery seanavery marked this pull request as ready for review November 12, 2024 19:17
Copy link
Member

@hexbabe hexbabe left a comment

Choose a reason for hiding this comment

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

Stream state and server are still maturing concepts in my head. Tried my best to focus on code quality stuff and learned stuff about the former along the way :)

@@ -61,6 +64,7 @@ func New(
robot: r,
msgChan: make(chan msg),
tickChan: make(chan struct{}),
resized: false,
Copy link
Member

Choose a reason for hiding this comment

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

[nit suggestion] could we call this isResized or wasResized

the past-tense-as-a-bool is kinda overlapping with certain interface naming conventions e.g. resource.Named

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed. Changed to isResized.

return nil, errors.New("stream name is required")
}
if req.Resolution == nil {
return nil, errors.New("resolution is required")
Copy link
Member

Choose a reason for hiding this comment

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

Should we prefix these errors with what stream it's about i.e. include the stream name in the error message

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 name to error returns and more details where appropriate.

test.That(t, getStreamOptionsResp, test.ShouldNotBeNil)
test.That(t, len(getStreamOptionsResp.Resolutions), test.ShouldEqual, 5)

// Test setting stream options with invalid name
Copy link
Member

Choose a reason for hiding this comment

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

[potential improvement] Is it possible to use use t.Run instead of flat tests with comments? Unless we want the tests to be sequential and do not want independent isolated execution?

Copy link
Member Author

@seanavery seanavery Nov 13, 2024

Choose a reason for hiding this comment

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

Yeah good call - better debugging if things fail. I think t.Run's should still be sequential if under a parent test fn (we would have to manually call t.Parallel() in the subtest).

test.That(tb, videoCnt, test.ShouldEqual, 2)
})

// Try setting stream options with RTPPassthrough enabled
Copy link
Member

Choose a reason for hiding this comment

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

Is there a way to write a test to assert that RTP passthrough is off? Or is that inherently tested through using a non-standard res/the stream state test file?

Copy link
Member Author

Choose a reason for hiding this comment

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

Testing this specifically in state_test.go. This is just testing that the API call succeeds and track is still available.

<-writeRTPCalledCtx.Done()

// Call Resize to simulate a resize event. This should stop rtp_passthrough and start gostream.
logger.Info("calling Resize() should stop rtp_passthrough and start gostream")
Copy link
Member

Choose a reason for hiding this comment

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

Same here might be nice to use t.Run

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep should be ok to nest t.Run tests here.

@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 Nov 13, 2024
@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 Nov 13, 2024
@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 Nov 13, 2024
@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 Nov 13, 2024
@@ -336,6 +336,8 @@ func (server *Server) GetStreamOptions(
ctx context.Context,
req *streampb.GetStreamOptionsRequest,
) (*streampb.GetStreamOptionsResponse, error) {
server.mu.RLock()
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need to add this read lock?

Copy link
Member

Choose a reason for hiding this comment

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

The mutex is protecting these struct members:

	nameToStreamState       map[string]*state.StreamState
	activePeerStreams       map[*webrtc.PeerConnection]map[string]*peerState
	activeBackgroundWorkers sync.WaitGroup
	isAlive                 bool

I don't see them being accessed so I don't understand why this mutex is being taken

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 - I am just pulling the camera from robot not accessing any of these. Removed.

Comment on lines 383 to 394
if req.Name == "" {
return nil, errors.New("stream name is required in request")
}
if req.Resolution == nil {
return nil, fmt.Errorf("resolution is required to resize stream %q", req.Name)
}
if req.Resolution.Width <= 0 || req.Resolution.Height <= 0 {
return nil, fmt.Errorf(
"invalid resolution to resize stream %q: width (%d) and height (%d) must be greater than 0",
req.Name, req.Resolution.Width, req.Resolution.Height,
)
}
Copy link
Member

Choose a reason for hiding this comment

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

Lets move this into a validation function

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 - moved to helper.

Comment on lines 398 to 408
if err != nil {
return nil, fmt.Errorf("failed to resize video source for stream %q: %w", req.Name, err)
}
streamState, ok := server.nameToStreamState[req.Name]
if !ok {
return nil, fmt.Errorf("stream state not found with name %q", req.Name)
}
err = streamState.Resize()
if err != nil {
return nil, fmt.Errorf("failed to resize stream %q: %w", req.Name, err)
}
Copy link
Member

Choose a reason for hiding this comment

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

Could we move this logic into resizeVideoSource?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep -- moved all resize logic into resizeVideoSource.

@@ -185,6 +200,10 @@ func (state *StreamState) sourceEventHandler() {
if state.activeClients == 0 {
state.tick()
}
case msgTypeResize:
state.logger.Debug("resize event received")
state.isResized = true
Copy link
Member

Choose a reason for hiding this comment

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

Can we ever go back to not resized?

Comment on lines +766 to +767
t.Run("when in rtppassthrough mode and a resize occurs test downgrade path to gostream", func(t *testing.T) {
var startCount atomic.Int64
Copy link
Member

Choose a reason for hiding this comment

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

A lot of your tests are time based and waiting for assertions, can you run with -race and -failfast in canon to verify that they're not flaky.

Copy link
Member Author

Choose a reason for hiding this comment

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

go test -timeout 30s -race -failfast -run ^TestSetStreamOptions$ go.viam.com/rdk/robot/web/stream
ok go.viam.com/rdk/robot/web/stream 3.255s


go test -timeout 30s -race -failfast -run ^TestStreamState$/^when_in_rtppassthrough_mode_and_a_resize_occurs_test_downgrade_path_to_gostream$ go.viam.com/rdk/robot/web/stream/state
ok go.viam.com/rdk/robot/web/stream/state 3.056s

robot/web/stream/server.go Outdated Show resolved Hide resolved
@@ -271,7 +302,7 @@ func (state *StreamState) tick() {
case state.streamSource == streamSourcePassthrough:
// no op if we are using passthrough & are healthy
state.logger.Debug("still healthy and using h264 passthrough")
case state.streamSource == streamSourceGoStream:
case state.streamSource == streamSourceGoStream && !state.isResized:
Copy link
Member

Choose a reason for hiding this comment

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

What does a user do to hit this state after they've selected a resolution in the UI?

@seanavery seanavery changed the title RSDK-8982 - Add SetStreamOptions to stream server [RSDK-8982, RSDK-9167, RSDK-8979]- Add SetStreamOptions to stream server Nov 14, 2024
@seanavery seanavery changed the title [RSDK-8982, RSDK-9167, RSDK-8979]- Add SetStreamOptions to stream server [RSDK-8982, RSDK-9167, RSDK-8979] - Add SetStreamOptions to stream server Nov 14, 2024
@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 Nov 14, 2024
@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 Nov 14, 2024
@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 Nov 14, 2024
@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 Nov 15, 2024
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.

5 participants