开发专题
人工智能AI
机器学习ML
深度学习DL
关于大模型的一些基础内容
一些人工智能的架构图
使用ollama在本地启动运行大语言模型
在本地部署sd使用AI生图功能
AIGC入门教程
队列
RabbitMQ消息队列
redis+mq实现秒杀功能
非结构化存储OSS
使用minio进行数据存储
非结构化文档在线预览
使用kkfileView实现在线文档预览
OnlyOffice实现文档在线编辑
全文搜索
Elasticsearch构建全文搜索系统
windowns下使用Logstash7.6.2同步Mysql数据到ElasticSearch,并使用kibana进行检索
工作流Flowable应用
Flowable基础入门知识
Springboot+mybatisplus+flowable6.5.0 开发房产审批模块
人脸识别
虹软人脸识别应用
人脸识别基础入门知识
WebSocket在线聊天
Springboot+WebSocket+redis实现在线客服系统
WebSocket基础入门
信创产业
领域驱动DDD
定时任务quartz
流媒体
流媒体服务LALMAX的部署安装与使用
使用go2rtc+webrtc-streamer在网页上播放rtsp 的摄像头视频
RPA数字员工
使用盘匠设计器进行RPA项目的开发
RPA开发过程中的一些经验之谈
本文档使用 MrDoc 发布
-
+
首页
流媒体服务LALMAX的部署安装与使用
> 流媒体使用的是开源项目lal的子项目lalmax 项目地址:https://github.com/q191201771/lalmax Lal官网:https://pengrl.com/ ## 1.LALMAX部署 > 这里假设的我流媒体服务器地址的IP是:10.13.1.36 将 lalmax 的程序 上传到 /home/lalmax/ 目录 启动 ~~~ ./lalmax ~~~ 启动完之后就可以使用他的api进行推流拉流,这里以Onvif协议的摄像头为例: ### 推流 推流地址:http://10.13.1.36:1290/api/ctrl/onvif/pull POST请求参数: ~~~ { "addr": "10.12.7.102:8000", "username": "admin", "password": "hn_123456", "rtspmode": 0, "pullallprofiles": false, "streamname": "test112" } ~~~ ### 查询流信息 GET请求:http://10.13.1.36:8083/api/stat/all_group ### 删除推流 GET请求:http://10.13.1.36:8083/api/ctrl/stop_relay_pull?stream_name=test110 stream_name 是流的名称。 ### 监控画面 监控画面web控制台:http://10.13.1.36:8083/lal.html  ## 2、lalmax的二开说明 lalmax的官方代码在推流的时候,是没有 streamname 这个参数的,默认的streamname 官方使用了 摄像头的型号 来命令,就是导致当同时查看 多个相同型号摄像头时,会占用同一个 streamname 需要对onvif/server.go的文件进行了一些修改: ~~~go package onvif import ( "bytes" "context" "encoding/json" "fmt" "net/http" "strings" "time" "github.com/gin-gonic/gin" "github.com/q191201771/lal/pkg/base" "github.com/q191201771/naza/pkg/nazalog" goonvif "github.com/use-go/onvif" "github.com/use-go/onvif/device" media "github.com/use-go/onvif/media" sdk "github.com/use-go/onvif/sdk/device" sdkmedia "github.com/use-go/onvif/sdk/media" onvifcmd "github.com/use-go/onvif/xsd/onvif" ) type OnvifPullRequest struct { StreamName string `json:"streamname"` // streamName Addr string `json:"addr"` // 摄像机IP:PORT Username string `json:"username"` // 用户名 Password string `json:"password"` // 密码 RtspMode int `json:"rtspmode"` // rtsp拉流模式,0-tcp, 1-udp PullAllProfiles bool `json:"pullallprofiles"` // 是否请求所有profiles } type OnvifServer struct { } func NewOnvifServer() *OnvifServer { return &OnvifServer{} } func (s *OnvifServer) HandlePull(c *gin.Context) { pullreq := OnvifPullRequest{} err := c.ShouldBind(&pullreq) if err != nil { c.Status(http.StatusBadRequest) return } // pullreq.StreamName不能为空 if pullreq.StreamName == "" { var v base.ApiCtrlStartRelayPullResp v.ErrorCode = base.ErrorCodeParamMissing v.Desp = "streamName is empty" c.JSON(http.StatusOK, v) return } dev, err := goonvif.NewDevice(goonvif.DeviceParams{ Xaddr: pullreq.Addr, Username: pullreq.Username, Password: pullreq.Password, }) if err != nil { nazalog.Error(err) // mww@2024-11-12 var v base.ApiCtrlStartRelayPullResp v.ErrorCode = base.ErrorCodeParamMissing v.Desp = err.Error() c.JSON(http.StatusOK, v) return } deviceInfoReq := device.GetDeviceInformation{} deviceInfoRes, err := sdk.Call_GetDeviceInformation(context.Background(), dev, deviceInfoReq) if err != nil { nazalog.Error(err) return } getCapabilities := device.GetCapabilities{Category: "All"} _, err = sdk.Call_GetCapabilities(context.Background(), dev, getCapabilities) if err != nil { nazalog.Error("Call_GetCapabilities failed, err:", err) c.Status(http.StatusInternalServerError) return } profilesReq := media.GetProfiles{} profilesRes, err := sdkmedia.Call_GetProfiles(context.Background(), dev, profilesReq) if err != nil { nazalog.Error("Call_GetProfiles failed, err:", err) c.Status(http.StatusInternalServerError) return } if len(profilesRes.Profiles) == 0 { nazalog.Error("profilesRes.Profiles invalid") c.Status(http.StatusInternalServerError) return } var protocol onvifcmd.TransportProtocol if pullreq.RtspMode == 1 { protocol = "UDP" } else { protocol = "TCP" } if pullreq.PullAllProfiles { for _, profile := range profilesRes.Profiles { streamUrlReq := media.GetStreamUri{ ProfileToken: profile.Token, StreamSetup: onvifcmd.StreamSetup{ Stream: "RTP-Unicast", Transport: onvifcmd.Transport{ Protocol: protocol, }, }, } streamUrlRes, err := sdkmedia.Call_GetStreamUri(context.Background(), dev, streamUrlReq) if err != nil { nazalog.Error(err) return } playUrl := buildPlayUrl(string(streamUrlRes.MediaUri.Uri), pullreq.Username, pullreq.Password) // 使用stream name作为streamid // DoPull(playUrl, fmt.Sprintf("%s-%s", deviceInfoRes.Model, profile.Name), pullreq.RtspMode) DoPull(playUrl, fmt.Sprintf("%s-%s", deviceInfoRes.Model, profile.Name), pullreq.RtspMode) } } else { streamUrlReq := media.GetStreamUri{ ProfileToken: profilesRes.Profiles[0].Token, StreamSetup: onvifcmd.StreamSetup{ Stream: "RTP-Unicast", Transport: onvifcmd.Transport{ Protocol: protocol, }, }, } streamUrlRes, err := sdkmedia.Call_GetStreamUri(context.Background(), dev, streamUrlReq) if err != nil { nazalog.Error(err) return } playUrl := buildPlayUrl(string(streamUrlRes.MediaUri.Uri), pullreq.Username, pullreq.Password) // mww@2024-11-12 // 使用OnvifPullRequest中的streamName DoPull(playUrl, pullreq.StreamName, pullreq.RtspMode) //DoPull(playUrl, fmt.Sprintf("%s-%s", deviceInfoRes.Model, profilesRes.Profiles[0].Name), pullreq.RtspMode) var v base.ApiCtrlStartRelayPullResp v.ErrorCode = base.ErrorCodeSucc v.Desp = "success" v.Data.StreamName = pullreq.StreamName c.JSON(http.StatusOK, v) } } func buildPlayUrl(rawurl, username, password string) string { if username != "" && password != "" { playUrl := fmt.Sprintf("rtsp://%s:%s@%s", username, password, strings.TrimLeft(rawurl, "rtsp://")) return playUrl } return rawurl } func DoPull(url, streamname string, rtspmod int) { request := base.ApiCtrlStartRelayPullReq{ Url: url, StreamName: streamname, RtspMode: rtspmod, AutoStopPullAfterNoOutMs: -1, } data, _ := json.Marshal(request) req, err := http.NewRequest("POST", "http://127.0.0.1:8083/api/ctrl/start_relay_pull", bytes.NewReader(data)) if err != nil { return } req.Header.Set("Content-Type", "application/json") cli := &http.Client{ Transport: http.DefaultTransport, Timeout: time.Duration(5) * time.Second, } resp, err := cli.Do(req) if err != nil { return } if resp.StatusCode != 200 { return } resp.Body.Close() return } ~~~
superadmin
2025年2月11日 11:20
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码