//go:build linux package v4l2 import ( "errors" "net/url" "strings" "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/h264/annexb" "github.com/AlexxIT/go2rtc/pkg/v4l2/device" "github.com/pion/rtp" ) type Producer struct { core.Connection dev *device.Device } func Open(rawURL string) (*Producer, error) { // Example (ffmpeg source compatible): // v4l2:device?video=/dev/video0&input_format=mjpeg&video_size=1280x720 u, err := url.Parse(rawURL) if err != nil { return nil, err } query := u.Query() dev, err := device.Open(query.Get("video")) if err != nil { return nil, err } codec := &core.Codec{ ClockRate: 90000, PayloadType: core.PayloadTypeRAW, } var width, height, pixFmt uint32 if wh := strings.Split(query.Get("video_size"), "x"); len(wh) == 2 { codec.FmtpLine = "width=" + wh[0] + ";height=" + wh[1] width = uint32(core.Atoi(wh[0])) height = uint32(core.Atoi(wh[1])) } switch query.Get("input_format") { case "yuyv422": if codec.FmtpLine == "" { return nil, errors.New("v4l2: invalid video_size") } codec.Name = core.CodecRAW codec.FmtpLine += ";colorspace=422" pixFmt = device.V4L2_PIX_FMT_YUYV case "nv12": if codec.FmtpLine == "" { return nil, errors.New("v4l2: invalid video_size") } codec.Name = core.CodecRAW codec.FmtpLine += ";colorspace=420mpeg2" // maybe 420jpeg pixFmt = device.V4L2_PIX_FMT_NV12 case "mjpeg": codec.Name = core.CodecJPEG pixFmt = device.V4L2_PIX_FMT_MJPEG case "h264": codec.Name = core.CodecH264 pixFmt = device.V4L2_PIX_FMT_H264 case "hevc": codec.Name = core.CodecH265 pixFmt = device.V4L2_PIX_FMT_HEVC default: return nil, errors.New("v4l2: invalid input_format") } if err = dev.SetFormat(width, height, pixFmt); err != nil { return nil, err } if fps := core.Atoi(query.Get("framerate")); fps > 0 { if err = dev.SetParam(uint32(fps)); err != nil { return nil, err } } medias := []*core.Media{ { Kind: core.KindVideo, Direction: core.DirectionRecvonly, Codecs: []*core.Codec{codec}, }, } return &Producer{ Connection: core.Connection{ ID: core.NewID(), FormatName: "v4l2", Medias: medias, }, dev: dev, }, nil } func (c *Producer) Start() error { if err := c.dev.StreamOn(); err != nil { return err } var bitstream bool switch c.Medias[0].Codecs[0].Name { case core.CodecH264, core.CodecH265: bitstream = true } for { buf, err := c.dev.Capture() if err != nil { return err } c.Recv += len(buf) if len(c.Receivers) == 0 { continue } if bitstream { buf = annexb.EncodeToAVCC(buf) } pkt := &rtp.Packet{ Header: rtp.Header{Timestamp: core.Now90000()}, Payload: buf, } c.Receivers[0].WriteRTP(pkt) } } func (c *Producer) Stop() error { _ = c.Connection.Stop() return errors.Join(c.dev.StreamOff(), c.dev.Close()) }