android版本的onvif及rtsp协议基本使用介绍

  • onvif协议实现源码
  • rtsp源码
  • Onvif协议基本介绍
    • 1.搜索可用的设备
    • 2.获取摄像头信息
    • 3.获取摄像头截图
    • 4.其他功能不一一介绍,可以在github中,自行学习
  • rtsp的基本使用和介绍
    • 总结

  5G时代即将到来,万物联网都将成为现实。世面上的物联网产品越来越多,智能机器人、智能家居、智能货柜等等。很荣幸从学校一毕业就接触了这个行业,也很喜欢这个行业,是的开发调试变得没有那么枯燥。废话不多说,讲讲看今天要写的东西。
  我在做的是一个智能货柜项目,基于动态图像识别技术,这个技术目前来看行业上面还是比较领先。当然我不也是搞算法那块,我没有那么牛逼,我负责的货柜上的主控系统。既然是图像识别,必然少不了的是摄像头,我们的识别算法在于云端,也选择的是网路摄像头,为了便宜选择的也是小厂牌的摄像头。目前世面上的网络摄像头,大都基于onvif协议实现,很不巧的是目前世面上大都是基于嵌入式c写的onvif代码。Andorid能找到的资源还是有限。然后自己只能蒙头网上找啊找,看啊看,终于摸到了一点门道,至少自己能够获取摄像头的截图了。这让我很是欣慰,兴奋的想记录一下。
  其中onvif已经实现了截图、重启、修改ip、修改获取参数、固件升级,修改添加摄像头账号密码等功能
   这个是我上传到github的onvif源码希望有帮助:

onvif协议实现源码

https://github.com/PetterJong/android-onvif

rtsp源码

https://github.com/PetterJong/rtsplib

Onvif协议基本介绍

ONVIF致力于通过全球性的开放接口标准来推进网络视频在安防市场的应用,这一接口标准将确保不同厂商生产的网络视频产品具有互通性。2008年11月,论坛正式发布了ONVIF第一版规范——ONVIF核心规范1.0。
  既然我们要控制摄像头,必不可少的我们需要首先对onvif协议做些基础知识的了解,另外还需要掌握其鉴权方式,需要用到http鉴权和WS-UsernameToken临牌验证,这个很重要,当然这两种鉴权方式都已在代码中集成,完全有兴趣的同学可以去了解一下。强烈推荐使用令牌验证的方式,此方式减少一半的请求响应次数,http鉴权在我们获取设备截图的时候必须会使用到,也需要加强了解。关于这两种鉴权方式,可以参考我的文章onvif协议控制之鉴权方式。
  注意:需要确保摄像头和我们的安卓设备的IP地址在同一网段下,否者将无法探测到该设备。
  建议学习方式如下,首先需要下载一个onvif协议2.0的文档做参考,第二步需要下载onvif device manager(简称odm)软件做功能测试,查看摄像头支持那些功能,第三部下载Wireshark抓包工具,结合odm和抓包工具找到设置方法。第4步下载onvif device test tool工具,验证抓到的数据是否能够执行
  所有的assets文件,包含鉴权信息及请求数据,重要必传参,建议先喽一眼:soap请求文件

1.搜索可用的设备

   package com.wp.android_onvif.onvif;import android.content.Context;
import android.util.Log;import com.wp.android_onvif.onvifBean.Device;
import com.wp.android_onvif.util.XmlDecodeUtil;import java.io.IOException;
import java.io.InputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;/*** Description : 利用线程搜索局域网内设备*/public class FindDevicesThread extends Thread {private static String tag = "OnvifSdk";private byte[] sendData;private boolean readResult = false;private String ipAdress;//回调借口private FindDevicesListener listener;/**** @param context* @param ipAdress ip地址(192.168.1.1)* @param listener*/public FindDevicesThread(Context context, String ipAdress, FindDevicesListener listener) {this.listener = listener;this.ipAdress = ipAdress;InputStream fis = null;try {//从assets读取文件fis = context.getAssets().open("probe.xml");sendData = new byte[fis.available()];readResult = fis.read(sendData) > 0;} catch (Exception e) {e.printStackTrace();} finally {if (fis != null) {try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}@Overridepublic void run() {super.run();DatagramSocket udpSocket = null;DatagramPacket receivePacket;DatagramPacket sendPacket;//设备列表集合ArrayList<Device> devices = new ArrayList<>();byte[] by = new byte[1024 * 16];if (readResult) {try {//端口号int BROADCAST_PORT = 3702;//初始化udpSocket = new DatagramSocket(BROADCAST_PORT);udpSocket.setSoTimeout(4 * 1000);udpSocket.setBroadcast(true);//DatagramPacketsendPacket = new DatagramPacket(sendData, sendData.length);sendPacket.setAddress(InetAddress.getByName(ipAdress));sendPacket.setPort(BROADCAST_PORT);//发送udpSocket.send(sendPacket);//接受数据receivePacket = new DatagramPacket(by, by.length);long startTime = System.currentTimeMillis();long endTime = System.currentTimeMillis();while (endTime - startTime < 4  * 1000) {udpSocket.receive(receivePacket);String str = new String(receivePacket.getData(), 0, receivePacket.getLength());Log.v(tag, str);devices.add(XmlDecodeUtil.getDeviceInfo(str));endTime = System.currentTimeMillis();}} catch (Exception e) {e.printStackTrace();} finally {if (udpSocket != null) {udpSocket.close();}}}//回调结果if (listener != null) {listener.searchResult(devices);}}/*** Author : BlackHao* Time : 2018/1/9 11:13* Description : 搜索设备回调*/public interface FindDevicesListener {void searchResult(ArrayList<Device> devices);}
}

2.获取摄像头信息

在这里我们会拿到摄像头videoToken、profileToken和网络参数等信息,后面的请求需要用到这些参数

package com.wp.android_onvif.onvif;import android.content.Context;import com.wp.android_onvif.onvifBean.Device;
import com.wp.android_onvif.onvifBean.Digest;
import com.wp.android_onvif.onvifBean.MediaProfile;
import com.wp.android_onvif.util.Gsoap;
import com.wp.android_onvif.util.HttpUtil;
import com.wp.android_onvif.util.XmlDecodeUtil;import java.io.IOException;
import java.io.InputStream;/*** 获取设备信息*/public class GetDeviceInfoThread extends Thread {private static String tag = "OnvifSdk";private Device device;private Context context;private GetDeviceInfoCallBack callBack;//    private WriteFileUtil util;public GetDeviceInfoThread(Device device, Context context, GetDeviceInfoCallBack callBack) {this.device = device;this.context = context;this.callBack = callBack;
//        util = new WriteFileUtil("onvif.txt");}@Overridepublic void run() {super.run();try {//getCapabilities,不需要鉴权String postString = OnvifUtils.getPostString("getCapabilities.xml", context, device, false);String caps = HttpUtil.postRequest(device.getServiceUrl(), postString);//解析返回的xml数据获取存在的urlXmlDecodeUtil.getCapabilitiesUrl(caps, device);// getDeviceInformation 获取设备信息String deviceInformation = OnvifUtils.getPostString("getDeviceInformation.xml", context, device, true);String deviceInformationReturn = HttpUtil.postRequest(device.getServiceUrl(), deviceInformation);XmlDecodeUtil.getDeviceInformation(deviceInformationReturn, device);// 获取网络接口配置String getNetworkInterface =  OnvifUtils.getPostString("getNetworkInterface.xml", context, device, true);String getNetworkInterfaceReturn = HttpUtil.postRequest(device.getServiceUrl(), getNetworkInterface);XmlDecodeUtil.getNetworkInterface(getNetworkInterfaceReturn, device);//getProfiles,需要鉴权postString = OnvifUtils.getPostString("getProfiles.xml", context, device, true);String profilesString = HttpUtil.postRequest(device.getMediaUrl(), postString);//解析获取MediaProfile 集合device.addProfiles(XmlDecodeUtil.getMediaProfiles(profilesString));//通过token获取RTSP urlfor (MediaProfile profile : device.getProfiles()) {postString = OnvifUtils.getPostString("getStreamUri.xml", context, device, true, profile.getToken());String profileString = HttpUtil.postRequest(device.getMediaUrl(), postString);//解析获取mediaUrlprofile.setRtspUrl(XmlDecodeUtil.getStreamUri(profileString));}callBack.getDeviceInfoResult(true, device, "NO_ERROR");//            postString = getPostString("getConfigOptions.xml", true);
//            caps = HttpUtil.postRequest(device.getPtzUrl(), postString);
//            util.writeData(caps.getBytes());//            util.finishWrite();} catch (Exception e) {e.printStackTrace();callBack.getDeviceInfoResult(false,device, e.toString());}}/*** Author : BlackHao* Time : 2018/1/11 14:24* Description : 获取 device 信息回调*/public interface GetDeviceInfoCallBack {void getDeviceInfoResult(boolean isSuccess, Device device, String errorMsg);}
}

3.获取摄像头截图

package com.wp.android_onvif.onvif;import android.content.Context;
import android.util.Log;import com.wp.android_onvif.onvifBean.Device;
import com.wp.android_onvif.onvifBean.MediaProfile;
import com.wp.android_onvif.util.FileUtils;
import com.wp.android_onvif.util.HttpUtil;
import com.wp.android_onvif.util.LogClientUtils;
import com.wp.android_onvif.util.XmlDecodeUtil;/*** 获取摄像机截图*/
public class GetSnapshotInfoThread extends Thread{private static String tag = "OnvifSdk";private Device device;private Context context;private GetSnapshotInfoThread.GetSnapshotInfoCallBack callBack;private MediaProfile mediaProfile;private String picRootPath;private String picFileName;//    private WriteFileUtil util;public GetSnapshotInfoThread(Device device, Context context, GetSnapshotInfoThread.GetSnapshotInfoCallBack callBack) {this.device = device;this.context = context;this.callBack = callBack;if(device.getProfiles() != null && device.getProfiles().size() > 0){this.mediaProfile = device.getProfiles().iterator().next();}
//        util = new WriteFileUtil("onvif.txt");}public void setPath(String picRootPath, String picFileName){this.picRootPath = picRootPath;this.picFileName = picFileName;}@Overridepublic void run() {super.run();try {//getProfiles,需要鉴权String postString = OnvifUtils.getPostString("getSnapshotUri.xml", context, device,true,mediaProfile == null?"000":mediaProfile.getToken());String getSnapshotString = HttpUtil.postRequest(device.getMediaUrl(), postString);Log.v(tag, getSnapshotString);//解析获取MediaProfile 集合String uri = XmlDecodeUtil.getSnapshotUri(getSnapshotString);byte[] bytes = HttpUtil.getByteArray2(uri, device.getUserName(), device.getPsw());String path = FileUtils.writeResoursToSDCard(picRootPath , picFileName, bytes);callBack.getSnapshotInfoResult(true, path);} catch (Exception e) {e.printStackTrace();callBack.getSnapshotInfoResult(false, e.toString());}}public interface GetSnapshotInfoCallBack{void getSnapshotInfoResult(boolean isSuccess, String errorMsg);}}
/*** HA1 = MD5(<username>:<reaml>:<psd>)* HA1 = MD5(<method>:<disgestUriPath>)* Response = MD5(MD5(A1):<nonce>:<nc>:<conce>:<qop>:MD5(A2))* 对用户名、认证域(realm)以及密码的合并值计算 MD5 哈希值,结果称为 HA1。* 对HTTP方法以及URI的摘要的合并值计算 MD5 哈希值,例如,"GET" 和 "/dir/index.html",结果称为 HA2。* 对 HA1、服务器密码随机数(nonce)、请求计数(nc)、客户端密码随机数(cnonce)、保护质量(qop)以及 HA2 的合并值计算 MD5 哈希值。结果即为客户端提供的 response 值。* 因为服务器拥有与客户端同样的信息,因此服务器可以进行同样的计算,以验证客户端提交的 response 值的正确性。在上面给出的例子中,结果是如下计算的。 (MD5()表示用于计算 MD5 哈希值的函数;“\”表示接下一行;引号并不参与计算)* 根据上面的算法所给出的示例,将在每步得出如下结果。*    HA1=MD5(admin:DS-2CD2310FD-I:hibox123) = 5a423defbb16f11b397b926915dfaa3b*    HA2 = MD5("GET:/onvif-http/snapshot?Profile_1") = 73d467d4c78c8f2e040b2943c9545432*     Response = MD5( "5a423defbb16f11b397b926915dfaa3b:4d6b5a444f5455774d7a6b364e575669597a67794d446b3d:00000001:cuYIxHJg3bt4gqYZwqndaAkuyUXtkE8b:auth:73d467d4c78c8f2e040b2943c9545432" )*             = 5920528fb9287d0ef90735acf122f695*  HEAD = Digest username="admin",realm="DS-2CD2310FD-I",nonce="4d6b5a444f5455774d7a6b364e575669597a67794d446b3d",uri="/onvif-http/snapshot?Profile_1",cnonce="cuYIxHJg3bt4gqYZwqndaAkuyUXtkE8b",nc=00000001,response="5920528fb9287d0ef90735acf122f695",qop="auth"* @param url* @return*/public static byte[] getByteArray2(String url, String user, String psd) throws IOException {OkHttpClient client = new OkHttpClient();Request request = new Request.Builder()
//                .header("Authorization", "Client-ID " + UUID.randomUUID()).url(url).get().build();Response response = client.newCall(request).execute();if (response.isSuccessful() ) {if( response.code() == 200 &&  response.body() != null){return response.body().bytes();}} else if(response.code() == 401){ // 未鉴权去鉴权//WWW-Authenticate: Digest qop="auth", realm="DS-2CD2310FD-I", nonce="4d6a4931516a51304e554d364e445935595759785954553d", stale="TRUE"//WWW-Authenticate: Basic realm="DS-2CD2310FD-I"Headers h = response.headers();List<String> auths = h.values("WWW-Authenticate");Pattern qopPattern = Pattern.compile("qop=\"(.*?)\"");Pattern realmPattern = Pattern.compile("realm=\"(.*?)\"");Pattern noncePattern = Pattern.compile("nonce=\"(.*?)\"");String qop = "";String realm = "";String nonce = "";String method = request.method();String host = response.request().url().host();String disgestUriPath = url.split(host)[1];for (String head: auths) {Matcher qopMatcher = qopPattern.matcher(head);while (qopMatcher.find()){try{qop = qopMatcher.group(1);} catch (Exception e){LogClientUtils.d(tag, e.getMessage());}}Matcher realmMatcher = realmPattern.matcher(head);while (realmMatcher.find()){try{realm = realmMatcher.group(1);} catch (Exception e){LogClientUtils.d(tag, e.getMessage());}}Matcher nonceMatcher = noncePattern.matcher(head);while (nonceMatcher.find()){try{nonce = nonceMatcher.group(1);} catch (Exception e){LogClientUtils.d(tag, e.getMessage());}}}return degistHttp(url, user, psd, method, disgestUriPath, nonce, realm, qop);}return null;}/*** http鉴权* @param url* @param user* @param psd* @param method* @param disgestUriPath* @param nonce* @param realm* @param qop* @return* @throws IOException*/private static byte[] degistHttp(String url, String user, String psd, String method, String disgestUriPath, String nonce, String realm, String qop) throws IOException {String nc = "00000001";String cnonce = getNonce();String ha1Data = getMd5Data(user, realm, psd);String ha1 = MD5Util.MD5Encode(ha1Data);String ha2Data = getMd5Data(method, disgestUriPath);String ha2 = MD5Util.MD5Encode(ha2Data);String ha3Data = getMd5Data(ha1, nonce, nc, cnonce, qop, ha2);String responseData = MD5Util.MD5Encode(ha3Data);String headFormat = "Digest username=\"%s\",realm=\"%s\",nonce=\"%s\",uri=\"%s\",cnonce=\"%s\",nc=%s,response=\"%s\",qop=\"%s\"" ;String head = String.format(headFormat, user, realm, nonce, disgestUriPath, cnonce, nc, responseData, qop);OkHttpClient client = new OkHttpClient();Request request = new Request.Builder()
//                .header("Authorization", "Client-ID " + UUID.randomUUID()).url(url).addHeader("Authorization", head).get().build();Response response = client.newCall(request).execute();if (response.isSuccessful() ) {if( response.code() == 200 &&  response.body() != null){return response.body().bytes();}}return null;}private static String getMd5Data(String... params){StringBuilder sb = new StringBuilder();for (String param : params){sb.append(param).append(":");}String data = sb.toString();return data.substring(0, data.length() - 1);}/*** 获取 Nonce** @return Nonce*/private static String getNonce() {//初始化随机数Random r = new Random();String text = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";String nonce = "";for (int i = 0; i < 32; i++) {int index = r.nextInt(text.length());nonce = nonce + text.charAt(index);}return nonce;}

4.其他功能不一一介绍,可以在github中,自行学习

rtsp的基本使用和介绍

rtsp-rtp是一个实时流协议,用来实时获取摄像头的视频流。
  首先我们需要大致了解一下需要用到的协议
  1.tcp协议 tcp协议是一个传输层的协议,具体不详细解释,自行百度。要说得一点就是,我们接下来的所有控制和操作都是基于这个协议的。详细介绍可以参考下这篇文章写的挺好的(https://blog.csdn.net/petershina/article/details/8189393 )
  2.rtsp协议 rtsp协议,是一个控制协议,其中包含的常见操作方法有option、describe、setup、play、teardown、stop,通过这些方法我们可以开始或者停止获取视频流
  3.sdp会话协议当我们通过rtsp协议去控制视频的时候,设备会给我们对应的回应,回应的数据用的就是sdp会话协议
  4.RTSP Interleaved Frame这个我的理解是rtp协议的外部头信息,可以认为他是一种格式,独立于rtp协议。它占4个字节,描述了接下来数据的格式和大小,请看下面的图。
  5.rtp-rtcp协议这个是视频流协议,我用的rtp协议,通过解码可以拿到原始的数据流,这个协议的数据紧跟在RTSP Interleaved Frame格式后面。rtcp适用于网络直播,会根据网络状态,动态调整帧率和码率,从而达到高效。
  6.h264协议 这个是一个视频协议,跟mp4协议是一个级别,播放器可以直接播放了。

我的rtsp源码不包含demo程序,初始化之后调用play就可以在回调接口里拿到视频流,最后输出的为h264的视频流。如果对mp4协议足够了解,你也可以讲rtp流转换成mp4流。

总结

rtsp-tcp/rtcp是一个实时流协议,只能用来获取实时流。
  onvif 媒体配置是通过SOAP/HTTP协议完成的,SOAP是简单对象访问协议,可以粗俗的看成是一种xml协议。简单来说rtsp获取实时流,通过http请求,soap数对象封装来完成与onvif协议摄像头参数请求。
  我们可以通过onvif的标准协议拿到设备的rtsp的uri和修改对应的摄像头参数,onvif协议是基于http的网络请求。需要注意的是很多摄像头本身可能本身并没有100%的实现onvif协议,这意味着这些设备的部分功能我们不能通过http onvif协议去修改或者操作该摄像头,我们可以借助odm软件工具去检查是否支持改功能,不支持的功能是灰色的无法选中。大部分摄像头厂商都会有自己的私有sdk,当通过公有的onvif协议无法获取到该摄像头参数时,不妨考虑联系下厂商给你提供帮助。
  这里需要谢谢Black_hao :https://blog.csdn.net/a512337862/article/details/79281648

android studio集成onvif协议的网络摄像头相关推荐

  1. 【Android RTMP】Android Studio 集成 x264 开源库 ( Ubuntu 交叉编译 | Android Studio 导入函数库 )

    文章目录 安卓直播推流专栏博客总结 一. x264 简介 二. x264 交叉编译 三. Android Studio 导入函数库 四. 交叉编译版本 五. GitHub 项目地址 安卓直播推流专栏博 ...

  2. 基于TCP协议的网络摄像头的设计与实现

    一.摘要 基于TCP协议的网络摄像头的设计大部分和博文"基于UDP协议的网络摄像头的设计与实现"相同,本篇博文采用的TCP协议栈为NicheStack协议栈(同理,可使用LWIP协 ...

  3. 集成android studio,Android Studio集成

    Android Studio集成 1.依赖方法,在Module下的Gradle文件中添加 compile 'com.chosen.kf5sdk:kf5sdklibrary:1.6.0' 2.全局初始化 ...

  4. android studio crashlytics,完美解决Android Studio集成crashlytics后无法编译的问题

    问题描述: 在用fabric集成后编译出现如下错误, Error:Cause: hostname in certificate didn't match: != OR OR build.gradle部 ...

  5. android 集成ijkplayer,android studio集成ijkplayer的示例代码

    介绍 ijkplayer是一款非常火的开源视频播放器,android和IOS通用.关于怎么编译怎么导入android Studio中自己的项目,其中坑很多,本篇记录下自己的操作记录.ijkplayer ...

  6. 安装配置Android Studio集成开发环境详细安装教程

    文章目录 一.Android Studio概述 二.下载Android Studio 三.安装Android Studio (一)进入安装向导 (二)选择安装组件 (三)选择安装位置 (四)选择开始菜 ...

  7. Android Studio集成NDK开发环境

    这几天需要使用C语言在底层编译,所以就打算在Android studio中打造可以编译C的环境,毕竟使用Android studio久了,就不怎么想用Eclipse开发了 废话不多说,直接来看一波集成 ...

  8. RTSP/Onvif安防网络摄像头无插件直播流媒体服务EasyNVR如何实现网络摄像机Onvif/RTSP接入直播与云台控制

    什么是Onvif协议 ONVIF规范描述了网络视频的模型.接口.数据类型以及数据交互的模式.并复用了一些现有的标准,如WS系列标准等.ONVIF规范的目标是实现一个网络视频框架协议,使不同厂商所生产的 ...

  9. 安装配置Android Studio集成开发环境

    文章目录 一.Android Studio概述 二.下载Android Studio 三.安装Android Studio (一)进入安装向导 (二)选择安装组件 (三)选择安装位置 (四)选择开始菜 ...

最新文章

  1. 面试官:Java 到底是值传递还是引用传递?
  2. java socket 一边关闭_java socket - 半关闭
  3. boost::core::bit_width的测试程序
  4. Codeforces Round #494 (Div. 3)
  5. php面向对象编程详解,PHP面向对象编程
  6. android 6.0 api 管理,Android 6.0(API23)权限申请问题
  7. python做硬件自动化测试-用python做自动化测试--Python实现远程性能监控
  8. How to: Configure an Azure SQL Database firewall using the Azure Portal
  9. Pytorch的BCEWithLogitsLoss函数中忽视标签怎么实现
  10. c语言中调用平均成绩,C语言、用调用函数、输入3个学生5门课程的成绩分别用函数求每个学生平均分每门课的平均分...
  11. PHP/PHPStudy所需的VC9-VC14的运行库
  12. Android智能硬件开发心得总结(一)
  13. matlab的取整函数
  14. jsp房屋出租管理系统带合同
  15. MAUI 跨平台应用开发实战
  16. [everydayNote] 零零散散不成篇
  17. 保持好距离才会保持好爱情!情侣间最好的距离!很值得一看!
  18. 从阿根廷队和法国队在世界杯的表现看团队建设
  19. Tomcat连接数据库的方法
  20. 使用 Leaflet.js 创建地图的入门指南

热门文章

  1. Django添加favicon.ico图标
  2. 006 二项分布、泊松分布、几何分布、指数分布、正态分布习题
  3. PyTorch学习笔记:nn.ReLU——ReLU激活函数
  4. 眼内衍射透镜的设计与分析
  5. 用于一般光学系统的光栅元件
  6. 无需代码,30张报表模板可直接套用,解决90%工作需求
  7. 福州宝宝巴士、顶点软件、联迪商用(解析持续更新)
  8. 实习期间遇到问题和解决方法
  9. 我的世界做计算机原理,我的世界计分板运算机制原理详解
  10. pdf.js在线打印在IE11上打印不全