diff --git a/dist/roverd-debian-laptop b/dist/roverd-debian-laptop index d8adec9a..4f1f0991 100755 Binary files a/dist/roverd-debian-laptop and b/dist/roverd-debian-laptop differ diff --git a/pi/bin/debian-laptop-video-publisher.sh b/pi/bin/debian-laptop-video-publisher.sh index 2e37c337..3038a010 100755 --- a/pi/bin/debian-laptop-video-publisher.sh +++ b/pi/bin/debian-laptop-video-publisher.sh @@ -58,6 +58,7 @@ load_env_file : "${ROVERD_VIDEO_ENABLE:?ROVERD_VIDEO_ENABLE not set in ${ENV_FILE}}" : "${ROVERD_VIDEO_PUBLISH_URL:?ROVERD_VIDEO_PUBLISH_URL not set in ${ENV_FILE}}" : "${ROVERD_VIDEO_DEVICE:?ROVERD_VIDEO_DEVICE not set in ${ENV_FILE}}" +: "${ROVERD_VIDEO_INPUT_FORMAT:?ROVERD_VIDEO_INPUT_FORMAT not set in ${ENV_FILE}}" : "${ROVERD_VIDEO_WIDTH:?ROVERD_VIDEO_WIDTH not set in ${ENV_FILE}}" : "${ROVERD_VIDEO_HEIGHT:?ROVERD_VIDEO_HEIGHT not set in ${ENV_FILE}}" : "${ROVERD_VIDEO_FPS:?ROVERD_VIDEO_FPS not set in ${ENV_FILE}}" @@ -74,6 +75,16 @@ if [[ -z "${ROVERD_VIDEO_DEVICE}" ]]; then exit 1 fi +INPUT_FORMAT_ARGS=() +if [[ -n "${ROVERD_VIDEO_INPUT_FORMAT}" ]]; then + # Many laptop webcams expose both compressed MJPEG and raw YUYV modes. The + # wrong negotiated format can still produce a decodable stream, but the + # picture appears as green/noisy mush because ffmpeg is interpreting the + # frame bytes with the wrong pixel format. Passing input_format pins V4L2 to + # the camera mode selected in roverd.yaml. + INPUT_FORMAT_ARGS=(-input_format "${ROVERD_VIDEO_INPUT_FORMAT}") +fi + if [[ -n "${FFMPEG_BIN:-}" ]]; then FFMPEG_BIN_PATH="$FFMPEG_BIN" elif command -v ffmpeg >/dev/null 2>&1; then @@ -99,6 +110,7 @@ run_pipeline() { -flags low_delay \ -thread_queue_size 4096 \ -f v4l2 \ + "${INPUT_FORMAT_ARGS[@]}" \ -framerate "${ROVERD_VIDEO_FPS}" \ -video_size "${ROVERD_VIDEO_WIDTH}x${ROVERD_VIDEO_HEIGHT}" \ -i "${ROVERD_VIDEO_DEVICE}" \ diff --git a/pi/install_roverd.sh b/pi/install_roverd.sh index 6a922aa6..13c6672e 100755 --- a/pi/install_roverd.sh +++ b/pi/install_roverd.sh @@ -424,6 +424,7 @@ ROVERD_VIDEO_ENABLE=1 ROVERD_VIDEO_PUBLISHER=pi-libcamera ROVERD_VIDEO_PUBLISH_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316 ROVERD_VIDEO_DEVICE= +ROVERD_VIDEO_INPUT_FORMAT= ROVERD_VIDEO_WIDTH=640 ROVERD_VIDEO_HEIGHT=480 ROVERD_VIDEO_FPS=30 @@ -450,6 +451,7 @@ ROVERD_VIDEO_ENABLE=1 ROVERD_VIDEO_PUBLISHER=debian-laptop-v4l2 ROVERD_VIDEO_PUBLISH_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316 ROVERD_VIDEO_DEVICE=/dev/video0 +ROVERD_VIDEO_INPUT_FORMAT=mjpeg ROVERD_VIDEO_WIDTH=640 ROVERD_VIDEO_HEIGHT=480 ROVERD_VIDEO_FPS=30 diff --git a/pi/roverd/config.go b/pi/roverd/config.go index caf1b153..e2b81030 100644 --- a/pi/roverd/config.go +++ b/pi/roverd/config.go @@ -93,17 +93,18 @@ type VideoMediaConfig struct { // Publisher selects the installed publisher script/pipeline family. The // first pass uses pi-libcamera for current rovers; laptop-v4l2 can be added // without changing the server-facing media shape again. - Enabled bool `yaml:"enabled" json:"enabled"` - Service string `yaml:"service" json:"service,omitempty"` - Publisher string `yaml:"publisher" json:"publisher,omitempty"` - PublishURL string `yaml:"publishUrl" json:"publishUrl,omitempty"` - Device string `yaml:"device" json:"device,omitempty"` - Width int `yaml:"width" json:"-"` - Height int `yaml:"height" json:"-"` - FPS int `yaml:"fps" json:"-"` - Bitrate int `yaml:"bitrate" json:"-"` - Inverted bool `yaml:"inverted" json:"-"` - SensorMode string `yaml:"sensorMode" json:"-"` + Enabled bool `yaml:"enabled" json:"enabled"` + Service string `yaml:"service" json:"service,omitempty"` + Publisher string `yaml:"publisher" json:"publisher,omitempty"` + PublishURL string `yaml:"publishUrl" json:"publishUrl,omitempty"` + Device string `yaml:"device" json:"device,omitempty"` + InputFormat string `yaml:"inputFormat" json:"-"` + Width int `yaml:"width" json:"-"` + Height int `yaml:"height" json:"-"` + FPS int `yaml:"fps" json:"-"` + Bitrate int `yaml:"bitrate" json:"-"` + Inverted bool `yaml:"inverted" json:"-"` + SensorMode string `yaml:"sensorMode" json:"-"` } type AudioCaptureConfig struct { @@ -450,6 +451,10 @@ func validateVideoMediaConfig(cfg *VideoMediaConfig, serverURL string, roverName if cfg.Publisher == "" { cfg.Publisher = "pi-libcamera" } + // V4L2 input formats are consumed by ffmpeg as lowercase names such as + // mjpeg or yuyv422. Normalizing here keeps the publisher script simple and + // makes hand-edited Debian laptop configs less sensitive to capitalization. + cfg.InputFormat = strings.ToLower(strings.TrimSpace(cfg.InputFormat)) if cfg.Width <= 0 { cfg.Width = 640 } diff --git a/pi/roverd/media_env.go b/pi/roverd/media_env.go index 81b35eae..a72dba03 100644 --- a/pi/roverd/media_env.go +++ b/pi/roverd/media_env.go @@ -36,6 +36,7 @@ func UpdatePublisherEnv(media MediaConfig) error { fmt.Fprintf(&buf, "ROVERD_VIDEO_PUBLISHER=%s\n", media.Video.Publisher) fmt.Fprintf(&buf, "ROVERD_VIDEO_PUBLISH_URL=%s\n", media.Video.PublishURL) fmt.Fprintf(&buf, "ROVERD_VIDEO_DEVICE=%s\n", media.Video.Device) + fmt.Fprintf(&buf, "ROVERD_VIDEO_INPUT_FORMAT=%s\n", media.Video.InputFormat) fmt.Fprintf(&buf, "ROVERD_VIDEO_WIDTH=%d\n", media.Video.Width) fmt.Fprintf(&buf, "ROVERD_VIDEO_HEIGHT=%d\n", media.Video.Height) fmt.Fprintf(&buf, "ROVERD_VIDEO_FPS=%d\n", media.Video.FPS) diff --git a/pi/roverd/roverd.debian-laptop.sample.yaml b/pi/roverd/roverd.debian-laptop.sample.yaml index 57b2a3c4..d4b2e750 100644 --- a/pi/roverd/roverd.debian-laptop.sample.yaml +++ b/pi/roverd/roverd.debian-laptop.sample.yaml @@ -31,6 +31,10 @@ media: service: debian-laptop-video-publisher.service publisher: debian-laptop-v4l2 device: /dev/video0 + # Check supported values with: + # v4l2-ctl --device=/dev/video0 --list-formats-ext + # Common working values are mjpeg and yuyv422. + inputFormat: mjpeg width: 640 height: 480 fps: 30