海纳思机顶盒遇到海思摄像头,正好家里有个海思Hi3516的摄像头模组开发板,结合机顶盒来做个录像。
准备工作
- 海纳斯机顶盒
- 摄像机模组
- 两根网线、两个电源、路由器
- 一块64G固态硬盘
摄像机模组和机顶盒都接入路由器的LAN口,确保网络正常通信。
调试录像
摄像机模组
摄像机模组里的程序其实是基于海思的SDK里的demo稍作修改而成,没有做太复杂的功能,只加入了RTSP,对外提供RTSP接口服务。
这里用的rtsp服务的库代码比较好用,源码链接:https://gitee.com/fensnote/RtspServer
在电脑上用VLC测试拉流播放:
海纳斯盒子录像
关于录像,这里只是实现简单的文件存储、循环覆盖,并不是专业的录像,专业录像里会做的比较复杂。
- 直接用Ffmpeg命令行录取数据到文件里,为了方便播放保存为MP4文件。
- 写代码实现rtsp拉流存储,可以自己定义传参。
Ffmpeg录像
这个比较简单,一条命令即可,不过直接采用命令录像没法指定实现循环覆盖,要想实现可以再写个脚本取定时检测录像文件的个数。
首先需要先下载安装Ffmpeg:
sudo apt install ffmpeg
我这里已经安装过了。
接下来就用可以执行录像了:
ffmpeg -rtsp_transport tcp -i rtsp://192.168.2.168:41667/live -c copy -f segment -segment_time 60 stream_piece_%d.mp4
这条命令里是指定了录像时长60秒,即一分钟切换一个文件。
如下截图,录取一分钟后已切换文件,1分钟录像数据15M,数据量挺大了:
上传上来播放一下看看:
播放:
使用win11的系统播放器就可以播放
写个代码录像
这里选用了go语言来写这个录像代码,是因为go语言的音视频、网络相关的库实在太多,比较好用,代码量也不大,可以提需求让AI去写,AI写的基本上稍作修改测试几次就可以用了。
Go还有个好处就是静态编译,真正的跨平台,一次编译,CPU架构一样都可以运行,感觉缺点就是可执行文件比较大。
这里采用的gortsplib,源码地址:https://gitee.com/fensnote/gortsplib.git
可以基于gortsplib/examples下的client-play-format-h264-save-to-disk示例代码做修改:
我复制了命名为client-play-format-h264-save-to-disk-file,在这里修改,下面代码是调试完成的代码:
package main
import (
"flag"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtph264"
"github.com/pion/rtp"
)
const (
filePrefix = "rec" // 文件名前缀
fileSuffix = ".mp4" // 文件名后缀
)
func main() {
// Define command line flags
rtspURL := flag.String("r", "", "RTSP URL")
maxFilesPtr := flag.Int("c", 0, "文件数")
startFileNumPtr := flag.Int("s", 0, "起始文件编号")
durationPtr := flag.Int("t", 60, "单个文件录像时长")
modePtr := flag.String("m", "loop", "录像模式,单次模式:\"once\",循环模式:\"loop\", 注意要加双引号")
// Parse command line flags
flag.Parse()
// Check if the required arguments are provided
if *rtspURL == "" || *maxFilesPtr == 0 { //|| *startFileNumPtr == 0
flag.PrintDefaults() // Print usage information
log.Fatal("Missing required command line arguments")
}
if *startFileNumPtr < 0 || *startFileNumPtr > *maxFilesPtr {
log.Fatalf("起始文件编号必须是0~%d", *maxFilesPtr)
}
// Check if the RTSP URL is valid
u, err := base.ParseURL(*rtspURL)
if err != nil {
log.Fatalf("无效的RTSP URL: %v", err)
}
c := gortsplib.Client{}
// Connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
log.Fatalf("连接 RTSP server 失败: %v", err)
}
// defer c.Close()
// Find available medias
desc, _, err := c.Describe(u)
if err != nil {
log.Fatalf("Failed to describe RTSP stream: %v", err)
}
// Find the H264 media and format
var forma *format.H264
medi := desc.FindFormat(&forma)
if medi == nil {
log.Fatal("H264 media not found")
}
// Setup RTP -> H264 decoder
rtpDec, err := forma.CreateDecoder()
if err != nil {
log.Fatalf("Failed to create H264 decoder: %v", err)
}
var mpegtsMuxer *mpegtsMuxer
var fileCounter int
var recordingStartTime time.Time
// var bakPts int64;
// var sub int
// Create the first file immediately when the program starts
fileCounter = *startFileNumPtr
newFileName := fmt.Sprintf("%s%03d%s", filePrefix, fileCounter, fileSuffix)
mpegtsMuxer = newMpegtsMuxer(newFileName, forma.SPS, forma.PPS)
err = mpegtsMuxer.initialize()
if err != nil {
log.Fatalf("Failed to initialize MPEG-TS muxer: %v", err)
}
log.Printf("New file created: %s", newFileName)
recordingStartTime = time.Now()
// Setup a single media
_, err = c.Setup(desc.BaseURL, medi, 0, 0)
if err != nil {
log.Fatalf("Failed to setup media: %v", err)
}
// Create a ticker to create a new file based on the specified duration
duration := time.Duration(*durationPtr) * time.Second
ticker := time.NewTicker(duration)
duration = duration + 100000000 // Add 200ms to the duration to ensure the ticker fires after the duration
defer ticker.Stop()
// bakPts = 0
// Called when a RTP packet arrives
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
// Decode timestamp
pts, ok := c.PacketPTS2(medi, pkt)
if !ok {
//log.Printf("Waiting for timestamp")
pts = int64(pkt.Timestamp)
//return
}
// if bakPts == 0 {
// bakPts = pts
// }
// Extract access unit from RTP packets
au, err := rtpDec.Decode(pkt)
if err != nil {
if err != rtph264.ErrNonStartingPacketAndNoPrevious && err != rtph264.ErrMorePacketsNeeded {
log.Printf("ERR: %v", err)
}
return
}
// sub = (int)(pts - bakPts)/100000
// Encode the access unit into MPEG-TS
if mpegtsMuxer != nil {
err = mpegtsMuxer.writeH264(au, pts)
if err != nil {
log.Printf("ERR: %v", err)
return
}
// log.Printf("Saved TS packet, pts: %d,sub:%d",pts,sub)
}
// Check if it's time to create a new file or exit
// if sub >= *durationPtr {
if time.Since(recordingStartTime) >= duration {
mpegtsMuxer.close()
if *modePtr == "once" {
log.Println("Recording duration reached, exiting...")
c.Close() // Close the RTSP client connection
os.Exit(0) // Exit the program immediately
} else {
fileCounter = (fileCounter + 1) % *maxFilesPtr
newFileName := fmt.Sprintf("%s%03d%s", filePrefix, fileCounter, fileSuffix)
mpegtsMuxer = newMpegtsMuxer(newFileName, forma.SPS, forma.PPS)
err = mpegtsMuxer.initialize()
if err != nil {
log.Fatalf("ERR: %v", err)
c.Close() // Close the RTSP client connection
os.Exit(-1) // Exit the program immediately
}
log.Printf("New file created: %s", newFileName)
recordingStartTime = time.Now()
//bakPts = pts
}
}
})
// Start playing
_, err = c.Play(nil)
if err != nil {
log.Fatalf("Failed to play RTSP stream: %v", err)
}
// Wait for interrupt signal or recording duration
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
for {
select {
case <-ticker.C:
if *modePtr == "once" {
log.Println("Recording duration reached, exiting...")
c.Close()
os.Exit(0)
}
case <-sigChan:
log.Println("Interrupt signal received, exiting...")
c.Close()
os.Exit(0)
}
}
}()
// Block main goroutine forever
select {}
}
代码编译:
export GOOS=linux
export GOARCH=arm
export GOARM=5
#export CGO_ENABLED=1
go build -ldflags '-s -w'
录像测试:
vrec
-c int
文件数
-m string
录像模式,单次模式:"once",循环模式:"loop", 注意要加双引号 (default "loop")
-r string
RTSP URL
-s int
起始文件编号
-t int
单个文件录像时长 (default 60)
2025/05/10 09:30:59 Missing required command line arguments
#录像命令参数:
vrec -c 1200 -m "loop" -s 0 -t 60 -r rtsp://192.168.2.168:41667/live
录像文件播放
录像文件查看,这是录了一晚上的,文件比较多:
通过电脑查看
在海纳思的内置web页面查看录像文件,首页还是挺好看的:
录像文件可以直接点击播放:
通过手机查看
点击播放