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

news2025/7/18 1:41:35

海纳思机顶盒遇到海思摄像头,正好家里有个海思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页面查看录像文件,首页还是挺好看的:
首页
文件管理器录像文件
录像文件可以直接点击播放:

通过手机查看

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

播放

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2375650.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Axure应用交互设计:表格跟随菜单移动效果(超长表单)

亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢!本文如有帮助请订阅 Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 课程主题:表格跟随菜单移动 主要内容:表格交互设计、动态面板嵌套、拖动时事件、移动动作 应用场景…

7系列 之 I/O标准和终端技术

背景 《ug471_7Series_SelectIO.pdf》介绍了Xilinx 7 系列 SelectIO 的输入/输出特性及逻辑资源的相关内容。 第 1 章《SelectIO Resources》介绍了输出驱动器和输入接收器的电气特性&#xff0c;并通过大量实例解析了各类标准接口的实现。 第 2 章《SelectIO Logic Resource…

github 上的 CI/CD 的尝试

效果 步骤 新建仓库设置仓库的 page 新建一个 vite 的项目&#xff0c;改一下 vite.config.js 中的 base 工作流 在项目的根目录下新建一个 .github/workflows/ci.yml 文件&#xff0c;然后编辑一下内容 name: Build & Deploy Vue 3 Appon:push:branches: [main]permi…

yup 使用 3 - 利用 meta 实现表单字段与表格列的统一结构配置(适配 React Table)

yup 使用 3 - 利用 meta 实现表单字段与表格列的统一结构配置&#xff08;适配 React Table&#xff09; Categories: Tools Last edited time: May 11, 2025 7:45 PM Status: Done Tags: form validation, schema design, yup 本文介绍如何通过 Yup 的 meta() 字段&#xff0…

【OpenCV】imread函数的简单分析

目录 1.imread()1.1 imread()1.2 imread_()1.2.1 查找解码器&#xff08;findDecoder&#xff09;1.2.2 读取数据头&#xff08;JpegDecoder-->readHeader&#xff09;1.2.2.1 初始化错误信息&#xff08;jpeg_std_error&#xff09;1.2.2.2 创建jpeg解压缩对象&#xff08;…

【Linux实践系列】:进程间通信:万字详解共享内存实现通信

&#x1f525; 本文专栏&#xff1a;Linux Linux实践项目 &#x1f338;作者主页&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客励志语录&#xff1a; 人生就像一场马拉松&#xff0c;重要的不是起点&#xff0c;而是坚持到终点的勇气 ★★★ 本文前置知识&#xff1a; …

【笔记】BCEWithLogitsLoss

工作原理 BCEWithLogitsLoss 是 PyTorch 中的一个损失函数&#xff0c;用于二分类问题。 它结合了 Sigmoid 激活函数和二元交叉熵&#xff08;Binary Cross Entropy, BCE&#xff09;损失在一个类中。 这不仅简化了代码&#xff0c;而且通过数值稳定性优化提高了模型训练的效…

关于Go语言的开发环境的搭建

1.Go开发环境的搭建 其实对于GO语言的这个开发环境的搭建的过程&#xff0c;类似于java的开发环境搭建&#xff0c;我们都是需要去安装这个开发工具包的&#xff0c;也就是俗称的这个SDK&#xff0c;他是对于我们的程序进行编译的&#xff0c;不然我们写的这个代码也是跑不起来…

Flutter PIP 插件 ---- 为iOS 重构PipController, Demo界面,更好的体验

接上文 Flutter PIP 插件 ---- 新增PipActivity&#xff0c;Android 11以下支持自动进入PIP Mode 项目地址 PIP&#xff0c; pub.dev也已经同步发布 pip 0.0.3&#xff0c;你的加星和点赞&#xff0c;将是我继续改进最大的动力 在之前的界面设计中&#xff0c;还原动画等体验一…

数据库管理-第325期 ADG Failover后该做啥(20250513)

数据库管理325期 2025-05-13 数据库管理-第325期 ADG Failover后该做啥&#xff08;20250513&#xff09;1 故障处置2 恢复原主库3 其他操作总结 数据库管理-第325期 ADG Failover后该做啥&#xff08;20250513&#xff09; 作者&#xff1a;胖头鱼的鱼缸&#xff08;尹海文&a…

SQLi-Labs 第21-24关

Less-21 http://127.0.0.1/sqli-labs/Less-21/ 1&#xff0c;抓个请求包看看 分析分析cookie被base64URL编码了&#xff0c;解码之后就是admin 2&#xff0c;那么这个网站的漏洞利用方式也是和Less-20关一样的&#xff0c;只是攻击语句要先base64编码&#xff0c;再URL编码&…

PVE WIN10直通无线网卡蓝牙

在 Proxmox VE (PVE) 中直通 Intel AC3165 无线网卡的 **蓝牙模块**&#xff08;通常属于 USB 设备&#xff0c;而非 PCIe 设备&#xff09;需要特殊处理&#xff0c;因为它的蓝牙部分通常通过 USB 连接&#xff0c;而 Wi-Fi 部分才是 PCIe 设备。以下是详细步骤&#xff1a; …

第六节第二部分:抽象类的应用-模板方法设计模式

模板方法设计模式的写法 建议使用final关键字修饰模板方法 总结 代码&#xff1a; People(父类抽象类) package com.Abstract3; public abstract class People {/*设计模板方法设计模式* 1.定义一个模板方法出来*/public final void write(){System.out.println("\t\t\t…

在另一个省发布抖音作品,IP属地会随之变化吗?

你是否曾有过这样的疑惑&#xff1a;出差旅游时在外地发布了一条抖音视频&#xff0c;评论区突然冒出“IP怎么显示xx省了&#xff1f;”的提问&#xff1f;随着各大社交平台上线“IP属地”功能&#xff0c;用户的地理位置标识成为公开信息&#xff0c;而属地显示的“灵敏性”也…

卷积神经网络-从零开始构建一个卷积神经网络

目录 一、什么是卷积神经网络CNN 1.1、核心概念 1.2、卷积层 二、什么是卷积计算 2.1、卷积计算的例子: 2.2、点积 2.3、卷积与点积的关系 2.4、Padding(填充) 2.4.1、Padding的主要作用 1、控制输出特征图尺寸 2、保留边缘信息 3. 支持深层网络训练 2.4.2、Str…

uniapp-文件查找失败:‘@dcloudio/uni-ui/lib/uni-icons/uni-icons.vue‘

uniapp-文件查找失败&#xff1a;‘dcloudio/uni-ui/lib/uni-icons/uni-icons.vue’ 今天在HBuilderX中使用uniapp开发微信小程序时遇到了这个问题&#xff0c;就是找不到uni-ui组件 当时创建项目&#xff0c;选择了一个中间带的底部带选项卡模板&#xff0c;并没有选择内置u…

Vue2.x 和 Vue3.x 对比-差异

Vue3的优点 diff算法的提升 vue2中的虚拟DOM是全量的对比&#xff0c;也就是不管是写死的还是动态节点都会一层层比较&#xff0c;浪费时间在静态节点上。 vue3新增静态标记&#xff08;patchflag &#xff09;&#xff0c;与之前虚拟节点对比&#xff0c;只对比带有patch fla…

MacOS 用brew 安装、配置、启动Redis

MacOS 用brew 安装、配置、启动Redis 一、安装 brew install redis 二、启动 brew services start redis 三、用命令行检测 set name tom get name

agentmain对业务的影响

前面一篇已经说了java agent技术主要有premain和agentmain两种形式&#xff0c;如果大部分业务已经在线上运行的话&#xff0c;不方便用premain的方式来实现&#xff0c;所以agentmain的方式是更加通用、灵活的 由于RASP是与用户业务运行在同一个jvm中的 &#xff0c;所以RASP…

uniapp小程序轮播图高度自适应优化详解

在微信小程序开发过程中&#xff0c;轮播图组件(swiper)是常用的UI元素&#xff0c;但在实际应用中经常遇到高度不匹配导致的空白问题。本文详细记录了一次轮播图高度优化的完整过程&#xff0c;特别是针对固定宽高比图片的精确适配方案。 问题背景 在开发"零工市场&quo…