From 8ecaabfce9b563f3fb6a0025531add7786865338 Mon Sep 17 00:00:00 2001 From: Alex X Date: Mon, 16 Dec 2024 20:24:45 +0300 Subject: [PATCH 01/26] Add support VIGI cameras #1470 --- internal/tapo/tapo.go | 4 ++ pkg/tapo/client.go | 106 +++++++++++++++++++++++++++++------------- pkg/tapo/producer.go | 2 +- 3 files changed, 79 insertions(+), 33 deletions(-) diff --git a/internal/tapo/tapo.go b/internal/tapo/tapo.go index 724c9e86..88eff5c4 100644 --- a/internal/tapo/tapo.go +++ b/internal/tapo/tapo.go @@ -15,4 +15,8 @@ func Init() { streams.HandleFunc("tapo", func(source string) (core.Producer, error) { return tapo.Dial(source) }) + + streams.HandleFunc("vigi", func(source string) (core.Producer, error) { + return tapo.Dial(source) + }) } diff --git a/pkg/tapo/client.go b/pkg/tapo/client.go index 3585011c..6ccafe4e 100644 --- a/pkg/tapo/client.go +++ b/pkg/tapo/client.go @@ -27,7 +27,7 @@ import ( type Client struct { core.Listener - url string + url *url.URL medias []*core.Media receivers []*core.Receiver @@ -52,17 +52,15 @@ type cbcMode interface { SetIV([]byte) } -func Dial(url string) (*Client, error) { - var err error - c := &Client{url: url} - if c.conn1, err = c.newConn(); err != nil { - return nil, err - } - return c, nil -} - -func (c *Client) newConn() (net.Conn, error) { - u, err := url.Parse(c.url) +// Dial support different urls: +// - tapo://{cloud-password}@192.168.1.123 - auth to Tapo cameras +// with cloud password (autodetect hash method) +// - tapo://admin:{hashed-cloud-password}@192.168.1.123 - auth to Tapo cameras +// with pre-hashed cloud password +// - vigi://admin:{password}@192.168.1.123 - auth to Vigi cameras with password +// for admin account (other not supported) +func Dial(rawURL string) (*Client, error) { + u, err := url.Parse(rawURL) if err != nil { return nil, err } @@ -71,21 +69,31 @@ func (c *Client) newConn() (net.Conn, error) { u.Host += ":8800" } - req, err := http.NewRequest("POST", "http://"+u.Host+"/stream", nil) + c := &Client{url: u} + if c.conn1, err = c.newConn(); err != nil { + return nil, err + } + return c, nil +} + +func (c *Client) newConn() (net.Conn, error) { + req, err := http.NewRequest("POST", "http://"+c.url.Host+"/stream", nil) if err != nil { return nil, err } - query := u.Query() + query := c.url.Query() if deviceId := query.Get("deviceId"); deviceId != "" { req.URL.RawQuery = "deviceId=" + deviceId } - req.URL.User = u.User req.Header.Set("Content-Type", "multipart/mixed; boundary=--client-stream-boundary--") - conn, res, err := dial(req) + username := c.url.User.Username() + password, _ := c.url.User.Password() + + conn, res, err := dial(req, c.url.Scheme, username, password) if err != nil { return nil, err } @@ -95,7 +103,7 @@ func (c *Client) newConn() (net.Conn, error) { } if c.decrypt == nil { - c.newDectypter(res) + c.newDectypter(res, c.url.Scheme, username, password) } channel := query.Get("channel") @@ -119,14 +127,18 @@ func (c *Client) newConn() (net.Conn, error) { return conn, nil } -func (c *Client) newDectypter(res *http.Response) { - username := res.Request.URL.User.Username() - password, _ := res.Request.URL.User.Password() +func (c *Client) newDectypter(res *http.Response, brand, username, password string) { + exchange := res.Header.Get("Key-Exchange") + nonce := core.Between(exchange, `nonce="`, `"`) - // extract nonce from response - // cipher="AES_128_CBC" username="admin" padding="PKCS7_16" algorithm="MD5" nonce="***" - nonce := res.Header.Get("Key-Exchange") - nonce = core.Between(nonce, `nonce="`, `"`) + if brand == "tapo" && password == "" { + if strings.Contains(exchange, `encrypt_type="3"`) { + password = fmt.Sprintf("%32X", sha256.Sum256([]byte(username))) + } else { + password = fmt.Sprintf("%16X", md5.Sum([]byte(username))) + } + username = "admin" + } key := md5.Sum([]byte(nonce + ":" + password)) iv := md5.Sum([]byte(username + ":" + nonce)) @@ -263,16 +275,12 @@ func (c *Client) Request(conn net.Conn, body []byte) (string, error) { } } -func dial(req *http.Request) (net.Conn, *http.Response, error) { +func dial(req *http.Request, brand, username, password string) (net.Conn, *http.Response, error) { conn, err := net.DialTimeout("tcp", req.URL.Host, core.ConnDialTimeout) if err != nil { return nil, nil, err } - username := req.URL.User.Username() - password, _ := req.URL.User.Password() - req.URL.User = nil - if err = req.Write(conn); err != nil { return nil, nil, err } @@ -291,7 +299,7 @@ func dial(req *http.Request) (net.Conn, *http.Response, error) { return nil, nil, fmt.Errorf("Expected StatusCode to be %d, received %d", http.StatusUnauthorized, res.StatusCode) } - if password == "" { + if brand == "tapo" && password == "" { // support cloud password in place of username if strings.Contains(auth, `encrypt_type="3"`) { password = fmt.Sprintf("%32X", sha256.Sum256([]byte(username))) @@ -299,6 +307,8 @@ func dial(req *http.Request) (net.Conn, *http.Response, error) { password = fmt.Sprintf("%16X", md5.Sum([]byte(username))) } username = "admin" + } else if brand == "vigi" && username == "admin" { + password = securityEncode(password) } realm := tcp.Between(auth, `realm="`, `"`) @@ -331,7 +341,39 @@ func dial(req *http.Request) (net.Conn, *http.Response, error) { return nil, nil, err } - req.URL.User = url.UserPassword(username, password) - return conn, res, nil } + +const ( + keyShort = "RDpbLfCPsJZ7fiv" + keyLong = "yLwVl0zKqws7LgKPRQ84Mdt708T1qQ3Ha7xv3H7NyU84p21BriUWBU43odz3iP4rBL3cD02KZciXTysVXiV8ngg6vL48rPJyAUw0HurW20xqxv9aYb4M9wK1Ae0wlro510qXeU07kV57fQMc8L6aLgMLwygtc0F10a0Dg70TOoouyFhdysuRMO51yY5ZlOZZLEal1h0t9YQW0Ko7oBwmCAHoic4HYbUyVeU3sfQ1xtXcPcf1aT303wAQhv66qzW" +) + +func securityEncode(s string) string { + size := len(s) + + var n int // max + if size > len(keyShort) { + n = size + } else { + n = len(keyShort) + } + + b := make([]byte, n) + + for i := 0; i < n; i++ { + c1 := 187 + c2 := 187 + if i >= size { + c1 = int(keyShort[i]) + } else if i >= len(keyShort) { + c2 = int(s[i]) + } else { + c1 = int(keyShort[i]) + c2 = int(s[i]) + } + b[i] = keyLong[(c1^c2)%len(keyLong)] + } + + return string(b) +} diff --git a/pkg/tapo/producer.go b/pkg/tapo/producer.go index 7d66d907..87a91ff5 100644 --- a/pkg/tapo/producer.go +++ b/pkg/tapo/producer.go @@ -77,7 +77,7 @@ func (c *Client) Stop() error { func (c *Client) MarshalJSON() ([]byte, error) { info := &core.Connection{ ID: core.ID(c), - FormatName: "tapo", + FormatName: c.url.Scheme, Protocol: "http", Medias: c.medias, Recv: c.recv, From 3a50b3678d132f301fe53de1be7ba8054665c4e4 Mon Sep 17 00:00:00 2001 From: Alex Cortelyou <1689668+acortelyou@users.noreply.github.com> Date: Mon, 23 Dec 2024 23:43:39 -0800 Subject: [PATCH 02/26] Extend onvif server to support Unifi Protect --- internal/onvif/init.go | 13 ++++++ pkg/onvif/server.go | 100 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 103 insertions(+), 10 deletions(-) diff --git a/internal/onvif/init.go b/internal/onvif/init.go index 014c5e18..b8b4fca6 100644 --- a/internal/onvif/init.go +++ b/internal/onvif/init.go @@ -70,6 +70,10 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) { // important for Hass: Media section res = onvif.GetCapabilitiesResponse(r.Host) + case onvif.ActionGetServices: + // important for Unifi: Media section + res = onvif.GetServicesResponse(r.Host) + case onvif.ActionGetSystemDateAndTime: // important for Hass res = onvif.GetSystemDateAndTimeResponse() @@ -95,8 +99,13 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) { case onvif.ActionGetProfiles: // important for Hass: H264 codec, width, height + // important for Unifi: framerate, bitrate, quality res = onvif.GetProfilesResponse(streams.GetAll()) + case onvif.ActionGetVideoSources: + // important for Unifi: framerate, resolution + res = onvif.GetVideoSourcesResponse(streams.GetAll()) + case onvif.ActionGetStreamUri: host, _, err := net.SplitHostPort(r.Host) if err != nil { @@ -107,6 +116,10 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) { uri := "rtsp://" + host + ":" + rtsp.Port + "/" + onvif.FindTagValue(b, "ProfileToken") res = onvif.GetStreamUriResponse(uri) + case onvif.ActionGetSnapshotUri: + uri := "http://" + r.Host + "/api/frame.jpeg?src=" + onvif.FindTagValue(b, "ProfileToken") + res = onvif.GetSnapshotUriResponse(uri) + default: http.Error(w, "unsupported action", http.StatusBadRequest) log.Debug().Msgf("[onvif] unsupported request:\n%s", b) diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go index f8f2883c..df53dfab 100644 --- a/pkg/onvif/server.go +++ b/pkg/onvif/server.go @@ -16,6 +16,7 @@ const ( ActionGetServiceCapabilities = "GetServiceCapabilities" ActionGetProfiles = "GetProfiles" ActionGetStreamUri = "GetStreamUri" + ActionGetSnapshotUri = "GetSnapshotUri" ActionSystemReboot = "SystemReboot" ActionGetServices = "GetServices" @@ -65,6 +66,32 @@ func GetCapabilitiesResponse(host string) string { ` } +func GetServicesResponse(host string) string { + return ` + + + + + http://www.onvif.org/ver10/device/wsdl + http://` + host + `/onvif/device_service + + 2 + 5 + + + + http://www.onvif.org/ver10/media/wsdl + http://` + host + `/onvif/media_service + + 2 + 5 + + + + +` +} + func GetSystemDateAndTimeResponse() string { loc := time.Now() utc := loc.UTC() @@ -142,7 +169,7 @@ func GetServiceCapabilitiesResponse() string { - + @@ -171,14 +198,27 @@ func GetProfilesResponse(names []string) string { for i, name := range names { buf.WriteString(` - ` + name + ` - - H264 - - 1920 - 1080 - - + ` + name + ` + + ` + name + ` + H264 + + 1920 + 1080 + + + 29.97003 + 1 + 5000 + + 4 + PT1000S + + + ` + name + ` + ` + strconv.Itoa(i) + ` + + `) } @@ -190,15 +230,55 @@ func GetProfilesResponse(names []string) string { return buf.String() } + +func GetVideoSourcesResponse(names []string) string { + buf := bytes.NewBuffer(nil) + buf.WriteString(` + + + `) + + for i, _ := range names { + buf.WriteString(` + + 29.97003 + + 1920 + 1080 + + `) + } + + buf.WriteString(` + + +`) + + return buf.String() +} + func GetStreamUriResponse(uri string) string { return ` - ` + uri + ` + ` + uri + ` ` } + +func GetSnapshotUriResponse(uri string) string { + return ` + + + + + ` + uri + ` + + + +` +} From 159d9425a732eedef06a9dd797ec6a284339a1b6 Mon Sep 17 00:00:00 2001 From: Alex Cortelyou <1689668+acortelyou@users.noreply.github.com> Date: Tue, 24 Dec 2024 11:08:18 -0800 Subject: [PATCH 03/26] Remove non-essential fields --- pkg/onvif/server.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go index df53dfab..e2d56556 100644 --- a/pkg/onvif/server.go +++ b/pkg/onvif/server.go @@ -208,11 +208,8 @@ func GetProfilesResponse(names []string) string { 29.97003 - 1 5000 - 4 - PT1000S ` + name + ` @@ -241,7 +238,6 @@ func GetVideoSourcesResponse(names []string) string { for i, _ := range names { buf.WriteString(` - 29.97003 1920 1080 From 0d6b8fc6fc207c5c89c3cf0923b0cbd38f04ad1b Mon Sep 17 00:00:00 2001 From: Alex X Date: Sun, 29 Dec 2024 11:44:56 +0300 Subject: [PATCH 04/26] Fix OPUS/48000/1 for RTSP from some cameras #1506 --- pkg/rtsp/helpers.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/rtsp/helpers.go b/pkg/rtsp/helpers.go index 6b07342d..346ecf73 100644 --- a/pkg/rtsp/helpers.go +++ b/pkg/rtsp/helpers.go @@ -70,8 +70,15 @@ func UnmarshalSDP(rawSDP []byte) ([]*core.Media, error) { // Check buggy SDP with fmtp for H264 on another track // https://github.com/AlexxIT/WebRTC/issues/419 for _, codec := range media.Codecs { - if codec.Name == core.CodecH264 && codec.FmtpLine == "" { - codec.FmtpLine = findFmtpLine(codec.PayloadType, sd.MediaDescriptions) + switch codec.Name { + case core.CodecH264: + if codec.FmtpLine == "" { + codec.FmtpLine = findFmtpLine(codec.PayloadType, sd.MediaDescriptions) + } + case core.CodecOpus: + // fix OPUS for some cameras https://datatracker.ietf.org/doc/html/rfc7587 + codec.ClockRate = 48000 + codec.Channels = 2 } } From a3f084dcde33b9fd341fb3ae00c0d7f6ca7210e9 Mon Sep 17 00:00:00 2001 From: Alex X Date: Sun, 29 Dec 2024 22:37:04 +0300 Subject: [PATCH 05/26] RTMP server enhancement to support OpenIPC cameras --- pkg/flv/producer.go | 42 +++++++++++++++++++++++++++--------------- pkg/rtmp/server.go | 13 +++++++------ 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/pkg/flv/producer.go b/pkg/flv/producer.go index 66755217..7535a8a4 100644 --- a/pkg/flv/producer.go +++ b/pkg/flv/producer.go @@ -140,23 +140,29 @@ func (c *Producer) probe() error { // 1. Empty video/audio flag // 2. MedaData without stereo key for AAC // 3. Audio header after Video keyframe tag - waitType := []byte{TagData} - timeout := time.Now().Add(core.ProbeTimeout) - for len(waitType) != 0 && time.Now().Before(timeout) { + // OpenIPC camera sends: + // 1. Empty video/audio flag + // 2. No MetaData packet + // 3. Sends a video packet in more than 3 seconds + waitVideo := true + waitAudio := true + timeout := time.Now().Add(time.Second * 5) + + for (waitVideo || waitAudio) && time.Now().Before(timeout) { pkt, err := c.readPacket() if err != nil { return err } - if i := bytes.IndexByte(waitType, pkt.PayloadType); i < 0 { - continue - } else { - waitType = append(waitType[:i], waitType[i+1:]...) - } + //log.Printf("%d %0.20s", pkt.PayloadType, pkt.Payload) switch pkt.PayloadType { case TagAudio: + if !waitAudio { + continue + } + _ = pkt.Payload[1] // bounds codecID := pkt.Payload[0] >> 4 // SoundFormat @@ -179,8 +185,13 @@ func (c *Producer) probe() error { Codecs: []*core.Codec{codec}, } c.Medias = append(c.Medias, media) + waitAudio = false case TagVideo: + if !waitVideo { + continue + } + var codec *core.Codec if isExHeader(pkt.Payload) { @@ -213,19 +224,20 @@ func (c *Producer) probe() error { Codecs: []*core.Codec{codec}, } c.Medias = append(c.Medias, media) + waitVideo = false case TagData: if !bytes.Contains(pkt.Payload, []byte("onMetaData")) { - waitType = append(waitType, TagData) + continue } // Dahua cameras doesn't send videocodecid - if bytes.Contains(pkt.Payload, []byte("videocodecid")) || - bytes.Contains(pkt.Payload, []byte("width")) || - bytes.Contains(pkt.Payload, []byte("framerate")) { - waitType = append(waitType, TagVideo) + if !bytes.Contains(pkt.Payload, []byte("videocodecid")) && + !bytes.Contains(pkt.Payload, []byte("width")) && + !bytes.Contains(pkt.Payload, []byte("framerate")) { + waitVideo = false } - if bytes.Contains(pkt.Payload, []byte("audiocodecid")) { - waitType = append(waitType, TagAudio) + if !bytes.Contains(pkt.Payload, []byte("audiocodecid")) { + waitAudio = false } } } diff --git a/pkg/rtmp/server.go b/pkg/rtmp/server.go index ed727b98..3dcd4048 100644 --- a/pkg/rtmp/server.go +++ b/pkg/rtmp/server.go @@ -117,10 +117,6 @@ func (c *Conn) acceptCommand(b []byte) error { } } - if c.App == "" { - return fmt.Errorf("rtmp: read command %x", b) - } - payload := amf.EncodeItems( "_result", tID, map[string]any{"fmsVer": "FMS/3,0,1,123"}, @@ -129,9 +125,16 @@ func (c *Conn) acceptCommand(b []byte) error { return c.writeMessage(3, TypeCommand, 0, payload) case CommandReleaseStream: + // if app is empty - will use key as app + if c.App == "" && len(items) == 4 { + c.App, _ = items[3].(string) + } + payload := amf.EncodeItems("_result", tID, nil) return c.writeMessage(3, TypeCommand, 0, payload) + case CommandFCPublish: // no response + case CommandCreateStream: payload := amf.EncodeItems("_result", tID, nil, 1) return c.writeMessage(3, TypeCommand, 0, payload) @@ -140,8 +143,6 @@ func (c *Conn) acceptCommand(b []byte) error { c.Intent = cmd c.streamID = 1 - case CommandFCPublish: // no response - default: println("rtmp: unknown command: " + cmd) } From b8303b9a22e1727b9a4db8979b6177f5e13dd35c Mon Sep 17 00:00:00 2001 From: Alex Cortelyou <1689668+acortelyou@users.noreply.github.com> Date: Sun, 29 Dec 2024 16:16:49 -0800 Subject: [PATCH 06/26] Remove optional fields, normalize indentation --- pkg/onvif/server.go | 66 ++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go index e2d56556..bc3f8ffe 100644 --- a/pkg/onvif/server.go +++ b/pkg/onvif/server.go @@ -46,23 +46,23 @@ func GetRequestAction(b []byte) string { func GetCapabilitiesResponse(host string) string { return ` - - - - - http://` + host + `/onvif/device_service - - - http://` + host + `/onvif/media_service - - false - false - true - - - - - + + + + + http://` + host + `/onvif/device_service + + + http://` + host + `/onvif/media_service + + false + false + true + + + + + ` } @@ -197,31 +197,29 @@ func GetProfilesResponse(names []string) string { for i, name := range names { buf.WriteString(` - - ` + name + ` - + + ` + name + ` + ` + name + ` - H264 - - 1920 + H264 + + 1920 1080 - - 29.97003 - 5000 + - + ` + name + ` ` + strconv.Itoa(i) + ` - `) + `) } buf.WriteString(` - - + + `) return buf.String() @@ -233,11 +231,11 @@ func GetVideoSourcesResponse(names []string) string { buf.WriteString(` - `) + `) for i, _ := range names { buf.WriteString(` - + 1920 1080 @@ -246,8 +244,8 @@ func GetVideoSourcesResponse(names []string) string { } buf.WriteString(` - - + + `) return buf.String() From cf88bf9c23e7196cec60dc62644f12b2f20d8083 Mon Sep 17 00:00:00 2001 From: Alex Cortelyou <1689668+acortelyou@users.noreply.github.com> Date: Sun, 29 Dec 2024 16:22:49 -0800 Subject: [PATCH 07/26] Remove inaccurate comments --- internal/onvif/init.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/onvif/init.go b/internal/onvif/init.go index b8b4fca6..e5ed9a7c 100644 --- a/internal/onvif/init.go +++ b/internal/onvif/init.go @@ -71,7 +71,6 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) { res = onvif.GetCapabilitiesResponse(r.Host) case onvif.ActionGetServices: - // important for Unifi: Media section res = onvif.GetServicesResponse(r.Host) case onvif.ActionGetSystemDateAndTime: @@ -99,11 +98,9 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) { case onvif.ActionGetProfiles: // important for Hass: H264 codec, width, height - // important for Unifi: framerate, bitrate, quality res = onvif.GetProfilesResponse(streams.GetAll()) case onvif.ActionGetVideoSources: - // important for Unifi: framerate, resolution res = onvif.GetVideoSourcesResponse(streams.GetAll()) case onvif.ActionGetStreamUri: From f601c4721833eb9fbb45dba7874890cab01cf974 Mon Sep 17 00:00:00 2001 From: Alex X Date: Mon, 30 Dec 2024 22:34:08 +0300 Subject: [PATCH 08/26] Improve ONVIF server --- examples/onvif_client/main.go | 72 +++++ internal/onvif/README.md | 25 ++ internal/onvif/{init.go => onvif.go} | 80 ++--- pkg/onvif/README.md | 38 +++ pkg/onvif/client.go | 113 ++----- pkg/onvif/envelope.go | 79 +++++ pkg/onvif/helpers.go | 23 ++ pkg/onvif/server.go | 422 +++++++++++++-------------- 8 files changed, 506 insertions(+), 346 deletions(-) create mode 100644 examples/onvif_client/main.go create mode 100644 internal/onvif/README.md rename internal/onvif/{init.go => onvif.go} (67%) create mode 100644 pkg/onvif/README.md create mode 100644 pkg/onvif/envelope.go diff --git a/examples/onvif_client/main.go b/examples/onvif_client/main.go new file mode 100644 index 00000000..03dd12ba --- /dev/null +++ b/examples/onvif_client/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "log" + "net" + "net/url" + "os" + + "github.com/AlexxIT/go2rtc/pkg/onvif" +) + +func main() { + var rawURL = os.Args[1] + var operation = os.Args[2] + var token string + if len(os.Args) > 3 { + token = os.Args[3] + } + + client, err := onvif.NewClient(rawURL) + if err != nil { + log.Panic(err) + } + + var b []byte + + switch operation { + case onvif.ServiceGetServiceCapabilities: + b, err = client.MediaRequest(operation) + case onvif.DeviceGetCapabilities, + onvif.DeviceGetDeviceInformation, + onvif.DeviceGetDiscoveryMode, + onvif.DeviceGetDNS, + onvif.DeviceGetHostname, + onvif.DeviceGetNetworkDefaultGateway, + onvif.DeviceGetNetworkInterfaces, + onvif.DeviceGetNetworkProtocols, + onvif.DeviceGetNTP, + onvif.DeviceGetScopes, + onvif.DeviceGetServices, + onvif.DeviceGetSystemDateAndTime, + onvif.DeviceSystemReboot: + b, err = client.DeviceRequest(operation) + case onvif.MediaGetProfiles, onvif.MediaGetVideoSources: + b, err = client.MediaRequest(operation) + case onvif.MediaGetProfile: + b, err = client.GetProfile(token) + case onvif.MediaGetVideoSourceConfiguration: + b, err = client.GetVideoSourceConfiguration(token) + case onvif.MediaGetStreamUri: + b, err = client.GetStreamUri(token) + case onvif.MediaGetSnapshotUri: + b, err = client.GetSnapshotUri(token) + default: + log.Printf("unknown action\n") + } + + if err != nil { + log.Printf("%s\n", err) + } + + u, err := url.Parse(rawURL) + if err != nil { + log.Fatal(err) + } + + host, _, _ := net.SplitHostPort(u.Host) + + if err = os.WriteFile(host+"_"+operation+".xml", b, 0644); err != nil { + log.Printf("%s\n", err) + } +} diff --git a/internal/onvif/README.md b/internal/onvif/README.md new file mode 100644 index 00000000..ee922fbf --- /dev/null +++ b/internal/onvif/README.md @@ -0,0 +1,25 @@ +# ONVIF + +A regular camera has a single video source (`GetVideoSources`) and two profiles (`GetProfiles`). + +Go2rtc has one video source and one profile per stream. + +## Tested clients + +Go2rtc works as ONVIF server: + +- Happytime onvif client (windows) +- Home Assistant ONVIF integration (linux) +- Onvier (android) +- ONVIF Device Manager (windows) + +PS. Support only TCP transport for RTSP protocol. UDP and HTTP transports - unsupported yet. + +## Tested cameras + +Go2rtc works as ONVIF client: + +- Dahua IPC-K42 +- OpenIPC +- Reolink RLC-520A +- TP-Link Tapo TC60 diff --git a/internal/onvif/init.go b/internal/onvif/onvif.go similarity index 67% rename from internal/onvif/init.go rename to internal/onvif/onvif.go index e5ed9a7c..d332ca38 100644 --- a/internal/onvif/init.go +++ b/internal/onvif/onvif.go @@ -55,55 +55,65 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) { return } - action := onvif.GetRequestAction(b) - if action == "" { + operation := onvif.GetRequestAction(b) + if operation == "" { http.Error(w, "malformed request body", http.StatusBadRequest) return } - log.Trace().Msgf("[onvif] %s", action) + log.Trace().Msgf("[onvif] server request %s %s:\n%s", r.Method, r.RequestURI, b) - var res string + switch operation { + case onvif.DeviceGetNetworkInterfaces, // important for Hass + onvif.DeviceGetSystemDateAndTime, // important for Hass + onvif.DeviceGetDiscoveryMode, + onvif.DeviceGetDNS, + onvif.DeviceGetHostname, + onvif.DeviceGetNetworkDefaultGateway, + onvif.DeviceGetNetworkProtocols, + onvif.DeviceGetNTP, + onvif.DeviceGetScopes: + b = onvif.StaticResponse(operation) - switch action { - case onvif.ActionGetCapabilities: + case onvif.DeviceGetCapabilities: // important for Hass: Media section - res = onvif.GetCapabilitiesResponse(r.Host) + b = onvif.GetCapabilitiesResponse(r.Host) - case onvif.ActionGetServices: - res = onvif.GetServicesResponse(r.Host) + case onvif.DeviceGetServices: + b = onvif.GetServicesResponse(r.Host) - case onvif.ActionGetSystemDateAndTime: - // important for Hass - res = onvif.GetSystemDateAndTimeResponse() - - case onvif.ActionGetNetworkInterfaces: - // important for Hass: none - res = onvif.GetNetworkInterfacesResponse() - - case onvif.ActionGetDeviceInformation: + case onvif.DeviceGetDeviceInformation: // important for Hass: SerialNumber (unique server ID) - res = onvif.GetDeviceInformationResponse("", "go2rtc", app.Version, r.Host) + b = onvif.GetDeviceInformationResponse("", "go2rtc", app.Version, r.Host) - case onvif.ActionGetServiceCapabilities: + case onvif.ServiceGetServiceCapabilities: // important for Hass - res = onvif.GetServiceCapabilitiesResponse() + // TODO: check path links to media + b = onvif.GetMediaServiceCapabilitiesResponse() - case onvif.ActionSystemReboot: - res = onvif.SystemRebootResponse() + case onvif.DeviceSystemReboot: + b = onvif.StaticResponse(operation) time.AfterFunc(time.Second, func() { os.Exit(0) }) - case onvif.ActionGetProfiles: + case onvif.MediaGetVideoSources: + b = onvif.GetVideoSourcesResponse(streams.GetAll()) + + case onvif.MediaGetProfiles: // important for Hass: H264 codec, width, height - res = onvif.GetProfilesResponse(streams.GetAll()) + b = onvif.GetProfilesResponse(streams.GetAll()) - case onvif.ActionGetVideoSources: - res = onvif.GetVideoSourcesResponse(streams.GetAll()) + case onvif.MediaGetProfile: + token := onvif.FindTagValue(b, "ProfileToken") + b = onvif.GetProfileResponse(token) - case onvif.ActionGetStreamUri: + case onvif.MediaGetVideoSourceConfiguration: + token := onvif.FindTagValue(b, "ConfigurationToken") + b = onvif.GetVideoSourceConfigurationResponse(token) + + case onvif.MediaGetStreamUri: host, _, err := net.SplitHostPort(r.Host) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -111,20 +121,22 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) { } uri := "rtsp://" + host + ":" + rtsp.Port + "/" + onvif.FindTagValue(b, "ProfileToken") - res = onvif.GetStreamUriResponse(uri) + b = onvif.GetStreamUriResponse(uri) - case onvif.ActionGetSnapshotUri: + case onvif.MediaGetSnapshotUri: uri := "http://" + r.Host + "/api/frame.jpeg?src=" + onvif.FindTagValue(b, "ProfileToken") - res = onvif.GetSnapshotUriResponse(uri) + b = onvif.GetSnapshotUriResponse(uri) default: - http.Error(w, "unsupported action", http.StatusBadRequest) + http.Error(w, "unsupported operation", http.StatusBadRequest) log.Debug().Msgf("[onvif] unsupported request:\n%s", b) return } + log.Trace().Msgf("[onvif] server response:\n%s", b) + w.Header().Set("Content-Type", "application/soap+xml; charset=utf-8") - if _, err = w.Write([]byte(res)); err != nil { + if _, err = w.Write(b); err != nil { log.Error().Err(err).Caller().Send() } } @@ -170,7 +182,7 @@ func apiOnvif(w http.ResponseWriter, r *http.Request) { } if l := log.Trace(); l.Enabled() { - b, _ := client.GetProfiles() + b, _ := client.MediaRequest(onvif.MediaGetProfiles) l.Msgf("[onvif] src=%s profiles:\n%s", src, b) } diff --git a/pkg/onvif/README.md b/pkg/onvif/README.md new file mode 100644 index 00000000..73267379 --- /dev/null +++ b/pkg/onvif/README.md @@ -0,0 +1,38 @@ +## Profiles + +- Profile A - For access control configuration +- Profile C - For door control and event management +- Profile S - For basic video streaming + - Video streaming and configuration +- Profile T - For advanced video streaming + - H.264 / H.265 video compression + - Imaging settings + - Motion alarm and tampering events + - Metadata streaming + - Bi-directional audio + +## Services + +https://www.onvif.org/profiles/specifications/ + +- https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl +- https://www.onvif.org/ver20/imaging/wsdl/imaging.wsdl +- https://www.onvif.org/ver10/media/wsdl/media.wsdl + +## TMP + +| | Dahua | Reolink | TP-Link | +|------------------------|---------|---------|---------| +| GetCapabilities | no auth | no auth | no auth | +| GetServices | no auth | no auth | no auth | +| GetServiceCapabilities | no auth | no auth | auth | +| GetSystemDateAndTime | no auth | no auth | no auth | +| GetNetworkInterfaces | auth | auth | auth | +| GetDeviceInformation | auth | auth | auth | +| GetProfiles | auth | auth | auth | +| GetScopes | auth | auth | auth | + +- Dahua - onvif://192.168.10.90:80 +- Reolink - onvif://192.168.10.92:8000 +- TP-Link - onvif://192.168.10.91:2020/onvif/device_service +- \ No newline at end of file diff --git a/pkg/onvif/client.go b/pkg/onvif/client.go index 97bfd8dc..cb6221e1 100644 --- a/pkg/onvif/client.go +++ b/pkg/onvif/client.go @@ -2,8 +2,6 @@ package onvif import ( "bytes" - "crypto/sha1" - "encoding/base64" "errors" "html" "io" @@ -12,8 +10,6 @@ import ( "regexp" "strings" "time" - - "github.com/AlexxIT/go2rtc/pkg/core" ) const PathDevice = "/onvif/device_service" @@ -41,7 +37,7 @@ func NewClient(rawURL string) (*Client, error) { client.deviceURL = baseURL + u.Path } - b, err := client.GetCapabilities() + b, err := client.DeviceRequest(DeviceGetCapabilities) if err != nil { return nil, err } @@ -95,7 +91,7 @@ func (c *Client) GetURI() (string, error) { } func (c *Client) GetName() (string, error) { - b, err := c.GetDeviceInformation() + b, err := c.DeviceRequest(DeviceGetDeviceInformation) if err != nil { return "", err } @@ -104,7 +100,7 @@ func (c *Client) GetName() (string, error) { } func (c *Client) GetProfilesTokens() ([]string, error) { - b, err := c.GetProfiles() + b, err := c.MediaRequest(MediaGetProfiles) if err != nil { return nil, err } @@ -127,86 +123,53 @@ func (c *Client) HasSnapshots() bool { return strings.Contains(string(b), `SnapshotUri="true"`) } -func (c *Client) GetCapabilities() ([]byte, error) { +func (c *Client) GetProfile(token string) ([]byte, error) { return c.Request( - c.deviceURL, - ` - All -`, + c.mediaURL, ``+token+``, ) } -func (c *Client) GetNetworkInterfaces() ([]byte, error) { - return c.Request( - c.deviceURL, ``, - ) -} - -func (c *Client) GetDeviceInformation() ([]byte, error) { - return c.Request( - c.deviceURL, ``, - ) -} - -func (c *Client) GetProfiles() ([]byte, error) { - return c.Request( - c.mediaURL, ``, - ) +func (c *Client) GetVideoSourceConfiguration(token string) ([]byte, error) { + return c.Request(c.mediaURL, ` + `+token+` +`) } func (c *Client) GetStreamUri(token string) ([]byte, error) { - return c.Request( - c.mediaURL, - ` + return c.Request(c.mediaURL, ` RTP-Unicast RTSP `+token+` -`, - ) +`) } func (c *Client) GetSnapshotUri(token string) ([]byte, error) { return c.Request( - c.imaginURL, - ` - `+token+` -`, - ) -} - -func (c *Client) GetSystemDateAndTime() ([]byte, error) { - return c.Request( - c.deviceURL, ``, + c.imaginURL, ``+token+``, ) } func (c *Client) GetServiceCapabilities() ([]byte, error) { // some cameras answer GetServiceCapabilities for media only for path = "/onvif/media" return c.Request( - c.mediaURL, ``, + c.mediaURL, ``, ) } -func (c *Client) SystemReboot() ([]byte, error) { - return c.Request( - c.deviceURL, ``, - ) +func (c *Client) DeviceRequest(operation string) ([]byte, error) { + if operation == DeviceGetServices { + operation = `true` + } else { + operation = `` + } + return c.Request(c.deviceURL, operation) } -func (c *Client) GetServices() ([]byte, error) { - return c.Request( - c.deviceURL, ` - true -`, - ) -} - -func (c *Client) GetScopes() ([]byte, error) { - return c.Request( - c.deviceURL, ``, - ) +func (c *Client) MediaRequest(operation string) ([]byte, error) { + operation = `` + return c.Request(c.mediaURL, operation) } func (c *Client) Request(url, body string) ([]byte, error) { @@ -214,35 +177,11 @@ func (c *Client) Request(url, body string) ([]byte, error) { return nil, errors.New("onvif: unsupported service") } - buf := bytes.NewBuffer(nil) - buf.WriteString( - ``, - ) - - if user := c.url.User; user != nil { - nonce := core.RandString(16, 36) - created := time.Now().UTC().Format(time.RFC3339Nano) - pass, _ := user.Password() - - h := sha1.New() - h.Write([]byte(nonce + created + pass)) - - buf.WriteString(` - - -` + user.Username() + ` -` + base64.StdEncoding.EncodeToString(h.Sum(nil)) + ` -` + base64.StdEncoding.EncodeToString([]byte(nonce)) + ` -` + created + ` - - -`) - } - - buf.WriteString(`` + body + ``) + e := NewEnvelopeWithUser(c.url.User) + e.Append(body) client := &http.Client{Timeout: time.Second * 5000} - res, err := client.Post(url, `application/soap+xml;charset=utf-8`, buf) + res, err := client.Post(url, `application/soap+xml;charset=utf-8`, bytes.NewReader(e.Bytes())) if err != nil { return nil, err } diff --git a/pkg/onvif/envelope.go b/pkg/onvif/envelope.go new file mode 100644 index 00000000..f0e1b29c --- /dev/null +++ b/pkg/onvif/envelope.go @@ -0,0 +1,79 @@ +package onvif + +import ( + "crypto/sha1" + "encoding/base64" + "fmt" + "net/url" + "time" + + "github.com/AlexxIT/go2rtc/pkg/core" +) + +type Envelope struct { + buf []byte +} + +const ( + prefix1 = ` + +` + prefix2 = ` +` + suffix = ` + +` +) + +func NewEnvelope() *Envelope { + e := &Envelope{buf: make([]byte, 0, 1024)} + e.Append(prefix1, prefix2) + return e +} + +func NewEnvelopeWithUser(user *url.Userinfo) *Envelope { + if user == nil { + return NewEnvelope() + } + + nonce := core.RandString(16, 36) + created := time.Now().UTC().Format(time.RFC3339Nano) + pass, _ := user.Password() + + h := sha1.New() + h.Write([]byte(nonce + created + pass)) + + e := &Envelope{buf: make([]byte, 0, 1024)} + e.Append(prefix1) + e.Appendf(` + + + %s + %s + %s + %s + + + +`, + user.Username(), + base64.StdEncoding.EncodeToString(h.Sum(nil)), + base64.StdEncoding.EncodeToString([]byte(nonce)), + created) + e.Append(prefix2) + return e +} + +func (e *Envelope) Append(args ...string) { + for _, s := range args { + e.buf = append(e.buf, s...) + } +} + +func (e *Envelope) Appendf(format string, args ...any) { + e.buf = fmt.Appendf(e.buf, format, args...) +} + +func (e *Envelope) Bytes() []byte { + return append(e.buf, suffix...) +} diff --git a/pkg/onvif/helpers.go b/pkg/onvif/helpers.go index fc9c8392..251f4579 100644 --- a/pkg/onvif/helpers.go +++ b/pkg/onvif/helpers.go @@ -1,6 +1,7 @@ package onvif import ( + "fmt" "net" "regexp" "strconv" @@ -106,3 +107,25 @@ func atoi(s string) int { } return i } + +func GetPosixTZ(current time.Time) string { + // Thanks to https://github.com/Path-Variable/go-posix-time + _, offset := current.Zone() + + if current.IsDST() { + _, end := current.ZoneBounds() + endPlus1 := end.Add(time.Hour * 25) + _, offset = endPlus1.Zone() + } + + var prefix string + if offset < 0 { + prefix = "GMT+" + offset = -offset / 60 + } else { + prefix = "GMT-" + offset = offset / 60 + } + + return prefix + fmt.Sprintf("%02d:%02d", offset/60, offset%60) +} diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go index bc3f8ffe..42343d37 100644 --- a/pkg/onvif/server.go +++ b/pkg/onvif/server.go @@ -2,31 +2,40 @@ package onvif import ( "bytes" - "fmt" "regexp" - "strconv" "time" ) -const ( - ActionGetCapabilities = "GetCapabilities" - ActionGetSystemDateAndTime = "GetSystemDateAndTime" - ActionGetNetworkInterfaces = "GetNetworkInterfaces" - ActionGetDeviceInformation = "GetDeviceInformation" - ActionGetServiceCapabilities = "GetServiceCapabilities" - ActionGetProfiles = "GetProfiles" - ActionGetStreamUri = "GetStreamUri" - ActionGetSnapshotUri = "GetSnapshotUri" - ActionSystemReboot = "SystemReboot" +const ServiceGetServiceCapabilities = "GetServiceCapabilities" - ActionGetServices = "GetServices" - ActionGetScopes = "GetScopes" - ActionGetVideoSources = "GetVideoSources" - ActionGetAudioSources = "GetAudioSources" - ActionGetVideoSourceConfigurations = "GetVideoSourceConfigurations" - ActionGetAudioSourceConfigurations = "GetAudioSourceConfigurations" - ActionGetVideoEncoderConfigurations = "GetVideoEncoderConfigurations" - ActionGetAudioEncoderConfigurations = "GetAudioEncoderConfigurations" +const ( + DeviceGetCapabilities = "GetCapabilities" + DeviceGetDeviceInformation = "GetDeviceInformation" + DeviceGetDiscoveryMode = "GetDiscoveryMode" + DeviceGetDNS = "GetDNS" + DeviceGetHostname = "GetHostname" + DeviceGetNetworkDefaultGateway = "GetNetworkDefaultGateway" + DeviceGetNetworkInterfaces = "GetNetworkInterfaces" + DeviceGetNetworkProtocols = "GetNetworkProtocols" + DeviceGetNTP = "GetNTP" + DeviceGetScopes = "GetScopes" + DeviceGetServices = "GetServices" + DeviceGetSystemDateAndTime = "GetSystemDateAndTime" + DeviceSystemReboot = "SystemReboot" +) + +const ( + MediaGetAudioEncoderConfigurations = "GetAudioEncoderConfigurations" + MediaGetAudioSources = "GetAudioSources" + MediaGetAudioSourceConfigurations = "GetAudioSourceConfigurations" + MediaGetProfile = "GetProfile" + MediaGetProfiles = "GetProfiles" + MediaGetSnapshotUri = "GetSnapshotUri" + MediaGetStreamUri = "GetStreamUri" + MediaGetVideoEncoderConfigurations = "GetVideoEncoderConfigurations" + MediaGetVideoSources = "GetVideoSources" + MediaGetVideoSourceConfiguration = "GetVideoSourceConfiguration" + MediaGetVideoSourceConfigurations = "GetVideoSourceConfigurations" ) func GetRequestAction(b []byte) string { @@ -43,236 +52,199 @@ func GetRequestAction(b []byte) string { return string(m[1]) } -func GetCapabilitiesResponse(host string) string { - return ` - - - - - - http://` + host + `/onvif/device_service - - - http://` + host + `/onvif/media_service - - false - false - true - - - - - -` +func GetCapabilitiesResponse(host string) []byte { + e := NewEnvelope() + e.Append(` + + + http://`, host, `/onvif/device_service + + + http://`, host, `/onvif/media_service + + false + false + true + + + +`) + return e.Bytes() } -func GetServicesResponse(host string) string { - return ` - - - - - http://www.onvif.org/ver10/device/wsdl - http://` + host + `/onvif/device_service - - 2 - 5 - - - - http://www.onvif.org/ver10/media/wsdl - http://` + host + `/onvif/media_service - - 2 - 5 - - - - -` +func GetServicesResponse(host string) []byte { + e := NewEnvelope() + e.Append(` + + http://www.onvif.org/ver10/device/wsdl + http://`, host, `/onvif/device_service + 25 + + + http://www.onvif.org/ver10/media/wsdl + http://`, host, `/onvif/media_service + 25 + +`) + return e.Bytes() } -func GetSystemDateAndTimeResponse() string { +func GetSystemDateAndTimeResponse() []byte { loc := time.Now() utc := loc.UTC() - return fmt.Sprintf(` - - - - - NTP - false - - GMT%s - - - - %d - %d - %d - - - %d - %d - %d - - - - - %d - %d - %d - - - %d - %d - %d - - - - - -`, - loc.Format("-07:00"), + e := NewEnvelope() + e.Appendf(` + + NTP + true + + %s + + + %d%d%d + %d%d%d + + + %d%d%d + %d%d%d + + +`, + GetPosixTZ(loc), utc.Hour(), utc.Minute(), utc.Second(), utc.Year(), utc.Month(), utc.Day(), loc.Hour(), loc.Minute(), loc.Second(), loc.Year(), loc.Month(), loc.Day(), ) + return e.Bytes() } -func GetNetworkInterfacesResponse() string { - return ` - - - - -` +func GetDeviceInformationResponse(manuf, model, firmware, serial string) []byte { + e := NewEnvelope() + e.Append(` + `, manuf, ` + `, model, ` + `, firmware, ` + `, serial, ` + 1.00 +`) + return e.Bytes() } -func GetDeviceInformationResponse(manuf, model, firmware, serial string) string { - return ` - - - - ` + manuf + ` - ` + model + ` - ` + firmware + ` - ` + serial + ` - 1.00 - - -` +func GetMediaServiceCapabilitiesResponse() []byte { + e := NewEnvelope() + e.Append(` + + + +`) + return e.Bytes() } -func GetServiceCapabilitiesResponse() string { - return ` - - - - - - - - -` +func GetProfilesResponse(names []string) []byte { + e := NewEnvelope() + e.Append(` +`) + for _, name := range names { + appendProfile(e, "Profiles", name) + } + e.Append(``) + return e.Bytes() } -func SystemRebootResponse() string { - return ` - - - - system reboot in 1 second... - - -` +func GetProfileResponse(name string) []byte { + e := NewEnvelope() + e.Append(` +`) + appendProfile(e, "Profile", name) + e.Append(``) + return e.Bytes() } -func GetProfilesResponse(names []string) string { - buf := bytes.NewBuffer(nil) - buf.WriteString(` - - - `) +func appendProfile(e *Envelope, tag, name string) { + e.Append(` + `, name, ` + + VSC + `, name, ` + + + + VEC + H264 + 19201080 + + +`) +} - for i, name := range names { - buf.WriteString(` - - ` + name + ` - - ` + name + ` - H264 - - 1920 - 1080 - - - - - - ` + name + ` - ` + strconv.Itoa(i) + ` - - - `) +func GetVideoSourceConfigurationResponse(name string) []byte { + e := NewEnvelope() + e.Append(` + + VSC + `, name, ` + + +`) + return e.Bytes() +} + +func GetVideoSourcesResponse(names []string) []byte { + e := NewEnvelope() + e.Append(` +`) + for _, name := range names { + e.Append(` + 30.000000 + 19201080 + +`) + } + e.Append(``) + return e.Bytes() +} + +func GetStreamUriResponse(uri string) []byte { + e := NewEnvelope() + e.Append(``, uri, ``) + return e.Bytes() +} + +func GetSnapshotUriResponse(uri string) []byte { + e := NewEnvelope() + e.Append(``, uri, ``) + return e.Bytes() +} + +func StaticResponse(operation string) []byte { + switch operation { + case DeviceGetSystemDateAndTime: + return GetSystemDateAndTimeResponse() } - buf.WriteString(` - - -`) - - return buf.String() -} - - -func GetVideoSourcesResponse(names []string) string { - buf := bytes.NewBuffer(nil) - buf.WriteString(` - - - `) - - for i, _ := range names { - buf.WriteString(` - - - 1920 - 1080 - - `) + e := NewEnvelope() + e.Append(responses[operation]) + b := e.Bytes() + if operation == DeviceGetNetworkInterfaces { + println() } - - buf.WriteString(` - - -`) - - return buf.String() + return b } -func GetStreamUriResponse(uri string) string { - return ` - - - - - ` + uri + ` - - - -` -} +var responses = map[string]string{ + DeviceGetDiscoveryMode: `Discoverable`, + DeviceGetDNS: ``, + DeviceGetHostname: ``, + DeviceGetNetworkDefaultGateway: ``, + DeviceGetNTP: ``, + DeviceSystemReboot: `OK`, -func GetSnapshotUriResponse(uri string) string { - return ` - - - - - ` + uri + ` - - - -` + DeviceGetNetworkInterfaces: ``, + DeviceGetNetworkProtocols: ``, + DeviceGetScopes: ` + Fixedonvif://www.onvif.org/name/go2rtc + Fixedonvif://www.onvif.org/location/github + Fixedonvif://www.onvif.org/Profile/Streaming + Fixedonvif://www.onvif.org/type/Network_Video_Transmitter +`, } From bc9194d74092fb9c4de4d9162bf7f9558fdec996 Mon Sep 17 00:00:00 2001 From: Alex X Date: Fri, 3 Jan 2025 13:33:12 +0300 Subject: [PATCH 09/26] Update go dependencies --- go.mod | 27 ++++++++++++++------------- go.sum | 55 ++++++++++++++++++++++++++++++++----------------------- 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/go.mod b/go.mod index ecd32f3a..5f0a193b 100644 --- a/go.mod +++ b/go.mod @@ -8,20 +8,20 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mattn/go-isatty v0.0.20 github.com/miekg/dns v1.1.62 - github.com/pion/ice/v2 v2.3.36 + github.com/pion/ice/v2 v2.3.37 github.com/pion/interceptor v0.1.37 - github.com/pion/rtcp v1.2.14 - github.com/pion/rtp v1.8.9 + github.com/pion/rtcp v1.2.15 + github.com/pion/rtp v1.8.10 github.com/pion/sdp/v3 v3.0.9 github.com/pion/srtp/v2 v2.0.20 github.com/pion/stun v0.6.1 - github.com/pion/webrtc/v3 v3.3.4 + github.com/pion/webrtc/v3 v3.3.5 github.com/rs/zerolog v1.33.0 github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 - golang.org/x/crypto v0.28.0 + golang.org/x/crypto v0.31.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -31,19 +31,20 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/pion/datachannel v1.5.9 // indirect + github.com/pion/datachannel v1.5.10 // indirect github.com/pion/dtls/v2 v2.2.12 // indirect github.com/pion/logging v0.2.2 // indirect github.com/pion/mdns v0.0.12 // indirect github.com/pion/randutil v0.1.0 // indirect - github.com/pion/sctp v1.8.33 // indirect + github.com/pion/sctp v1.8.35 // indirect github.com/pion/transport/v2 v2.2.10 // indirect + github.com/pion/transport/v3 v3.0.7 // indirect github.com/pion/turn/v2 v2.1.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/wlynxg/anet v0.0.5 // indirect - golang.org/x/mod v0.18.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/tools v0.22.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/tools v0.24.0 // indirect ) diff --git a/go.sum b/go.sum index 804ecc43..c75ffced 100644 --- a/go.sum +++ b/go.sum @@ -31,13 +31,13 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= -github.com/pion/datachannel v1.5.9 h1:LpIWAOYPyDrXtU+BW7X0Yt/vGtYxtXQ8ql7dFfYUVZA= -github.com/pion/datachannel v1.5.9/go.mod h1:kDUuk4CU4Uxp82NH4LQZbISULkX/HtzKa4P7ldf9izE= +github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= +github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= -github.com/pion/ice/v2 v2.3.36 h1:SopeXiVbbcooUg2EIR8sq4b13RQ8gzrkkldOVg+bBsc= -github.com/pion/ice/v2 v2.3.36/go.mod h1:mBF7lnigdqgtB+YHkaY/Y6s6tsyRyo4u4rPGRuOjUBQ= +github.com/pion/ice/v2 v2.3.37 h1:ObIdaNDu1rCo7hObhs34YSBcO7fjslJMZV0ux+uZWh0= +github.com/pion/ice/v2 v2.3.37/go.mod h1:mBF7lnigdqgtB+YHkaY/Y6s6tsyRyo4u4rPGRuOjUBQ= github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI= github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= @@ -47,13 +47,13 @@ github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYF github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4= -github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE= -github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4= +github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= +github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= -github.com/pion/rtp v1.8.9 h1:E2HX740TZKaqdcPmf4pw6ZZuG8u5RlMMt+l3dxeu6Wk= -github.com/pion/rtp v1.8.9/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= -github.com/pion/sctp v1.8.33 h1:dSE4wX6uTJBcNm8+YlMg7lw1wqyKHggsP5uKbdj+NZw= -github.com/pion/sctp v1.8.33/go.mod h1:beTnqSzewI53KWoG3nqB282oDMGrhNxBdb+JZnkCwRM= +github.com/pion/rtp v1.8.10 h1:puphjdbjPB+L+NFaVuZ5h6bt1g5q4kFIoI+r5q/g0CU= +github.com/pion/rtp v1.8.10/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4= +github.com/pion/sctp v1.8.35 h1:qwtKvNK1Wc5tHMIYgTDJhfZk7vATGVHhXbUDfHbYwzA= +github.com/pion/sctp v1.8.35/go.mod h1:EcXP8zCYVTRy3W9xtOF7wJm1L1aXfKRQzaM33SjQlzg= github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY= github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M= github.com/pion/srtp/v2 v2.0.20 h1:HNNny4s+OUmG280ETrCdgFndp4ufx3/uy85EawYEhTk= @@ -67,11 +67,12 @@ github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQp github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= +github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc= github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= -github.com/pion/webrtc/v3 v3.3.4 h1:v2heQVnXTSqNRXcaFQVOhIOYkLMxOu1iJG8uy1djvkk= -github.com/pion/webrtc/v3 v3.3.4/go.mod h1:liNa+E1iwyzyXqNUwvoMRNQ10x8h8FOeJKL8RkIbamE= +github.com/pion/webrtc/v3 v3.3.5 h1:ZsSzaMz/i9nblPdiAkZoP+E6Kmjw+jnyq3bEmU3EtRg= +github.com/pion/webrtc/v3 v3.3.5/go.mod h1:liNa+E1iwyzyXqNUwvoMRNQ10x8h8FOeJKL8RkIbamE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE= @@ -95,8 +96,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 h1:aeN+ghOV0b2VCmKKO3gqnDQ8mLbpABZgRR2FVYx4ouI= github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE= github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= @@ -108,12 +110,13 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -122,13 +125,13 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -143,8 +146,8 @@ golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -165,6 +168,12 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= From 4035e916723e1bbde5711b88f84e01faa5b1e60c Mon Sep 17 00:00:00 2001 From: Alex X Date: Fri, 3 Jan 2025 15:08:38 +0300 Subject: [PATCH 10/26] Fix ONVIF XML tag parsing in some cases --- pkg/onvif/helpers.go | 2 +- pkg/onvif/onvif_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/pkg/onvif/helpers.go b/pkg/onvif/helpers.go index 251f4579..f240f2ec 100644 --- a/pkg/onvif/helpers.go +++ b/pkg/onvif/helpers.go @@ -12,7 +12,7 @@ import ( ) func FindTagValue(b []byte, tag string) string { - re := regexp.MustCompile(`(?s)[:<]` + tag + `>([^<]+)`) + re := regexp.MustCompile(`(?s)<(?:\w+:)?` + tag + `\b[^>]*>([^<]+)`) m := re.FindSubmatch(b) if len(m) != 2 { return "" diff --git a/pkg/onvif/onvif_test.go b/pkg/onvif/onvif_test.go index cd57d60b..e9ffab04 100644 --- a/pkg/onvif/onvif_test.go +++ b/pkg/onvif/onvif_test.go @@ -84,6 +84,34 @@ func TestGetStreamUri(t *testing.T) { `, url: "rtsp://192.168.5.53:8090/profile1=r", }, + { + name: "go2rtc 1.9.4", + xml: ` + + + + rtsp://192.168.1.123:8554/rtsp-dahua1 + + + +`, + url: "rtsp://192.168.1.123:8554/rtsp-dahua1", + }, + { + name: "go2rtc 1.9.8", + xml: ` + + + + + rtsp://192.168.1.123:8554/rtsp-dahua2 + + + + +`, + url: "rtsp://192.168.1.123:8554/rtsp-dahua2", + }, } for _, test := range tests { From 199fdd6728eb932d3a34e5cebdfc00326a44ced8 Mon Sep 17 00:00:00 2001 From: Alex X Date: Fri, 3 Jan 2025 16:24:31 +0300 Subject: [PATCH 11/26] Update version to 1.9.8 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index d5c59ffc..b8d58b27 100644 --- a/main.go +++ b/main.go @@ -37,7 +37,7 @@ import ( ) func main() { - app.Version = "1.9.7" + app.Version = "1.9.8" // 1. Core modules: app, api/ws, streams From 55af09a3502ad72b3909df637b03740321d7c404 Mon Sep 17 00:00:00 2001 From: Alex X Date: Sun, 5 Jan 2025 11:03:44 +0300 Subject: [PATCH 12/26] Add support fix JPEG from some MJPEG sources --- pkg/mjpeg/helpers.go | 63 +++++++++++++++++++++++++++++++++++--------- pkg/mjpeg/jpeg.go | 10 +++++++ pkg/mjpeg/rfc2435.go | 30 ++++++++++----------- 3 files changed, 75 insertions(+), 28 deletions(-) create mode 100644 pkg/mjpeg/jpeg.go diff --git a/pkg/mjpeg/helpers.go b/pkg/mjpeg/helpers.go index 08b4408b..d1acbd45 100644 --- a/pkg/mjpeg/helpers.go +++ b/pkg/mjpeg/helpers.go @@ -9,24 +9,38 @@ import ( "github.com/pion/rtp" ) -// FixJPEG - reencode JPEG if it has wrong header -// -// for example, this app produce "bad" images: -// https://github.com/jacksonliam/mjpg-streamer -// -// and they can't be uploaded to the Telegram servers: -// {"ok":false,"error_code":400,"description":"Bad Request: IMAGE_PROCESS_FAILED"} func FixJPEG(b []byte) []byte { // skip non-JPEG - if len(b) < 10 || b[0] != 0xFF || b[1] != 0xD8 { - return b - } - // skip if header OK for imghdr library - // https://docs.python.org/3/library/imghdr.html - if string(b[2:4]) == "\xFF\xDB" || string(b[6:10]) == "JFIF" || string(b[6:10]) == "Exif" { + if len(b) < 10 || b[0] != 0xFF || b[1] != markerSOI { return b } + // skip JPEG without app marker + if b[2] == 0xFF && b[3] == markerDQT { + return b + } + + switch string(b[6:10]) { + case "JFIF", "Exif": + // skip if header OK for imghdr library + // - https://docs.python.org/3/library/imghdr.html + return b + case "AVI1": + // adds DHT tables to JPEG file before SOS marker + // useful when you want to save a JPEG frame from an MJPEG stream + // - https://github.com/image-rs/jpeg-decoder/issues/76 + // - https://github.com/pion/mediadevices/pull/493 + // - https://bugzilla.mozilla.org/show_bug.cgi?id=963907#c18 + return InjectDHT(b) + } + + // reencode JPEG if it has wrong header + // + // for example, this app produce "bad" images: + // https://github.com/jacksonliam/mjpg-streamer + // + // and they can't be uploaded to the Telegram servers: + // {"ok":false,"error_code":400,"description":"Bad Request: IMAGE_PROCESS_FAILED"} img, err := jpeg.Decode(bytes.NewReader(b)) if err != nil { return b @@ -54,3 +68,26 @@ func Encoder(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc { handler(&clone) } } + +const dhtSize = 432 // known size for 4 default tables + +func InjectDHT(b []byte) []byte { + if bytes.Index(b, []byte{0xFF, markerDHT}) > 0 { + return b // already exist + } + + i := bytes.Index(b, []byte{0xFF, markerSOS}) + if i < 0 { + return b + } + + dht := make([]byte, 0, dhtSize) + dht = MakeHuffmanHeaders(dht) + + tmp := make([]byte, len(b)+dhtSize) + copy(tmp, b[:i]) + copy(tmp[i:], dht) + copy(tmp[i+dhtSize:], b[i:]) + + return tmp +} diff --git a/pkg/mjpeg/jpeg.go b/pkg/mjpeg/jpeg.go new file mode 100644 index 00000000..8d6d13d1 --- /dev/null +++ b/pkg/mjpeg/jpeg.go @@ -0,0 +1,10 @@ +package mjpeg + +const ( + markerSOF = 0xC0 // Start Of Frame (Baseline Sequential) + markerSOI = 0xD8 // Start Of Image + markerEOI = 0xD9 // End Of Image + markerSOS = 0xDA // Start Of Scan + markerDQT = 0xDB // Define Quantization Table + markerDHT = 0xC4 // Define Huffman Table +) diff --git a/pkg/mjpeg/rfc2435.go b/pkg/mjpeg/rfc2435.go index 44307896..aa34c2f1 100644 --- a/pkg/mjpeg/rfc2435.go +++ b/pkg/mjpeg/rfc2435.go @@ -143,9 +143,7 @@ var chm_ac_symbols = []byte{ func MakeHeaders(p []byte, t byte, w, h uint16, lqt, cqt []byte) []byte { // Appendix A from https://www.rfc-editor.org/rfc/rfc2435 - p = append(p, 0xFF, - 0xD8, // SOI - ) + p = append(p, 0xFF, markerSOI) p = MakeQuantHeader(p, lqt, 0) p = MakeQuantHeader(p, cqt, 1) @@ -156,8 +154,7 @@ func MakeHeaders(p []byte, t byte, w, h uint16, lqt, cqt []byte) []byte { t = 0x22 // hsamp = 2, vsamp = 2 } - p = append(p, 0xFF, - 0xC0, // SOF + p = append(p, 0xFF, markerSOF, 0, 17, // size 8, // bits per component byte(h>>8), byte(h&0xFF), @@ -174,13 +171,9 @@ func MakeHeaders(p []byte, t byte, w, h uint16, lqt, cqt []byte) []byte { 1, // quant table 1 ) - p = MakeHuffmanHeader(p, lum_dc_codelens, lum_dc_symbols, 0, 0) - p = MakeHuffmanHeader(p, lum_ac_codelens, lum_ac_symbols, 0, 1) - p = MakeHuffmanHeader(p, chm_dc_codelens, chm_dc_symbols, 1, 0) - p = MakeHuffmanHeader(p, chm_ac_codelens, chm_ac_symbols, 1, 1) + p = MakeHuffmanHeaders(p) - return append(p, 0xFF, - 0xDA, // SOS + return append(p, 0xFF, markerSOS, 0, 12, // size 3, // 3 components 0, // comp 0 @@ -196,16 +189,23 @@ func MakeHeaders(p []byte, t byte, w, h uint16, lqt, cqt []byte) []byte { } func MakeQuantHeader(p []byte, qt []byte, tableNo byte) []byte { - p = append(p, 0xFF, 0xDB, 0, 67, tableNo) + p = append(p, 0xFF, markerDQT, 0, 67, tableNo) return append(p, qt...) } func MakeHuffmanHeader(p, codelens, symbols []byte, tableNo, tableClass byte) []byte { - p = append(p, - 0xFF, 0xC4, 0, - byte(3+len(codelens)+len(symbols)), + p = append(p, 0xFF, markerDHT, + 0, byte(3+len(codelens)+len(symbols)), // size (tableClass<<4)|tableNo, ) p = append(p, codelens...) return append(p, symbols...) } + +func MakeHuffmanHeaders(p []byte) []byte { + p = MakeHuffmanHeader(p, lum_dc_codelens, lum_dc_symbols, 0, 0) + p = MakeHuffmanHeader(p, lum_ac_codelens, lum_ac_symbols, 0, 1) + p = MakeHuffmanHeader(p, chm_dc_codelens, chm_dc_symbols, 1, 0) + p = MakeHuffmanHeader(p, chm_ac_codelens, chm_ac_symbols, 1, 1) + return p +} From a9e1ebc0a8da09f321e5eb08973c7c7b6118d8b5 Mon Sep 17 00:00:00 2001 From: Bruno Tomassetti Couto Date: Sun, 5 Jan 2025 22:54:20 -0300 Subject: [PATCH 13/26] Improve ONVIF server by adding rate control for video encoder configuration --- pkg/onvif/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go index 42343d37..66f27095 100644 --- a/pkg/onvif/server.go +++ b/pkg/onvif/server.go @@ -172,6 +172,7 @@ func appendProfile(e *Envelope, tag, name string) { VEC H264 19201080 + `) From c065db6da149c06dc7f6988848e9246ca80d6ac0 Mon Sep 17 00:00:00 2001 From: Alex X Date: Mon, 6 Jan 2025 06:32:13 +0300 Subject: [PATCH 14/26] Code refactoring after #1539 --- pkg/onvif/server.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go index 66f27095..db0bb2fb 100644 --- a/pkg/onvif/server.go +++ b/pkg/onvif/server.go @@ -161,6 +161,7 @@ func GetProfileResponse(name string) []byte { } func appendProfile(e *Envelope, tag, name string) { + // empty `RateControl` important for UniFi Protect e.Append(` `, name, ` @@ -172,7 +173,7 @@ func appendProfile(e *Envelope, tag, name string) { VEC H264 19201080 - + `) From df831833b1fcea9fd5e0cab0c11fea6920de47eb Mon Sep 17 00:00:00 2001 From: Alex X Date: Mon, 6 Jan 2025 19:31:03 +0300 Subject: [PATCH 15/26] Collect list of dependency license --- scripts/README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/scripts/README.md b/scripts/README.md index 36f667b2..acc6e0c9 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -54,6 +54,41 @@ go list -deps .\cmd\go2rtc_rtsp\ - golang.org/x/tools ``` +## Licenses + +- github.com/asticode/go-astits - MIT +- github.com/expr-lang/expr - MIT +- github.com/gorilla/websocket - BSD-2 +- github.com/mattn/go-isatty - MIT +- github.com/miekg/dns - BSD-3 +- github.com/pion/ice/v2 - MIT +- github.com/pion/interceptor - MIT +- github.com/pion/rtcp - MIT +- github.com/pion/rtp - MIT +- github.com/pion/sdp/v3 - MIT +- github.com/pion/srtp/v2 - MIT +- github.com/pion/stun - MIT +- github.com/pion/webrtc/v3 - MIT +- github.com/rs/zerolog - MIT +- github.com/sigurn/crc16 - MIT +- github.com/sigurn/crc8 - MIT +- github.com/stretchr/testify - MIT +- github.com/tadglines/go-pkgs - Apache +- golang.org/x/crypto - BSD-3 +- gopkg.in/yaml.v3 - MIT and Apache +- github.com/asticode/go-astikit - MIT +- github.com/davecgh/go-spew - ISC (BSD/MIT like) +- github.com/google/uuid - BSD-3 +- github.com/kr/pretty - MIT +- github.com/mattn/go-colorable - MIT +- github.com/pmezard/go-difflib - ??? +- github.com/wlynxg/anet - BSD-3 +- golang.org/x/mod - BSD-3 +- golang.org/x/net - BSD-3 +- golang.org/x/sync - BSD-3 +- golang.org/x/sys - BSD-3 +- golang.org/x/tools - BSD-3 + ## Virus - https://go.dev/doc/faq#virus From d59139a2ab200eb064bab7be9fdc4b1d9f1227e1 Mon Sep 17 00:00:00 2001 From: Alex X Date: Mon, 6 Jan 2025 23:47:35 +0300 Subject: [PATCH 16/26] Add support v4l2 source --- examples/go2rtc_mjpeg/main.go | 21 +++ internal/v4l2/v4l2.go | 7 + internal/v4l2/v4l2_linux.go | 79 ++++++++++ main.go | 2 + pkg/v4l2/device/device.go | 244 ++++++++++++++++++++++++++++++ pkg/v4l2/device/formats.go | 40 +++++ pkg/v4l2/device/videodev2_test.go | 34 +++++ pkg/v4l2/device/videodev2_x32.go | 152 +++++++++++++++++++ pkg/v4l2/device/videodev2_x64.go | 153 +++++++++++++++++++ pkg/v4l2/producer.go | 115 ++++++++++++++ www/add.html | 12 ++ 11 files changed, 859 insertions(+) create mode 100644 examples/go2rtc_mjpeg/main.go create mode 100644 internal/v4l2/v4l2.go create mode 100644 internal/v4l2/v4l2_linux.go create mode 100644 pkg/v4l2/device/device.go create mode 100644 pkg/v4l2/device/formats.go create mode 100644 pkg/v4l2/device/videodev2_test.go create mode 100644 pkg/v4l2/device/videodev2_x32.go create mode 100644 pkg/v4l2/device/videodev2_x64.go create mode 100644 pkg/v4l2/producer.go diff --git a/examples/go2rtc_mjpeg/main.go b/examples/go2rtc_mjpeg/main.go new file mode 100644 index 00000000..a3e08ff5 --- /dev/null +++ b/examples/go2rtc_mjpeg/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "github.com/AlexxIT/go2rtc/internal/api" + "github.com/AlexxIT/go2rtc/internal/app" + "github.com/AlexxIT/go2rtc/internal/mjpeg" + "github.com/AlexxIT/go2rtc/internal/streams" + "github.com/AlexxIT/go2rtc/internal/v4l2" + "github.com/AlexxIT/go2rtc/pkg/shell" +) + +func main() { + app.Init() + streams.Init() + + api.Init() + mjpeg.Init() + v4l2.Init() + + shell.RunUntilSignal() +} diff --git a/internal/v4l2/v4l2.go b/internal/v4l2/v4l2.go new file mode 100644 index 00000000..9cef99a5 --- /dev/null +++ b/internal/v4l2/v4l2.go @@ -0,0 +1,7 @@ +//go:build !linux + +package v4l2 + +func Init() { + // not supported +} diff --git a/internal/v4l2/v4l2_linux.go b/internal/v4l2/v4l2_linux.go new file mode 100644 index 00000000..4a54e1e1 --- /dev/null +++ b/internal/v4l2/v4l2_linux.go @@ -0,0 +1,79 @@ +package v4l2 + +import ( + "encoding/binary" + "fmt" + "net/http" + "os" + "strings" + + "github.com/AlexxIT/go2rtc/internal/api" + "github.com/AlexxIT/go2rtc/internal/streams" + "github.com/AlexxIT/go2rtc/pkg/core" + "github.com/AlexxIT/go2rtc/pkg/v4l2" + "github.com/AlexxIT/go2rtc/pkg/v4l2/device" +) + +func Init() { + streams.HandleFunc("v4l2", func(source string) (core.Producer, error) { + return v4l2.Open(source) + }) + + api.HandleFunc("api/v4l2", apiV4L2) +} + +func apiV4L2(w http.ResponseWriter, r *http.Request) { + files, err := os.ReadDir("/dev") + if err != nil { + return + } + + var sources []*api.Source + + for _, file := range files { + if !strings.HasPrefix(file.Name(), core.KindVideo) { + continue + } + + path := "/dev/" + file.Name() + + dev, err := device.Open(path) + if err != nil { + continue + } + + formats, _ := dev.ListFormats() + for _, fourCC := range formats { + source := &api.Source{} + + for _, format := range device.Formats { + if format.FourCC == fourCC { + source.Name = format.Name + source.URL = "v4l2:device?video=" + path + "&input_format=" + format.FFmpeg + "&video_size=" + break + } + } + + if source.Name != "" { + sizes, _ := dev.ListSizes(fourCC) + for i := 0; i < len(sizes); i += 2 { + size := fmt.Sprintf("%dx%d", sizes[i], sizes[i+1]) + if i > 0 { + source.Info += " " + size + } else { + source.Info = size + source.URL += size + } + } + } else { + source.Name = string(binary.LittleEndian.AppendUint32(nil, fourCC)) + } + + sources = append(sources, source) + } + + _ = dev.Close() + } + + api.ResponseSources(w, sources) +} diff --git a/main.go b/main.go index b8d58b27..db8de9f4 100644 --- a/main.go +++ b/main.go @@ -31,6 +31,7 @@ import ( "github.com/AlexxIT/go2rtc/internal/srtp" "github.com/AlexxIT/go2rtc/internal/streams" "github.com/AlexxIT/go2rtc/internal/tapo" + "github.com/AlexxIT/go2rtc/internal/v4l2" "github.com/AlexxIT/go2rtc/internal/webrtc" "github.com/AlexxIT/go2rtc/internal/webtorrent" "github.com/AlexxIT/go2rtc/pkg/shell" @@ -84,6 +85,7 @@ func main() { expr.Init() // expr source gopro.Init() // gopro source doorbird.Init() // doorbird source + v4l2.Init() // v4l2 source // 6. Helper modules diff --git a/pkg/v4l2/device/device.go b/pkg/v4l2/device/device.go new file mode 100644 index 00000000..4e4d6ade --- /dev/null +++ b/pkg/v4l2/device/device.go @@ -0,0 +1,244 @@ +//go:build linux + +package device + +import ( + "bytes" + "errors" + "fmt" + "syscall" + "unsafe" +) + +type Device struct { + fd int + bufs [][]byte +} + +func Open(path string) (*Device, error) { + fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CLOEXEC, 0) + if err != nil { + return nil, err + } + return &Device{fd: fd}, nil +} + +const buffersCount = 2 + +type Capability struct { + Driver string + Card string + BusInfo string + Version string +} + +func (d *Device) Capability() (*Capability, error) { + c := v4l2_capability{} + if err := ioctl(d.fd, VIDIOC_QUERYCAP, unsafe.Pointer(&c)); err != nil { + return nil, err + } + return &Capability{ + Driver: str(c.driver[:]), + Card: str(c.card[:]), + BusInfo: str(c.bus_info[:]), + Version: fmt.Sprintf("%d.%d.%d", byte(c.version>>16), byte(c.version>>8), byte(c.version)), + }, nil +} + +func (d *Device) ListFormats() ([]uint32, error) { + var items []uint32 + + for i := uint32(0); ; i++ { + fd := v4l2_fmtdesc{ + index: i, + typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, + } + if err := ioctl(d.fd, VIDIOC_ENUM_FMT, unsafe.Pointer(&fd)); err != nil { + if !errors.Is(err, syscall.EINVAL) { + return nil, err + } + break + } + + items = append(items, fd.pixelformat) + } + + return items, nil +} + +func (d *Device) ListSizes(pixFmt uint32) ([]uint32, error) { + var items []uint32 + + for i := uint32(0); ; i++ { + fs := v4l2_frmsizeenum{ + index: i, + pixel_format: pixFmt, + } + if err := ioctl(d.fd, VIDIOC_ENUM_FRAMESIZES, unsafe.Pointer(&fs)); err != nil { + if !errors.Is(err, syscall.EINVAL) { + return nil, err + } + break + } + + if fs.typ != V4L2_FRMSIZE_TYPE_DISCRETE { + continue + } + + items = append(items, fs.discrete.width, fs.discrete.height) + } + + return items, nil +} + +func (d *Device) ListFrameRates(pixFmt, width, height uint32) ([]uint32, error) { + var items []uint32 + + for i := uint32(0); ; i++ { + fi := v4l2_frmivalenum{ + index: i, + pixel_format: pixFmt, + width: width, + height: height, + } + if err := ioctl(d.fd, VIDIOC_ENUM_FRAMEINTERVALS, unsafe.Pointer(&fi)); err != nil { + if !errors.Is(err, syscall.EINVAL) { + return nil, err + } + break + } + + if fi.typ != V4L2_FRMIVAL_TYPE_DISCRETE || fi.discrete.numerator != 1 { + continue + } + + items = append(items, fi.discrete.denominator) + } + + return items, nil +} + +func (d *Device) SetFormat(width, height, pixFmt uint32) error { + f := v4l2_format{ + typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, + fmt: v4l2_pix_format{ + width: width, + height: height, + pixelformat: pixFmt, + field: V4L2_FIELD_NONE, + colorspace: V4L2_COLORSPACE_DEFAULT, + }, + } + return ioctl(d.fd, VIDIOC_S_FMT, unsafe.Pointer(&f)) +} + +func (d *Device) SetParam(fps uint32) error { + p := v4l2_streamparm{ + typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, + capture: v4l2_captureparm{ + timeperframe: v4l2_fract{numerator: 1, denominator: fps}, + }, + } + return ioctl(d.fd, VIDIOC_S_PARM, unsafe.Pointer(&p)) +} + +func (d *Device) StreamOn() (err error) { + rb := v4l2_requestbuffers{ + count: buffersCount, + typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, + memory: V4L2_MEMORY_MMAP, + } + if err = ioctl(d.fd, VIDIOC_REQBUFS, unsafe.Pointer(&rb)); err != nil { + return err + } + + d.bufs = make([][]byte, buffersCount) + for i := uint32(0); i < buffersCount; i++ { + qb := v4l2_buffer{ + index: i, + typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, + memory: V4L2_MEMORY_MMAP, + } + if err = ioctl(d.fd, VIDIOC_QUERYBUF, unsafe.Pointer(&qb)); err != nil { + return err + } + + if d.bufs[i], err = syscall.Mmap( + d.fd, int64(qb.offset), int(qb.length), syscall.PROT_READ, syscall.MAP_SHARED, + ); nil != err { + return err + } + + if err = ioctl(d.fd, VIDIOC_QBUF, unsafe.Pointer(&qb)); err != nil { + return err + } + } + + typ := uint32(V4L2_BUF_TYPE_VIDEO_CAPTURE) + return ioctl(d.fd, VIDIOC_STREAMON, unsafe.Pointer(&typ)) +} + +func (d *Device) StreamOff() (err error) { + typ := uint32(V4L2_BUF_TYPE_VIDEO_CAPTURE) + if err = ioctl(d.fd, VIDIOC_STREAMOFF, unsafe.Pointer(&typ)); err != nil { + return err + } + + for i := range d.bufs { + _ = syscall.Munmap(d.bufs[i]) + } + + rb := v4l2_requestbuffers{ + count: 0, + typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, + memory: V4L2_MEMORY_MMAP, + } + return ioctl(d.fd, VIDIOC_REQBUFS, unsafe.Pointer(&rb)) +} + +func (d *Device) Capture(cositedYUV bool) ([]byte, error) { + dec := v4l2_buffer{ + typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, + memory: V4L2_MEMORY_MMAP, + } + if err := ioctl(d.fd, VIDIOC_DQBUF, unsafe.Pointer(&dec)); err != nil { + return nil, err + } + + buf := make([]byte, dec.bytesused) + if cositedYUV { + YUYV2YUV(buf, d.bufs[dec.index][:dec.bytesused]) + } else { + copy(buf, d.bufs[dec.index][:dec.bytesused]) + } + + enc := v4l2_buffer{ + typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, + memory: V4L2_MEMORY_MMAP, + index: dec.index, + } + if err := ioctl(d.fd, VIDIOC_QBUF, unsafe.Pointer(&enc)); err != nil { + return nil, err + } + + return buf, nil +} + +func (d *Device) Close() error { + return syscall.Close(d.fd) +} + +func ioctl(fd int, req uint, arg unsafe.Pointer) error { + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg)) + if err != 0 { + return err + } + return nil +} + +func str(b []byte) string { + if i := bytes.IndexByte(b, 0); i >= 0 { + return string(b[:i]) + } + return string(b) +} diff --git a/pkg/v4l2/device/formats.go b/pkg/v4l2/device/formats.go new file mode 100644 index 00000000..94d12504 --- /dev/null +++ b/pkg/v4l2/device/formats.go @@ -0,0 +1,40 @@ +package device + +const ( + V4L2_PIX_FMT_YUYV = 'Y' | 'U'<<8 | 'Y'<<16 | 'V'<<24 + V4L2_PIX_FMT_MJPEG = 'M' | 'J'<<8 | 'P'<<16 | 'G'<<24 +) + +type Format struct { + FourCC uint32 + Name string + FFmpeg string +} + +var Formats = []Format{ + {V4L2_PIX_FMT_YUYV, "YUV 4:2:2", "yuyv422"}, + {V4L2_PIX_FMT_MJPEG, "Motion-JPEG", "mjpeg"}, +} + +// YUYV2YUV convert [Y0 Cb Y1 Cr] to cosited [Y0Y1... Cb... Cr...] +func YUYV2YUV(dst, src []byte) { + n := len(src) + i0 := 0 + iy := 0 + iu := n / 2 + iv := n / 4 * 3 + for i0 < n { + dst[iy] = src[i0] + i0++ + iy++ + dst[iu] = src[i0] + i0++ + iu++ + dst[iy] = src[i0] + i0++ + iy++ + dst[iv] = src[i0] + i0++ + iv++ + } +} diff --git a/pkg/v4l2/device/videodev2_test.go b/pkg/v4l2/device/videodev2_test.go new file mode 100644 index 00000000..2556feef --- /dev/null +++ b/pkg/v4l2/device/videodev2_test.go @@ -0,0 +1,34 @@ +package device + +import ( + "runtime" + "testing" + "unsafe" + + "github.com/stretchr/testify/require" +) + +func TestSize(t *testing.T) { + switch runtime.GOARCH { + case "amd64", "arm64": + require.Equal(t, 104, int(unsafe.Sizeof(v4l2_capability{}))) + require.Equal(t, 208, int(unsafe.Sizeof(v4l2_format{}))) + require.Equal(t, 204, int(unsafe.Sizeof(v4l2_streamparm{}))) + require.Equal(t, 20, int(unsafe.Sizeof(v4l2_requestbuffers{}))) + require.Equal(t, 88, int(unsafe.Sizeof(v4l2_buffer{}))) + require.Equal(t, 16, int(unsafe.Sizeof(v4l2_timecode{}))) + require.Equal(t, 64, int(unsafe.Sizeof(v4l2_fmtdesc{}))) + require.Equal(t, 44, int(unsafe.Sizeof(v4l2_frmsizeenum{}))) + require.Equal(t, 52, int(unsafe.Sizeof(v4l2_frmivalenum{}))) + case "386", "arm": + require.Equal(t, 104, int(unsafe.Sizeof(v4l2_capability{}))) + require.Equal(t, 204, int(unsafe.Sizeof(v4l2_format{}))) + require.Equal(t, 204, int(unsafe.Sizeof(v4l2_streamparm{}))) + require.Equal(t, 20, int(unsafe.Sizeof(v4l2_requestbuffers{}))) + require.Equal(t, 68, int(unsafe.Sizeof(v4l2_buffer{}))) + require.Equal(t, 16, int(unsafe.Sizeof(v4l2_timecode{}))) + require.Equal(t, 64, int(unsafe.Sizeof(v4l2_fmtdesc{}))) + require.Equal(t, 44, int(unsafe.Sizeof(v4l2_frmsizeenum{}))) + require.Equal(t, 52, int(unsafe.Sizeof(v4l2_frmivalenum{}))) + } +} diff --git a/pkg/v4l2/device/videodev2_x32.go b/pkg/v4l2/device/videodev2_x32.go new file mode 100644 index 00000000..4c4db26d --- /dev/null +++ b/pkg/v4l2/device/videodev2_x32.go @@ -0,0 +1,152 @@ +//go:build 386 || arm + +package device + +// https://github.com/torvalds/linux/blob/master/include/uapi/linux/videodev2.h + +const ( + VIDIOC_QUERYCAP = 0x80685600 + VIDIOC_ENUM_FMT = 0xc0405602 + VIDIOC_G_FMT = 0xc0cc5604 + VIDIOC_S_FMT = 0xc0cc5605 + VIDIOC_REQBUFS = 0xc0145608 + VIDIOC_QUERYBUF = 0xc0445609 + + VIDIOC_QBUF = 0xc044560f + VIDIOC_DQBUF = 0xc0445611 + VIDIOC_STREAMON = 0x40045612 + VIDIOC_STREAMOFF = 0x40045613 + VIDIOC_G_PARM = 0xc0cc5615 + VIDIOC_S_PARM = 0xc0cc5616 + + VIDIOC_ENUM_FRAMESIZES = 0xc02c564a + VIDIOC_ENUM_FRAMEINTERVALS = 0xc034564b +) + +const ( + V4L2_BUF_TYPE_VIDEO_CAPTURE = 1 + V4L2_COLORSPACE_DEFAULT = 0 + V4L2_FIELD_NONE = 1 + V4L2_FRMIVAL_TYPE_DISCRETE = 1 + V4L2_FRMSIZE_TYPE_DISCRETE = 1 + V4L2_MEMORY_MMAP = 1 +) + +type v4l2_capability struct { + driver [16]byte + card [32]byte + bus_info [32]byte + version uint32 + capabilities uint32 + device_caps uint32 + reserved [3]uint32 +} + +type v4l2_format struct { + typ uint32 + fmt v4l2_pix_format +} + +type v4l2_pix_format struct { + width uint32 // 0 + height uint32 // 4 + pixelformat uint32 // 8 + field uint32 // 12 + bytesperline uint32 // 16 + sizeimage uint32 // 20 + colorspace uint32 // 24 + priv uint32 // 28 + flags uint32 // 32 + ycbcr_enc uint32 // 36 + quantization uint32 // 40 + xfer_func uint32 // 44 + + _ [152]byte // 48 +} + +type v4l2_streamparm struct { + typ uint32 + capture v4l2_captureparm +} + +type v4l2_captureparm struct { + capability uint32 // 0 + capturemode uint32 // 4 + timeperframe v4l2_fract // 8 + extendedmode uint32 // 16 + readbuffers uint32 // 20 + + _ [176]byte // 24 +} + +type v4l2_fract struct { + numerator uint32 + denominator uint32 +} + +type v4l2_requestbuffers struct { + count uint32 + typ uint32 + memory uint32 + capabilities uint32 + flags uint8 + reserved [3]uint8 +} + +type v4l2_buffer struct { + index uint32 // 0 + typ uint32 // 4 + bytesused uint32 // 8 + flags uint32 // 12 + field uint32 // 16 + _ [8]byte // 20 + timecode v4l2_timecode // 28 + sequence uint32 // 44 + memory uint32 // 48 + offset uint32 // 52 + length uint32 // 56 + _ [8]byte // 60 +} + +type v4l2_timecode struct { + typ uint32 + flags uint32 + frames uint8 + seconds uint8 + minutes uint8 + hours uint8 + userbits [4]uint8 +} + +type v4l2_fmtdesc struct { + index uint32 + typ uint32 + flags uint32 + description [32]byte + pixelformat uint32 + mbus_code uint32 + reserved [3]uint32 +} + +type v4l2_frmsizeenum struct { + index uint32 // 0 + pixel_format uint32 // 4 + typ uint32 // 8 + discrete v4l2_frmsize_discrete // 12 + _ [24]byte +} + +type v4l2_frmsize_discrete struct { + width uint32 + height uint32 +} + +type v4l2_frmivalenum struct { + index uint32 + pixel_format uint32 + width uint32 + height uint32 + typ uint32 + discrete v4l2_fract + _ [24]byte +} diff --git a/pkg/v4l2/device/videodev2_x64.go b/pkg/v4l2/device/videodev2_x64.go new file mode 100644 index 00000000..97c3ab95 --- /dev/null +++ b/pkg/v4l2/device/videodev2_x64.go @@ -0,0 +1,153 @@ +//go:build amd64 || arm64 + +package device + +// https://github.com/torvalds/linux/blob/master/include/uapi/linux/videodev2.h + +const ( + VIDIOC_QUERYCAP = 0x80685600 + VIDIOC_ENUM_FMT = 0xc0405602 + VIDIOC_G_FMT = 0xc0d05604 + VIDIOC_S_FMT = 0xc0d05605 + VIDIOC_REQBUFS = 0xc0145608 + VIDIOC_QUERYBUF = 0xc0585609 + + VIDIOC_QBUF = 0xc058560f + VIDIOC_DQBUF = 0xc0585611 + VIDIOC_STREAMON = 0x40045612 + VIDIOC_STREAMOFF = 0x40045613 + VIDIOC_G_PARM = 0xc0cc5615 + VIDIOC_S_PARM = 0xc0cc5616 + + VIDIOC_ENUM_FRAMESIZES = 0xc02c564a + VIDIOC_ENUM_FRAMEINTERVALS = 0xc034564b +) + +const ( + V4L2_BUF_TYPE_VIDEO_CAPTURE = 1 + V4L2_COLORSPACE_DEFAULT = 0 + V4L2_FIELD_NONE = 1 + V4L2_FRMIVAL_TYPE_DISCRETE = 1 + V4L2_FRMSIZE_TYPE_DISCRETE = 1 + V4L2_MEMORY_MMAP = 1 +) + +type v4l2_capability struct { + driver [16]byte + card [32]byte + bus_info [32]byte + version uint32 + capabilities uint32 + device_caps uint32 + reserved [3]uint32 +} + +type v4l2_format struct { + typ uint64 + fmt v4l2_pix_format +} + +type v4l2_pix_format struct { + width uint32 // 0 + height uint32 // 4 + pixelformat uint32 // 8 + field uint32 // 12 + bytesperline uint32 // 16 + sizeimage uint32 // 20 + colorspace uint32 // 24 + priv uint32 // 28 + flags uint32 // 32 + ycbcr_enc uint32 // 36 + quantization uint32 // 40 + xfer_func uint32 // 44 + + _ [152]byte // 48 +} + +type v4l2_streamparm struct { + typ uint32 + capture v4l2_captureparm +} + +type v4l2_captureparm struct { + capability uint32 // 0 + capturemode uint32 // 4 + timeperframe v4l2_fract // 8 + extendedmode uint32 // 16 + readbuffers uint32 // 20 + + _ [176]byte // 24 +} + +type v4l2_fract struct { + numerator uint32 + denominator uint32 +} + +type v4l2_requestbuffers struct { + count uint32 + typ uint32 + memory uint32 + capabilities uint32 + flags uint8 + reserved [3]uint8 +} + +type v4l2_buffer struct { + index uint32 // 0 + typ uint32 // 4 + bytesused uint32 // 8 + flags uint32 // 12 + field uint32 // 16 + _ [20]byte // 20 + timecode v4l2_timecode // 40 + sequence uint32 // 56 + memory uint32 // 60 + offset uint32 // 64 + _ [4]byte // 68 + length uint32 // 72 + _ [12]byte // 76 +} + +type v4l2_timecode struct { + typ uint32 + flags uint32 + frames uint8 + seconds uint8 + minutes uint8 + hours uint8 + userbits [4]uint8 +} + +type v4l2_fmtdesc struct { + index uint32 + typ uint32 + flags uint32 + description [32]byte + pixelformat uint32 + mbus_code uint32 + reserved [3]uint32 +} + +type v4l2_frmsizeenum struct { + index uint32 // 0 + pixel_format uint32 // 4 + typ uint32 // 8 + discrete v4l2_frmsize_discrete // 12 + _ [24]byte +} + +type v4l2_frmsize_discrete struct { + width uint32 + height uint32 +} + +type v4l2_frmivalenum struct { + index uint32 + pixel_format uint32 + width uint32 + height uint32 + typ uint32 + discrete v4l2_fract + _ [24]byte +} diff --git a/pkg/v4l2/producer.go b/pkg/v4l2/producer.go new file mode 100644 index 00000000..644c5ee5 --- /dev/null +++ b/pkg/v4l2/producer.go @@ -0,0 +1,115 @@ +//go:build linux + +package v4l2 + +import ( + "errors" + "net/url" + "strings" + + "github.com/AlexxIT/go2rtc/pkg/core" + "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 "mjpeg": + codec.Name = core.CodecJPEG + pixFmt = device.V4L2_PIX_FMT_MJPEG + 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 + default: + return nil, errors.New("v4l2: invalid input_format") + } + + if err = dev.SetFormat(width, height, pixFmt); 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 + } + + cositedYUV := c.Medias[0].Codecs[0].Name == core.CodecRAW + + for { + buf, err := c.dev.Capture(cositedYUV) + if err != nil { + return err + } + + c.Recv += len(buf) + + if len(c.Receivers) == 0 { + continue + } + + 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()) +} diff --git a/www/add.html b/www/add.html index 4b40f431..49e954d3 100644 --- a/www/add.html +++ b/www/add.html @@ -292,6 +292,18 @@ + +
+
+
+ + +
From 33e0ccdd109d3073b1fd05b00621f6955ea08845 Mon Sep 17 00:00:00 2001 From: Alex X Date: Tue, 7 Jan 2025 00:19:53 +0300 Subject: [PATCH 17/26] Fix build for mipsle --- internal/v4l2/v4l2.go | 2 +- internal/v4l2/v4l2_linux.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/v4l2/v4l2.go b/internal/v4l2/v4l2.go index 9cef99a5..cfef9667 100644 --- a/internal/v4l2/v4l2.go +++ b/internal/v4l2/v4l2.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build !(linux && (386 || arm || amd64 || arm64)) package v4l2 diff --git a/internal/v4l2/v4l2_linux.go b/internal/v4l2/v4l2_linux.go index 4a54e1e1..4c67235d 100644 --- a/internal/v4l2/v4l2_linux.go +++ b/internal/v4l2/v4l2_linux.go @@ -1,3 +1,5 @@ +//go:build linux && (386 || arm || amd64 || arm64) + package v4l2 import ( From e4b8d1807dee455122d2953a2f0e98de41d86bfc Mon Sep 17 00:00:00 2001 From: Alex X Date: Tue, 7 Jan 2025 20:08:29 +0300 Subject: [PATCH 18/26] Add support snapshot for raw image format --- pkg/magic/keyframe.go | 10 ++++++++++ pkg/mjpeg/consumer.go | 2 +- pkg/mjpeg/helpers.go | 9 ++++++++- pkg/y4m/y4m.go | 24 ++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/pkg/magic/keyframe.go b/pkg/magic/keyframe.go index 8f70eec6..9b6ef562 100644 --- a/pkg/magic/keyframe.go +++ b/pkg/magic/keyframe.go @@ -24,6 +24,7 @@ func NewKeyframe() *Keyframe { Direction: core.DirectionSendonly, Codecs: []*core.Codec{ {Name: core.CodecJPEG}, + {Name: core.CodecRAW}, {Name: core.CodecH264}, {Name: core.CodecH265}, }, @@ -87,6 +88,15 @@ func (k *Keyframe) AddTrack(media *core.Media, _ *core.Codec, track *core.Receiv if track.Codec.IsRTP() { sender.Handler = mjpeg.RTPDepay(sender.Handler) } + + case core.CodecRAW: + sender.Handler = func(packet *rtp.Packet) { + if n, err := k.wr.Write(packet.Payload); err == nil { + k.Send += n + } + } + + sender.Handler = mjpeg.Encoder(track.Codec, 5, sender.Handler) } sender.HandleRTP(track) diff --git a/pkg/mjpeg/consumer.go b/pkg/mjpeg/consumer.go index 16edc895..819c558a 100644 --- a/pkg/mjpeg/consumer.go +++ b/pkg/mjpeg/consumer.go @@ -46,7 +46,7 @@ func (c *Consumer) AddTrack(media *core.Media, _ *core.Codec, track *core.Receiv if track.Codec.IsRTP() { sender.Handler = RTPDepay(sender.Handler) } else if track.Codec.Name == core.CodecRAW { - sender.Handler = Encoder(track.Codec, sender.Handler) + sender.Handler = Encoder(track.Codec, 0, sender.Handler) } sender.HandleRTP(track) diff --git a/pkg/mjpeg/helpers.go b/pkg/mjpeg/helpers.go index d1acbd45..87f59e07 100644 --- a/pkg/mjpeg/helpers.go +++ b/pkg/mjpeg/helpers.go @@ -52,12 +52,19 @@ func FixJPEG(b []byte) []byte { return buf.Bytes() } -func Encoder(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc { +// Encoder convert YUV frame to Img. +// Support skipping empty frames, for example if USB cam needs time to start. +func Encoder(codec *core.Codec, skipEmpty int, handler core.HandlerFunc) core.HandlerFunc { newImage := y4m.NewImage(codec.FmtpLine) return func(packet *rtp.Packet) { img := newImage(packet.Payload) + if skipEmpty != 0 && y4m.HasSameColor(img) { + skipEmpty-- + return + } + buf := bytes.NewBuffer(nil) if err := jpeg.Encode(buf, img, nil); err != nil { return diff --git a/pkg/y4m/y4m.go b/pkg/y4m/y4m.go index 4ac54da6..24c43164 100644 --- a/pkg/y4m/y4m.go +++ b/pkg/y4m/y4m.go @@ -123,3 +123,27 @@ func NewImage(fmtp string) func(frame []byte) image.Image { return nil } + +// HasSameColor checks if all pixels has same color +func HasSameColor(img image.Image) bool { + var pix []byte + + switch img := img.(type) { + case *image.Gray: + pix = img.Pix + case *image.YCbCr: + pix = img.Y + } + + if len(pix) == 0 { + return false + } + + i0 := pix[0] + for _, i := range pix { + if i != i0 { + return false + } + } + return true +} From 93252fc5d271cae590ac902cfbd6185ddcd4687a Mon Sep 17 00:00:00 2001 From: Alex X Date: Tue, 7 Jan 2025 22:17:35 +0300 Subject: [PATCH 19/26] Change ListSizes function for V4L2 device --- internal/v4l2/v4l2_linux.go | 6 +++--- pkg/v4l2/device/device.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/v4l2/v4l2_linux.go b/internal/v4l2/v4l2_linux.go index 4c67235d..a16779f4 100644 --- a/internal/v4l2/v4l2_linux.go +++ b/internal/v4l2/v4l2_linux.go @@ -58,9 +58,9 @@ func apiV4L2(w http.ResponseWriter, r *http.Request) { if source.Name != "" { sizes, _ := dev.ListSizes(fourCC) - for i := 0; i < len(sizes); i += 2 { - size := fmt.Sprintf("%dx%d", sizes[i], sizes[i+1]) - if i > 0 { + for _, wh := range sizes { + size := fmt.Sprintf("%dx%d", wh[0], wh[1]) + if source.Info != "" { source.Info += " " + size } else { source.Info = size diff --git a/pkg/v4l2/device/device.go b/pkg/v4l2/device/device.go index 4e4d6ade..7377aef6 100644 --- a/pkg/v4l2/device/device.go +++ b/pkg/v4l2/device/device.go @@ -66,8 +66,8 @@ func (d *Device) ListFormats() ([]uint32, error) { return items, nil } -func (d *Device) ListSizes(pixFmt uint32) ([]uint32, error) { - var items []uint32 +func (d *Device) ListSizes(pixFmt uint32) ([][2]uint32, error) { + var items [][2]uint32 for i := uint32(0); ; i++ { fs := v4l2_frmsizeenum{ @@ -85,7 +85,7 @@ func (d *Device) ListSizes(pixFmt uint32) ([]uint32, error) { continue } - items = append(items, fs.discrete.width, fs.discrete.height) + items = append(items, [2]uint32{fs.discrete.width, fs.discrete.height}) } return items, nil From 59161c663b9c6d3beab9f81202782c606c1d1459 Mon Sep 17 00:00:00 2001 From: Alex X Date: Tue, 7 Jan 2025 22:18:09 +0300 Subject: [PATCH 20/26] Add support framerate param for v4l2 source --- pkg/v4l2/producer.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/v4l2/producer.go b/pkg/v4l2/producer.go index 644c5ee5..b979d346 100644 --- a/pkg/v4l2/producer.go +++ b/pkg/v4l2/producer.go @@ -65,6 +65,12 @@ func Open(rawURL string) (*Producer, error) { 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, From 7e0a163f120c98051b35b2f32690a323cb1aeb82 Mon Sep 17 00:00:00 2001 From: Alex X Date: Thu, 9 Jan 2025 00:20:48 +0300 Subject: [PATCH 21/26] Add support mips arch for v4l2 source --- internal/v4l2/v4l2.go | 2 +- internal/v4l2/v4l2_linux.go | 2 - pkg/v4l2/device/README.md | 19 +++ pkg/v4l2/device/arch.c | 163 ++++++++++++++++++++++++ pkg/v4l2/device/device.go | 2 +- pkg/v4l2/device/videodev2_386.go | 149 ++++++++++++++++++++++ pkg/v4l2/device/videodev2_arm.go | 149 ++++++++++++++++++++++ pkg/v4l2/device/videodev2_mipsle.go | 149 ++++++++++++++++++++++ pkg/v4l2/device/videodev2_test.go | 34 ----- pkg/v4l2/device/videodev2_x32.go | 152 ---------------------- pkg/v4l2/device/videodev2_x64.go | 190 ++++++++++++++-------------- 11 files changed, 725 insertions(+), 286 deletions(-) create mode 100644 pkg/v4l2/device/README.md create mode 100644 pkg/v4l2/device/arch.c create mode 100644 pkg/v4l2/device/videodev2_386.go create mode 100644 pkg/v4l2/device/videodev2_arm.go create mode 100644 pkg/v4l2/device/videodev2_mipsle.go delete mode 100644 pkg/v4l2/device/videodev2_test.go delete mode 100644 pkg/v4l2/device/videodev2_x32.go diff --git a/internal/v4l2/v4l2.go b/internal/v4l2/v4l2.go index cfef9667..9cef99a5 100644 --- a/internal/v4l2/v4l2.go +++ b/internal/v4l2/v4l2.go @@ -1,4 +1,4 @@ -//go:build !(linux && (386 || arm || amd64 || arm64)) +//go:build !linux package v4l2 diff --git a/internal/v4l2/v4l2_linux.go b/internal/v4l2/v4l2_linux.go index a16779f4..b9cc82a0 100644 --- a/internal/v4l2/v4l2_linux.go +++ b/internal/v4l2/v4l2_linux.go @@ -1,5 +1,3 @@ -//go:build linux && (386 || arm || amd64 || arm64) - package v4l2 import ( diff --git a/pkg/v4l2/device/README.md b/pkg/v4l2/device/README.md new file mode 100644 index 00000000..a816b23e --- /dev/null +++ b/pkg/v4l2/device/README.md @@ -0,0 +1,19 @@ +Build on Ubuntu + +```bash +sudo apt install gcc-x86-64-linux-gnu +sudo apt install gcc-i686-linux-gnu +sudo apt install gcc-aarch64-linux-gnu binutils +sudo apt install gcc-arm-linux-gnueabihf +sudo apt install gcc-mipsel-linux-gnu + +x86_64-linux-gnu-gcc -w -static arch.c -o arch_x86_64 +i686-linux-gnu-gcc -w -static arch.c -o arch_i686 +aarch64-linux-gnu-gcc -w -static arch.c -o arch_aarch64 +arm-linux-gnueabihf-gcc -w -static arch.c -o arch_armhf +mipsel-linux-gnu-gcc -static arch.c -o arch_mipsel +``` + +## Useful links + +- https://github.com/torvalds/linux/blob/master/include/uapi/linux/videodev2.h diff --git a/pkg/v4l2/device/arch.c b/pkg/v4l2/device/arch.c new file mode 100644 index 00000000..0b119584 --- /dev/null +++ b/pkg/v4l2/device/arch.c @@ -0,0 +1,163 @@ +#include +#include +#include + +#define printconst1(con) printf("\t%s = 0x%08lx\n", #con, con) +#define printconst2(con) printf("\t%s = %d\n", #con, con) +#define printstruct(str) printf("type %s struct { // size %lu\n", #str, sizeof(struct str)) +#define printmember(str, mem, typ) printf("\t%s %s // offset %lu, size %lu\n", #mem == "type" ? "typ" : #mem, typ, offsetof(struct str, mem), sizeof((struct str){0}.mem)) +#define printunimem(str, uni, mem, typ) printf("\t%s %s // offset %lu, size %lu\n", #mem, typ, offsetof(struct str, uni.mem), sizeof((struct str){0}.uni.mem)) +#define printalign1(str, mem2, mem1, siz1) printf("\t_ [%lu]byte // align\n", offsetof(struct str, mem2) - offsetof(struct str, mem1) - siz1) +#define printfiller(str, mem1, siz1) printf("\t_ [%lu]byte // filler\n", sizeof(struct str) - offsetof(struct str, mem1) - siz1) + +int main() { + printf("const (\n"); + printconst1(VIDIOC_QUERYCAP); + printconst1(VIDIOC_ENUM_FMT); + printconst1(VIDIOC_G_FMT); + printconst1(VIDIOC_S_FMT); + printconst1(VIDIOC_REQBUFS); + printconst1(VIDIOC_QUERYBUF); + printf("\n"); + printconst1(VIDIOC_QBUF); + printconst1(VIDIOC_DQBUF); + printconst1(VIDIOC_STREAMON); + printconst1(VIDIOC_STREAMOFF); + printconst1(VIDIOC_G_PARM); + printconst1(VIDIOC_S_PARM); + printf("\n"); + printconst1(VIDIOC_ENUM_FRAMESIZES); + printconst1(VIDIOC_ENUM_FRAMEINTERVALS); + printf(")\n\n"); + + printf("const (\n"); + printconst2(V4L2_BUF_TYPE_VIDEO_CAPTURE); + printconst2(V4L2_COLORSPACE_DEFAULT); + printconst2(V4L2_FIELD_NONE); + printconst2(V4L2_FRMIVAL_TYPE_DISCRETE); + printconst2(V4L2_FRMSIZE_TYPE_DISCRETE); + printconst2(V4L2_MEMORY_MMAP); + printf(")\n\n"); + + printstruct(v4l2_capability); + printmember(v4l2_capability, driver, "[16]byte"); + printmember(v4l2_capability, card, "[32]byte"); + printmember(v4l2_capability, bus_info, "[32]byte"); + printmember(v4l2_capability, version, "uint32"); + printmember(v4l2_capability, capabilities, "uint32"); + printmember(v4l2_capability, device_caps, "uint32"); + printmember(v4l2_capability, reserved, "[3]uint32"); + printf("}\n\n"); + + printstruct(v4l2_format); + printmember(v4l2_format, type, "uint32"); + printalign1(v4l2_format, fmt, type, 4); + printunimem(v4l2_format, fmt, pix, "v4l2_pix_format"); + printfiller(v4l2_format, fmt, sizeof(struct v4l2_pix_format)); + printf("}\n\n"); + + printstruct(v4l2_pix_format); + printmember(v4l2_pix_format, width, "uint32"); + printmember(v4l2_pix_format, height, "uint32"); + printmember(v4l2_pix_format, pixelformat, "uint32"); + printmember(v4l2_pix_format, field, "uint32"); + printmember(v4l2_pix_format, bytesperline, "uint32"); + printmember(v4l2_pix_format, sizeimage, "uint32"); + printmember(v4l2_pix_format, colorspace, "uint32"); + printmember(v4l2_pix_format, priv, "uint32"); + printmember(v4l2_pix_format, flags, "uint32"); + printmember(v4l2_pix_format, ycbcr_enc, "uint32"); + printmember(v4l2_pix_format, quantization, "uint32"); + printmember(v4l2_pix_format, xfer_func, "uint32"); + printf("}\n\n"); + + printstruct(v4l2_streamparm); + printmember(v4l2_streamparm, type, "uint32"); + printunimem(v4l2_streamparm, parm, capture, "v4l2_captureparm"); + printfiller(v4l2_streamparm, parm, sizeof(struct v4l2_captureparm)); + printf("}\n\n"); + + printstruct(v4l2_captureparm); + printmember(v4l2_captureparm, capability, "uint32"); + printmember(v4l2_captureparm, capturemode, "uint32"); + printmember(v4l2_captureparm, timeperframe, "v4l2_fract"); + printmember(v4l2_captureparm, extendedmode, "uint32"); + printmember(v4l2_captureparm, readbuffers, "uint32"); + printmember(v4l2_captureparm, reserved, "[4]uint32"); + printf("}\n\n"); + + printstruct(v4l2_fract); + printmember(v4l2_fract, numerator, "uint32"); + printmember(v4l2_fract, denominator, "uint32"); + printf("}\n\n"); + + printstruct(v4l2_requestbuffers); + printmember(v4l2_requestbuffers, count, "uint32"); + printmember(v4l2_requestbuffers, type, "uint32"); + printmember(v4l2_requestbuffers, memory, "uint32"); + printmember(v4l2_requestbuffers, capabilities, "uint32"); + printmember(v4l2_requestbuffers, flags, "uint8"); + printmember(v4l2_requestbuffers, reserved, "[3]uint8"); + printf("}\n\n"); + + printstruct(v4l2_buffer); + printmember(v4l2_buffer, index, "uint32"); + printmember(v4l2_buffer, type, "uint32"); + printmember(v4l2_buffer, bytesused, "uint32"); + printmember(v4l2_buffer, flags, "uint32"); + printmember(v4l2_buffer, field, "uint32"); + printalign1(v4l2_buffer, timecode, field, 4); + printmember(v4l2_buffer, timecode, "v4l2_timecode"); + printmember(v4l2_buffer, sequence, "uint32"); + printmember(v4l2_buffer, memory, "uint32"); + printunimem(v4l2_buffer, m, offset, "uint32"); + printalign1(v4l2_buffer, length, m, 4); + printmember(v4l2_buffer, length, "uint32"); + printfiller(v4l2_buffer, length, 4); + printf("}\n\n"); + + printstruct(v4l2_timecode); + printmember(v4l2_timecode, type, "uint32"); + printmember(v4l2_timecode, flags, "uint32"); + printmember(v4l2_timecode, frames, "uint8"); + printmember(v4l2_timecode, seconds, "uint8"); + printmember(v4l2_timecode, minutes, "uint8"); + printmember(v4l2_timecode, hours, "uint8"); + printmember(v4l2_timecode, userbits, "[4]uint8"); + printf("}\n\n"); + + printstruct(v4l2_fmtdesc); + printmember(v4l2_fmtdesc, index, "uint32"); + printmember(v4l2_fmtdesc, type, "uint32"); + printmember(v4l2_fmtdesc, flags, "uint32"); + printmember(v4l2_fmtdesc, description, "[32]byte"); + printmember(v4l2_fmtdesc, pixelformat, "uint32"); + printmember(v4l2_fmtdesc, mbus_code, "uint32"); + printmember(v4l2_fmtdesc, reserved, "[3]uint32"); + printf("}\n\n"); + + printstruct(v4l2_frmsizeenum); + printmember(v4l2_frmsizeenum, index, "uint32"); + printmember(v4l2_frmsizeenum, pixel_format, "uint32"); + printmember(v4l2_frmsizeenum, type, "uint32"); + printmember(v4l2_frmsizeenum, discrete, "v4l2_frmsize_discrete"); + printfiller(v4l2_frmsizeenum, discrete, sizeof(struct v4l2_frmsize_discrete)); + printf("}\n\n"); + + printstruct(v4l2_frmsize_discrete); + printmember(v4l2_frmsize_discrete, width, "uint32"); + printmember(v4l2_frmsize_discrete, height, "uint32"); + printf("}\n\n"); + + printstruct(v4l2_frmivalenum); + printmember(v4l2_frmivalenum, index, "uint32"); + printmember(v4l2_frmivalenum, pixel_format, "uint32"); + printmember(v4l2_frmivalenum, width, "uint32"); + printmember(v4l2_frmivalenum, height, "uint32"); + printmember(v4l2_frmivalenum, type, "uint32"); + printmember(v4l2_frmivalenum, discrete, "v4l2_fract"); + printfiller(v4l2_frmivalenum, discrete, sizeof(struct v4l2_fract)); + printf("}\n\n"); + + return 0; +} \ No newline at end of file diff --git a/pkg/v4l2/device/device.go b/pkg/v4l2/device/device.go index 7377aef6..27bbf350 100644 --- a/pkg/v4l2/device/device.go +++ b/pkg/v4l2/device/device.go @@ -121,7 +121,7 @@ func (d *Device) ListFrameRates(pixFmt, width, height uint32) ([]uint32, error) func (d *Device) SetFormat(width, height, pixFmt uint32) error { f := v4l2_format{ typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, - fmt: v4l2_pix_format{ + pix: v4l2_pix_format{ width: width, height: height, pixelformat: pixFmt, diff --git a/pkg/v4l2/device/videodev2_386.go b/pkg/v4l2/device/videodev2_386.go new file mode 100644 index 00000000..8737ca9d --- /dev/null +++ b/pkg/v4l2/device/videodev2_386.go @@ -0,0 +1,149 @@ +package device + +const ( + VIDIOC_QUERYCAP = 0x80685600 + VIDIOC_ENUM_FMT = 0xc0405602 + VIDIOC_G_FMT = 0xc0cc5604 + VIDIOC_S_FMT = 0xc0cc5605 + VIDIOC_REQBUFS = 0xc0145608 + VIDIOC_QUERYBUF = 0xc0445609 + + VIDIOC_QBUF = 0xc044560f + VIDIOC_DQBUF = 0xc0445611 + VIDIOC_STREAMON = 0x40045612 + VIDIOC_STREAMOFF = 0x40045613 + VIDIOC_G_PARM = 0xc0cc5615 + VIDIOC_S_PARM = 0xc0cc5616 + + VIDIOC_ENUM_FRAMESIZES = 0xc02c564a + VIDIOC_ENUM_FRAMEINTERVALS = 0xc034564b +) + +const ( + V4L2_BUF_TYPE_VIDEO_CAPTURE = 1 + V4L2_COLORSPACE_DEFAULT = 0 + V4L2_FIELD_NONE = 1 + V4L2_FRMIVAL_TYPE_DISCRETE = 1 + V4L2_FRMSIZE_TYPE_DISCRETE = 1 + V4L2_MEMORY_MMAP = 1 +) + +type v4l2_capability struct { // size 104 + driver [16]byte // offset 0, size 16 + card [32]byte // offset 16, size 32 + bus_info [32]byte // offset 48, size 32 + version uint32 // offset 80, size 4 + capabilities uint32 // offset 84, size 4 + device_caps uint32 // offset 88, size 4 + reserved [3]uint32 // offset 92, size 12 +} + +type v4l2_format struct { // size 204 + typ uint32 // offset 0, size 4 + _ [0]byte // align + pix v4l2_pix_format // offset 4, size 48 + _ [152]byte // filler +} + +type v4l2_pix_format struct { // size 48 + width uint32 // offset 0, size 4 + height uint32 // offset 4, size 4 + pixelformat uint32 // offset 8, size 4 + field uint32 // offset 12, size 4 + bytesperline uint32 // offset 16, size 4 + sizeimage uint32 // offset 20, size 4 + colorspace uint32 // offset 24, size 4 + priv uint32 // offset 28, size 4 + flags uint32 // offset 32, size 4 + ycbcr_enc uint32 // offset 36, size 4 + quantization uint32 // offset 40, size 4 + xfer_func uint32 // offset 44, size 4 +} + +type v4l2_streamparm struct { // size 204 + typ uint32 // offset 0, size 4 + capture v4l2_captureparm // offset 4, size 40 + _ [160]byte // filler +} + +type v4l2_captureparm struct { // size 40 + capability uint32 // offset 0, size 4 + capturemode uint32 // offset 4, size 4 + timeperframe v4l2_fract // offset 8, size 8 + extendedmode uint32 // offset 16, size 4 + readbuffers uint32 // offset 20, size 4 + reserved [4]uint32 // offset 24, size 16 +} + +type v4l2_fract struct { // size 8 + numerator uint32 // offset 0, size 4 + denominator uint32 // offset 4, size 4 +} + +type v4l2_requestbuffers struct { // size 20 + count uint32 // offset 0, size 4 + typ uint32 // offset 4, size 4 + memory uint32 // offset 8, size 4 + capabilities uint32 // offset 12, size 4 + flags uint8 // offset 16, size 1 + reserved [3]uint8 // offset 17, size 3 +} + +type v4l2_buffer struct { // size 68 + index uint32 // offset 0, size 4 + typ uint32 // offset 4, size 4 + bytesused uint32 // offset 8, size 4 + flags uint32 // offset 12, size 4 + field uint32 // offset 16, size 4 + _ [8]byte // align + timecode v4l2_timecode // offset 28, size 16 + sequence uint32 // offset 44, size 4 + memory uint32 // offset 48, size 4 + offset uint32 // offset 52, size 4 + _ [0]byte // align + length uint32 // offset 56, size 4 + _ [8]byte // filler +} + +type v4l2_timecode struct { // size 16 + typ uint32 // offset 0, size 4 + flags uint32 // offset 4, size 4 + frames uint8 // offset 8, size 1 + seconds uint8 // offset 9, size 1 + minutes uint8 // offset 10, size 1 + hours uint8 // offset 11, size 1 + userbits [4]uint8 // offset 12, size 4 +} + +type v4l2_fmtdesc struct { // size 64 + index uint32 // offset 0, size 4 + typ uint32 // offset 4, size 4 + flags uint32 // offset 8, size 4 + description [32]byte // offset 12, size 32 + pixelformat uint32 // offset 44, size 4 + mbus_code uint32 // offset 48, size 4 + reserved [3]uint32 // offset 52, size 12 +} + +type v4l2_frmsizeenum struct { // size 44 + index uint32 // offset 0, size 4 + pixel_format uint32 // offset 4, size 4 + typ uint32 // offset 8, size 4 + discrete v4l2_frmsize_discrete // offset 12, size 8 + _ [24]byte // filler +} + +type v4l2_frmsize_discrete struct { // size 8 + width uint32 // offset 0, size 4 + height uint32 // offset 4, size 4 +} + +type v4l2_frmivalenum struct { // size 52 + index uint32 // offset 0, size 4 + pixel_format uint32 // offset 4, size 4 + width uint32 // offset 8, size 4 + height uint32 // offset 12, size 4 + typ uint32 // offset 16, size 4 + discrete v4l2_fract // offset 20, size 8 + _ [24]byte // filler +} diff --git a/pkg/v4l2/device/videodev2_arm.go b/pkg/v4l2/device/videodev2_arm.go new file mode 100644 index 00000000..098ca5a3 --- /dev/null +++ b/pkg/v4l2/device/videodev2_arm.go @@ -0,0 +1,149 @@ +package device + +const ( + VIDIOC_QUERYCAP = 0x80685600 + VIDIOC_ENUM_FMT = 0xc0405602 + VIDIOC_G_FMT = 0xc0cc5604 + VIDIOC_S_FMT = 0xc0cc5605 + VIDIOC_REQBUFS = 0xc0145608 + VIDIOC_QUERYBUF = 0xc0505609 + + VIDIOC_QBUF = 0xc050560f + VIDIOC_DQBUF = 0xc0505611 + VIDIOC_STREAMON = 0x40045612 + VIDIOC_STREAMOFF = 0x40045613 + VIDIOC_G_PARM = 0xc0cc5615 + VIDIOC_S_PARM = 0xc0cc5616 + + VIDIOC_ENUM_FRAMESIZES = 0xc02c564a + VIDIOC_ENUM_FRAMEINTERVALS = 0xc034564b +) + +const ( + V4L2_BUF_TYPE_VIDEO_CAPTURE = 1 + V4L2_COLORSPACE_DEFAULT = 0 + V4L2_FIELD_NONE = 1 + V4L2_FRMIVAL_TYPE_DISCRETE = 1 + V4L2_FRMSIZE_TYPE_DISCRETE = 1 + V4L2_MEMORY_MMAP = 1 +) + +type v4l2_capability struct { // size 104 + driver [16]byte // offset 0, size 16 + card [32]byte // offset 16, size 32 + bus_info [32]byte // offset 48, size 32 + version uint32 // offset 80, size 4 + capabilities uint32 // offset 84, size 4 + device_caps uint32 // offset 88, size 4 + reserved [3]uint32 // offset 92, size 12 +} + +type v4l2_format struct { // size 204 + typ uint32 // offset 0, size 4 + _ [0]byte // align + pix v4l2_pix_format // offset 4, size 48 + _ [152]byte // filler +} + +type v4l2_pix_format struct { // size 48 + width uint32 // offset 0, size 4 + height uint32 // offset 4, size 4 + pixelformat uint32 // offset 8, size 4 + field uint32 // offset 12, size 4 + bytesperline uint32 // offset 16, size 4 + sizeimage uint32 // offset 20, size 4 + colorspace uint32 // offset 24, size 4 + priv uint32 // offset 28, size 4 + flags uint32 // offset 32, size 4 + ycbcr_enc uint32 // offset 36, size 4 + quantization uint32 // offset 40, size 4 + xfer_func uint32 // offset 44, size 4 +} + +type v4l2_streamparm struct { // size 204 + typ uint32 // offset 0, size 4 + capture v4l2_captureparm // offset 4, size 40 + _ [160]byte // filler +} + +type v4l2_captureparm struct { // size 40 + capability uint32 // offset 0, size 4 + capturemode uint32 // offset 4, size 4 + timeperframe v4l2_fract // offset 8, size 8 + extendedmode uint32 // offset 16, size 4 + readbuffers uint32 // offset 20, size 4 + reserved [4]uint32 // offset 24, size 16 +} + +type v4l2_fract struct { // size 8 + numerator uint32 // offset 0, size 4 + denominator uint32 // offset 4, size 4 +} + +type v4l2_requestbuffers struct { // size 20 + count uint32 // offset 0, size 4 + typ uint32 // offset 4, size 4 + memory uint32 // offset 8, size 4 + capabilities uint32 // offset 12, size 4 + flags uint8 // offset 16, size 1 + reserved [3]uint8 // offset 17, size 3 +} + +type v4l2_buffer struct { // size 80 + index uint32 // offset 0, size 4 + typ uint32 // offset 4, size 4 + bytesused uint32 // offset 8, size 4 + flags uint32 // offset 12, size 4 + field uint32 // offset 16, size 4 + _ [20]byte // align + timecode v4l2_timecode // offset 40, size 16 + sequence uint32 // offset 56, size 4 + memory uint32 // offset 60, size 4 + offset uint32 // offset 64, size 4 + _ [0]byte // align + length uint32 // offset 68, size 4 + _ [8]byte // filler +} + +type v4l2_timecode struct { // size 16 + typ uint32 // offset 0, size 4 + flags uint32 // offset 4, size 4 + frames uint8 // offset 8, size 1 + seconds uint8 // offset 9, size 1 + minutes uint8 // offset 10, size 1 + hours uint8 // offset 11, size 1 + userbits [4]uint8 // offset 12, size 4 +} + +type v4l2_fmtdesc struct { // size 64 + index uint32 // offset 0, size 4 + typ uint32 // offset 4, size 4 + flags uint32 // offset 8, size 4 + description [32]byte // offset 12, size 32 + pixelformat uint32 // offset 44, size 4 + mbus_code uint32 // offset 48, size 4 + reserved [3]uint32 // offset 52, size 12 +} + +type v4l2_frmsizeenum struct { // size 44 + index uint32 // offset 0, size 4 + pixel_format uint32 // offset 4, size 4 + typ uint32 // offset 8, size 4 + discrete v4l2_frmsize_discrete // offset 12, size 8 + _ [24]byte // filler +} + +type v4l2_frmsize_discrete struct { // size 8 + width uint32 // offset 0, size 4 + height uint32 // offset 4, size 4 +} + +type v4l2_frmivalenum struct { // size 52 + index uint32 // offset 0, size 4 + pixel_format uint32 // offset 4, size 4 + width uint32 // offset 8, size 4 + height uint32 // offset 12, size 4 + typ uint32 // offset 16, size 4 + discrete v4l2_fract // offset 20, size 8 + _ [24]byte // filler +} diff --git a/pkg/v4l2/device/videodev2_mipsle.go b/pkg/v4l2/device/videodev2_mipsle.go new file mode 100644 index 00000000..19e8164f --- /dev/null +++ b/pkg/v4l2/device/videodev2_mipsle.go @@ -0,0 +1,149 @@ +package device + +const ( + VIDIOC_QUERYCAP = 0x40685600 + VIDIOC_ENUM_FMT = 0xc0405602 + VIDIOC_G_FMT = 0xc0cc5604 + VIDIOC_S_FMT = 0xc0cc5605 + VIDIOC_REQBUFS = 0xc0145608 + VIDIOC_QUERYBUF = 0xc0505609 + + VIDIOC_QBUF = 0xc050560f + VIDIOC_DQBUF = 0xc0505611 + VIDIOC_STREAMON = 0x80045612 + VIDIOC_STREAMOFF = 0x80045613 + VIDIOC_G_PARM = 0xc0cc5615 + VIDIOC_S_PARM = 0xc0cc5616 + + VIDIOC_ENUM_FRAMESIZES = 0xc02c564a + VIDIOC_ENUM_FRAMEINTERVALS = 0xc034564b +) + +const ( + V4L2_BUF_TYPE_VIDEO_CAPTURE = 1 + V4L2_COLORSPACE_DEFAULT = 0 + V4L2_FIELD_NONE = 1 + V4L2_FRMIVAL_TYPE_DISCRETE = 1 + V4L2_FRMSIZE_TYPE_DISCRETE = 1 + V4L2_MEMORY_MMAP = 1 +) + +type v4l2_capability struct { // size 104 + driver [16]byte // offset 0, size 16 + card [32]byte // offset 16, size 32 + bus_info [32]byte // offset 48, size 32 + version uint32 // offset 80, size 4 + capabilities uint32 // offset 84, size 4 + device_caps uint32 // offset 88, size 4 + reserved [3]uint32 // offset 92, size 12 +} + +type v4l2_format struct { // size 204 + typ uint32 // offset 0, size 4 + _ [0]byte // align + pix v4l2_pix_format // offset 4, size 48 + _ [152]byte // filler +} + +type v4l2_pix_format struct { // size 48 + width uint32 // offset 0, size 4 + height uint32 // offset 4, size 4 + pixelformat uint32 // offset 8, size 4 + field uint32 // offset 12, size 4 + bytesperline uint32 // offset 16, size 4 + sizeimage uint32 // offset 20, size 4 + colorspace uint32 // offset 24, size 4 + priv uint32 // offset 28, size 4 + flags uint32 // offset 32, size 4 + ycbcr_enc uint32 // offset 36, size 4 + quantization uint32 // offset 40, size 4 + xfer_func uint32 // offset 44, size 4 +} + +type v4l2_streamparm struct { // size 204 + typ uint32 // offset 0, size 4 + capture v4l2_captureparm // offset 4, size 40 + _ [160]byte // filler +} + +type v4l2_captureparm struct { // size 40 + capability uint32 // offset 0, size 4 + capturemode uint32 // offset 4, size 4 + timeperframe v4l2_fract // offset 8, size 8 + extendedmode uint32 // offset 16, size 4 + readbuffers uint32 // offset 20, size 4 + reserved [4]uint32 // offset 24, size 16 +} + +type v4l2_fract struct { // size 8 + numerator uint32 // offset 0, size 4 + denominator uint32 // offset 4, size 4 +} + +type v4l2_requestbuffers struct { // size 20 + count uint32 // offset 0, size 4 + typ uint32 // offset 4, size 4 + memory uint32 // offset 8, size 4 + capabilities uint32 // offset 12, size 4 + flags uint8 // offset 16, size 1 + reserved [3]uint8 // offset 17, size 3 +} + +type v4l2_buffer struct { // size 80 + index uint32 // offset 0, size 4 + typ uint32 // offset 4, size 4 + bytesused uint32 // offset 8, size 4 + flags uint32 // offset 12, size 4 + field uint32 // offset 16, size 4 + _ [20]byte // align + timecode v4l2_timecode // offset 40, size 16 + sequence uint32 // offset 56, size 4 + memory uint32 // offset 60, size 4 + offset uint32 // offset 64, size 4 + _ [0]byte // align + length uint32 // offset 68, size 4 + _ [8]byte // filler +} + +type v4l2_timecode struct { // size 16 + typ uint32 // offset 0, size 4 + flags uint32 // offset 4, size 4 + frames uint8 // offset 8, size 1 + seconds uint8 // offset 9, size 1 + minutes uint8 // offset 10, size 1 + hours uint8 // offset 11, size 1 + userbits [4]uint8 // offset 12, size 4 +} + +type v4l2_fmtdesc struct { // size 64 + index uint32 // offset 0, size 4 + typ uint32 // offset 4, size 4 + flags uint32 // offset 8, size 4 + description [32]byte // offset 12, size 32 + pixelformat uint32 // offset 44, size 4 + mbus_code uint32 // offset 48, size 4 + reserved [3]uint32 // offset 52, size 12 +} + +type v4l2_frmsizeenum struct { // size 44 + index uint32 // offset 0, size 4 + pixel_format uint32 // offset 4, size 4 + typ uint32 // offset 8, size 4 + discrete v4l2_frmsize_discrete // offset 12, size 8 + _ [24]byte // filler +} + +type v4l2_frmsize_discrete struct { // size 8 + width uint32 // offset 0, size 4 + height uint32 // offset 4, size 4 +} + +type v4l2_frmivalenum struct { // size 52 + index uint32 // offset 0, size 4 + pixel_format uint32 // offset 4, size 4 + width uint32 // offset 8, size 4 + height uint32 // offset 12, size 4 + typ uint32 // offset 16, size 4 + discrete v4l2_fract // offset 20, size 8 + _ [24]byte // filler +} diff --git a/pkg/v4l2/device/videodev2_test.go b/pkg/v4l2/device/videodev2_test.go deleted file mode 100644 index 2556feef..00000000 --- a/pkg/v4l2/device/videodev2_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package device - -import ( - "runtime" - "testing" - "unsafe" - - "github.com/stretchr/testify/require" -) - -func TestSize(t *testing.T) { - switch runtime.GOARCH { - case "amd64", "arm64": - require.Equal(t, 104, int(unsafe.Sizeof(v4l2_capability{}))) - require.Equal(t, 208, int(unsafe.Sizeof(v4l2_format{}))) - require.Equal(t, 204, int(unsafe.Sizeof(v4l2_streamparm{}))) - require.Equal(t, 20, int(unsafe.Sizeof(v4l2_requestbuffers{}))) - require.Equal(t, 88, int(unsafe.Sizeof(v4l2_buffer{}))) - require.Equal(t, 16, int(unsafe.Sizeof(v4l2_timecode{}))) - require.Equal(t, 64, int(unsafe.Sizeof(v4l2_fmtdesc{}))) - require.Equal(t, 44, int(unsafe.Sizeof(v4l2_frmsizeenum{}))) - require.Equal(t, 52, int(unsafe.Sizeof(v4l2_frmivalenum{}))) - case "386", "arm": - require.Equal(t, 104, int(unsafe.Sizeof(v4l2_capability{}))) - require.Equal(t, 204, int(unsafe.Sizeof(v4l2_format{}))) - require.Equal(t, 204, int(unsafe.Sizeof(v4l2_streamparm{}))) - require.Equal(t, 20, int(unsafe.Sizeof(v4l2_requestbuffers{}))) - require.Equal(t, 68, int(unsafe.Sizeof(v4l2_buffer{}))) - require.Equal(t, 16, int(unsafe.Sizeof(v4l2_timecode{}))) - require.Equal(t, 64, int(unsafe.Sizeof(v4l2_fmtdesc{}))) - require.Equal(t, 44, int(unsafe.Sizeof(v4l2_frmsizeenum{}))) - require.Equal(t, 52, int(unsafe.Sizeof(v4l2_frmivalenum{}))) - } -} diff --git a/pkg/v4l2/device/videodev2_x32.go b/pkg/v4l2/device/videodev2_x32.go deleted file mode 100644 index 4c4db26d..00000000 --- a/pkg/v4l2/device/videodev2_x32.go +++ /dev/null @@ -1,152 +0,0 @@ -//go:build 386 || arm - -package device - -// https://github.com/torvalds/linux/blob/master/include/uapi/linux/videodev2.h - -const ( - VIDIOC_QUERYCAP = 0x80685600 - VIDIOC_ENUM_FMT = 0xc0405602 - VIDIOC_G_FMT = 0xc0cc5604 - VIDIOC_S_FMT = 0xc0cc5605 - VIDIOC_REQBUFS = 0xc0145608 - VIDIOC_QUERYBUF = 0xc0445609 - - VIDIOC_QBUF = 0xc044560f - VIDIOC_DQBUF = 0xc0445611 - VIDIOC_STREAMON = 0x40045612 - VIDIOC_STREAMOFF = 0x40045613 - VIDIOC_G_PARM = 0xc0cc5615 - VIDIOC_S_PARM = 0xc0cc5616 - - VIDIOC_ENUM_FRAMESIZES = 0xc02c564a - VIDIOC_ENUM_FRAMEINTERVALS = 0xc034564b -) - -const ( - V4L2_BUF_TYPE_VIDEO_CAPTURE = 1 - V4L2_COLORSPACE_DEFAULT = 0 - V4L2_FIELD_NONE = 1 - V4L2_FRMIVAL_TYPE_DISCRETE = 1 - V4L2_FRMSIZE_TYPE_DISCRETE = 1 - V4L2_MEMORY_MMAP = 1 -) - -type v4l2_capability struct { - driver [16]byte - card [32]byte - bus_info [32]byte - version uint32 - capabilities uint32 - device_caps uint32 - reserved [3]uint32 -} - -type v4l2_format struct { - typ uint32 - fmt v4l2_pix_format -} - -type v4l2_pix_format struct { - width uint32 // 0 - height uint32 // 4 - pixelformat uint32 // 8 - field uint32 // 12 - bytesperline uint32 // 16 - sizeimage uint32 // 20 - colorspace uint32 // 24 - priv uint32 // 28 - flags uint32 // 32 - ycbcr_enc uint32 // 36 - quantization uint32 // 40 - xfer_func uint32 // 44 - - _ [152]byte // 48 -} - -type v4l2_streamparm struct { - typ uint32 - capture v4l2_captureparm -} - -type v4l2_captureparm struct { - capability uint32 // 0 - capturemode uint32 // 4 - timeperframe v4l2_fract // 8 - extendedmode uint32 // 16 - readbuffers uint32 // 20 - - _ [176]byte // 24 -} - -type v4l2_fract struct { - numerator uint32 - denominator uint32 -} - -type v4l2_requestbuffers struct { - count uint32 - typ uint32 - memory uint32 - capabilities uint32 - flags uint8 - reserved [3]uint8 -} - -type v4l2_buffer struct { - index uint32 // 0 - typ uint32 // 4 - bytesused uint32 // 8 - flags uint32 // 12 - field uint32 // 16 - _ [8]byte // 20 - timecode v4l2_timecode // 28 - sequence uint32 // 44 - memory uint32 // 48 - offset uint32 // 52 - length uint32 // 56 - _ [8]byte // 60 -} - -type v4l2_timecode struct { - typ uint32 - flags uint32 - frames uint8 - seconds uint8 - minutes uint8 - hours uint8 - userbits [4]uint8 -} - -type v4l2_fmtdesc struct { - index uint32 - typ uint32 - flags uint32 - description [32]byte - pixelformat uint32 - mbus_code uint32 - reserved [3]uint32 -} - -type v4l2_frmsizeenum struct { - index uint32 // 0 - pixel_format uint32 // 4 - typ uint32 // 8 - discrete v4l2_frmsize_discrete // 12 - _ [24]byte -} - -type v4l2_frmsize_discrete struct { - width uint32 - height uint32 -} - -type v4l2_frmivalenum struct { - index uint32 - pixel_format uint32 - width uint32 - height uint32 - typ uint32 - discrete v4l2_fract - _ [24]byte -} diff --git a/pkg/v4l2/device/videodev2_x64.go b/pkg/v4l2/device/videodev2_x64.go index 97c3ab95..6e1018e0 100644 --- a/pkg/v4l2/device/videodev2_x64.go +++ b/pkg/v4l2/device/videodev2_x64.go @@ -2,8 +2,6 @@ package device -// https://github.com/torvalds/linux/blob/master/include/uapi/linux/videodev2.h - const ( VIDIOC_QUERYCAP = 0x80685600 VIDIOC_ENUM_FMT = 0xc0405602 @@ -32,122 +30,122 @@ const ( V4L2_MEMORY_MMAP = 1 ) -type v4l2_capability struct { - driver [16]byte - card [32]byte - bus_info [32]byte - version uint32 - capabilities uint32 - device_caps uint32 - reserved [3]uint32 +type v4l2_capability struct { // size 104 + driver [16]byte // offset 0, size 16 + card [32]byte // offset 16, size 32 + bus_info [32]byte // offset 48, size 32 + version uint32 // offset 80, size 4 + capabilities uint32 // offset 84, size 4 + device_caps uint32 // offset 88, size 4 + reserved [3]uint32 // offset 92, size 12 } -type v4l2_format struct { - typ uint64 - fmt v4l2_pix_format +type v4l2_format struct { // size 208 + typ uint32 // offset 0, size 4 + _ [4]byte // align + pix v4l2_pix_format // offset 8, size 48 + _ [152]byte // filler } -type v4l2_pix_format struct { - width uint32 // 0 - height uint32 // 4 - pixelformat uint32 // 8 - field uint32 // 12 - bytesperline uint32 // 16 - sizeimage uint32 // 20 - colorspace uint32 // 24 - priv uint32 // 28 - flags uint32 // 32 - ycbcr_enc uint32 // 36 - quantization uint32 // 40 - xfer_func uint32 // 44 - - _ [152]byte // 48 +type v4l2_pix_format struct { // size 48 + width uint32 // offset 0, size 4 + height uint32 // offset 4, size 4 + pixelformat uint32 // offset 8, size 4 + field uint32 // offset 12, size 4 + bytesperline uint32 // offset 16, size 4 + sizeimage uint32 // offset 20, size 4 + colorspace uint32 // offset 24, size 4 + priv uint32 // offset 28, size 4 + flags uint32 // offset 32, size 4 + ycbcr_enc uint32 // offset 36, size 4 + quantization uint32 // offset 40, size 4 + xfer_func uint32 // offset 44, size 4 } -type v4l2_streamparm struct { - typ uint32 - capture v4l2_captureparm +type v4l2_streamparm struct { // size 204 + typ uint32 // offset 0, size 4 + capture v4l2_captureparm // offset 4, size 40 + _ [160]byte // filler } -type v4l2_captureparm struct { - capability uint32 // 0 - capturemode uint32 // 4 - timeperframe v4l2_fract // 8 - extendedmode uint32 // 16 - readbuffers uint32 // 20 - - _ [176]byte // 24 +type v4l2_captureparm struct { // size 40 + capability uint32 // offset 0, size 4 + capturemode uint32 // offset 4, size 4 + timeperframe v4l2_fract // offset 8, size 8 + extendedmode uint32 // offset 16, size 4 + readbuffers uint32 // offset 20, size 4 + reserved [4]uint32 // offset 24, size 16 } -type v4l2_fract struct { - numerator uint32 - denominator uint32 +type v4l2_fract struct { // size 8 + numerator uint32 // offset 0, size 4 + denominator uint32 // offset 4, size 4 } -type v4l2_requestbuffers struct { - count uint32 - typ uint32 - memory uint32 - capabilities uint32 - flags uint8 - reserved [3]uint8 +type v4l2_requestbuffers struct { // size 20 + count uint32 // offset 0, size 4 + typ uint32 // offset 4, size 4 + memory uint32 // offset 8, size 4 + capabilities uint32 // offset 12, size 4 + flags uint8 // offset 16, size 1 + reserved [3]uint8 // offset 17, size 3 } -type v4l2_buffer struct { - index uint32 // 0 - typ uint32 // 4 - bytesused uint32 // 8 - flags uint32 // 12 - field uint32 // 16 - _ [20]byte // 20 - timecode v4l2_timecode // 40 - sequence uint32 // 56 - memory uint32 // 60 - offset uint32 // 64 - _ [4]byte // 68 - length uint32 // 72 - _ [12]byte // 76 +type v4l2_buffer struct { // size 88 + index uint32 // offset 0, size 4 + typ uint32 // offset 4, size 4 + bytesused uint32 // offset 8, size 4 + flags uint32 // offset 12, size 4 + field uint32 // offset 16, size 4 + _ [20]byte // align + timecode v4l2_timecode // offset 40, size 16 + sequence uint32 // offset 56, size 4 + memory uint32 // offset 60, size 4 + offset uint32 // offset 64, size 4 + _ [4]byte // align + length uint32 // offset 72, size 4 + _ [12]byte // filler } -type v4l2_timecode struct { - typ uint32 - flags uint32 - frames uint8 - seconds uint8 - minutes uint8 - hours uint8 - userbits [4]uint8 +type v4l2_timecode struct { // size 16 + typ uint32 // offset 0, size 4 + flags uint32 // offset 4, size 4 + frames uint8 // offset 8, size 1 + seconds uint8 // offset 9, size 1 + minutes uint8 // offset 10, size 1 + hours uint8 // offset 11, size 1 + userbits [4]uint8 // offset 12, size 4 } -type v4l2_fmtdesc struct { - index uint32 - typ uint32 - flags uint32 - description [32]byte - pixelformat uint32 - mbus_code uint32 - reserved [3]uint32 +type v4l2_fmtdesc struct { // size 64 + index uint32 // offset 0, size 4 + typ uint32 // offset 4, size 4 + flags uint32 // offset 8, size 4 + description [32]byte // offset 12, size 32 + pixelformat uint32 // offset 44, size 4 + mbus_code uint32 // offset 48, size 4 + reserved [3]uint32 // offset 52, size 12 } -type v4l2_frmsizeenum struct { - index uint32 // 0 - pixel_format uint32 // 4 - typ uint32 // 8 - discrete v4l2_frmsize_discrete // 12 - _ [24]byte +type v4l2_frmsizeenum struct { // size 44 + index uint32 // offset 0, size 4 + pixel_format uint32 // offset 4, size 4 + typ uint32 // offset 8, size 4 + discrete v4l2_frmsize_discrete // offset 12, size 8 + _ [24]byte // filler } -type v4l2_frmsize_discrete struct { - width uint32 - height uint32 +type v4l2_frmsize_discrete struct { // size 8 + width uint32 // offset 0, size 4 + height uint32 // offset 4, size 4 } -type v4l2_frmivalenum struct { - index uint32 - pixel_format uint32 - width uint32 - height uint32 - typ uint32 - discrete v4l2_fract - _ [24]byte +type v4l2_frmivalenum struct { // size 52 + index uint32 // offset 0, size 4 + pixel_format uint32 // offset 4, size 4 + width uint32 // offset 8, size 4 + height uint32 // offset 12, size 4 + typ uint32 // offset 16, size 4 + discrete v4l2_fract // offset 20, size 8 + _ [24]byte // filler } From 879ef603fe2c4ec80ee56e0e5243f7c623db91b5 Mon Sep 17 00:00:00 2001 From: Alex X Date: Thu, 9 Jan 2025 00:30:01 +0300 Subject: [PATCH 22/26] Update v4l2 discovery --- internal/v4l2/v4l2_linux.go | 48 ++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/internal/v4l2/v4l2_linux.go b/internal/v4l2/v4l2_linux.go index b9cc82a0..2cd60692 100644 --- a/internal/v4l2/v4l2_linux.go +++ b/internal/v4l2/v4l2_linux.go @@ -44,32 +44,33 @@ func apiV4L2(w http.ResponseWriter, r *http.Request) { formats, _ := dev.ListFormats() for _, fourCC := range formats { - source := &api.Source{} + name, ffmpeg := findFormat(fourCC) + source := &api.Source{Name: name} - for _, format := range device.Formats { - if format.FourCC == fourCC { - source.Name = format.Name - source.URL = "v4l2:device?video=" + path + "&input_format=" + format.FFmpeg + "&video_size=" - break + sizes, _ := dev.ListSizes(fourCC) + for _, wh := range sizes { + if source.Info != "" { + source.Info += " " } - } - if source.Name != "" { - sizes, _ := dev.ListSizes(fourCC) - for _, wh := range sizes { - size := fmt.Sprintf("%dx%d", wh[0], wh[1]) - if source.Info != "" { - source.Info += " " + size - } else { - source.Info = size - source.URL += size + source.Info += fmt.Sprintf("%dx%d", wh[0], wh[1]) + + frameRates, _ := dev.ListFrameRates(fourCC, wh[0], wh[1]) + for _, fr := range frameRates { + source.Info += fmt.Sprintf("@%d", fr) + + if source.URL == "" && ffmpeg != "" { + source.URL = fmt.Sprintf( + "v4l2:device?video=%s&input_format=%s&video_size=%dx%d&framerate=%d", + path, ffmpeg, wh[0], wh[1], fr, + ) } } - } else { - source.Name = string(binary.LittleEndian.AppendUint32(nil, fourCC)) } - sources = append(sources, source) + if source.Info != "" { + sources = append(sources, source) + } } _ = dev.Close() @@ -77,3 +78,12 @@ func apiV4L2(w http.ResponseWriter, r *http.Request) { api.ResponseSources(w, sources) } + +func findFormat(fourCC uint32) (name, ffmpeg string) { + for _, format := range device.Formats { + if format.FourCC == fourCC { + return format.Name, format.FFmpeg + } + } + return string(binary.LittleEndian.AppendUint32(nil, fourCC)), "" +} From 773e415dff51a471a83e6a4e3bd227b3c445eb99 Mon Sep 17 00:00:00 2001 From: Alex X Date: Thu, 9 Jan 2025 07:18:36 +0300 Subject: [PATCH 23/26] Code refactoring for v4l2 device --- pkg/v4l2/device/README.md | 12 +++++++----- pkg/v4l2/device/{arch.c => videodev2_arch.c} | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 15 deletions(-) rename pkg/v4l2/device/{arch.c => videodev2_arch.c} (88%) diff --git a/pkg/v4l2/device/README.md b/pkg/v4l2/device/README.md index a816b23e..802eca93 100644 --- a/pkg/v4l2/device/README.md +++ b/pkg/v4l2/device/README.md @@ -1,3 +1,5 @@ +# Video For Linux Two + Build on Ubuntu ```bash @@ -7,11 +9,11 @@ sudo apt install gcc-aarch64-linux-gnu binutils sudo apt install gcc-arm-linux-gnueabihf sudo apt install gcc-mipsel-linux-gnu -x86_64-linux-gnu-gcc -w -static arch.c -o arch_x86_64 -i686-linux-gnu-gcc -w -static arch.c -o arch_i686 -aarch64-linux-gnu-gcc -w -static arch.c -o arch_aarch64 -arm-linux-gnueabihf-gcc -w -static arch.c -o arch_armhf -mipsel-linux-gnu-gcc -static arch.c -o arch_mipsel +x86_64-linux-gnu-gcc -w -static videodev2_arch.c -o videodev2_x86_64 +i686-linux-gnu-gcc -w -static videodev2_arch.c -o videodev2_i686 +aarch64-linux-gnu-gcc -w -static videodev2_arch.c -o videodev2_aarch64 +arm-linux-gnueabihf-gcc -w -static videodev2_arch.c -o videodev2_armhf +mipsel-linux-gnu-gcc -w -static videodev2_arch.c -o videodev2_mipsel ``` ## Useful links diff --git a/pkg/v4l2/device/arch.c b/pkg/v4l2/device/videodev2_arch.c similarity index 88% rename from pkg/v4l2/device/arch.c rename to pkg/v4l2/device/videodev2_arch.c index 0b119584..1053a088 100644 --- a/pkg/v4l2/device/arch.c +++ b/pkg/v4l2/device/videodev2_arch.c @@ -7,8 +7,8 @@ #define printstruct(str) printf("type %s struct { // size %lu\n", #str, sizeof(struct str)) #define printmember(str, mem, typ) printf("\t%s %s // offset %lu, size %lu\n", #mem == "type" ? "typ" : #mem, typ, offsetof(struct str, mem), sizeof((struct str){0}.mem)) #define printunimem(str, uni, mem, typ) printf("\t%s %s // offset %lu, size %lu\n", #mem, typ, offsetof(struct str, uni.mem), sizeof((struct str){0}.uni.mem)) -#define printalign1(str, mem2, mem1, siz1) printf("\t_ [%lu]byte // align\n", offsetof(struct str, mem2) - offsetof(struct str, mem1) - siz1) -#define printfiller(str, mem1, siz1) printf("\t_ [%lu]byte // filler\n", sizeof(struct str) - offsetof(struct str, mem1) - siz1) +#define printalign1(str, mem2, mem1) printf("\t_ [%lu]byte // align\n", offsetof(struct str, mem2) - offsetof(struct str, mem1) - sizeof((struct str){0}.mem1)) +#define printfiller(str, mem) printf("\t_ [%lu]byte // filler\n", sizeof(struct str) - offsetof(struct str, mem) - sizeof((struct str){0}.mem)) int main() { printf("const (\n"); @@ -51,9 +51,9 @@ int main() { printstruct(v4l2_format); printmember(v4l2_format, type, "uint32"); - printalign1(v4l2_format, fmt, type, 4); + printalign1(v4l2_format, fmt, type); printunimem(v4l2_format, fmt, pix, "v4l2_pix_format"); - printfiller(v4l2_format, fmt, sizeof(struct v4l2_pix_format)); + printfiller(v4l2_format, fmt.pix); printf("}\n\n"); printstruct(v4l2_pix_format); @@ -74,7 +74,7 @@ int main() { printstruct(v4l2_streamparm); printmember(v4l2_streamparm, type, "uint32"); printunimem(v4l2_streamparm, parm, capture, "v4l2_captureparm"); - printfiller(v4l2_streamparm, parm, sizeof(struct v4l2_captureparm)); + printfiller(v4l2_streamparm, parm.capture); printf("}\n\n"); printstruct(v4l2_captureparm); @@ -106,14 +106,14 @@ int main() { printmember(v4l2_buffer, bytesused, "uint32"); printmember(v4l2_buffer, flags, "uint32"); printmember(v4l2_buffer, field, "uint32"); - printalign1(v4l2_buffer, timecode, field, 4); + printalign1(v4l2_buffer, timecode, field); printmember(v4l2_buffer, timecode, "v4l2_timecode"); printmember(v4l2_buffer, sequence, "uint32"); printmember(v4l2_buffer, memory, "uint32"); printunimem(v4l2_buffer, m, offset, "uint32"); - printalign1(v4l2_buffer, length, m, 4); + printalign1(v4l2_buffer, length, m.offset); printmember(v4l2_buffer, length, "uint32"); - printfiller(v4l2_buffer, length, 4); + printfiller(v4l2_buffer, length); printf("}\n\n"); printstruct(v4l2_timecode); @@ -141,7 +141,7 @@ int main() { printmember(v4l2_frmsizeenum, pixel_format, "uint32"); printmember(v4l2_frmsizeenum, type, "uint32"); printmember(v4l2_frmsizeenum, discrete, "v4l2_frmsize_discrete"); - printfiller(v4l2_frmsizeenum, discrete, sizeof(struct v4l2_frmsize_discrete)); + printfiller(v4l2_frmsizeenum, discrete); printf("}\n\n"); printstruct(v4l2_frmsize_discrete); @@ -156,7 +156,7 @@ int main() { printmember(v4l2_frmivalenum, height, "uint32"); printmember(v4l2_frmivalenum, type, "uint32"); printmember(v4l2_frmivalenum, discrete, "v4l2_fract"); - printfiller(v4l2_frmivalenum, discrete, sizeof(struct v4l2_fract)); + printfiller(v4l2_frmivalenum, discrete); printf("}\n\n"); return 0; From 0664e46a4bc45107e478619a2c1bdca215dca27a Mon Sep 17 00:00:00 2001 From: Alex X Date: Fri, 10 Jan 2025 12:57:37 +0300 Subject: [PATCH 24/26] Fix v4l2 source for MIPS --- pkg/v4l2/device/README.md | 2 +- pkg/v4l2/device/videodev2_mipsle.go | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/v4l2/device/README.md b/pkg/v4l2/device/README.md index 802eca93..de686ea0 100644 --- a/pkg/v4l2/device/README.md +++ b/pkg/v4l2/device/README.md @@ -13,7 +13,7 @@ x86_64-linux-gnu-gcc -w -static videodev2_arch.c -o videodev2_x86_64 i686-linux-gnu-gcc -w -static videodev2_arch.c -o videodev2_i686 aarch64-linux-gnu-gcc -w -static videodev2_arch.c -o videodev2_aarch64 arm-linux-gnueabihf-gcc -w -static videodev2_arch.c -o videodev2_armhf -mipsel-linux-gnu-gcc -w -static videodev2_arch.c -o videodev2_mipsel +mipsel-linux-gnu-gcc -w -static videodev2_arch.c -o videodev2_mipsel -D_TIME_BITS=32 ``` ## Useful links diff --git a/pkg/v4l2/device/videodev2_mipsle.go b/pkg/v4l2/device/videodev2_mipsle.go index 19e8164f..cecc54c4 100644 --- a/pkg/v4l2/device/videodev2_mipsle.go +++ b/pkg/v4l2/device/videodev2_mipsle.go @@ -6,10 +6,10 @@ const ( VIDIOC_G_FMT = 0xc0cc5604 VIDIOC_S_FMT = 0xc0cc5605 VIDIOC_REQBUFS = 0xc0145608 - VIDIOC_QUERYBUF = 0xc0505609 + VIDIOC_QUERYBUF = 0xc0445609 - VIDIOC_QBUF = 0xc050560f - VIDIOC_DQBUF = 0xc0505611 + VIDIOC_QBUF = 0xc044560f + VIDIOC_DQBUF = 0xc0445611 VIDIOC_STREAMON = 0x80045612 VIDIOC_STREAMOFF = 0x80045613 VIDIOC_G_PARM = 0xc0cc5615 @@ -89,19 +89,19 @@ type v4l2_requestbuffers struct { // size 20 reserved [3]uint8 // offset 17, size 3 } -type v4l2_buffer struct { // size 80 +type v4l2_buffer struct { // size 68 index uint32 // offset 0, size 4 typ uint32 // offset 4, size 4 bytesused uint32 // offset 8, size 4 flags uint32 // offset 12, size 4 field uint32 // offset 16, size 4 - _ [20]byte // align - timecode v4l2_timecode // offset 40, size 16 - sequence uint32 // offset 56, size 4 - memory uint32 // offset 60, size 4 - offset uint32 // offset 64, size 4 + _ [8]byte // align + timecode v4l2_timecode // offset 28, size 16 + sequence uint32 // offset 44, size 4 + memory uint32 // offset 48, size 4 + offset uint32 // offset 52, size 4 _ [0]byte // align - length uint32 // offset 68, size 4 + length uint32 // offset 56, size 4 _ [8]byte // filler } From 7dc9beb1712ac1074657ca052f7df3ee1230c954 Mon Sep 17 00:00:00 2001 From: Alex X Date: Fri, 10 Jan 2025 14:56:42 +0300 Subject: [PATCH 25/26] Add ws and ffmpeg modules to go2rtc_mjpeg --- examples/go2rtc_mjpeg/main.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/go2rtc_mjpeg/main.go b/examples/go2rtc_mjpeg/main.go index a3e08ff5..3c915b3c 100644 --- a/examples/go2rtc_mjpeg/main.go +++ b/examples/go2rtc_mjpeg/main.go @@ -2,7 +2,9 @@ package main import ( "github.com/AlexxIT/go2rtc/internal/api" + "github.com/AlexxIT/go2rtc/internal/api/ws" "github.com/AlexxIT/go2rtc/internal/app" + "github.com/AlexxIT/go2rtc/internal/ffmpeg" "github.com/AlexxIT/go2rtc/internal/mjpeg" "github.com/AlexxIT/go2rtc/internal/streams" "github.com/AlexxIT/go2rtc/internal/v4l2" @@ -14,6 +16,9 @@ func main() { streams.Init() api.Init() + ws.Init() + + ffmpeg.Init() mjpeg.Init() v4l2.Init() From 83907132b57466e9d094675a2f1c972f8e5bceb8 Mon Sep 17 00:00:00 2001 From: Alex X Date: Fri, 10 Jan 2025 15:01:01 +0300 Subject: [PATCH 26/26] Update about packed and planar YUV formats --- pkg/v4l2/device/device.go | 4 ++-- pkg/v4l2/device/formats.go | 2 +- pkg/v4l2/producer.go | 4 ++-- pkg/y4m/README.md | 14 ++++++++++++++ 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/pkg/v4l2/device/device.go b/pkg/v4l2/device/device.go index 27bbf350..7f16fd23 100644 --- a/pkg/v4l2/device/device.go +++ b/pkg/v4l2/device/device.go @@ -196,7 +196,7 @@ func (d *Device) StreamOff() (err error) { return ioctl(d.fd, VIDIOC_REQBUFS, unsafe.Pointer(&rb)) } -func (d *Device) Capture(cositedYUV bool) ([]byte, error) { +func (d *Device) Capture(planarYUV bool) ([]byte, error) { dec := v4l2_buffer{ typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, memory: V4L2_MEMORY_MMAP, @@ -206,7 +206,7 @@ func (d *Device) Capture(cositedYUV bool) ([]byte, error) { } buf := make([]byte, dec.bytesused) - if cositedYUV { + if planarYUV { YUYV2YUV(buf, d.bufs[dec.index][:dec.bytesused]) } else { copy(buf, d.bufs[dec.index][:dec.bytesused]) diff --git a/pkg/v4l2/device/formats.go b/pkg/v4l2/device/formats.go index 94d12504..fb54bbd1 100644 --- a/pkg/v4l2/device/formats.go +++ b/pkg/v4l2/device/formats.go @@ -16,7 +16,7 @@ var Formats = []Format{ {V4L2_PIX_FMT_MJPEG, "Motion-JPEG", "mjpeg"}, } -// YUYV2YUV convert [Y0 Cb Y1 Cr] to cosited [Y0Y1... Cb... Cr...] +// YUYV2YUV convert packed YUV to planar YUV func YUYV2YUV(dst, src []byte) { n := len(src) i0 := 0 diff --git a/pkg/v4l2/producer.go b/pkg/v4l2/producer.go index b979d346..87199762 100644 --- a/pkg/v4l2/producer.go +++ b/pkg/v4l2/producer.go @@ -93,10 +93,10 @@ func (c *Producer) Start() error { return err } - cositedYUV := c.Medias[0].Codecs[0].Name == core.CodecRAW + planarYUV := c.Medias[0].Codecs[0].Name == core.CodecRAW for { - buf, err := c.dev.Capture(cositedYUV) + buf, err := c.dev.Capture(planarYUV) if err != nil { return err } diff --git a/pkg/y4m/README.md b/pkg/y4m/README.md index 6f4d863e..ff97813b 100644 --- a/pkg/y4m/README.md +++ b/pkg/y4m/README.md @@ -1,5 +1,19 @@ +## Planar YUV formats + +Packed YUV - yuyv422 - YUYV 4:2:2 +Semi-Planar - nv12 - Y/CbCr 4:2:0 +Planar YUV - yuv420p - Planar YUV 4:2:0 - aka. [cosited](https://manned.org/yuv4mpeg.5) + +``` +[video4linux2,v4l2 @ 0x55fddc42a940] Raw : yuyv422 : YUYV 4:2:2 : 1920x1080 +[video4linux2,v4l2 @ 0x55fddc42a940] Raw : nv12 : Y/CbCr 4:2:0 : 1920x1080 +[video4linux2,v4l2 @ 0x55fddc42a940] Raw : yuv420p : Planar YUV 4:2:0 : 1920x1080 +``` + ## Useful links - https://learn.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering - https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Video_concepts - https://fourcc.org/yuv.php#YV12 +- https://docs.kernel.org/userspace-api/media/v4l/pixfmt-yuv-planar.html +- https://gist.github.com/Jim-Bar/3cbba684a71d1a9d468a6711a6eddbeb