txlyre 1 月之前
當前提交
6a88f4d1fb
共有 4 個文件被更改,包括 187 次插入0 次删除
  1. 1 0
      .gitignore
  2. 10 0
      go.mod
  3. 11 0
      go.sum
  4. 165 0
      main.go

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+client

+ 10 - 0
go.mod

@@ -0,0 +1,10 @@
+module yebi.su/client
+
+go 1.24.0
+
+require (
+	github.com/gorilla/websocket v1.5.3
+	github.com/vladimirvivien/go4vl v0.0.5
+	golang.org/x/crypto v0.36.0
+	golang.org/x/sys v0.31.0 // indirect
+)

+ 11 - 0
go.sum

@@ -0,0 +1,11 @@
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/vladimirvivien/go4vl v0.0.5 h1:jHuo/CZOAzYGzrSMOc7anOMNDr03uWH5c1B5kQ+Chnc=
+github.com/vladimirvivien/go4vl v0.0.5/go.mod h1:FP+/fG/X1DUdbZl9uN+l33vId1QneVn+W80JMc17OL8=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

+ 165 - 0
main.go

@@ -0,0 +1,165 @@
+package main
+
+import (
+	"context"
+	"flag"
+	"log"
+	"net/http"
+
+	"golang.org/x/crypto/blake2b"
+
+	dev "github.com/vladimirvivien/go4vl/device"
+	"github.com/vladimirvivien/go4vl/v4l2"
+
+	"github.com/gorilla/websocket"
+)
+
+func sender(queue chan []byte, done chan int, secret string) {
+	log.Println("start sender")
+
+	defer func() {
+		log.Println("sender done")
+
+		done <- 0
+	}()
+
+	h := make(http.Header, 1)
+	h.Set("X-Secret", secret)
+
+	ws, _, err := websocket.DefaultDialer.Dial("wss://yebi.su/ws", h)
+	if err != nil {
+		log.Println("dial error")
+
+		return
+	}
+
+	defer ws.Close()
+
+	log.Println("connected")
+
+	for {
+		data := <-queue
+
+		log.Printf("sending %db\n", len(data))
+
+		if err := ws.WriteMessage(websocket.BinaryMessage, data); err != nil {
+			log.Println("WriteMessage() failed")
+
+			break
+		}
+	}
+}
+
+func chunkBy[T any](items []T, chunkSize int) (chunks [][]T) {
+	for chunkSize < len(items) {
+		items, chunks = items[chunkSize:], append(chunks, items[0:chunkSize:chunkSize])
+	}
+	return chunks
+}
+
+func processor(inQueue chan []byte, outQueue chan []byte) {
+	log.Println("start processor")
+
+	for {
+		data := <-inQueue
+
+		newData := []byte{}
+		for _, chunk := range chunkBy(data, 2) {
+			newData = append(newData, chunk[0]^chunk[1])
+		}
+
+		data = newData
+
+		newData = []byte{}
+		for _, chunk := range chunkBy(data, 8) {
+			var b byte = 0
+
+			for j := range 8 {
+				if chunk[j]&1 != 0 {
+					b |= (1 << (7 - j))
+				}
+			}
+
+			newData = append(newData, b)
+		}
+
+		data = newData
+
+		newData = []byte{}
+		for _, chunk := range chunkBy(data, 32) {
+			sum := blake2b.Sum256(chunk)
+
+			newData = append(newData, sum[:]...)
+		}
+
+		outQueue <- newData
+	}
+}
+
+func main() {
+	var deviceName string
+	var width uint
+	var height uint
+	var bufferSize uint
+	var ident string
+	var secret string
+
+	flag.StringVar(&deviceName, "device", "/dev/video0", "v4l2 device to capture from")
+	flag.UintVar(&width, "width", 1280, "frame width")
+	flag.UintVar(&height, "height", 720, "frame height")
+	flag.UintVar(&bufferSize, "bufferSize", 1024, "sending queue max capacity")
+	flag.StringVar(&secret, "ident", "", "yebi.su ident")
+	flag.StringVar(&secret, "secret", "", "yebi.su secret")
+
+	flag.Parse()
+
+	if ident == "" {
+		log.Panicln("ident not specified")
+	}
+
+	if secret == "" {
+		log.Panicln("secret not specified")
+	}
+
+	secret = ident + " " + secret
+
+	log.Printf("open v4l2 device: %s\n", deviceName)
+
+	device, err := dev.Open(
+		deviceName,
+		dev.WithPixFormat(v4l2.PixFormat{Width: uint32(width), Height: uint32(height), PixelFormat: v4l2.PixelFmtYUYV, Field: v4l2.FieldNone}),
+	)
+	if err != nil {
+		log.Panicln("failed to open device")
+	}
+
+	defer device.Close()
+
+	senderDone := make(chan int, 1)
+	processorQueue := make(chan []byte, bufferSize)
+	senderQueue := make(chan []byte, bufferSize)
+
+	go sender(senderQueue, senderDone, secret)
+	go processor(processorQueue, senderQueue)
+
+	log.Println("start recording")
+
+	ctx, stop := context.WithCancel(context.TODO())
+
+	if err := device.Start(ctx); err != nil {
+		log.Panicln("failed to start recording on device")
+	}
+
+	defer stop()
+
+	log.Println("start loop")
+
+	for {
+		select {
+		case <-senderDone:
+			go sender(senderQueue, senderDone, secret)
+		case frame := <-device.GetOutput():
+			processorQueue <- frame
+		}
+	}
+}