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-9573] Autodetect height and width of source cam in video store #32

Merged
merged 3 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ On the new component panel, copy and paste the following attribute template into
}
}
```

Additionally, make sure to add your configured data manager service to the `depends_on` array of your `video-store` component.
hexbabe marked this conversation as resolved.
Show resolved Hide resolved

> For more information, see [Configure a Machine](https://docs.viam.com/manage/configuration/).

### Attributes
Expand All @@ -71,9 +74,9 @@ On the new component panel, copy and paste the following attribute template into
| | `bitrate` | integer | optional | Throughput of encoder in bits per second. Higher for better quality video, and lower for better storage efficiency. |
| | `preset` | string | optional | Name of codec video preset to use. See [here](https://trac.ffmpeg.org/wiki/Encode/H.264#a2.Chooseapresetandtune) for preset options. |
| `cam_props` | | object | required | |
| | `width` | integer | required | Width of the source camera frames in pixels. |
| | `height` | integer | required | Height of the source camera frames in pixels. |
| | `framerate` | integer | required | Number of frames per second provided by the source camera. |
| | `width` | integer | optional | Width of the source camera frames in pixels. If unspecified, will try to autodetect by fetching a frame from the source camera. |
| | `height` | integer | optional | Height of the source camera frames in pixels. If unspecified, will try to autodetect by fetching a frame from the source camera. |
| | `framerate` | integer | optional | Number of frames per second provided by the source camera. Default is 20. |

### Example Configuration

Expand Down
43 changes: 33 additions & 10 deletions cam/cam.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var Model = resource.ModelNamespace("viam").WithFamily("video").WithModel("stora

const (
// Default values for the video storage camera component.
defaultFramerate = 20 // frames per second
defaultSegmentSeconds = 30 // seconds
defaultStorageSize = 10 // GB
defaultVideoCodec = codecH264
Expand All @@ -36,11 +37,12 @@ const (
defaultStoragePath = ".viam/video-storage"
defaultLogLevel = "error"

maxGRPCSize = 1024 * 1024 * 32 // bytes
deleterInterval = 10 // minutes
retryInterval = 1 // seconds
asyncTimeout = 60 // seconds
tempPath = "/tmp"
maxGRPCSize = 1024 * 1024 * 32 // bytes
deleterInterval = 10 // minutes
retryInterval = 1 // seconds
asyncTimeout = 60 // seconds
numFetchFrameAttempts = 3 // iterations
tempPath = "/tmp"
)

type videostore struct {
Expand Down Expand Up @@ -107,10 +109,6 @@ func (cfg *Config) Validate(path string) ([]string, error) {
if cfg.Sync == "" {
return nil, utils.NewConfigValidationFieldRequiredError(path, "sync")
}
// TODO(seanp): Remove once camera properties are returned from camera component.
if cfg.Properties == (cameraProperties{}) {
return nil, utils.NewConfigValidationFieldRequiredError(path, "cam_props")
}

return []string{cfg.Camera}, nil
}
Expand All @@ -125,7 +123,7 @@ func init() {
}

func newvideostore(
_ context.Context,
ctx context.Context,
deps resource.Dependencies,
conf resource.Config,
logger logging.Logger,
Expand Down Expand Up @@ -166,6 +164,31 @@ func newvideostore(
if newConf.Video.Format != "" {
format = newConf.Video.Format
}

if newConf.Properties.Width == 0 && newConf.Properties.Height == 0 {
vs.logger.Info("received unspecified frame width and height, fetching frame to get dimensions")
for range make([]struct{}, numFetchFrameAttempts) {
frame, err := camera.DecodeImageFromCamera(ctx, rutils.MimeTypeJPEG, nil, vs.cam)
if err != nil {
vs.logger.Warn("failed to get and decode frame from camera, retrying. Error: ", err)
time.Sleep(retryInterval * time.Second)
continue
}
bounds := frame.Bounds()
newConf.Properties.Width = bounds.Dx()
newConf.Properties.Height = bounds.Dy()
vs.logger.Infof("received frame width and height: %d, %d", newConf.Properties.Width, newConf.Properties.Height)
break
}
}
if newConf.Properties.Width == 0 && newConf.Properties.Height == 0 {
return nil, fmt.Errorf("failed to get source camera width and height after %d attempts", numFetchFrameAttempts)
}

if newConf.Properties.Framerate == 0 {
newConf.Properties.Framerate = defaultFramerate
}

vs.enc, err = newEncoder(
logger,
codec,
Expand Down
8 changes: 3 additions & 5 deletions tests/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,16 +483,14 @@ func TestModuleConfiguration(t *testing.T) {
test.That(t, err.Error(), test.ShouldContainSubstring, "size_gb")
})

t.Run("Fails Configuration No CamProps", func(t *testing.T) {
t.Run("No CamProps succeeds with defaults", func(t *testing.T) {
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
r, err := setupViamServer(timeoutCtx, config5)
test.That(t, err, test.ShouldBeNil)
defer r.Close(timeoutCtx)
cam, err := camera.FromRobot(r, videoStoreComponentName)
test.That(t, err, test.ShouldNotBeNil)
test.That(t, cam, test.ShouldBeNil)
test.That(t, err.Error(), test.ShouldContainSubstring, "cam_props")
_, err = camera.FromRobot(r, videoStoreComponentName)
test.That(t, err, test.ShouldBeNil)
})

t.Run("Fails Configuration No DataManager", func(t *testing.T) {
Expand Down
Loading