Android平台GB28181实时回传流程和技术实现

news2024/11/18 13:29:20

规范解读

GB28181 中的 “INVITE” 是会话初始协议(SIP)中的一种请求方法,主要用于邀请一个或多个参与者加入特定的会话。在 GB28181 标准中,“INVITE” 请求通常用于发起媒体流的传输请求。当一个设备想要接收来自另一个设备的媒体流时,它会向目标设备发送一个 “INVITE” 请求,其中包含了关于会话的描述信息,如媒体类型、编码格式、传输协议等。

以下是 GB28181 中 “INVITE” 请求的一些关键特点和作用:

一、发起会话

  1. 触发媒体流传输:“INVITE” 请求是启动媒体流传输的关键步骤。当源设备发送 “INVITE” 请求时,它向目标设备表明了希望建立一个媒体会话的意图。这个请求中包含了源设备支持的媒体格式、编码方式和传输协议等信息,以便目标设备能够确定是否可以满足这些要求并接受会话邀请。
  2. 携带会话描述信息:“INVITE” 请求中通常包含会话描述协议(SDP)信息,用于描述媒体流的特性。SDP 信息包括媒体类型(音频、视频或两者兼有)、编码格式、媒体的传输地址和端口等。目标设备通过解析 SDP 信息,可以了解源设备的媒体能力,并决定是否能够参与会话。

二、协商媒体参数

  1. 媒体能力协商:在 GB28181 中,不同的设备可能具有不同的媒体处理能力。通过 “INVITE” 请求和响应的交互过程,可以进行媒体能力的协商。目标设备在接收到 “INVITE” 请求后,会检查自己的媒体能力,并根据源设备的要求进行相应的调整。如果目标设备无法满足源设备的要求,它可以返回一个错误响应,或者提出替代的媒体参数建议。
  2. 编码格式选择:“INVITE” 请求还可以用于协商媒体流的编码格式。不同的编码格式在压缩效率、图像质量和带宽需求等方面有所不同。通过协商,可以选择一种双方都支持的编码格式,以确保媒体流的正常传输和播放。

三、建立连接

  1. 确定传输路径:一旦目标设备接受了 “INVITE” 请求,双方就可以开始建立媒体流的传输连接。这个过程涉及确定媒体流的传输协议(如 RTP/RTCP)、传输地址和端口等信息。根据 GB28181 标准,媒体流可以通过 IP 网络进行传输,使用 UDP 或 TCP 协议。
  2. 会话管理:在媒体流传输过程中,“INVITE” 请求所建立的会话可以通过其他 SIP 方法进行管理,如 “BYE” 用于结束会话,“ACK” 用于确认会话的建立等。这些方法可以帮助设备在会话期间进行状态监测、错误处理和资源管理。

Android GB28181技术实现

本文以大牛直播SDK的GB28181设备接入模块为例,大牛直播SDK推出的Android平台GB28181接入SDK(SmartGBD),可实现不具备国标音视频能力的 Android终端,通过平台注册接入到现有的GB/T28181—2016服务,可用于如执法记录仪、智能安全帽、智能监控、智慧零售、智慧教育、远程办公、明厨亮灶、智慧交通、智慧工地、雪亮工程、平安乡村、生产运输、车载终端等场景。进入系统后,先启动GB28181,注册到平台,等待国标平台发起回传请求。

class ButtonGB28181AgentListener implements View.OnClickListener {
	public void onClick(View v) {
		record_executor_.cancel_tasks();

		stopRecordDownloads(true);
		stopPlaybacks(true);

		stopAudioPlayer();
		destoryRTPReceiver();

		gb_broadcast_source_id_ = null;
		gb_broadcast_target_id_ = null;
		btnGB28181AudioBroadcast.setText("GB28181语音广播");
		btnGB28181AudioBroadcast.setEnabled(false);

		stopGB28181Stream();
		destoryRTPSender();

		if (null == gb28181_agent_ ) {
			if( !initGB28181Agent() )
				return;
		}

		if (gb28181_agent_.isRunning()) {
			gb28181_agent_.terminateAllAudioBroadcasts(true);
			gb28181_agent_.terminateAllPlays(true);// 目前测试下来,发送BYE之后,有些服务器会立即发送INVITE,是否发送BYE根据实际情况看
			gb28181_agent_.stop();
			btnGB28181Agent.setText("启动GB28181");
		}
		else {
			record_executor_.cancel_tasks();
			initPlaybacks(null);
			initRecordDownloads(null);
			if ( gb28181_agent_.start() ) {
				btnGB28181Agent.setText("停止GB28181");
			}
		}
	}
}

 GBSIPAgentPlayListener主要系GB28181的Invite、Ack、Bye等处理:

public interface GBSIPAgentPlayListener {

    /*
     *收到s=Play的实时视音频点播
     */
    void ntsOnInvitePlay(String deviceId, SessionDescription sessionDescription);

    /*
     *发送play invite response 异常
     */
    void ntsOnPlayInviteResponseException(String deviceId, int statusCode, String errorInfo);

    /*
     * 收到CANCEL play INVITE请求
     */
    void ntsOnCancelPlay(String deviceId);

    /*
     * 收到Ack
     */
    void ntsOnAckPlay(String deviceId);

    /*
     * 收到Bye
     */
    void ntsOnByePlay(String deviceId);

    /*
     * 不是在收到BYE Message情况下, 终止Play
     */
    void ntsOnTerminatePlay(String deviceId);

    /*
     * Play会话对应的对话终止, 一般不会出发这个回调,目前只有在响应了200K, 但在64*T1时间后还没收到ACK,才可能会出发
    收到这个, 请做相关清理处理
    */
    void ntsOnPlayDialogTerminated(String deviceId);
}

平台发起回传请求时,ntsOnInvitePlay()来响应invite请求。

/*
 * Camera2MainActivity.java
 * Author: daniusdk.com
 * WeChat: xinsheng120
 */
@Override
public void ntsOnInvitePlay(String deviceId, SessionDescription session_des) {
	handler_.postDelayed(new Runnable() {
		@Override
		public void run() {
			// 先振铃响应下
			gb28181_agent_.respondPlayInvite(180, device_id_);

			MediaSessionDescription video_des = null;
			SDPRtpMapAttribute ps_rtpmap_attr = null;

			// 28181 视频使用PS打包
			Vector<MediaSessionDescription> video_des_list = session_des_.getVideoPSDescriptions();
			if (video_des_list != null && !video_des_list.isEmpty()) {
				for(MediaSessionDescription m : video_des_list) {
					if (m != null && m.isValidAddressType() && m.isHasAddress() ) {
						video_des = m;
						ps_rtpmap_attr = video_des.getPSRtpMapAttribute();
						break;
					}
				}
			}

			if (null == video_des) {
				gb28181_agent_.respondPlayInvite(488, device_id_);
				Log.i(TAG, "ntsOnInvitePlay get video description is null, response 488, device_id:" + device_id_);
				return;
			}

			if (null == ps_rtpmap_attr) {
				gb28181_agent_.respondPlayInvite(488, device_id_);
				Log.i(TAG, "ntsOnInvitePlay get ps rtp map attribute is null, response 488, device_id:" + device_id_);
				return;
			}

			Log.i(TAG,"ntsOnInvitePlay, device_id:" +device_id_+", is_tcp:" + video_des.isRTPOverTCP()
					+ " rtp_port:" + video_des.getPort() + " ssrc:" + video_des.getSSRC()
					+ " address_type:" + video_des.getAddressType() + " address:" + video_des.getAddress());

			long rtp_sender_handle = libPublisher.CreateRTPSender(0);
			if ( rtp_sender_handle == 0 ) {
				gb28181_agent_.respondPlayInvite(488, device_id_);
				Log.i(TAG, "ntsOnInvitePlay CreateRTPSender failed, response 488, device_id:" + device_id_);
				return;
			}

			gb28181_rtp_payload_type_  = ps_rtpmap_attr.getPayloadType();
			gb28181_rtp_encoding_name_ =  ps_rtpmap_attr.getEncodingName();

			libPublisher.SetRTPSenderTransportProtocol(rtp_sender_handle, video_des.isRTPOverUDP()?0:1);
			libPublisher.SetRTPSenderIPAddressType(rtp_sender_handle, video_des.isIPv4()?0:1);
			libPublisher.SetRTPSenderLocalPort(rtp_sender_handle, 0);
			libPublisher.SetRTPSenderSSRC(rtp_sender_handle, video_des.getSSRC());
			libPublisher.SetRTPSenderSocketSendBuffer(rtp_sender_handle, 2*1024*1024); // 设置到2M
			libPublisher.SetRTPSenderClockRate(rtp_sender_handle, ps_rtpmap_attr.getClockRate());
			libPublisher.SetRTPSenderDestination(rtp_sender_handle, video_des.getAddress(), video_des.getPort());

			if ( libPublisher.InitRTPSender(rtp_sender_handle) != 0 ) {
				gb28181_agent_.respondPlayInvite(488, device_id_);
				libPublisher.DestoryRTPSender(rtp_sender_handle);
				return;
			}

			int local_port = libPublisher.GetRTPSenderLocalPort(rtp_sender_handle);
			if (local_port == 0) {
				gb28181_agent_.respondPlayInvite(488, device_id_);
				libPublisher.DestoryRTPSender(rtp_sender_handle);
				return;
			}

			Log.i(TAG,"get local_port:" + local_port);

			String local_ip_addr = IPAddrUtils.getIpAddress(context_);

			MediaSessionDescription local_video_des = new MediaSessionDescription(video_des.getType());

			local_video_des.addFormat(String.valueOf(ps_rtpmap_attr.getPayloadType()));
			local_video_des.addRtpMapAttribute(ps_rtpmap_attr);

			local_video_des.setAddressType(video_des.getAddressType());
			local_video_des.setAddress(local_ip_addr);
			local_video_des.setPort(local_port);

			local_video_des.setTransportProtocol(video_des.getTransportProtocol());
			local_video_des.setSSRC(video_des.getSSRC());

			if (!gb28181_agent_.respondPlayInviteOK(device_id_,local_video_des) ) {
				libPublisher.DestoryRTPSender(rtp_sender_handle);
				Log.e(TAG, "ntsOnInvitePlay call respondPlayInviteOK failed.");
				return;
			}

			gb28181_rtp_sender_handle_ = rtp_sender_handle;
		}

		private String device_id_;
		private SessionDescription session_des_;

		public Runnable set(String device_id, SessionDescription session_des) {
			this.device_id_ = device_id;
			this.session_des_ = session_des;
			return this;
		}
	}.set(deviceId, session_des),0);
}

@Override
public void ntsOnCancelPlay(String deviceId) {
	// 这里取消Play会话
	handler_.postDelayed(new Runnable() {
		@Override
		public void run() {
			Log.i(TAG, "ntsOnCancelPlay, deviceId=" + device_id_);

			destoryRTPSender();
		}

		private String device_id_;

		public Runnable set(String device_id) {
			this.device_id_ = device_id;
			return this;
		}

	}.set(deviceId),0);
}

收到Ack后,ntsOnAckPlay()处理后续逻辑:

@Override
public void ntsOnAckPlay(String deviceId) {
	handler_.postDelayed(new Runnable() {
		@Override
		public void run() {
			Log.i(TAG,"ntsOnACKPlay, device_id:" +device_id_);

			InitAndSetConfig();

			stream_publisher_.SetGB28181RTPSender(gb28181_rtp_sender_handle_, gb28181_rtp_payload_type_, gb28181_rtp_encoding_name_);

			//libPublisher.SetGBTCPConnectTimeout(publisherHandle, 10*60*1000);
			//libPublisher.SetGBInitialTCPReconnectInterval(publisherHandle, 1000);
			//libPublisher.SetGBInitialTCPMaxReconnectAttempts(publisherHandle, 3);

			boolean start_ret  = stream_publisher_.StartGB28181MediaStream();
			if (!start_ret) {
				stream_publisher_.try_release();
				destoryRTPSender();
				Log.e(TAG, "Failed to start GB28181 service..");
				return;
			}

			startAudioRecorder();
			startLayerPostThread();
		}

		private String device_id_;

		public Runnable set(String device_id) {
			this.device_id_ = device_id;
			return this;
		}

	}.set(deviceId),0);
}

@Override
public void ntsOnPlayInviteResponseException(String deviceId, int statusCode, String errorInfo) {
	// 这里要释放掉响应的资源
	Log.i(TAG, "ntsOnPlayInviteResponseException, deviceId=" + deviceId + " statusCode=" +statusCode
	+ " errorInfo:" + errorInfo);

	handler_.postDelayed(new Runnable() {
		@Override
		public void run() {
			Log.i(TAG, "ntsOnPlayInviteResponseException, deviceId=" + device_id_);

			destoryRTPSender();
		}

		private String device_id_;

		public Runnable set(String device_id) {
			this.device_id_ = device_id;
			return this;
		}

	}.set(deviceId),0);
}

总结

GB28181 中的 “INVITE” 请求在媒体流传输中起着至关重要的作用,它通过发起会话、协商媒体参数和建立连接等步骤,实现了设备之间的媒体通信。在实际应用中,需要根据具体的需求和场景,合理地使用 “INVITE” 请求和其他 SIP 方法,以确保媒体流的稳定传输和高质量播放。

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

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

相关文章

Linux上安装Jenkins并展示allure报告

1. 确认安装正确的java版本 到官网War Jenkins Packages查看Jenkins版本匹配的java版本&#xff0c;我这里选择安装java11 使用java --version命令是否已安装java版本 java --version 如上图所示&#xff0c;暂未安装java版本&#xff0c;我这里选择安装java11&#xff08;je…

基于SpringBoot+Vue+MySQL的在线酷听音乐系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着互联网技术的飞速发展&#xff0c;网络已成为人们日常生活中不可或缺的一部分。在线音乐服务因其便捷性和丰富性&#xff0c;逐渐成为用户获取音乐内容的主要渠道。然而&#xff0c;传统的音乐播放平台往往存在歌曲资源有限…

进程组、会话、守护进程和线程的概念

1.进程组和会话 1.1 概念和特性 进程组&#xff0c;也称之为作业。BSD于1980年前后向Unix中增加的一个新特性。代表一个或多个进程的集合。每个进程都属于一个进程组。在waitpid函数和kill函数的参数中都曾使用到。操作系统设计的进程组的概念&#xff0c;是为了简化对多个进…

微信小程序-数据模型与动态赋值

首先新建一个小程序项目. 这边有创建基础项目的流程:从0新建一个微信小程序实现一个简单跳转_小白开发小程序源代码-CSDN博客 一共两步: 1.建立页面的 数据模型 和 默认赋值: 默认赋值: 2.接收输入框的新文案,动态替换上面的文案展示 //文件 testUI.js增加方法:onInputChan…

当 ucx --with-cuda 时做了什么

1&#xff0c;找一只活麻雀&#xff0c;下载编译 ucx git clone https://github.com/openucx/ucx.git cd ucx/ git checkout v1.16.0 ./autogen.sh ./autogen.sh mkdir build cd build ../contrib/configure-devel --with-cuda/usr/local/cuda --without-rocm --without-java …

JavaScript 知识点 - 作用域(变量提升、垃圾回收机制、闭包)

一、作用域 1、基本概念 是什么? 指变量、对象和函数在【代码中的可访问性范围】。 有什么用? 理解作用域对【编写高效和无错误的代码】至关重要 分类 局部作用域&#xff08;函数作用域、块作用域&#xff09;、全局作用域 涉及到那些知识点 作用域链、JS垃圾回收机…

在线支付系统

一、系统概述 本在线支付系统基于 Servlet 技术构建&#xff0c;旨在为用户提供安全、便捷的支付服务。系统具备简洁的用户界面和高效的支付处理能力&#xff0c;满足用户在各种场景下的支付需求。 二、主要功能 首页登录注册&#xff1a; 用户可以在首页进行登录和注册操作。注…

MacBook 使用 brew 安装 MySQL

目录 &#xff08;1&#xff09;准备工作1.1 更新 brew &#xff08;2&#xff09;正式安装2.1 安装MySQL&#xff1a;2.2 启动mysql &#xff08;3&#xff09;初始化数据库3.1 选择验证密码组件3.2 密码强度3.3 删除匿名用户3.4 禁用root用户远程连接3.5 删除test数据库3.6 重…

C语言 17 宏定义

前面认识了#include指令&#xff0c;接着来看#define指令&#xff0c;它可以实现宏定义。宏是啥意思&#xff1f; 把参数批量替换到文本中&#xff0c;这种实现通常称为宏&#xff08;macro&#xff09;或定义宏 (define macro) 可以通过#define来定义宏&#xff0c;规则如下&a…

Cyber Weekly #26

赛博新闻 1、Meta发布最强AR眼镜 Meta Connect 2024大会展示了多款新产品和技术&#xff0c;包括更便宜的Quest 3S系列AR眼镜、新功能丰富的Meta Rayban眼镜、OrionAR眼镜原型机&#xff0c;以及月活5亿用户的Meta AI。其中&#xff0c;OrionAR眼镜以其先进的交互体验和强大的…

鸿蒙开发(NEXT/API 12)【硬件(传感器开发3)】传感器服务

场景介绍 当设备需要获取传感器数据时&#xff0c;可以使用sensor模块&#xff0c;例如&#xff1a;通过订阅方向传感器数据感知用户设备当前的朝向&#xff0c;通过订阅计步传感器数据统计用户的步数等。 函数说明 名称描述OH_Sensor_GetInfos(Sensor_Info **infos, uint32…

算力运力解决方案:构建未来智算新生态

中国联通国际有限公司产品之算力运力解决方案&#xff1a;构建未来智算新生态 在当今这个数据爆炸、技术日新月异的时代&#xff0c;算力已成为推动社会进步和产业升级的关键力量。中国联通国际有限公司紧跟时代步伐&#xff0c;依托其强大的网络资源和深厚的技术积累&#xf…

Linux进程间的通信(四)System-V共享内存

什么是共享内存 共享内存&#xff0c;顾名思义就是允许两个不相关的进程访问同一个逻辑内存&#xff0c;共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。 不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空…

深度学习后门攻击分析与实现(二)

前言 在本系列的第一部分中&#xff0c;我们已经掌握了深度学习中的后门攻击的特点以及基础的攻击方式&#xff0c;现在我们在第二部分中首先来学习深度学习后门攻击在传统网络空间安全中的应用。然后再来分析与实现一些颇具特点的深度学习后门攻击方式。 深度学习与网络空间…

Node.js安装Express,Node.js支持Typescript以及Express支持Typescript的步骤

1. Node.js 安装Express 运行如下命令&#xff1a; $ mkdir express-demo $ cd express-demo$ npm install express $ npm install body-parser //(可选)中间件&#xff0c;用于处理 JSON, Raw, Text 和 URL 编码的数据 $ npm install cookie-parser //(可选)通过req.cookies…

怎么用gitee做一个图片仓库,在md文档中用这个图片网络地址,然后显示图片

痛因&#xff1a;我为什么要这样做&#xff0c;呃&#xff0c;我一开始图片都是存本地地址的&#xff0c;放在和这个md文档同级的assets文件夹下面&#xff0c;这样子确实当时很方便&#xff0c;复制粘贴什么也不用管&#xff0c;但是想把这个文档分享给别的人的时候&#xff0…

Windows打开HDF5图像:HDFView软件的下载、安装

本文介绍在Windows电脑中&#xff0c;下载、安装用以查看HDF5图像数据的软件HDFView的方法。 HDF5&#xff08;Hierarchical Data Format 5&#xff09;是一种用于存储和管理大量科学数据的文件格式&#xff0c;其由HDF Group开发和维护&#xff0c;广泛应用于科学计算、工程、…

ESP8266wifi模块的使用

文章目录 概要整体架构流程技术名词解释具体配置过程小结 概要 本文旨在介绍ESP8266,做为客户端 电脑做为服务端&#xff0c;通过TCP/IP协议在同一个局域网下通过WiFi进行数据交互 设备选用 esp8266 软件选择 安信可串口调试助手 网络调试助手 &#xff08;若没有软件可私…

OpenCV threhold()函数

OpenCV threhold()函数的主要用途是将灰度图转换为二值图像,实现灰度图的二值化&#xff0c;在机器视觉中使用频度较高&#xff0c;如尺寸量测&#xff0c;物体识别等。其原型如下&#xff1a; 函数参数&#xff1a; src 输入数组&#xff08;多通道、8 位或 32 位浮点&#xf…

SpringBoot3脚手架

MySpringBootAPI SpringBoot3脚手架&#xff0c;基于SpringBoot3DruidPgSQLMyBatisPlus13FastJSON2Lombok&#xff0c;启动web容器为Undertow(非默认tomcat)&#xff0c;其他的请自行添加和配置。 <java.version>17</java.version> <springboot.version>3.3…