嵌入式开发知识

大小端数

小端数:低位在前,高位在后

例如:变量x在内存中的地址为0x0010,x的值为0x1122,那么0x11为高位字节序,而0x22为低位字节序。

对于大端存储,将0x11放在低地址中,既0x0010,0x22则放在高地址中,既0x0011。

对于小端模式,内存地址不用管,值0x1234,小端模式为0x3412

	binary.LittleEndian.PutUint16(data[:2], tmpX)

高低八位

//Conv2Uint8 将实际参数转换为命令序参数,x10000

func Conv2Uint8(param float32) []uint8 {

	var data [4]uint8

	tmp := uint32(param * 10000)

	data[0] = uint8(tmp)

	data[1] = uint8(tmp >> 8)

	data[2] = uint8(tmp >> 16)

	data[3] = uint8(tmp >> 24)

	return data[:]

}

uint32转uint82

a:=uint32(32000) 二进制 0111 01101 0000 0000 ,取低八位0000 0000 ,所以uint8(a)=0

传入3.2返回 []uint8 =[0 125 0 0]

aa:=uint32(98000) 二进制 0001 0111 1110 1101 0000 ,取低八位1101 0000 ,所以uint8(aa)208,以此类推。

传入9.8返回 []uint8 =[208 126 1 0]

字符串

str 存的是data和len,只准读内存,不允许修改。如果要修改直接给变量整体赋新值,它存储的地址就会指向新的内容。

字符串修改

  • str转换成切片
  • unsafe包

unsafe

https://itpika.com/2020/02/05/go/library-unsafe-1/

unsafe可以绕过go的内存安全机制,直接对内存进行读写,操作任意的内存地址,操作私有变量(转换成员属性相同,但名字不同的结构体)

内存对齐

https://www.cnblogs.com/hujingnb/p/14013678.html

为什么要内存对齐?

1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

  • 数据在内存的存储并不是连续存放的,而是按照一定的对齐规则。使用对齐规则则有一个最大的好处是避免cpu的二次读取,也就是说对齐后的数据cpu只需要读取一次。
  • CPU读取数据,不是一个字节一个字节的读取,而是一块一块的读取;根据CPU位数不同而不同。

检测工具

  1. 需要先安装Ubuntu 20.04安装 linuxbrew

    https://blog.csdn.net/jinking01/article/details/108678196

  2. 那么, 有没有什么办法能够帮我们检测是否存在内存对齐的优化呢? 毕竟平常写的时候, 谁会关心这玩意呢. 别说, 还真有. golangci-lint

    官网: https://golangci-lint.run/

    安装: brew install golangci-lint

    检测所有文件命令: golangci-lint run ./..

    检测一下最开始的结构体文件(添加参数指定检测内存对齐):

    golangci-lint run --disable-all -E maligned main.go
    
package main

import (
	"fmt"
	"unsafe"
)

// 内存对齐 memory alignment
// cpu读取内存是一块一块读取的,内存对齐是空间换时间
type Test struct { //结构体的对齐保证是8 Byte的整数倍
	b   bool
	i3  int32
	i8  int8
	i64 int64
	by  byte
}

type RevisedTest struct {
	b   bool
	by  byte
	i8  int8
	i3  int32
	i64 int64
}

func main() {
	t := Test{}                        //Test结构体占用应该是: 1+4+1+8+1=15B. 15个字节对吧
	fmt.Printf("%d", unsafe.Sizeof(t)) //32
	fmt.Println("")

	fmt.Printf("bool:%d\n", unsafe.Alignof(t.b))
	fmt.Printf("int32:%d\n", unsafe.Alignof(t.i3))
	fmt.Printf("int8:%d\n", unsafe.Alignof(t.i8))
	fmt.Printf("int64:%d\n", unsafe.Alignof(t.i64))
	fmt.Printf("byte:%d\n", unsafe.Alignof(t.by))
	//修改后的结构体
	rt := RevisedTest{}
	fmt.Printf("%d",unsafe.Sizeof(rt))//16
	fmt.Println("")
}

slice类型

https://www.bilibili.com/video/BV1CV411d7W8?spm_id_from=333.999.0.0

https://www.cnblogs.com/awesomeHai/p/liuhai-0212.html

slice类型啥结构?

切片本身并不是动态数组或者数组指针。它内部实现的数据结构通过指针引用
底层数组,设定相关属性将数据读写操作限定在指定的区域内。切片本身是一
个只读对象,其工作机制类似数组指针的一种封装.

三部分:data(元素存哪里),len(存了多少个元素),cap(可以存多少个元素)

make一个slice

slice要存在一段连续的内存中

var ints []int

data(数组起始地址,没有分配底层数组,所以为nil),len长度和cap容量都为0;

var ints []int = make([]int,2,5)

已经存储的元素是可以安全读写的,但超出这个范围,会出现越界访问发生panic。

再new一个呗!

ps:=new([]string)
*ps = append(*ps,"ling")

new不负责底层数组的分配,new的返回值是ps的起始地址,此时这个slice变量还没有底层数组,ps = append(ps,"ling")会给slice开辟底层数组;字符串有两部分组成,一个是内存的起始地址,指向字符串内容,另一个是字节长度。

这个,底层数组呀

arr:=[3]int{1,2,3}
var s1 []int = arr[1,2]

数组容量声明了就不能改变,var s1 []int = arr[1,2]共用底层数组

new和make的区别

  1. 都是用于分配内存;
  2. 都是在堆上分配内存;
  3. new对指针类型分配内存,返回值是分配类型的指针,new不能直接对slice、map、channel分配内存;
  4. make仅用于slice、map和channel的初始化,返回值为类型本身,而不是指针;

你咋知道扩容到多大?

  1. 预估扩容后的容量newCap

    如果期望的最小容量大于原始的两倍容量时,那么新的容量就是等于期望的最小容量;

    不满足第一种情况,那么判断原slice的底层数组长度是不是小于1024,小于,新容量是原来的两倍;大于等于1024,则最终容量从旧容量开始循环增加原来的 1/4, 直到最终容量大于等于新申请的容量 ,新容量是原来的1.25倍。

  2. newCap个元素需要多大内存

    预估容量×元素类型大小=所需内存,不是直接分配所需内存;在许多编程语言中申请分配内存(malloc)并不是直接与操作系统交涉,而是和语言自身实现的内存管理模块,它会提前向操作系统申请一批内存,分成常用的规格管理起来(8,16,32,48,64...)。

  3. 匹配到合适的内存规格

    我们申请内存时,他会帮我们匹配到足够大且最接近的规则。

扩容源码 1.14.2

func growslice(et *_type, old slice, cap int) slice {
...........
    newcap := old.cap
        doublecap := newcap + newcap
        if cap > doublecap {
            newcap = cap
        } else {
            if old.len < 1024 {
                newcap = doublecap
            } else {
                // Check 0 < newcap to detect overflow
                // and prevent an infinite loop.
                for 0 < newcap && newcap < cap {
                    newcap += newcap / 4
                }
                // Set newcap to the requested cap when
                // the newcap calculation overflowed.
                if newcap <= 0 {
                    newcap = cap
                }
            }
        }
    ...........
}

扩容前后的slice是否相同

  • 情况1

    原数组还有容量可以扩容(实际容量没有填充完),这种情况下,扩容以后的数组还是指向原来的数组,对一个切片的操作可能影响多个指针指向相同地址的slice。

  • 情况2

    原来数组的容量已经达到了最大值,再想扩容,go默认会先开一片内存区域,把原来的值拷贝过来,然后再执行append()操作。这个情况丝毫不影响原数组。需要复制一个切片,最好使用Copy函数

slice实现订阅定时判断,定义了一个全局的切片,新增订阅的时候才会分配内存,没有数据绑定,判断的时候取到的是扩容后拷贝的切片,导致不能删除。

数组和切片传递时候的区别

数组是值传递,切片是引用传递。

golang的参数传递(值传递)、引用传递

go语言中所有的传参都是值传递,都是一个副本(拷贝),因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中无法修改原内容数据;

引用类型(指针、map、slice、chan等),这些可以修改原内容数据。他们有复杂的内部结构,除了申请内存外,还需要初始化相关属性。内置函数new计算类型大小,为其分配零值内存,返回指针。而make会被编译器翻译成具体的创建函数,由其分配内存和初始化成员结构,返回对象而非指针。

Map

https://www.bilibili.com/video/BV1Nr4y1w7aa

https://github.com/WuPeiqi/go_course/blob/master/day06 数据类型:指针、切片、字典/笔记/day06 数据类型.md

底层存储是基于哈希表(数组+链表)存储的,这种类型最大的特点就是查找速度非常快。

取模+拉链法来快速了解一下哈希表存储原理:

这种结构之所以快,是因为根据key可以直接找到数据存放的位置;而其他的数据类型是需要从前到后去逐一比对,相对来说比较耗时。

Map的特点

  • 键不能重复
  • 键必须可哈希(目前我们已学的数据类型中,可哈希的有:int/bool/float/string/array)
  • 无序

Map底层原理剖析

核心是有hmapbmap两个结构体实现

1.初始化

image-20211118162457187

2.写入数据

3.读取数据

4.扩容

5.迁移

怎么让goroutine跑一半就退出?runtime.Goexit()

https://mp.weixin.qq.com/s/GDyQWLaM_T9_f8gw6XYqWg

package main
 
import (
    "fmt"
    "runtime"
    "time"
)
func Foo() {
    fmt.Println("打印1")
    defer fmt.Println("打印2")
    runtime.Goexit() // 加入这行
    fmt.Println("打印3")
}
 
func main() {
    go  Foo()
    fmt.Println("打印4")
    time.Sleep(1000*time.Second)
}
 
 
// 输出结果
打印4
打印1
打印2

runtime.Goexit()

package main
 
import (
    "fmt"
    "runtime"
    "time"
)
func Foo() {
    fmt.Println("打印1")
    defer fmt.Println("打印2")
    runtime.Goexit() // 加入这行
    fmt.Println("打印3")
}
 
func main() {
    go  Foo()
    fmt.Println("打印4")
    time.Sleep(1000*time.Second)
}
 
 
// 输出结果
打印4
打印1
打印2

main函数也是个协程,通过runtime·newproc创建runtime.main协程,然后在runtime.main里会启动main.main函数,这个就是我们平时写的那个main函数了。

结论是,其实main函数也是由newproc创建的,只要通过newproc创建的goroutine,栈底就会有一个goexit。

GMP

Go程序执行过程

https://segmentfault.com/a/1190000020996545

编译器流程

Git上传流程

从远程仓库(git remote)拉取到本地仓库(git pull)--->暂存(git add .)--->本地仓库(git commit)--->远程仓库(git push)

四个区域

Git本地有三个工作区域:工作目录(Working Directory)、暂存区(Stage/Index)、资源库(Repository或Git Directory)。如果在加上远程的git仓库(Remote Directory)就可以分为四个工作区域。文件在这四个区域之间的转换关系如下:

图片

  • Workspace:工作区,就是你平时存放项目代码的地方
  • Index / Stage:暂存区,用于临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表信息
  • Repository:仓库区(或本地仓库),就是安全存放数据的位置,这里面有你提交到所有版本的数据。其中HEAD指向最新放入仓库的版本
  • Remote:远程仓库,托管代码的服务器,可以简单的认为是你项目组中的一台电脑用于远程数据交换

工作流程

git的工作流程一般是这样的:

1、在工作目录中添加、修改文件;

2、将需要进行版本管理的文件放入暂存区域;

3、将暂存区域的文件提交到git仓库。

因此,git管理的文件有三种状态:已修改(modified),已暂存(staged),已提交(committed)

桶排序

https://blog.csdn.net/jackwang_dev/article/details/82627246

https://blog.csdn.net/qq_25026989/article/details/89367954

桶排序的基本思想是:把数组 arr 划分为n个大小相同子区间(桶),每个子区间各自排序,最后合并
计数排序是桶排序的一种特殊情况,可以把计数排序当成每个桶里只有一个元素的情况。

基数排序

根据个十百千万进行排序

image-20211120171253123

计数排序

用待排序的数作为计数数组的下标,统计每个数字的个数。然后依次输出即可得到有序序列。

算法步骤:
(1)找出待排序的数组中最大的元素;
(2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
(3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
(4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

时间复杂度:Ο(n+k)。

空间复杂度:Ο(k)。

要求:待排序数中最大数值不能太大。最大值确定并且不大,必须是正整数。

图片上传

package mainimport (	"github.com/gogf/gf/frame/g"	"github.com/gogf/gf/net/ghttp")// Upload uploads files to /tmp .func Upload(r *ghttp.Request) {	files := r.GetUploadFiles("upload-file")    names, err := files.Save("/tmp/")    if err != nil {		r.Response.WriteExit(err)	}	r.Response.WriteExit("upload successfully: ", names)}// UploadShow shows uploading simgle file page.func UploadShow(r *ghttp.Request) {	r.Response.Write(`    <html>    <head>        <title>GF Upload File Demo</title>    </head>        <body>            <form enctype="multipart/form-data" action="/upload" method="post">                <input type="file" name="upload-file" />                <input type="submit" value="upload" />            </form>        </body>    </html>    `)}// UploadShowBatch shows uploading multiple files page.func UploadShowBatch(r *ghttp.Request) {	r.Response.Write(`    <html>    <head>        <title>GF Upload Files Demo</title>    </head>        <body>            <form enctype="multipart/form-data" action="/upload" method="post">                <input type="file" name="upload-file" />                <input type="file" name="upload-file" />                <input type="submit" value="upload" />            </form>        </body>    </html>    `)}func main() {	s := g.Server()	s.Group("/upload", func(group *ghttp.RouterGroup) {		group.POST("/", Upload)		group.ALL("/show", UploadShow)		group.ALL("/batch", UploadShowBatch)	})	s.SetPort(8199)	s.Run()}

子图片单独表crud

vscode调试go launch.json配置

{	// 使用 IntelliSense 了解相关属性。 	// 悬停以查看现有属性的描述。	// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387	"version": "0.2.0",	"configurations": [	{		"name": "Launch Package",		"type": "go",		"request": "launch",		"mode": "debug",		"program": "${workspaceFolder}"	},		{			"name": "Launch",			"type": "go",			"request": "launch",			"mode": "auto",			"program": "${fileDirname}",			"env": {},			"args": []		}	]}

进制转化

16进制转10进制

img

3f 0011 1111 1 2 4 8 16 32 =15+16+32=31+32=63

分库分表

https://zhuanlan.zhihu.com/p/137368446

阅读528代码

1.智能分析(四个主要功能,线和区域入侵、动态检测、火点报警)

通用行为分析

绊线入侵

最多支持八条线

arch_amd64.go

空实现,为了在x86上可以编译,生成swagger文档等。

arch_arm.go

分析结果算法,需要在arm板子上跑,调用良师傅给的函数接口。

iva_dev.go

  1. DevBasic 抽象出基础智能分析器设备

    • 通道号、开启状态、循环队列(分析结果缓冲队列)、缓冲队列当前读取位置、重复计数

    • quit chan int32 取消信号量(共享数据),GetResult() 获取智能分析结果

      • select是Go中的一个控制结构,类似于用于通信的 switch 语句。每个case必须是一个通信操作,要么是发送要么是接收。

        select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。一个默认的子句应该总是可运行的。

    • cfgLine []conf.DetectLine

      • LineInfo 绊线事件类型,1:单向,2:双向"`、检测线的起始结束点、检测方向 双向 a->b b->a、人车检测
      • types.Linkage 联动动作 录像抓图、蜂鸣语言报警
      • types.WeekSchedule 布防计划
  2. 启动分析 arch->C.IVA_Start (mediaIva.h)->go dev.GetResult() 获取智能分析结果

  3. 停止分析 if dev.start →C.IVA_Stop (mediaIva.h)→dev.quit <- 1→清空队列→dev.start = false

SetLine 设置绊线区域

  1. 加互斥锁 dev.mtx.Lock() defer dev.mtx.Unlock()
  2. var lines = make([]conf.LineInfo, len(cfg)) 在堆上分配内存
  3. dev.avhLine = make([]define.LineRule, 0) 生效的规则线
  4. SetLine(cfg []conf.DetectLine) 遍历cfg ,先判断使能,然后赋值,最后arch SetLine
  5. 判断超出最大长度为最大长度,lines_c赋值,发送给底层C.IVA_SetLines

GetData GetResult() 获取分析结果

  1. 加互斥锁
  2. GetData 开始获取分析结果 ,判断缓冲队列为空和到队尾,最多重复取10次,第一次找到最近一个
    • queue.Look() 使用序列号访问数据,sn序列号,必须介于begin和end之间
  3. GetResult() dev.quit 直接返回,从底层获取数据,为空结束本次循环,睡眠10毫秒(防止分析结果慢,取不到数据),执行procResult 处理智能分析结果

读写的为了线程安全都需要加互斥锁

开始处理智能分析结果 procResult

人车过滤 回调函数获取人车配置,判断使能,识别出来的结果先入队列,队列满后出队列

  • 没有使能car和man都是false就var obj []define.ObjectInfo,所有的人车结果都不推送出去
  • 使能都为true,人车结果都推送出去
  • 直选人或车,跳出本次循环,把人或车的结果追加到数组,然后推送出去
		var cfgline conf.DetectLineConfigs		config.GetConfig(&cfgline)

【当时用这个一直循环获取配置,导致板子内存不足】

分发智能事件

  • 遍历rlt.Events(遍历数组,禁用arr[0]易越界),规则类型和事件类型是否相同
  • 获取配置,事件码,检测线信息,判断触发事件,事件通知

CheckLineSchedule 判断当前时间点布防计划与前一次是否有变化

  • 先判断回调函数的线与配置的线长度是否相等
  • 遍历lines,判断使能和布防检查,返回布尔值
  • checkSchedule() SetLine 设置绊线检测参数

blp.go

init()

//初始化默认的目标过滤器,8191*8191 4346*3846 设置默认配置SetConfig,加读写锁修复配置频繁设置导致死机问题conf.InitDefaultTargetFilter()//获取当前配置config.GetConfig(&cfgTargetFilter)//注册配置回调函数config.Attach(cfgTargetFilter, OnApplyConfigTargetFilter)遍历cfgTargetFilter (TargetFilters 多通道目标过滤配置)arch.SetTargetFilter(ch, val) 设置过滤目标

OnApplyConfigTargetFilter(val interface{}) (bool,int32)

cfg := val.(conf.TargetFilters)遍历cfg,arch.SetTargetFilter(ch, val) 设置过滤目标return true, config.ApplySuccessconfig.ApplySuccessApplySuccess = int32(0x00000000) //成功 0 1 2 4 8

iva_mgr.go

var (	instance *IvaManager //定义指针实例	once     sync.Once)// New init ,分配内存,第一个参数传递类型,返回类型指针*IvaManagerfunc New() (ptr *IvaManager) {	ptr = new(IvaManager)	return ptr}// GetIns get instance object,once只执行一次func GetIns() *IvaManager {	once.Do(func() {		instance = New()	})	return instance}//IvaManager IVA管理器type IvaManager struct {	dev       []DevBasic               //基础智能分析设备	cfgLine   conf.DetectLineConfigs   //当前检测线配置	cfgRegion conf.DetectRegionConfigs //当前检测区域配置}

func (mgr *IvaManager) Init() bool {}

mgr *IvaManager 类型绑定,

The make built-in function allocates and initializes an object of type slice, map, or chan (only). Like new, the first argument is a type, not a value. Unlike new, make's return type is the same as the type of its argument, not a pointer to it. The specification of the result depends on the type:

make 内置函数分配和初始化 slice、map 或 chan(仅限)类型的对象。 和 new 一样,第一个参数是一个类型,而不是一个值。 与 new 不同,make 的返回类型与其参数的类型相同,而不是指向它的指针。 结果的规范取决于类型:

mgr.dev = make([]DevBasic, caps.MaxVideoChannel)//循环创建2个智能分析设备	for chn := 0; chn < caps.MaxVideoChannel; chn++ {        //New 赋值		mgr.dev[chn].New(int32(chn), 10)	}mgr.cfgLine.Default() //Default() 循环赋值config.SetDefault(mgr.cfgLine) //设置默认配置config.Attach(mgr.cfgLine, mgr.OnApplyConfigDetectLine) //注册回调函数

start

  1. 获取点和区域的当前配置
  2. 2次循环,首次启动,判断是否需要开启分析(根据点和线的使能),设置绊线检测参数
  3. 检查布防 5秒检查一次,未使能就跳过本次循环,CheckLineSchedule 判断当前时间点布防计划与前一次是否有变化,有变化就重新设置参数

stop

调用iva_dev.go里面的停止分析

func (dev *DevBasic) Stop() bool {//是否处于开始状态    if dev.start {		arch.Stop(dev.chn)//调用良师傅底层的停止		dev.quit <- 1		dev.queue.Clear()		dev.start = false	}	return true}

OnApplyConfigDetectLine 配置改变回调

global.go

Name() 获取配置名称 ,有很多的配置,加上配置名方便区分

ParamVerify() 参数校验

  1. 类型绑定TargetFilters 多通道目标过滤配置,循环先判断 最大目标尺寸是否在8191*8191外,再判断是否是矩形,直接返回false
  2. 最小尺寸,是否是矩形;最小目标尺寸长度和宽度是否小于32
  3. 校正矩形框最大和最小尺寸,Correct() 判断矩形框上下左右是否左大于右或者上大于下,用中间变量重新赋值

InitDefaultTargetFilter() 设置默认区域配置8191 * 8191 4346 * 3846

line.go

func (cfg *DetectLine) Default() 检测线默认值 为了让ParamVerify调用

func (cfg DetectLineConfigs) Default() 多通道智能分析配置-检测线,设置值,返回 cfg = cfgdft

func (cfg *DetectLineConfig) ParamVerify() 检测线数量大于最多检测线数量,判断是否越界和是否是线,否则设置默认值

并发安全 mutex锁

http.go

  1. beego解析请求参数和json数据
  2. 参数校验
  3. 获取当前配置,解析的配置赋值给当前配置,设置当前配置SetConfig(config IConfig, opts ...ConfigOption)

service.go

GetExtendData 扩展数据,江淮哪里取到的是拓展数据,私有码流

回调函数(类似钩子函数)

  1. init的时候根据Name() 先注册OnApplyConfigDetectLine 配置改变回调
  2. 当调用 /line 接口,会调用config.SetConfig (),执行配置修改回调函数

私有码流封装

Z:\03-公共资料\07-码流文档

火点报警

http.go

GetCaps() 获取火点能力集

  • amd64 直接返回 Cap = dsd.FireCaps{Row: 16, Col: 12},channel2 分成16*12的小格
  • arm cgo获取底层的cap.Row和cap.Col

GetFireAlarm() 未使能获取火点报警默认配置,使能获取当前配置,json返回。

SetFireAlarm()

  1. 循环cfg.RegionInfo,判断区域是否使能,未使能跳出本次循环
  2. 循环cfg.Region ,判断cfg.RegionInfo的i是否等于cfg.Region的index
  3. 相等则报警检测区域使能为true,跳出循环
  4. 设置当前配置 config.SetConfig()

iva_dev.go

SetParam(cfg conf.FireAlarm) 设置火点区域检测参数

  1. ok:=arch.SetParam(cfg.Mode,cfg.Region,cfg.RegionInfo)
  2. 判断ok设置成功与否,循环检测火点检测报警区域,报警状态为报警中,通知报警事件,否则报警状态为停止
  3. 设置联动项和简单检测区域
  4. 循环cfg.RegionInfo,使能后简单检测区域赋值

GetResult() 获取智能分析结果 是否有取消信号量,有的话直接返回,默认从底层获取结果,存在分析结果,则把事件通知出去

procResult(rlt []define.SimEvent) rlt底层传过来的

  1. dev.avhEvent = rlt ,结构体SimRegion比SimEvent多了一个Name字段,Region和Rect都是Types.Rect
  2. Rects追加循环得到rlt的值,判断火警状态是否为空,把火警状态改为开始
  3. 循环类型绑定报警,报警状态为报警中,更改报警状态为停止;循环rlt的ID,等于报警id就改报警状态为报警中,事件通知

GetData() []define.SimEvent

  1. 判断是否处于开始状态和事件为空,返回nil
  2. make分配内存,类型[]define.SimEvent,追加事件dev.avhEvent
  3. dev.avhEvent = dev.avhEvent[0:0] //清空防止事件重复上报,结果返回

NotifyFireAlarm(id uint32) 火点报警通知

  1. 报警状态开始,事件开始,该报警状态为报警中,报警事件追加到订阅事件推送信息数组里
  2. 报警状态停止,事件停止,状态为空,清空防止事件重复上报;default直接返回
  3. 新建事件,设置私有数据、详细信息、触发对象,事件通知

iva_mgr.go

OnApplyConfig 配置改变回调 调用mgr.dev.SetParam(cfg) 设置火点区域检测参数

highlight

设置和获取强光保护参数

太阳光久照热成像摄像头,会把热成像摄像头灼伤,强光照射的时候,根据能量值和灵敏度报警后触发联动动作。

可见光是良师傅负责,热成像是艾睿算法那边的。

2.测温

thermaltemp

http.go

SetTempLine() 设置测温线配置

  1. 初始化tmpcfg := conf.TempLineConfigs{},解析json,解析错误判断
  2. 当前系统温度单位转换为开尔文
  3. Verify 验证使能项有效性,非使能项恢复默认;实现:判断是否超出最大12条规则数,超过直接返回,判断是否使能、超出最大尺寸、两点是否构成线,符合直接返回,否则为默认配置
  4. config.SetConfig 配置设置下去

GetTempLine() 获取测温线配置

  1. 获取前端传来的 default,true获取默认配置,false获取当前配置,判断获取配置是否出错
  2. 开尔文温度转化为系统温度,返回结果

GetTempLineTemp() 获取测温线温度值

  1. data := blp.GetIns().GetLineTempData() //从底层获取温度id, _ := c.GetUint32("id", 0) //线测温序号id,热成像通道
    

    GetIns() 实例化一次(sync.Once) once.Do() ptr = new(TempManager)

    GetLineTempData(0).Line ,获取到的数据追加到 []TempData

    GetLineTempData() 调用 GetTempData 获取温度数据

    1. 加读写锁(适用于读多写少

    2. 通道类型是否等于热成像通道(可见光1 热成像2)

    3. result.Line, _ = arch.GetTempData(dsd.TempModeLine) 获取温度数据集合

    4. arch_arm.go GetTempData()

      //确定好类型,分配内存空间var data_c [dsd.MaxTempRules]C.struct_tpc_temp_data// ret只是返回值,&data_c[0]是从底层获取到的地址,把值赋给data_cret := C.VI_GetTpcTemperatureArray(C.uint32_t(dsd.MaxTempRules), C.uint32_t(mode), &data_c[0])
      

      判断ret返回值不等于0直接返回

      数据转换

  2. 温度转化返回结果

温差分析 temp/difference

比如两个区域R1和R2判断高低平均温,符合条件触发报警输出 01,可接音响等。

显示温度条 thermal/env/state?enable=true

点、线、区域 设置获取配置,获取温度都差不多;整帧温度有什么用,整帧里面获取到的温度供谁使用?

256*192热成像特殊分辨率,整帧温度就是取整帧的高低平均温;供查询整帧温度。

GetRandomPointTemp() 获取任意点温度值

  1. 接收传入x,y轴坐标(8192*8192坐标系),视频通道号
  2. point 是数组点坐标,传入arch.GetTempPoint(point)
  3. ConvTemp2Std 开尔文转系统温度,数据返回

GetTempDifference() 从配置中获取 SetTempDifference()

GetVisibleOsd 获取可见光测温信息叠加配置

前叠加:良师傅底层描好传上来,线比较粗(转码会有数据丢失)。

temp_dev.go

new() 创建测温分析设备

初始化通道、退出信号量、事件、报警,为点线区域整帧,屏蔽区域和温差分析初始化默认配置

Start() 启动分析 判断没有处于开启状态,启动协程获取测温分析结果,状态改为true

Stop()停止分析 判断开始状态,更改取消信号量,状态改为关闭,加互斥锁,循环关闭报警事件,更改报警状态,测温报警事件通知(根据状态判断)

func (dev *DevTemp) SetPoint(cfg []conf.TempPointConfig) bool 设置测温点参数

  1. 加锁,循环配置,判断当前线配置,报警事件通知
  2. arch.SetTempLine 设置测温线
  3. 测温分析管理器dev DevTemp ,dev.avhLine循环当前测温线配置,判断是否使能,赋值结果追加到dev.avhLine

GetResult()

  1. conv匿名函数测温数据转化
  2. 循环 判断取消信号量,取消则直接返回
  3. 获取测温数据,加锁,判断是否获取到点、线、区域、整帧温度,获取到把温度数据copy(dev.tempPoint[:], rlt.Point)
  4. 循环dev.avhPoint 当前有效测温点配置,封装扩展帧使用,测温数据转化的结果赋值温度信息描述
  5. 区域温差分析
  6. 睡眠500毫秒,存在分析结果,则把时间通知出去

procResult 开始处理测温分析结果

  1. 循环 dev.avhPoint 当前有效测温点配置,封装扩展帧使用
  2. 获取当前点配置和点测温数据
  3. 判断报警规则是否等于null,等于就不检查布防时间和不使能,跳出本次循环
  4. getEventStatus()获取事件状态,根据测温规则,报警事件通知

debounce(es *dsd.EventStatus,debounce uint32) bool

  1. 取当前时间戳
  2. 测温分析管理器开始时间大于0,返回当前时间戳大于starttime(记录报警时间是否触发)+去抖动时间默认5s
  3. 报警触发时间等于当前时间戳,拼接字符串
  4. 把报警状态赋值到报警事件,返回false

getEventStatus(mode,id uint32,linkage types.Lineage) *dsd.EventStatus

  1. mode 测温类型,id测温id ,字符串拼接,记录温度报警事件,返回
  2. 否则新建事件状态

ExecRuleGe(es *dsd.EventStatus,temp float32,rule conf.TempRule)

  1. temp温度大于温度阀值,报警阀值为true
  2. 判断是否达到测温报警阀值,temp是否大于温度阀值-容差温度,去抖动,改事件状态
  3. 否则事件停止

GetEvent() []define.TempEvent

  1. 事件数组长度为空,返回nil
  2. 为[]define.TempEvent类型分配内存,事件追加到数组,清空类型绑定事件(防止事件重复上报)

NotifyAlarmEvent(es *dsd.EventStatus,param interface{})

  1. 事件状态判断分支,事件开始状态,事件开始,更改事件状态为进行中,记录温度报警事件,单个事件信息(类型和id)追加到事件分析管理器数组,创建时间对象,进行通知。
  2. 事件状态正在通知,则单个事件信息(类型和id)追加到事件分析管理器数组
  3. 事件状态停止,事件开始改为结束状态,builtin/builtin.go 用delete()删除map里的key
  4. 默认判断是否触发报警事件,删除报警,返回

DiffAbs() 求温差绝对值

func DiffAbs(n,m float32) float32 {    if n>m {        return n - m    }    return m -n}

temp_visible.go

New(chn int32) 初始赋值通道号和取消信号量

Start() 可见光没有使能,启用协程获取数据,使能状态为true

Stop() 判断可见光是否已经使能,取消信号量改为1,使能状态为false

SetPoint(cfg []conf.tempPointConfig)

  1. 加锁
  2. make分配类型空间,循环配置cfg,判断使能
  3. tmp := define.TempPoint{} 定义类型变量,循环取得的值赋值给tmp
  4. tmp.Point, _ = arch.CoordinateConvertVisible(val.Point) 坐标转化(数据坐标映射,可先乘)
  5. 当前有效测温点配置,封装可见光扩展帧使用

☆☆☆config.SetDefault(mgr.cfgVisibleOsd) 设置默认配置

func SetDefault(config IConfig, opts ...ConfigOption) (err error) {	cfgname := config.Name() //获取配置名    tmpcfg, errr := deepCopy(config) //deepCopy() json解码和编码一下	if errr != nil {		return errors.ErrSetCfgFailed	}    err = cfgDefault.Set(cfgname, tmpcfg)//Set() Set 通过 `key` 和 `value` 修改 `JsonConfig` 映射 用于轻松更改 `JsonConfig` 对象中的单个键/值。	if err != nil {		return	}	// 判断当前配置是否为空,若为空则合并默认值	if ok := cfgCurrent.Have(cfgname); !ok {		err = SetConfig(config, opts...)		if err == nil {			log.Infof("merage config[%s] from default config.", cfgname)		}	}	// TODO:配置结构有变化,做合并处理	// 执行延时保存	err = save(cfgDefault, fileDefault) // 保存配置文件	return err}

☆☆☆func SetConfig(config IConfig, opts ...ConfigOption) (err error) 设置配置

func SetConfig(config IConfig, opts ...ConfigOption) (err error) {	cfgname := config.Name() //获取配置名	cfgopts := newConfigOptions(opts...) //system配置操作者,不记录日志	// 执行配置修改回调函数	appRlt := int32(ApplySuccess)	configCbMutex.RLock()	cbMap, ok := configCbMap[cfgname]	configCbMutex.RUnlock()	if ok {		for cbname, callback := range cbMap {			log.Infof("exec config[%s] callback function[%s].", cfgname, cbname)			ok, rlt := callback(config)			appRlt = appRlt | rlt //或位运算			if !ok {				log.Errorf("exec config[%s] callback failed.", cfgname)				return errors.ErrSetCfgFailed			}		}	}	tmpcfg, errr := deepCopy(config) //json解码编码	if errr != nil {		return errors.ErrSetCfgFailed	}	//TODO:存在多线程(多个http请求)或多个goroutine并发map写入风险,后续优化	cfgCurrentMutex.Lock() //加读写锁	defer cfgCurrentMutex.Unlock()	err = cfgCurrent.Set(cfgname, tmpcfg)	if err != nil {		log.Errorf("set config[%s] failed. error:", cfgname, err)		return errors.ErrSetCfgFailed	}	// 执行延时保存	err = save(cfgCurrent, fileCurrent, fileCurrentBak)	if err != nil {		log.Errorf("save config[%s] failed. error:", cfgname, err)		return errors.ErrSetCfgFailed	}	// 记录配置修改日志	if !cfgopts.NoLog {		ev := event.NewEvent(event.EventConfigModify, 0, event.ActionPulse)		ev.SetData(config.Name())		ev.SetUser(cfgopts.User)		event.Notify(ev, false)	}	if (appRlt & ApplyNeedReboot) != 0 {		return errors.SuccessNeedReboot	}	if (appRlt & ApplyAutoReboot) != 0 {		return errors.SuccessAutoReboot	}	return nil}
  1. 获取配置名,newConfigOptions() system用户不记录日志,appRlt返回结果0成功,加读写锁执行配置修改回调函数

  2. cbMap, ok := configCbMap[cfgname] configCbMap配置函数回调表(map[string]map[string]CbFunc)

  3. 循环cbMap 回调函数表,cbname 类型:string , callback 类型:CbFunc

    CbFunc 配置回调函数类型 type CbFunc func(val interface{}) (bool, int32)

    ok, rlt := callback(config)

  4. appRlt = appRlt | rlt 位运算响应信息

  5. deepCopy() 解析编码json

  6. 改变当前配置,执行延时保存(写入到配置文件),记录配置修改日志,返回成功与否

☆☆☆config.Attach(mgr.cfgVisibleOsd, mgr.OnApplyConfigVisibleTemp) 注册配置回调函数

// Attach 注册配置回调函数func Attach(config IConfig, cb CbFunc) bool {	cfgname := config.Name()	cbname := runtime.FuncForPC(reflect.ValueOf(cb).Pointer()).Name()	if ok := cfgCurrent.Have(cfgname); !ok { // Have check config have key or not		return false	}	configCbMutex.Lock()	defer configCbMutex.Unlock()	cbMap, ok := configCbMap[cfgname]	if !ok {		cbMap = make(map[string]CbFunc)		configCbMap[cfgname] = cbMap	}	cbMap[cbname] = cb	return true}

回调函数的注册和取消,通过KV对map回调表进行操作,用builtin操作map。

OnApplyConfigVisibleTemp 配置改变回调

func (mgr *TempManager) OnApplyConfigVisibleTemp(val interface{}) (bool, int32) {	rlt := config.ApplySuccess //返回结果1	cfg := val.(conf.VisibleTempConfig)	if mgr.cfgTempAttr.Enable {		if cfg.Enable {			if !mgr.cfgVisibleOsd.Enable {				mgr.devVisible.Start()//未使能开始			}		} else {			if mgr.cfgVisibleOsd.Enable {				mgr.devVisible.Stop()			}		}	}	mgr.cfgVisibleOsd = cfg //可见光测温信息叠加配置赋值,这里很重要,上面是调用start获取结果,这个是更改配置	return true, rlt}

thermalparam

func CalibrateBlackbodyTemp(id uint8, param float32) bool {	tmpparam := blp.Conv2Uint8Multiply10(param)	cmd := blp.CmdPack{}	cmd.Create(0x07, 0x6F, id, tmpparam)	return cmd.Write()}
  1. 接收参数命令,将实际参数转换为命令序参数

  2. 发送指令封装

  3. 创建命令字

  4. 写入

    命令字序列号,发送命令

    arch.RecvCmd 机芯返回的指令读取,休眠100毫秒防止读取的快,结果上来的慢

    返回指令解析,返回结果

thermalenv

  1. Create() 创建命令字 func (cmd *CmdPack) Create(set, code, oper uint8, param []uint8)

    头和尾部信息都是固定的

  2. serialize() 命令字序列号

    func (cmd *CmdPack) serialize() []uint8

    把数据组织好,返回数组

  3. parse() 命令字解析 校验

  4. Write()

  5. Read()

相机每周二重启会导致拉不到流

image-20211207094816865

全天布防不检查 CheckSchedule()

func (mgr *IvaManager) CheckSchedule() {	// TODO: 此处可以优化,默认全天布防时不检查	var flag bool	for {		log.Error("// checkSchedule 进入循环检查")		time.Sleep(time.Second * 5)		if !mgr.cfgFire.Enable {			continue		}		for _, v := range mgr.cfgFire.Schedule.WeekDay {			log.Error("mgr.cfgFire.Schedule.WeekDay", mgr.cfgFire.Schedule.WeekDay)			log.Error("---------------")			log.Error("len(v.Section)", len(v.Section))			if len(v.Section) == 1 {				log.Error("退出了")				flag = true			}			if flag {				if !mgr.dev.start {					mgr.dev.Start()				}				continue			} else {				if mgr.cfgFire.Schedule.Check() {					if !mgr.dev.start {						mgr.dev.Start()					}				} else {					if mgr.dev.start {						mgr.dev.Stop()					}				}			}		}	}}

RTSP Live Server 设计与实现

img

3.抓图

图片存储

neuron/storage/picture

losf_store 图片集中存储对象

picture_file.go

Start()

  1. mmtpoint,ok:=sdcard.GetDir() 获取SD卡当前读写目录

    f.curMntPoint = mntpoint 当前挂载点

  2. f.StoreSnap,err = losf_store.NewStore() 新建图片存储(losf_store 图片集中存储模块)

  3. err = f.StoreSnap.Init() 初始化赋值

  4. //打开当前挂载点的图片存储块id,dir := f.parserMntPoint(f.curMntPoint)f.curVolume,err = f.StoreSnap.OpenVolume(int32(id))
    
  5. f.curVolume,err = f.StoreSnap.AddVomume(int32(id),dir) 设备启动时是新的挂载点,添加一个新卷

  6. Attach 注册事件处理监听/处理函数

    EventCode 事件码

    Listener 监听事件

    EventPriority 事件回调执行优先级

Stop()

  1. 图片集中存储为nil,关闭图片集中存储对象
  2. 事件注销 Detach 注销事件处理监听/处理函数, name为空表示全部注销

Close()

  1. 图片集中存储为nil,关闭图片集中存储对象

func (f *PictureFile) EventHandle(e *event.Event) error 时间处理

  1. 获取事件名用switch处理

  2. SD卡分区切换

    		mntpoint, _ := sdcard.GetDir() //获取SD卡当前读写目录		f.switchVolume(mntpoint) //切换到新分区,实现:获取新分区id和dir路径		f.curMntPoint = mntpoint
    
  3. SD卡分区格式化
    GetParam 获取事件私有数据

    删除相关分区的图片数据
    删除sqlite 表数据

  4. SD卡格式化 直接删除表

func (f *PictureFile) Write(chn int32, time int64, data []byte, snapType uint8) (err error) 写图片

  1. 当前挂载点和图片集中存储为nil,直接返回
  2. 获取图片字节流数据([] byte) ,写入卷
  3. 图片数据库文件插入记录

func (f *PictureFile) Read(path string) []byte

  1. 图片集中存储对象 为nil直接返回
  2. http://192.168.1.140/v1/picture/download?path=/tmp/mmcblk1p7/0/1639010484096.jpg
    获取挂载点目录,字符串分割strings.Split(path,"/")
    判断路径是否为4段,否则直接返回
  3. 获取文件idx和卷id,目录不存在需要重新挂载,dirs[3] 获取通道号
  4. 获取图片前缀(时间戳),打开卷,读取图片文件,返回字节流

func (f *PictureFile) parserMntPoint(mntPoint string) (id int, dir string)

// 获取字符串中的数字//LastIndexFunc返回满足f(c)的最后一个Unicode码位的索引s,如果没有则返回-1。index:=strings.LastIndexFunc(mntPoint,func(c rune) bool{     return uincode.IsLetter(c)//uincode是否是字母})//Atoi等价于ParseInt(s, 10,0),被转换为int类型。id, _ = strconv.Atoi(mntPoint[index+1:])

图片编码

抓图

neuron/function/snap

4.录像

5.流媒体

流媒体RTSP

https://www.cnblogs.com/haibindev/p/7918733.html

img

流媒体数据传输不是rtsp协议的内容,有RTP包来做。

AAC编码(音频编码)

h264 h265区别

https://zhuanlan.zhihu.com/p/71270595

H265是H264 的升级版。

  1. 改善码流、编码质量、延迟和算法复杂度;
  2. 降码率 H.264 中每个宏块(macroblock/MB)大小都是固定的16×16像素,而H.265的编码单元可以选择从最小的8×8到最大的64×64;
  3. 块的四叉树划分结构,采用一系列自适应的预测和变换等编码技术;
  4. 标清数字图像传送H265更快
  5. 占用储存空间缩小
posted @ 2022-01-22 14:42  凌易说-lingyisay  阅读(137)  评论(0编辑  收藏  举报