• 正文
  • 相关推荐
申请入驻 产业图谱

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

05/12 17:00
1704
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

周末折腾了一下这个海思机顶盒,家里还有个海思的摄像机模组开发板,结合机顶盒来做个录像。

准备工作

    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)
    gofunc() {
        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页面查看录像文件,首页还是挺好看的:

首页
文件管理器录像文件
录像文件可以直接点击播放:

通过手机查看

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

海思

海思

海思面向智能终端、显示面板、家电、汽车电子等行业提供感知、联接、计算、显示等端到端的板级芯片和模组解决方案,覆盖PLC、8K、NB-IoT、SoC和XR等技术领域,是全球领先的Fabless半导体芯片公司。

海思面向智能终端、显示面板、家电、汽车电子等行业提供感知、联接、计算、显示等端到端的板级芯片和模组解决方案,覆盖PLC、8K、NB-IoT、SoC和XR等技术领域,是全球领先的Fabless半导体芯片公司。收起

查看更多

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录