diff --git a/README.md b/README.md index 15f9d0a..dd03b9d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # go-bsb-cams -Simple program to take and output the Bigscreen Beyond 2e cameras to a webserver to be used with eyetracking software, the stream.go is taken from https://github.com/garymcbay/mjpeg but has the content length removed as our stream is live. +Simple program to take and output the Bigscreen Beyond 2e cameras to a webserver to be used with eyetracking software. ## Usage Clone This repo and get the dependencies with: `go get .` -To run, execute the following command within the root directory: `go run .` or build the package and run the resulting executable. +To run, execute the following command within the root directory: `go run main.go` or build the package and run the resulting executable. -The code by default outputs to `localhost:8080/stream/` but can be configured within the code. +The code by default outputs to `localhost:8080/stream` but can be configured within the code. diff --git a/go.mod b/go.mod index b0d31f9..02445a1 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,11 @@ require ( github.com/labstack/echo v3.3.10+incompatible ) +replace github.com/hybridgroup/mjpeg v0.0.0-20250330094202-16d243df0e35 => github.com/LilliaElaine/mjpeg v0.0.0-20251021192541-2c29015ad34b + require ( github.com/google/uuid v1.6.0 // indirect + github.com/hybridgroup/mjpeg v0.0.0-20250330094202-16d243df0e35 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/go.sum b/go.sum index a6aea00..d69dea8 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,11 @@ +github.com/LilliaElaine/mjpeg v0.0.0-20251021192541-2c29015ad34b h1:2XgthQFq/yXkCJUNNVz3CGE5p3wI+JOGXphJaOgyz8I= +github.com/LilliaElaine/mjpeg v0.0.0-20251021192541-2c29015ad34b/go.mod h1:GAKUz9knlg+UB2oaCqgLTlWdsuVlf7ieJGt+oZzL4eY= github.com/google/gousb v1.1.3 h1:xt6M5TDsGSZ+rlomz5Si5Hmd/Fvbmo2YCJHN+yGaK4o= github.com/google/gousb v1.1.3/go.mod h1:GGWUkK0gAXDzxhwrzetW592aOmkkqSGcj5KLEgmCVUg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hybridgroup/mjpeg v0.0.0-20250330094202-16d243df0e35 h1:bTJxSThEisMQhK+VQUaHK72RqcUr6hXzYtRnuJByiQo= +github.com/hybridgroup/mjpeg v0.0.0-20250330094202-16d243df0e35/go.mod h1:GAKUz9knlg+UB2oaCqgLTlWdsuVlf7ieJGt+oZzL4eY= github.com/kevmo314/go-uvc v0.0.0-20250915020343-0eb292711e9f h1:4wmr7EOCjLjhSsdmMZz0gI+yko0Xs3liB6Dv6sroETM= github.com/kevmo314/go-uvc v0.0.0-20250915020343-0eb292711e9f/go.mod h1:tTMA/Kw0QEfReK+VotFviO3Gntk3ito14rh9m0W7Flg= github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= diff --git a/main.go b/main.go index 3405c32..18096bc 100644 --- a/main.go +++ b/main.go @@ -2,14 +2,16 @@ package main import ( "bytes" + "flag" "fmt" "image/jpeg" "log" "net/http" + "strconv" "syscall" - // "github.com/garymcbay/mjpeg" "github.com/google/gousb" + "github.com/hybridgroup/mjpeg" "github.com/kevmo314/go-uvc" "github.com/kevmo314/go-uvc/pkg/descriptors" ) @@ -25,18 +27,24 @@ func getdevice() (device string) { return fmt.Sprintf("/dev/bus/usb/%03v/%03v", dev.Desc.Bus, dev.Desc.Address) } +var verbosePtr = flag.Bool("verbose", false, "Whether or not to show libusb errors") +var port = flag.Int("port", 8080, "What Port To Output Frames To (Default is 8080)") + func main() { - stream := NewStream() + flag.Parse() + stream := mjpeg.NewLiveStream() device := getdevice() // Pass your jpegBuffer frames using stream.UpdateJPEG() go imagestreamer(stream, device) mux := http.NewServeMux() mux.Handle("/stream", stream) - log.Fatal(http.ListenAndServe(":8080", mux)) + log.Print("Server Is Running And Can Be Accessed At: http://localhost:" + strconv.Itoa(*port) + "/stream") + log.Print("Make Sure You Have No Ending / When Inputting The Url Into Baballonia !!!") + log.Fatal(http.ListenAndServe(":"+strconv.Itoa(*port), mux)) } -func imagestreamer(stream *Stream, device string) { +func imagestreamer(stream *mjpeg.Stream, device string) { frame: fd, err := syscall.Open(device, syscall.O_RDWR, 0) var deviceFd = fd @@ -69,8 +77,10 @@ frame: for { fr, err := resp.ReadFrame() if err != nil { - log.Print(err) - log.Print("Reclaiming Frame Reader and continuing to get frames... ") + if *verbosePtr { + log.Print(err) + log.Print("Reclaiming Frame Reader and continuing to get frames... ") + } syscall.Close(deviceFd) goto frame } diff --git a/stream.go b/stream.go deleted file mode 100644 index 3d65a6f..0000000 --- a/stream.go +++ /dev/null @@ -1,118 +0,0 @@ -// Package mjpeg implements a simple MJPEG streamer. -// -// Stream objects implement the http.Handler interface, allowing to use them with the net/http package like so: -// -// stream = mjpeg.NewStream() -// http.Handle("/camera", stream) -// -// Then push new JPEG frames to the connected clients using stream.UpdateJPEG(). -package main - -import ( - "fmt" - "log" - "net/http" - "sync" - "time" - - "github.com/labstack/echo" -) - -// Stream represents a single video feed. -type Stream struct { - m map[chan []byte]bool - frame []byte - lock sync.Mutex - FrameInterval time.Duration -} - -const boundaryWord = "MJPEGBOUNDARY" -const headerf = "\r\n" + - "--" + boundaryWord + "\r\n" + - "Content-Type: image/jpeg\r\n" + - // "Content-Length: %d\r\n" + - "X-Timestamp: 0.000000\r\n" + - "\r\n" - -// ServeHTTP responds to HTTP requests with the MJPEG stream, implementing the http.Handler interface. -func (s *Stream) ServeHTTP(w http.ResponseWriter, r *http.Request) { - log.Println("Stream:", r.RemoteAddr, "connected") - w.Header().Add("Content-Type", "multipart/x-mixed-replace") - - c := make(chan []byte) - s.lock.Lock() - s.m[c] = true - s.lock.Unlock() - - for { - time.Sleep(s.FrameInterval) - b := <-c - _, err := w.Write(b) - if err != nil { - break - } - } - - s.lock.Lock() - delete(s.m, c) - s.lock.Unlock() - log.Println("Stream:", r.RemoteAddr, "disconnected") -} - -// StreamToEcho implements Echo headers to respond to Echo HTTP requests with an MJPEG stream. -func (s *Stream) StreamToEcho(c echo.Context) error { - log.Println("Stream:", c.Request().RemoteAddr, "connected") - c.Response().Header().Set("Content-Type", "multipart/x-mixed-replace;boundary="+boundaryWord) - - ch := make(chan []byte) - s.lock.Lock() - s.m[ch] = true - s.lock.Unlock() - - for { - time.Sleep(s.FrameInterval) - b := <-ch - _, err := c.Response().Write(b) - if err != nil { - break - } - } - - s.lock.Lock() - delete(s.m, ch) - s.lock.Unlock() - log.Println("Stream:", c.Request().RemoteAddr, "disconnected") - return nil -} - -// UpdateJPEG pushes a new JPEG frame onto the clients. -func (s *Stream) UpdateJPEG(jpeg []byte) { - header := fmt.Sprintf(headerf, len(jpeg)) - if len(s.frame) < len(jpeg)+len(header) { - s.frame = make([]byte, (len(jpeg)+len(header))*2) - // s.frame = make([]byte, (len(jpeg) + len(header))) - } - - copy(s.frame, header) - copy(s.frame[len(header):], jpeg) - - s.lock.Lock() - for c := range s.m { - // Select to skip streams which are sleeping to drop frames. - // This might need more thought. - select { - case c <- s.frame: - default: - } - } - s.lock.Unlock() -} - -// NewStream initializes and returns a new Stream. -func NewStream() *Stream { - return &Stream{ - m: make(map[chan []byte]bool), - frame: make([]byte, len(headerf)), - FrameInterval: 50 * time.Millisecond, - } -}