海纳思(Hi3798MV300)机顶盒遇到海思摄像头

发布于:2025-05-11 ⋅ 阅读:(13) ⋅ 点赞:(0)

海纳思机顶盒遇到海思摄像头,正好家里有个海思Hi3516的摄像头模组开发板,结合机顶盒来做个录像。

准备工作

  1. 海纳斯机顶盒
  2. 摄像机模组
  3. 两根网线、两个电源、路由器
  4. 一块64G固态硬盘

摄像机模组和机顶盒都接入路由器的LAN口,确保网络正常通信。
道具
摄像机模组

调试录像

摄像机模组

摄像机模组里的程序其实是基于海思的SDK里的demo稍作修改而成,没有做太复杂的功能,只加入了RTSP,对外提供RTSP接口服务。
这里用的rtsp服务的库代码比较好用,源码链接:https://gitee.com/fensnote/RtspServer

在电脑上用VLC测试拉流播放:
VLC

海纳斯盒子录像

关于录像,这里只是实现简单的文件存储、循环覆盖,并不是专业的录像,专业录像里会做的比较复杂。

  1. 直接用Ffmpeg命令行录取数据到文件里,为了方便播放保存为MP4文件。
  2. 写代码实现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秒,即一分钟切换一个文件。
ffmpeg
如下截图,录取一分钟后已切换文件,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页面查看录像文件,首页还是挺好看的:
首页
文件管理器录像文件
录像文件可以直接点击播放:

通过手机查看

手机
文件管理录像列表
点击播放

播放