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 24 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.
  • If Resolution is provided, we dispatch 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.
  • If Resolution is not provided, we dispatch a Reset msg to the streamState handler.
    • Attempts to restart passthrough if available.
  • In order to unify RTP Sequence Number handling between GoStream and Passthrough paths we now override the Sequence Number header field in the WriteRTP method.

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 ✅
    • Gostream back Passthrough reset state transition ✅
  • Manual tests (using NewStreamServiceClient script as seen below):
    • 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 ✅
    • GetImage->Passthrough reset livestream ✅
      • fake cam ✅
      • viamrtsp ✅

Demos

  • viamrtsp passthrough -> resize getimage stream via SetStreamOptions
Screen.Recording.2024-11-12.at.2.05.18.PM.mov
  • passthrough <-> gostream getimage loop using resize and reset
passthrough_gostream_handoff.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.
  • Fix flaky handoff between Gostream and Passthrough RTP sources.

Test Script

package main

import (
	"context"
	"time"

	streampb "go.viam.com/api/stream/v1"
	rgrpc "go.viam.com/rdk/grpc"
	rdklogger "go.viam.com/rdk/logging"
	"go.viam.com/utils/rpc"
)

func main() {
	logger := rdklogger.NewLogger("main")
	addr := "..."
	keyID := "..."
	apiKey := "..."
	camName := "..."
	ctx := context.Background()
	conn, err := rgrpc.Dial(ctx, addr, logger, rpc.WithEntityCredentials(
		keyID,
		rpc.Credentials{
			Type:    rpc.CredentialsTypeAPIKey,
			Payload: apiKey,
		}))
	if err != nil {
		logger.Error("Failed to dial: %v", err)
		return
	}
	livestreamClient := streampb.NewStreamServiceClient(conn)
	if livestreamClient == nil {
		logger.Error("Failed to create stream service client")
		return
	}
	opts, err := livestreamClient.GetStreamOptions(ctx, &streampb.GetStreamOptionsRequest{
		Name: camName,
	})
	if err != nil {
		logger.Error("Failed to get stream options: %v", err)
		return
	}
	logger.Info("Stream options: %v", opts)
	for {
		for _, resolution := range opts.Resolutions {
			logger.Info("Resizing to %v", resolution)
			_, err = livestreamClient.SetStreamOptions(ctx, &streampb.SetStreamOptionsRequest{
				Name:       camName,
				Resolution: resolution,
			})
			if err != nil {
				logger.Error("Failed to set stream options: %v", err)
				return
			}
			time.Sleep(5 * time.Second)
			logger.Info("Resetting to default")
			_, err = livestreamClient.SetStreamOptions(ctx, &streampb.SetStreamOptionsRequest{
				Name: camName,
			})
			if err != nil {
				logger.Error("Failed to set stream options: %v", err)
				return
			}
			time.Sleep(5 * time.Second)
		}
	}
}

@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
@viambot viambot added the safe to test This pull request is marked safe to test from a trusted zone label 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?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for bringing this up -- the Reset path has been implemented to handle getting out of resized mode.

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

Implemented the Reset path for this. Not exactly sure how to handle this from a UI perspective -- may need to update the wire-diagram in the scope doc to include a Reset button.

@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
Comment on lines 764 to 781

// validateSetStreamOptionsRequest validates the request to set the stream options.
func validateSetStreamOptionsRequest(req *streampb.SetStreamOptionsRequest) (int, error) {
if req.Name == "" {
return optionsCommandUnknown, errors.New("stream name is required in request")
}
if req.Resolution == nil {
return optionsCommandReset, nil
}
if req.Resolution.Width <= 0 || req.Resolution.Height <= 0 {
return optionsCommandUnknown,
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,
)
}
return optionsCommandResize, nil
}
Copy link
Member

Choose a reason for hiding this comment

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

nit: put this above where it's used.

Copy link
Member Author

Choose a reason for hiding this comment

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

thanks, moved.

@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 20, 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 20, 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 21, 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