Java onvif协议通用协议获取rtsp地址

本文介绍如何使用 ONVIF 协议的 Java 库获取摄像头的实时流和历史流 RTSP 地址,并通过 JavaCV 和 FFmpeg 实现低延迟的视频推流。文中详细讲解了代码实现过程,包括处理不同品牌摄像头如海康、大华和宇视的 RTSP 地址格式。
摘要由CSDN通过智能技术生成

这里就会获取到实时流和历史流的rtsp地址 后续文章会使用这个地址实现视频推流

Java 视频直播JavaCV(ffmpeg h264)+RTSP实现低延时1秒推流

吐槽下可搜索的资源真的是少,好多都是4 5年前的资料

onvif通用协议就不说了,大多摄像头都可用,利用onvif获取实时流和历史流转换成h264编码推送到前端,用h5新标签直接播放

onvif协议大部分是c或者android 资料 java的比较少 感谢大佬留下的宝贵资源:

1. https://github.com/RootSoft/ONVIF-Java   (功能不够完善,没有使用例子)

2. https://github.com/fpompermaier/onvif   (这个是基于上面的项目封装,有简单的例子,这里使用的这个)

实际使用:

前言:因为例子有限,没有历史流rtsp地址的使用,在参考例子和源码下,走了很多坑

后续更新利用javacv 使用rtsp地址拉取流推送出去。

上代码:

重点!重点!重点!总要的地方说三遍,将上述的第二个github项目拉下来打包到本地仓库 下面的

pom 中的onvif-ws-client就是打包到本地仓库的

项目地址:https://github.com/xsjzf/onvif_java

核心的地方是:

这里说明下例子中只有实时流rtsp地址的方法获取

在onvif-ws-client的源码中有很多功能例子中只有media 也就是实时流的

要使用历史流则需要自行添加 就是replay和recording 其他功能详见onvif的官网文档

Capabilities capabilities = this.device.getCapabilities(Arrays.asList(CapabilityCategory.ALL));
@javax.xml.bind.annotation.XmlElement(name = "Analytics")
    protected org.onvif.ver10.schema.AnalyticsCapabilities analytics;
    @javax.xml.bind.annotation.XmlElement(name = "Device")
    protected org.onvif.ver10.schema.DeviceCapabilities device;
    @javax.xml.bind.annotation.XmlElement(name = "Events")
    protected org.onvif.ver10.schema.EventCapabilities events;
    @javax.xml.bind.annotation.XmlElement(name = "Imaging")
    protected org.onvif.ver10.schema.ImagingCapabilities imaging;
    @javax.xml.bind.annotation.XmlElement(name = "Media")
    protected org.onvif.ver10.schema.MediaCapabilities media;
    @javax.xml.bind.annotation.XmlElement(name = "PTZ")
    protected org.onvif.ver10.schema.PTZCapabilities ptz;
    @javax.xml.bind.annotation.XmlElement(name = "Extension")
    protected org.onvif.ver10.schema.CapabilitiesExtension extension;
若需要历史回放功能呢,则需要对上面第二个github 地址进行修改,修改如下:OnvifDevice 的init方法中
Capabilities对象里面除了基本的功能大多在Extension里面
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "CapabilitiesExtension", propOrder = {
    "any",
    "deviceIO",
    "display",
    "recording",
    "search",
    "replay",
    "receiver",
    "analyticsDevice",
    "extensions"
})
public class CapabilitiesExtension {

    @XmlAnyElement(lax = true)
    protected List<java.lang.Object> any;
    @XmlElement(name = "DeviceIO")
    protected DeviceIOCapabilities deviceIO;
    @XmlElement(name = "Display")
    protected DisplayCapabilities display;
    @XmlElement(name = "Recording")
    protected RecordingCapabilities recording;
    @XmlElement(name = "Search")
    protected SearchCapabilities search;
    @XmlElement(name = "Replay")
    protected ReplayCapabilities replay;
    @XmlElement(name = "Receiver")
    protected ReceiverCapabilities receiver;
    @XmlElement(name = "AnalyticsDevice")
    protected AnalyticsDeviceCapabilities analyticsDevice;
    @XmlElement(name = "Extensions")
    protected CapabilitiesExtension2 extensions;

例如我这里需要历史流的功能那么我在init方法中初始化添加上


    if (capabilities.getExtension().getReplay() != null && capabilities.getExtension().getReplay().getXAddr() != null) {
      this.replayPort = new ReplayService().getReplayPort();
      this.replayPort =
              getServiceProxy((BindingProvider) replayPort, capabilities.getExtension().getReplay().getXAddr())
                      .create(ReplayPort.class);
    }

    if (capabilities.getExtension().getRecording() != null && capabilities.getExtension().getRecording().getXAddr() != null) {
      this.recordingPort = new RecordingService().getRecordingPort();
      this.recordingPort =
              getServiceProxy((BindingProvider) recordingPort, capabilities.getExtension().getRecording().getXAddr())
                      .create(RecordingPort.class);
    }
    if (capabilities.getExtension().getSearch() != null && capabilities.getExtension().getSearch().getXAddr() != null) {
      this.searchPort = new SearchService().getSearchPort();
      this.searchPort =
              getServiceProxy((BindingProvider) searchPort, capabilities.getExtension().getSearch().getXAddr())
                      .create(SearchPort.class);
    }

具体使用:

 /**
     * 获取到OnvifDevice对象
     * @param host 摄像头地址 92.168.xx.yy, or http://host[:port]
     * @param username 用户名
     * @param password 密码
     * @param profileToken "MediaProfile000"  If empty, will use first profile.
     * @return
     */
    public static OnvifDevice getOnvifCredentials(String host, String username, String password, String profileToken){
        try {
            OnvifCredentials credentials = new OnvifCredentials(host, username, password, profileToken);
            //补全host
            URL u = credentials.getHost().startsWith("http")
                    ? new URL(credentials.getHost())
                    : new URL("http://" + credentials.getHost());
            return new OnvifDevice(u, credentials.getUser(), credentials.getPassword());
        } catch (MalformedURLException | ConnectException | SOAPException e) {
            e.printStackTrace();
            throw new RrException(e.getMessage());
        }
    }

    /**
     * 获取实时rtsp地址
     * @param onvifDevice 设备
     * @return
     * @throws Exception
     */
    public static String getRtspUrl(OnvifDevice onvifDevice) throws Exception {
        List<Profile> profiles = onvifDevice.getMedia().getProfiles();
        for (Profile profile : profiles) {
            String profileToken = profile.getToken();
            String rtsp = onvifDevice.getStreamUri(profileToken, TransportProtocol.RTSP);
            String uri = "rtsp://" + onvifDevice.getUser() + ":" + onvifDevice.getPassword() + "@" + rtsp.replace("rtsp://", "");
        }
        return "";
    }

    /**
     * 获取历史rtsp地址
     * @param onvifDevice 设备
     * @return
     */
    public static String getReplayUrl(OnvifDevice onvifDevice){
        List<GetRecordingsResponseItem> recordings = onvifDevice.getRecordingPort().getRecordings();
        for (GetRecordingsResponseItem recording : recordings) {
            String recordingToken = recording.getRecordingToken();
            String rtsp = onvifDevice.getReplayUri(onvifDevice, recordingToken, TransportProtocol.RTSP);
            String uri = "rtsp://" + onvifDevice.getUser() + ":" + onvifDevice.getPassword() + "@" + rtsp.replace("rtsp://", "");
        }
        return "";
    }

    /**
     * 转换 时间段为rtsp时间格式
     * @param start 开始
     * @param end 结束
     * @return ?starttime=20200908t093812z&endtime=20200908t104816z
     */
    public static String getRtspTimeSpace(LocalDateTime start, LocalDateTime end){
        Long st = start.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        Long ed = end.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        String ios8601St = getDate(st);
        String ios8601Ed = getDate(st);
        return "?starttime=" + ios8601St + "&endtime=" + ios8601Ed;
    }

    /**
     * 时间戳转换成IOS8601格式
     * @param beginTime
     * @return
     */
    public static String getDate(Long beginTime) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String bt = format.format(beginTime);
        Date date = null;
        try {
            date = format.parse(bt);
        } catch (Exception e) {
            e.printStackTrace();
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'z'");
        return sdf.format(date);
    }

2020-11-27:

兼容海康,大华和宇视,修改相关rtsp组装

 /**
     * 获取实时rtsp地址
     * 海康nvr:
     * 	(主码流)rtsp://**.168.101.**:554/Streaming/Unicast/channels/101
     * 	(子码流)rtsp://**.168.101.**:554/Streaming/Unicast/channels/102
     * 海康单机:
     * 	(主码流)rtsp://**.168.30.**:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1
     * 	(子码流)rtsp://**.168.30.**:554/Streaming/Channels/102?transportmode=unicast&profile=Profile_2
     * 大华单机:
     * 	(主码流)rtsp://**.168.30.**:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
     * 	(子码流)rtsp://**.168.30.**:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
     * @param onvifDevice 设备
     * @return
     * @throws Exception
     */
    public static Map<String, String> getRtspUrl(OnvifDevice onvifDevice, Integer id) {
        Map<String, String> map = Maps.newLinkedHashMap();
        //兼容大华设备

        List<Profile> profiles = onvifDevice.getMedia().getProfiles();
        if (profiles == null || profiles.size() == 0){
            LOG.error("未查询到实时Profiles");
            return map;
        }
        for (Profile profile : profiles) {
            String profileToken = profile.getToken();
            System.out.println(profileToken);
            String rtsp = onvifDevice.getStreamUri(profileToken, TransportProtocol.RTSP);
            String uri = "rtsp://" + onvifDevice.getUser() + ":" + onvifDevice.getPassword() + "@" + rtsp.replace("rtsp://", "");
            //只取主码流 01结尾
            //兼容单个摄像头无法从profileToken获取到通道号
            //兼容大华单个摄像头
            String channel = id + profileToken;
//            String channel = profileToken.substring(profileToken.length() - 3);
            String hkUrl = rtsp.contains("?") ? rtsp.substring(0, rtsp.indexOf("?")) : rtsp;
            if (hkUrl.endsWith("01") || rtsp.contains("subtype=0")){
                map.put(channel, uri);
            }
        }
        return map;
    }
    /**
     * 获取历史rtsp地址
     * rtsp://admin:zouwei678@192.168.101.2:554/Streaming/tracks/301?starttime=20201119T134258z&endtime=20201119T134316z
     * rtsp://admin:zouwei678@192.168.101.2:554/Streaming/Unicast/channels/301?starttime=20201119T134258z&endtime=20201119T134316z
     * @param onvifDevice 设备
     * @return
     */
    public static Map<String, String> getReplayUrl(OnvifDevice onvifDevice, Integer id){
        Map<String, String> map = Maps.newLinkedHashMap();
        RecordingPort recordingPort = onvifDevice.getRecordingPort();
        if (recordingPort == null){
            LOG.error("该设备不支持历史回放");
            return map;
        }
        List<GetRecordingsResponseItem> recordings = recordingPort.getRecordings();
        if (recordings == null || recordings.size() == 0){
            LOG.error("未查询到历史Profiles");
            return map;
        }
        for (GetRecordingsResponseItem recording : recordings) {
            String recordingToken = recording.getRecordingToken();
//            String channel = recordingToken.substring(recordingToken.length() - 3);
            String channel = id + recordingToken;
            String rtsp = onvifDevice.getReplayUri(onvifDevice, recordingToken, TransportProtocol.RTSP);
            String uri = "rtsp://" + onvifDevice.getUser() + ":" + onvifDevice.getPassword() + "@" + rtsp.replace("rtsp://", "");
            map.put(channel, uri);
        }
        return map;
    }

在实际项目中使用发现,海康的nvr录像机在使用rtsp拼接时间方式获取历史流中,同一时间只能开启一路,再开启时会报错

这个错误经过多方搜索并不是ffmpeg或者javacv的问题,直接使用vlc打开也是这样的,根本原因是带宽限制,目前没有解决方法,有大佬有方法的麻烦告知下,不胜感激

****持续更新****

2021-04-12 介于很多人导入

<dependency>
    <groupId>org.onvif</groupId>
    <artifactId>onvif-ws-client</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

在此提供直接放入本地仓库包

百度网盘 请输入提取码

提取码:v974

直接解压到本地仓库org文件包下

实际使用封装:

   /**
     * 初始化Onvif
     * @param ip
     * @param userName
     * @param passwd
     * @param id
     */
    private void initOnvif(String ip, String userName, String passwd, Integer id) {
        try {
            OnvifDevice onvifDevice = MediaUtils.getOnvifCredentials(ip, userName, passwd, "MediaProfile000");
            Map<String, String> rtspUrl = MediaUtils.getRtspUrl(onvifDevice, id);
            Map<String, String> replayUrl = MediaUtils.getReplayUrl(onvifDevice, id);
            if (rtspUrl.isEmpty()){
                LOG.error("初始化加载未获取到实时RTSP地址: " + ip);
            }
            if (replayUrl.isEmpty()){
                LOG.error("初始化加载未获取到历史RTSP地址: " + ip);
            }
            map.put(id, new CameraInfo(rtspUrl, replayUrl));
            System.out.println("加载成功服务:" + ip);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("加载失败:" + ip);
        }
    }

package com.onvif.java.utils;

import com.google.common.collect.Maps;
import com.onvif.java.model.OnvifCredentials;
import com.onvif.java.service.OnvifDevice;
import com.onvif.java.service.UdpService;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;
import org.onvif.ver10.recording.wsdl.RecordingPort;
import org.onvif.ver10.schema.GetRecordingsResponseItem;
import org.onvif.ver10.schema.Profile;
import org.onvif.ver10.schema.TransportProtocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Random;

import static org.bytedeco.ffmpeg.global.avcodec.av_packet_unref;

/**
 * @program: javaOnvif
 * @description: 获取rtsp地址
 * @author: zf
 * @create: 2020-09-08 10:50
 **/
@Service
public class MediaUtils {

    /**
     * 获取到OnvifDevice对象
     * @param host 摄像头地址 92.168.xx.yy, or http://host[:port]
     * @param username 用户名
     * @param password 密码
     * @param profileToken "MediaProfile000"  If empty, will use first profile.
     * @return
     */
    public static OnvifDevice getOnvifCredentials(String host, String username, String password, String profileToken) throws Exception {
        OnvifCredentials credentials = new OnvifCredentials(host, username, password, profileToken);
        //补全host
        URL u = credentials.getHost().startsWith("http")
                ? new URL(credentials.getHost())
                : new URL("http://" + credentials.getHost());
        return new OnvifDevice(u, credentials.getUser(), credentials.getPassword());
    }

    /**
     * 获取实时rtsp地址
     * 海康nvr:
     * 	(主码流)rtsp://**.168.101.**:554/Streaming/Unicast/channels/101
     * 	(子码流)rtsp://**.168.101.**:554/Streaming/Unicast/channels/102
     * 海康单机:
     * 	(主码流)rtsp://**.168.30.**:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1
     * 	(子码流)rtsp://**.168.30.**:554/Streaming/Channels/102?transportmode=unicast&profile=Profile_2
     * 大华单机:
     * 	(主码流)rtsp://**.168.30.**:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
     * 	(子码流)rtsp://**.168.30.**:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
     * @param onvifDevice 设备
     * @return
     * @throws Exception
     */
    public static void getRtspUrl(OnvifDevice onvifDevice, Integer id) {
        Map<String, String> map = Maps.newLinkedHashMap();
        //兼容大华设备

        List<Profile> profiles = onvifDevice.getMedia().getProfiles();
        if (profiles == null || profiles.size() == 0){
            return;
        }
        for (Profile profile : profiles) {
            String profileToken = profile.getToken();
            String rtsp = onvifDevice.getStreamUri(profileToken, TransportProtocol.RTSP);
            String uri = "rtsp://" + onvifDevice.getUser() + ":" + onvifDevice.getPassword() + "@" + rtsp.replace("rtsp://", "");
            System.out.println(uri);
        }
    }

    /**
     * 获取历史rtsp地址
     * rtsp://admin:zouwei678@192.168.101.2:554/Streaming/tracks/301?starttime=20201119T134258z&endtime=20201119T134316z
     * rtsp://admin:zouwei678@192.168.101.2:554/Streaming/Unicast/channels/301?starttime=20201119T134258z&endtime=20201119T134316z
     * @param onvifDevice 设备
     * @return
     */
    public static void getReplayUrl(OnvifDevice onvifDevice, Integer id){
        Map<String, String> map = Maps.newLinkedHashMap();
        RecordingPort recordingPort = onvifDevice.getRecordingPort();
        if (recordingPort == null){
            return;
        }
        List<GetRecordingsResponseItem> recordings = recordingPort.getRecordings();
        if (recordings == null || recordings.size() == 0){
            return;
        }
        for (GetRecordingsResponseItem recording : recordings) {
            String recordingToken = recording.getRecordingToken();
//            String channel = recordingToken.substring(recordingToken.length() - 3);
            String channel = id + recordingToken;
            String rtsp = onvifDevice.getReplayUri(onvifDevice, recordingToken, TransportProtocol.RTSP);
            String uri = "rtsp://" + onvifDevice.getUser() + ":" + onvifDevice.getPassword() + "@" + rtsp.replace("rtsp://", "");
            System.out.println(uri);
        }
    }

    /**
     * 转换 时间段为rtsp时间格式
     * @param st 开始
     * @param end 结束
     * @return ?starttime=20200908t093812z&endtime=20200908t104816z
     */
    public static String getRtspTimeSpace(Long st, Long end){
        String ios8601St = getDate(st);
        String ios8601Ed = getDate(end);
        return "?starttime=" + ios8601St + "&endtime=" + ios8601Ed;
    }

    /**
     * 时间戳转换成IOS8601格式
     * @param beginTime
     * @return
     */
    public static String getDate(Long beginTime) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String bt = format.format(beginTime);
        Date date = null;
        try {
            date = format.parse(bt);
        } catch (Exception e) {
            e.printStackTrace();
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'z'");
        return sdf.format(date);
    }
}
package com.onvif.java.model;

import lombok.Data;

/**
 * onvif 连接信息
 * @author zf
 */
@Data
public class OnvifCredentials {
  /**
   * 92.168.xx.yy, or http://host[:port]
   */
  private String host;
  /**
   * admin
   */
  private String user;
  /**
   * secret
   */
  private String password;
  /**
   * "MediaProfile000"  If empty, will use first profile.
   */
  private String profile;

  public OnvifCredentials(String host, String user, String password, String profile) {
    this.host = host;
    this.user = user;
    this.password = password;
    this.profile = profile;
  }

  public String details() {
    return host + "," + user + "," + password + "," + profile;
  }
}

### 回答1: Onvif协议是一种用于网络视频监控设备之间通信的协议,能够提供一种简单、标准化的接口,使得大多数监控设备之间容易实现互操作。Java onvif协议是利用Java语言开发的一个基于Onvif协议的控制云台的解决方案。 Java onvif协议可以帮助用户轻松地控制监控设备,特别是能够控制云台。它基于Onvif协议,提供了丰富的SDK接口,并且支持多种编程语言,如Java、C++和C#等,使得开发者能够轻松地开发出功能强大的监控设备控制软件。 Java onvif协议控制云台的工作原理是,首先需要获取云台的控制权限,然后通过协议命令控制云台的运动,例如:上下、左右转动、缩小放大等。同时,Java onvif协议还支持多种云台协议,例如:Pelco-D、Pelco-P、Dahua等,能够与各种不同型号的云台进行适配。 在实际应用中,Java onvif协议控制云台的优点在于其易于集成、稳定性高、兼容性强,且具有较强的可扩展性,可以在不同场景下广泛应用。例如,在安防领域,Java onvif协议控制云台可以应用于视频监控、入侵报警、门禁等场景。在其他领域,例如:工业智能、交通管理等领域,Java onvif协议控制云台也具有广泛的应用前景。 ### 回答2: ONVIF是基于IP的开放式网络视频接口标准,旨在促进不同厂商的网络视频设备互相兼容和交互。Java是一种广泛使用的编程语言,可用于开发各种类型的应用程序,包括网络视频控制应用。 在Java中,我们可以使用ONVIF协议控制云台。这通常涉及使用ONVIF API和SOAP协议与网络摄像机通信。通过这些API和协议,我们可以执行各种操作,如控制云台的方向和速度,设置预置位和运动轨迹等。 使用Java编写控制云台的应用程序需要一定的编程知识和经验,尤其是在处理复杂的网络摄像机系统时。但是,一旦掌握了这些技能,您就可以轻松地编写能够高效地控制云台的应用程序,尤其是在监控、安防等场景下。 综上所述,Java onvif协议控制云台是一个具有挑战性和实用性的任务,需要正确的技能和工具,并且需要快速响应进行实时监测和恰当的调整。
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值