Categorygithub.com/ilya2ik/wcwebcamclient_go
modulepackage
0.0.0-20240425073918-d07283ce8a32
Repository: https://github.com/ilya2ik/wcwebcamclient_go.git
Documentation: pkg.go.dev

# README

wcWebCamClient Go Module

This is a library for convenient client work with the wcWebCamServer server via the JSON protocol.

The structure of the client-server software package

The server designed to collect images and data streams from cameras (devices) and forwards messages between devices to control the periphery via an HTTP 2 connection is wcwebcamserver (Lazarus/Free Pascal). Library for a C/C++ client is wcwebcamclient (C/C++). Abstract client for Lazarus is wccurlclient (Lazarus/Free Pascal). A detailed implementation of an external device based on "ESP32-CAM" is given in the example webcamdevice (С). The example of a desktop application for external device controlling and viewing images is webcamclientviewer (Lazarus). An example of an Android application for controlling external devices, chatting and streaming is wcwebcameracontrol (Java).

Usage

Use go get -u to download and install the prebuilt package.

go get -u github.com/ilya2ik/wcwebcamclient_go

or

go install github.com/ilya2ik/wcwebcamclient_go@latest

Example for in/out streaming

/* Streaming files from a folder as a set of frames  */

package main

import (
   "bytes"
   "encoding/binary"
   "flag"
   "fmt"
   "os"
   "strings"
   "sync"
   "time"

   wclib "github.com/ilya2ik/wcwebcamclient_go"
)

/* Relative path to files */
const TO_SEND_FOLDER = "tosend"

/* The max delta time between two frames in milliseconds */
const MAX_DELTA = 600

/* The total program timeout in seconds */
const TIME_OUT = 600

type appStatus int

const (
   StatusWaiting appStatus = iota
   StatusAuthorized
   StatusIOStarted
   StatusIOFinished
   StatusError
)

type appStreamStruct struct {
   mux              sync.Mutex
   frames           []string
   cur_frame        int
   status           appStatus
   mem_frame_buffer *bytes.Buffer
   sequence         chan *appStreamStruct
}

func (app *appStreamStruct) Lock() {
   app.mux.Lock()
}

func (app *appStreamStruct) Unlock() {
   app.mux.Unlock()
}

func (app *appStreamStruct) SetStatus(st appStatus) {
   app.Lock()
   defer app.Unlock()

   if st != app.status {
      app.status = st
      app.sequence <- app
   }
}

func (app *appStreamStruct) GetStatus() appStatus {
   app.Lock()
   defer app.Unlock()

   return app.status
}

func (app *appStreamStruct) FramesCount() int {
   app.Lock()
   defer app.Unlock()

   return len(app.frames)
}

func (app *appStreamStruct) CurFrame() string {
   app.Lock()
   defer app.Unlock()

   return app.frames[app.cur_frame]
}

func (app *appStreamStruct) NextFrame() {
   app.Lock()
   defer app.Unlock()

   app.cur_frame++
   if app.cur_frame >= len(app.frames) {
      app.cur_frame = 0
   }
}

var appStream = appStreamStruct{
   status:           StatusWaiting,
   mem_frame_buffer: bytes.NewBuffer(make([]byte, 0)),
   sequence:         make(chan *appStreamStruct, 16),
}

/* Read file to memory */
func raw_read(pathname string) error {

   fp, err := os.Open(pathname)
   if err != nil {
      return err
   }

   defer fp.Close()

   fi, err := fp.Stat()
   if err != nil {
      return err
   }

   var fsize uint32 = uint32(fi.Size())

   appStream.Lock()
   defer appStream.Unlock()

   appStream.mem_frame_buffer.Reset()

   frame_buffer := (((fsize + uint32(wclib.WC_STREAM_FRAME_HEADER_SIZE)) >> 12) + 1) << 12
   if appStream.mem_frame_buffer.Cap() < int(fsize) {
      appStream.mem_frame_buffer.Grow(int(frame_buffer))
   }
   binary.Write(appStream.mem_frame_buffer, binary.LittleEndian, wclib.WC_FRAME_START_SEQ)
   binary.Write(appStream.mem_frame_buffer, binary.LittleEndian, fsize)
   _, err = appStream.mem_frame_buffer.ReadFrom(fp)
   if err != nil {
      return err
   }

   return nil
}

func AuthSuccess(tsk wclib.ITask) {
   fmt.Println("SID ", tsk.GetClient().GetSID())
   appStream.SetStatus(StatusAuthorized)
}

/* Callback. IO stream closed. */
func onIOTaskFinished(tsk wclib.ITask) {
   fmt.Println("Output stream closed")
   appStream.SetStatus(StatusIOFinished)
}

/* Callback. IO stream started. */
func onIOTaskStarted(tsk wclib.ITask) {
   fmt.Println("Output stream started")
   appStream.SetStatus(StatusIOStarted)
}

func check(e error) {
   if e != nil {
      panic(e)
   }
}

func main() {
   flag.Parse()

   cfg := wclib.ClientCfgNew()
   check(cfg.SetHostURL("https://username:password@localhost:8080"))
   cfg.SetDevice("device_to_listen")

   c, err := wclib.ClientNew(cfg)
   check(err)
   c.SetOnAuthSuccess(AuthSuccess)

   c.SetOnAfterLaunchOutStream(onIOTaskStarted)
   c.SetOnSuccessIOStream(onIOTaskFinished)

   fmt.Println("Trying to start client")
   check(c.Start())

   start_ts := time.Now().UnixMilli()
   frame_start_ts := start_ts

   for loop := true; loop; {

      switch c.GetClientStatus() {
      case wclib.StateConnectedWrongSID:
         {
            fmt.Println("Trying to authorize")
            check(c.AuthFromHostUrl())
         }
      case wclib.StateDisconnected:
         {
            loop = false
            break
         }
      default:
         {
            select {
            case v := <-appStream.sequence:
               {
                  switch st := v.GetStatus(); st {
                  case StatusError:
                     {
                        fmt.Println("Some error occurred")
                        c.Disconnect()
                     }
                  case StatusAuthorized:
                     {
                        files, err := os.ReadDir(TO_SEND_FOLDER)
                        if err != nil {
                           check(err)
                        }

                        for _, f := range files {
                           if strings.HasSuffix(strings.ToUpper(f.Name()), ".RAW") {
                              appStream.frames =
                                            append(appStream.frames,
                                                    TO_SEND_FOLDER+"/"+f.Name())
                           }
                        }

                        if appStream.FramesCount() == 0 {
                           fmt.Println("No frames found")
                           appStream.SetStatus(StatusError)
                        }

                        go func() {
                           fmt.Println("Starting output stream...")
                           if err := c.LaunchOutStream("RAW", MAX_DELTA, nil); err != nil {
                              fmt.Printf("Error on starting stream: %v\n", err)
                              appStream.SetStatus(StatusError)
                           }
                        }()
                     }
                  case StatusIOStarted:
                     {
                        cur_ts := time.Now().UnixMilli()

                        timeout := cur_ts - frame_start_ts

                        if timeout >= MAX_DELTA {
                           frame_start_ts = cur_ts
                           go func() {
                              check(raw_read(appStream.CurFrame()))
                              appStream.NextFrame()

                              appStream.Lock()
                              defer appStream.Unlock()
                              buf := appStream.mem_frame_buffer
                              if buf.Len() > 0 {
                                 appStream.mem_frame_buffer = bytes.NewBuffer(make([]byte, 0))
                                 fmt.Println("Next frame sended")
                                 c.PushOutData(buf)
                              }
                              appStream.sequence <- &appStream
                           }()
                        } else {
                           time.Sleep(10 * time.Millisecond)
                           appStream.sequence <- &appStream
                        }
                     }
                  case StatusIOFinished:
                     {
                        fmt.Println("Process fully finished")
                        c.Disconnect()
                     }
                  }
               }
            default:
               time.Sleep(250 * time.Millisecond)
               cur_ts := time.Now().UnixMilli()

               if (cur_ts-start_ts)/1000 > TIME_OUT {
                  fmt.Println("Timeout")
                  appStream.SetStatus(StatusIOFinished)
               }
            }
         }
      }

   }

   close(appStream.sequence)

   fmt.Println("Client finished")
}
/* Streaming of the incoming data.
   Frames are extracted from the stream and
   saved in OUTPUT_FOLDER in separate files  */

package main

import (
   "bytes"
   "fmt"
   "os"
   "sync"
   "time"

   wclib "github.com/ilya2ik/wcwebcamclient_go"
)

/* Relative path to save output data */
const OUTPUT_FOLDER = "output"

/* The total program timeout in seconds */
const TIME_OUT = 60

type appStatus int

const (
   StatusWaiting appStatus = iota
   StatusAuthorized
   StatusStreamDetected
   StatusIOStarted
   StatusIOFinished
   StatusError
)

type appStreamStruct struct {
   mux              sync.Mutex
   deltaTime        int
   curFrame         int
   status           appStatus
   mem_frame_buffer *bytes.Buffer
   sequence         chan *appStreamStruct
}

func (app *appStreamStruct) Lock() {
   app.mux.Lock()
}

func (app *appStreamStruct) Unlock() {
   app.mux.Unlock()
}

func (app *appStreamStruct) SetStatus(st appStatus) {
   app.Lock()
   defer app.Unlock()

   if st != app.status {
      app.status = st
      app.sequence <- app
   }
}

func (app *appStreamStruct) GetStatus() appStatus {
   app.Lock()
   defer app.Unlock()

   return app.status
}

var appStream = appStreamStruct{
   status:           StatusWaiting,
   mem_frame_buffer: bytes.NewBuffer(make([]byte, 0)),
   sequence:         make(chan *appStreamStruct, 16),
   deltaTime:        1000,
}

func AuthSuccess(tsk wclib.ITask) {
   fmt.Println("SID ", tsk.GetClient().GetSID())
   appStream.SetStatus(StatusAuthorized)
}

func OnUpdateStreams(tsk wclib.ITask, jsonobj []map[string]any) {
   for _, jStrm := range jsonobj {
      var aStrm wclib.StreamStruct
      err := aStrm.JSONDecode(jStrm)
      check(err)

      if aStrm.Device == "device_to_listen" {
         fmt.Printf("Stream detected `%s`; subproto: `%s`; delta: %d\n",
            aStrm.Device, aStrm.SubProto, int(appStream.deltaTime))
         appStream.deltaTime = int(aStrm.Delta)
         appStream.SetStatus(StatusStreamDetected)
         return
      }
   }
   fmt.Println("Streaming device is not online. Retry")
   appStream.sequence <- &appStream
}

/* Callback. IO stream closed. */
func onIOTaskFinished(tsk wclib.ITask) {
   fmt.Println("Output stream closed")
   appStream.SetStatus(StatusIOFinished)
}

func onNextFrame(tsk wclib.ITask) {
   fmt.Println("New frame captured")
   appStream.sequence <- &appStream
}

func check(e error) {
   if e != nil {
      panic(e)
   }
}

func main() {
   cfg := wclib.ClientCfgNew()
   check(cfg.SetHostURL("https://user:password@localhost:8080"))
   cfg.SetDevice("test")

   c, err := wclib.ClientNew(cfg)
   check(err)
   c.SetOnAuthSuccess(AuthSuccess)
   c.SetOnUpdateStreams(OnUpdateStreams)
   c.SetOnSuccessIOStream(onIOTaskFinished)

   fmt.Println("Trying to start client")
   check(c.Start())

   var start_ts int64

   for loop := true; loop; {

      switch c.GetClientStatus() {
      case wclib.StateConnectedWrongSID:
         {
            fmt.Println("Trying to authorize")
            check(c.AuthFromHostUrl())
         }
      case wclib.StateDisconnected:
         {
            loop = false
            break
         }
      default:
         {
            select {
            case v := <-appStream.sequence:
               {
                  switch st := v.GetStatus(); st {
                  case StatusError:
                     {
                        fmt.Println("Some error occurred")
                        c.Disconnect()
                     }
                  case StatusAuthorized:
                     {
                        check(c.UpdateStreams(nil))
                     }
                  case StatusStreamDetected:
                     {
                        start_ts = time.Now().UnixMilli()
                        go func() {
                           fmt.Println("Starting incoming stream...")
                           if err := c.LaunchInStream("device_to_listen", onNextFrame, nil); err != nil {
                              fmt.Printf("Error on starting stream: %v\n", err)
                              appStream.SetStatus(StatusError)
                           } else {
                              appStream.SetStatus(StatusIOStarted)
                           }
                        }()
                     }
                  case StatusIOStarted:
                     {
                        fr, err := c.PopInFrame()
                        if err != nil {
                           fmt.Printf("Error on frame extract: %v\n", err)
                           appStream.SetStatus(StatusError)
                        }

                        go func(frame *bytes.Buffer) {
                           outfile, err := os.Create(
                              fmt.Sprintf("%s/frame%05d.raw", OUTPUT_FOLDER, appStream.curFrame))
                           if err != nil {
                              appStream.SetStatus(StatusError)
                              panic(err)
                           }

                           appStream.curFrame++

                           defer outfile.Close()

                           _, err = frame.WriteTo(outfile)

                           if err != nil {
                              appStream.SetStatus(StatusError)
                              panic(err)
                           }
                        }(fr)
                     }
                  case StatusIOFinished:
                     {
                        fmt.Println("Process fully finished")
                        c.Disconnect()
                     }
                  }
               }
            default:
               time.Sleep(time.Duration(appStream.deltaTime) * time.Millisecond)
               cur_ts := time.Now().UnixMilli()

               if (cur_ts-start_ts)/1000 > TIME_OUT {
                  fmt.Println("Timeout")
                  appStream.SetStatus(StatusIOFinished)
               }
            }
         }
      }

   }

   close(appStream.sequence)

   fmt.Println("Client finished")
}

Documents

wcWebCamClient library API User's Guide - Doxygen

# Functions

Create new empty client configuration.
Create client.
No description provided by the author
You can use JSONHelper methods to convert json maps to structs.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Constants

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
The initial frame buffer size.
The frame buffer size (the maximum size of one frame).
The frame header sequence.
The size of frame header (6 bytes).

# Variables

No description provided by the author

# Structs

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Interfaces

No description provided by the author

# Type aliases

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author