diff --git a/camera/camera.go b/camera/camera.go index 4a7cbd5..2e5a72b 100644 --- a/camera/camera.go +++ b/camera/camera.go @@ -22,6 +22,45 @@ type DeviceInfo struct { Name string } +// Info describes the negotiated capture format. +type Info struct { + Format string + Width int + Height int +} + +// fourccName returns a readable name for a V4L/VfW pixel format. +func fourccName(format uint32) string { + switch format { + case mjpgFourCC: + return "MJPEG" + case jpegFourCC: + return "JPEG" + case yuyvFourCC, yuy2FourCC: + return "YUYV" + case uyvyFourCC: + return "UYVY" + case yvyuFourCC: + return "YVYU" + case vyuyFourCC: + return "VYUY" + case nv12FourCC: + return "NV12" + case yu12FourCC: + return "YU12" + case yv12FourCC: + return "YV12" + case rgb24FourCC: + return "RGB24" + case bgr24FourCC: + return "BGR24" + case greyFourCC: + return "GREY" + } + + return string([]byte{byte(format), byte(format >> 8), byte(format >> 16), byte(format >> 24)}) +} + var ( yuy2FourCC = fourcc("YUY2") yuyvFourCC = fourcc("YUYV") diff --git a/camera/camera_darwin.go b/camera/camera_darwin.go index 0979128..ea9dc3a 100644 --- a/camera/camera_darwin.go +++ b/camera/camera_darwin.go @@ -100,6 +100,11 @@ func New(opts Options) (c *Camera, err error) { return c, nil } +// Info returns the negotiated capture format. +func (c *Camera) Info() Info { + return Info{Format: "BGRA", Width: int(c.opts.Width), Height: int(c.opts.Height)} +} + // Devices returns the available capture devices. func Devices() ([]DeviceInfo, error) { if err := loadFrameworks(); err != nil { diff --git a/camera/camera_linux.go b/camera/camera_linux.go index 2261cff..c44039b 100644 --- a/camera/camera_linux.go +++ b/camera/camera_linux.go @@ -215,6 +215,11 @@ func (c *Camera) Close() (err error) { return } +// Info returns the negotiated capture format. +func (c *Camera) Info() Info { + return Info{Format: fourccName(c.config.Format), Width: c.config.Width, Height: c.config.Height} +} + // Devices returns the available capture devices. func Devices() ([]DeviceInfo, error) { infos := v4l.FindDevices() diff --git a/camera/camera_test.go b/camera/camera_test.go index 96b3682..315c735 100644 --- a/camera/camera_test.go +++ b/camera/camera_test.go @@ -10,6 +10,10 @@ import ( ) func TestCamera(t *testing.T) { + if testing.Short() { + t.Skip("skipping camera test in short mode") + } + camera, err := New(Options{0, 0, "", 640, 480, false, ""}) if err != nil { t.Skipf("no camera available: %v", err) diff --git a/camera/camera_windows_msmf.go b/camera/camera_windows_msmf.go index 5f5121d..b417118 100644 --- a/camera/camera_windows_msmf.go +++ b/camera/camera_windows_msmf.go @@ -449,6 +449,11 @@ func nv12ToYCbCr420(data []byte, stride, width, height int, dst *image.YCbCr) { } } +// Info returns the negotiated capture format. +func (c *Camera) Info() Info { + return Info{Format: "NV12", Width: c.width, Height: c.height} +} + // Devices returns the available capture devices. func Devices() ([]DeviceInfo, error) { runtime.LockOSThread() diff --git a/camera/camera_windows_vfw.go b/camera/camera_windows_vfw.go index 41106b3..6b32125 100644 --- a/camera/camera_windows_vfw.go +++ b/camera/camera_windows_vfw.go @@ -25,6 +25,8 @@ type Camera struct { className string format uint32 bpp int + width int + height int } // New returns new Camera for given camera index. @@ -145,6 +147,9 @@ func (c *Camera) run(ready chan<- error) { height = -height } + c.width = width + c.height = height + rect := image.Rect(0, 0, width, height) switch { @@ -483,6 +488,16 @@ func capCreateCaptureWindow(lpszWindowName string, dwStyle, x, y, width, height return syscall.Handle(ret), nil } +// Info returns the negotiated capture format. +func (c *Camera) Info() Info { + format := fourccName(c.format) + if c.format == 0 { + format = fmt.Sprintf("RGB%d", c.bpp*8) + } + + return Info{Format: format, Width: c.width, Height: c.height} +} + // Devices returns the available capture devices. func Devices() ([]DeviceInfo, error) { var devices []DeviceInfo diff --git a/cmd/cam2ip/main.go b/cmd/cam2ip/main.go index de2ce5d..160cf90 100644 --- a/cmd/cam2ip/main.go +++ b/cmd/cam2ip/main.go @@ -139,7 +139,13 @@ func main() { defer srv.Reader.Close() - stderr("%s %s listening on %s\n", name, version, srv.Bind) + info := cam.Info() + desc := fmt.Sprintf("%dx%d %s", info.Width, info.Height, info.Format) + if dn := deviceName(srv.Index); dn != "" { + desc = dn + ", " + desc + } + + stderr("%s %s [%s] listening on %s\n", name, version, desc, srv.Bind) err = srv.ListenAndServe() if err != nil { @@ -152,6 +158,22 @@ func stderr(format string, a ...any) { _, _ = fmt.Fprintf(os.Stderr, format, a...) } +// deviceName returns the name of the camera at the given index, or "" if unknown. +func deviceName(index int) string { + devices, err := camera.Devices() + if err != nil { + return "" + } + + for _, d := range devices { + if d.Index == index { + return d.Name + } + } + + return "" +} + // deviceIndex returns the index of the first camera whose name contains the query. func deviceIndex(name string) (int, error) { devices, err := camera.Devices()