为什么偏偏要用Go来干这活儿?
上个月我家的sh365智能摄像头又抽风了,晚上录的猫主子跑酷视频愣是没同步到云端,翻遍官方APP发现导出功能要会员,免费用户只能一张张截图——这谁受得了?于是翻出吃灰的树莓派,决定用Go语言写个视频拷贝脚本。
你可能要问,Python不更香吗?但sh365摄像头走的是RTSP流,Go的ffmpeg绑定库在并发处理多路视频流时,内存占用比Python低40%左右,更关键的是,Go编译出来的单文件扔到任何Linux设备上就能跑,不用装依赖——这对需要7x24小时运行的视频备份方案来说,简直是救命的优势。
拷贝前必须搞懂的三个协议暗坑
RTSP流的“伪文件”本质
sh365摄像头的视频流不是传统文件,而是实时传输流,直接os.Create去写肯定不行,得先建立RTSP会话,我用的github.com/deepch/vdk库,它能自动处理SDP协商:
client, err := vdk.NewRTSPClient("rtsp://admin:password@192.168.1.64:554/stream1")
if err != nil {
log.Fatalf("连接失败: %v", err) // 记得改默认密码啊!
}
时间戳是魔鬼
第一次录出来的视频时长对不上,后来发现是没处理RTP时间戳,sh365默认是90000Hz的时钟频率,但某些固件会变成80000,用vdk库里的time.Duration转换时,我加了个监测函数:
if packet.Timestamp >= 90000 {
duration := time.Duration(packet.Timestamp/90000) * time.Second
} else {
duration := time.Duration(packet.Timestamp/80000) * time.Second
}
这代码虽然丑,但真的救了急。
H.265编码的“伪无损”陷阱
sh365新固件默认输出H.265流,很多播放器不认识,转成MP4时我踩了大坑——直接用ffmpeg库转出来的文件,在Windows上能播,在Mac上却黑屏,后来发现要设置-tag:v hvc1参数:
ffmpegCmd := exec.Command("ffmpeg", "-i", "pipe:0", "-c:v", "copy", "-tag:v", "hvc1", "output.mp4")
手把手教学:三小时攒出的完整拷贝方案
第一步:并发拉流,但别贪心
sh365支持双码流,主码流(4K@15fps)用于本地存储,子码流(720p@20fps)用于移动端,我发现同时拉两路流容易导致摄像头重启,所以改成“主码流失败时自动降级子码流”的策略:
func copyStream(rtspURL string, outputName string) error {
streams := []string{rtspURL+"/stream1", rtspURL+"/stream2"} // 很多型号反着来
var lastErr error
for _, url := range streams {
if err := tryCopy(url, outputName); err == nil {
return nil
} else {
lastErr = err
log.Printf("尝试 %s 失败,原因: %v", url, err)
}
}
return lastErr
}
第二步:每小时自动分片
摄像头连续录的话文件会越来越大,万一断电就全没了,我参考了NVR的录像策略,用time.Ticker每55分钟切一次文件(留5分钟拼接缓冲):
ticker := time.NewTicker(55 * time.Minute)
for {
select {
case <-ticker.C:
currentFile.Close()
currentFile = createNewFileBasedOnTime()
case pkt := <-packetChan:
currentFile.Write(pkt.Data)
}
}
第三步:断点续传的土办法
sh365的RTSP协议支持Range参数理论上能续传,但实测经常失灵,我另辟蹊径——用Last-Modified时间戳对比本地文件:

func getRemoteModTime() time.Time {
resp, _ := http.Head("http://192.168.1.64:8080/recordings/"+today+".mp4")
if t, err := http.ParseTime(resp.Header.Get("Last-Modified")); err == nil {
return t
}
}
如果本地文件小于远程内容且修改时间早于远程,就重新拉取增量部分——但这种方案在断网超过5分钟后会失败,需要配合ffprobe做完整性校验。
踩坑记录:那些文档里根本没写的细节
摄像头Wi-Fi会“假死”
sh365的2.4G Wi-Fi在持续传输1小时后会主动降速,解决办法是每50分钟发送一个RTSP OPTIONS 心跳包:
client.Control(&rtsp.Options{}) // 比设置KeepAlive靠谱
时间戳居然会“回流”
某次升级固件后,发现摄像头半夜会自动重启,导致时间戳回到1970年,代码里必须做差值判断:
if pkt.Timestamp < lastTimestamp {
log.Println("检测到时间戳倒退,触发同步")
// 重置FFmpeg编码器上下文
}
文件系统inode不够用
ext4默认每个目录能放32000个文件,但sh365的循环录制用了time.Now().Format("2006-01-02/15-04-05.mp4")这种命名,一小时产生3600个文件,我改成每分钟一个文件,然后定期用os.ReadDir清理旧文件:
func cleanupOldFiles(maxRetentionDays int) {
entries, _ := os.ReadDir("./recordings")
for _, e := range entries {
if t, err := time.Parse("2006-01-02", strings.Split(e.Name(), "_")[0]); err == nil {
if time.Since(t) > time.Duration(maxRetentionDays)*24*time.Hour {
os.Remove("./recordings/" + e.Name())
}
}
}
}
进阶玩法:把拷贝变成智能监控
运动检测期间的优先拷贝
sh365内置移动检测,但触发事件时录像码率会突然飙升到8Mbps,我的脚本会监听/event接口(好吧这个接口其实没开放),所以退而求其次——实时读取视频帧的AVPacket大小:
if len(pkt.Data) > 500*1024 { // 大于500KB的帧,通常是关键帧/运动场景
priorityQueue.Push(pkt)
}
然后只拷贝重点片段,用github.com/icza/mjpeg库提取JPEG缩略图:
jpeg, _ := mjpeg.Encode(decodedFrame, 320, 240)
把缩略图和视频关联,方便快速检索。
多摄像头轮询的时序问题
家里有3台sh365时,每台设备的时钟偏移不同,我用了Google的cloud.google.com/go/civil库来生成基于NTP的时间戳:
civil.Now().DateTime // 输出2024-06-15T14:30:22
这样多台设备录像合并时,时间轴对得齐,需要注意sh365不提供NTP服务,得从RTCP中提取SR包的时间戳做补偿。
代码之外的思考:当摄像头开始“说谎”
有次发现拷贝的视频播放卡顿,查了半天发现是路由器的MTU设为1400导致UDP包分片,这个故障导致视频里偶尔出现马赛克,但用Go的net.DialUDP设置WriteBuffer到64KB后明显改善:
conn, _ := net.DialUDP("udp", nil, &raddr)
conn.SetWriteBuffer(64 * 1024)
更搞笑的是有一次sh365自动更新后换了RTSP端口,我怀疑是制造商的服务器远程改的,所以现在脚本里加了个端口扫描:
for port := 554; port <= 556; port++ {
if conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, port), 3*time.Second); err == nil {
log.Printf("发现RTSP服务在端口%d", port)
conn.Close()
break
}
}
这才是真正的“拷贝”
折腾两周后,我终于实现了摄像头SD卡内视频的自动备份,通过Samba协议扔给NAS,这套Go程序现在每天凌晨两点执行,顺便还生成了带时间戳的索引文件,可能有些代码在编译器看来不够优雅,但实用主义至上——能干活就是好代码。
最后提醒一句:sh365的固件版本经常乱跳,记得先抓包确认RTSP路径,这台摄像头虽然不完美,好在Go的自动重连机制(对,我没提自动重连,那就是个坑)能让我少熬夜,哪天商家又锁固件,这些代码或许还能派上别的用场——谁知道呢?
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.hljbesthome.com/keji/2704.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《用Go语言搞定sh365智能摄像头视频拷贝,一场与时间赛跑的实战》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:为什么偏偏要用Go来干这活儿?上个月我家的sh365智能摄像头又抽风了,晚上录的猫主子跑酷视频愣是没同步到云端,翻遍官方APP发现导...