? ? ??最近对安卓现有代码进行了扩展,支持了GB28181, GB28181协议实现分两块,一块是信令部分,一块是流媒体数据传输。代码分两部分:
? ? ?信令接口:
public interface GBSIPAgent {
void addListener(GBSIPAgentListener listener);
/*
* 设置SIP本地链接地址
* @param address 本地IP地址, 如192.168.0.111
* @param port本地SIP端口, 如 15070
*/
void setLocalAddressInfo(String address, int port);
/*
* 设置SIP Server配置参数
* @param address SIP服务器地址, 如 192.168.0.101
* @param port SIP服务器端口, 如 15060
* @param id SIP服务器ID, 如 34020000002000000001
* @param domain SIP服务器域, 如 3402000000
*/
void setServerParameter(String address, int port, String id, String domain);
/*
* 设置GB28181 SIP User配置参数
* @param userName SIP用户名, 如 34020000001110000045
* @param password 密码, 如 123456
*/
void setUserInfo(String userName, String password);
/*
* 设置SIP请求头中的UserAgent
* @param userAgent用户代理
*/
void setUserAgent(String userAgent);
/*
* 设置SIP传输协议
* @param transport_protocol, 设置SIP信令传输协议: UDP, TCP, 默认是UDP
*/
void setTransportProtocol(String transport_protocol);
/*
* 设置GB28181配置参数
* @param regExpired 注册有效期, 单位: 秒, 如 3600
* @param heartBeatInterval 心跳间隔, 单位: 秒, 默认60
* @param heartBeatCount 心跳超时次数, 默认3次
*/
void config(int regExpired, int heartBeatInterval, int heartBeatCount);
void clearDevices();
boolean addDevice(Device device);
boolean initialize();
/*
*启动
*/
boolean start();
boolean isRunning();
/*
*响应Invite play 200OK
*/
boolean respondPlayInviteOK(String deviceId, String localAddress, int localPort);
/*
*响应Invite play其他状态码
*/
boolean respondPlayInvite(int statusCode, String deviceId);
/*
*终止会话
*/
void terminatePlay(String deviceId, boolean isSendBYE);
/*
*终止所有会话
*/
void terminateAllPlays(boolean isSendBYE);
/*
*停止
*/
void stop();
void unInitialize();
}
? ? ?信令Listener:
public interface GBSIPAgentListener
{
/*注册成功
* @param dateString: 服务器日期,用来矫正设备端时间,用户自行决定是否矫正设备时间
*/
void ntsRegisterOK(String dateString);
/*
*注册超时
*/
void ntsRegisterTimeout();
/*
*注册网络传输曾异常
*/
void ntsRegisterTransportError(String errorInfo);
/*
*心跳达到异常次数
*/
void ntsOnHeartBeatException(int exceptionCount, String lastExceptionInfo);
/*
*收到s=Play的实时视音频点播
*/
void ntsOnInvitePlay(String deviceId, InvitePlaySessionDescription 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);
/*
* Play会话对应的对话终止, 一般不会出发这个回调,目前只有在响应了200K, 但在64*T1时间后还没收到ACK,才可能会出发
收到这个, 请做相关清理处理
*/
void ntsOnPlayDialogTerminated(String deviceId);
}
? ? 流媒体传输接口(只是和GB2818相关的部分接口, 其他接口未展示):
public class SmartPublisherJniV2 {
/**
* Open publisher(启动推送实例)
*
* @param ctx: get by this.getApplicationContext()
*
* @param audio_opt:
* if 0: 不推送音频
* if 1: 推送编码前音频(PCM)
* if 2: 推送编码后音频(aac/pcma/pcmu/speex).
*
* @param video_opt:
* if 0: 不推送视频
* if 1: 推送编码前视频(YUV420SP/YUV420P/RGBA/ARGB)
* if 2: 推送编码后视频(H.264)
*
* @param width: capture width; height: capture height.
*
* <pre>This function must be called firstly.</pre>
*
* @return the handle of publisher instance
*/
public native long SmartPublisherOpen(Object ctx, int audio_opt, int video_opt, int width, int height);
/*+++++++++++++++RTP Sender相关接口+++++++++++++++*/
/*
* 创建RTP Sender实例
*
* @param reserve:保留参数传0
*
* @return RTP Sender 句柄,0表示失败
*/
public native long CreateRTPSender(int reserve);
/**
*设置 RTP Sender传输协议
*
* @param rtp_sender_handle, CreateRTPSender返回值
* @param transport_protocol, 0:UDP, 1:TCP, 默认是UDP
*
* @return {0} if successful
*/
public native int SetRTPSenderTransportProtocol(long rtp_sender_handle, int transport_protocol);
/**
*设置 RTP Sender IP地址类型
*
* @param rtp_sender_handle, CreateRTPSender返回值
* @param ip_address_type, 0:IPV4, 1:IPV6, 默认是IPV4, 当前仅支持IPV4
*
* @return {0} if successful
*/
public native int SetRTPSenderIPAddressType(long rtp_sender_handle, int ip_address_type);
/**
*设置 RTP Sender RTP Socket本地端口
*
* @param rtp_sender_handle, CreateRTPSender返回值
* @param port, 必须是偶数,设置0的话SDK会自动分配, 默认值是0
*
* @return {0} if successful
*/
public native int SetRTPSenderLocalPort(long rtp_sender_handle, int port);
/**
*设置 RTP Sender SSRC
*
* @param rtp_sender_handle, CreateRTPSender返回值
* @param ssrc, 如果设置的话,这个字符串要能转换成uint32类型, 否则设置失败
*
* @return {0} if successful
*/
public native int SetRTPSenderSSRC(long rtp_sender_handle, String ssrc);
/**
*设置 RTP Sender RTP socket 发送Buffer大小
*
* @param rtp_sender_handle, CreateRTPSender返回值
* @param buffer_size, 必须大于0, 默认是512*1024, 当前仅对UDP socket有效, 根据视频码率考虑设置合适的值
*
* @return {0} if successful
*/
public native int SetRTPSenderSocketSendBuffer(long rtp_sender_handle, int buffer_size);
/**
*设置 RTP Sender RTP时间戳时钟频率
*
* @param rtp_sender_handle, CreateRTPSender返回值
* @param clock_rate, 必须大于0, 对于GB28181 PS规定是90kHz, 也就是90000
*
* @return {0} if successful
*/
public native int SetRTPSenderClockRate(long rtp_sender_handle, int clock_rate);
/**
*设置 RTP Sender 目的IP地址, 注意当前用在GB2818推送上,只设置一个地址,将来扩展如果用在其他地方,可能要设置多个目的地址,到时候接口可能会调整
*
* @param rtp_sender_handle, CreateRTPSender返回值
* @param address, IP地址
* @param port, 端口
*
* @return {0} if successful
*/
public native int SetRTPSenderDestination(long rtp_sender_handle, String address, int port);
/**
*初始化RTP Sender, 初始化之前先调用上面的接口配置相关参数
*
* @param rtp_sender_handle, CreateRTPSender返回值
*
* @return {0} if successful
*/
public native int InitRTPSender(long rtp_sender_handle);
/**
*获取RTP Sender RTP Socket本地端口
*
* @param rtp_sender_handle, CreateRTPSender返回值
*
* @return 失败返回0, 成功的话返回响应的端口, 请在InitRTPSender返回成功之后调用
*/
public native int GetRTPSenderLocalPort(long rtp_sender_handle);
/**
* UnInit RTP Sender
*
* @param rtp_sender_handle, CreateRTPSender返回值
*
* @return {0} if successful
*/
public native int UnInitRTPSender(long rtp_sender_handle);
/**
* 释放RTP Sender, 释放之后rtp_sender_handle就无效了,请不要再使用
*
* @param rtp_sender_handle, CreateRTPSender返回值
*
* @return {0} if successful
*/
public native int DestoryRTPSender(long rtp_sender_handle);
/*+++++++++++++++RTP Sender相关接口+++++++++++++++*/
/*+++++++++++++++GB28181相关接口+++++++++++++++*/
/**
* 设置GB28181 RTP Sender
*
* @param rtp_sender_handle, CreateRTPSender返回值
* @param rtp_payload_type, 对于GB28181 PS, 协议定义是96, 具体以SDP为准
*
* @return {0} if successful
*/
public native int SetGB28181RTPSender(long handle, long rtp_sender_handle, int rtp_payload_type);
/**
* 启动 GB28181 媒体流
*
* @return {0} if successful
*/
public native int StartGB28181MediaStream(long handle);
/**
* 停止 GB28181 媒体流
*
* @return {0} if successful
*/
public native int StopGB28181MediaStream(long handle);
/*---------------GB28181相关接口---------------*/
/**
* 关闭推送实例,结束时必须调用close接口释放资源
*
* @return {0} if successful
*/
public native int SmartPublisherClose(long handle);
}
? ? 下面是Demo相关调用代码:
public class GB28181Demo extends Activity, implements Callback, PreviewCallback, GBSIPAgentListener
{
/*** GB28181 相关参数,可以修改相关参数后测试 ***/
GBSIPAgent gb28181_agent_ = null;
private int gb28181_sip_local_port_ = 12070;
private String gb28181_sip_server_id_ = "34020000002000000001";
private String gb28181_sip_server_domain_ = "3402000000";
private String gb28181_sip_server_addr_ = "192.168.0.104";
private int gb28181_sip_server_port_ = 15060;
private String gb28181_sip_user_agent_filed_ = "NT GB2818 User Agent V1.0";
private String gb28181_sip_username_ = "31011500991320000069";
private String gb28181_sip_password_ = "12345678";
private int gb28181_reg_expired_ = 3600; // 注册有效期时间最小3600秒
private int gb28181_heartbeat_interval_ = 20; // 心跳间隔GB28181默认是60, 目前调整到20秒
private int gb28181_heartbeat_count_ = 3; // 心跳间隔3次失败,表示和服务器断开了
private int gb28181_sip_trans_protocol_ = 0; // 0表示信令用UDP传输, 1表示信令用TCP传输
private long gb28181_rtp_sender_handle_ = 0;
private int gb28181_rtp_payload_type_ = 96;
/*** GB28181 相关参数,可以修改相关参数后测试 ***/
@Override
public void onCreate(Bundle savedInstanceState) {
/*其他初始化代码**/
libPublisher = new SmartPublisherJniV2();
if ( initGB28181Agent() ) {
gb28181_agent_.start();
}
}
private boolean initGB28181Agent()
{
if ( gb28181_agent_ != null )
return true;
String local_ip_addr = IPAddrUtils.getIpAddress(myContext);
Log.i(TAG, "initGB28181Agent local ip addr: " + local_ip_addr);
if ( local_ip_addr == null || local_ip_addr.isEmpty() ) {
Log.e(TAG, "initGB28181Agent local ip is empty");
return false;
}
gb28181_agent_ = GBSIPAgentFactory.getInstance().create();
if ( gb28181_agent_ == null ) {
Log.e(TAG, "initGB28181Agent create agent failed");
return false;
}
gb28181_agent_.addListener(this);
// 必填信息
gb28181_agent_.setLocalAddressInfo(local_ip_addr, gb28181_sip_local_port_);
gb28181_agent_.setServerParameter(gb28181_sip_server_addr_, gb28181_sip_server_port_, gb28181_sip_server_id_, gb28181_sip_server_domain_);
gb28181_agent_.setUserInfo(gb28181_sip_username_, gb28181_sip_password_);
// 可选参数
gb28181_agent_.setUserAgent(gb28181_sip_user_agent_filed_);
gb28181_agent_.setTransportProtocol(gb28181_sip_trans_protocol_==0?"UDP":"TCP");
// GB28181配置
gb28181_agent_.config(gb28181_reg_expired_, gb28181_heartbeat_interval_, gb28181_heartbeat_count_);
com.gb28181.ntsignalling.Device gb_device = new com.gb28181.ntsignalling.Device("34020000001380000001", "安卓测试设备", Build.MANUFACTURER, Build.MODEL,
"宇宙","火星1","火星", true);
getLocation(this);
gb_device.setLongitude(mLongitude);
gb_device.setLatitude(mLatitude);
gb28181_agent_.addDevice(gb_device);
if (!gb28181_agent_.initialize()) {
gb28181_agent_ = null;
Log.e(TAG, "initGB28181Agent gb28181_agent_.initialize failed.");
return false;
}
return true;
}
@Override
public void ntsRegisterOK(String dateString) {
Log.i(TAG, "ntsRegisterOK Date: " + (dateString!= null? dateString : ""));
}
@Override
public void ntsRegisterTimeout() {
Log.e(TAG, "ntsRegisterTimeout");
}
@Override
public void ntsRegisterTransportError(String errorInfo) {
Log.e(TAG, "ntsRegisterTransportError error:" + (errorInfo != null?errorInfo :""));
}
@Override
public void ntsOnHeartBeatException(int exceptionCount, String lastExceptionInfo) {
Log.e(TAG, "ntsOnHeartBeatException heart beat timeout count reached, count:" + exceptionCount+
", exception info:" + (lastExceptionInfo!=null?lastExceptionInfo:""));
// 10毫秒后,停止信令, 然后重启
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "gb28281_heart_beart_timeout");
stopGB28181Stream();
destoryRTPSender();
if (gb28181_agent_ != null) {
Log.i(TAG, "gb28281_heart_beart_timeout sip stop");
gb28181_agent_.stop();
Log.i(TAG, "gb28281_heart_beart_timeout sip start");
gb28181_agent_.start();
}
}
},10);
}
@Override
public void ntsOnInvitePlay(String deviceId, InvitePlaySessionDescription session_des) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG,"ntsInviteReceived, device_id:" +device_id_+", is_tcp:" + session_des_.isRTPOverTCP()
+ " rtp_port:" + session_des_.getMediaPort() + " ssrc:" + session_des_.getSSRC()
+ " address_type:" + session_des_.getAddressType() + " address:" + session_des_.getAddress());
// 可以先给信令服务器发送临时振铃响应
//sip_stack_android.respondPlayInvite(180, device_id_);
long rtp_sender_handle = libPublisher.CreateRTPSender(0);
if ( rtp_sender_handle == 0 ) {
gb28181_agent_.respondPlayInvite(488, device_id_);
Log.i(TAG, "ntsInviteReceived CreateRTPSender failed, response 488, device_id:" + device_id_);
return;
}
gb28181_rtp_payload_type_ = session_des_.getPSRtpMapAttribute().getPayloadType();
libPublisher.SetRTPSenderTransportProtocol(rtp_sender_handle, session_des_.isRTPOverUDP()?0:1);
libPublisher.SetRTPSenderIPAddressType(rtp_sender_handle, session_des_.isIPv4()?0:1);
libPublisher.SetRTPSenderLocalPort(rtp_sender_handle, 0);
libPublisher.SetRTPSenderSSRC(rtp_sender_handle, session_des_.getSSRC());
libPublisher.SetRTPSenderSocketSendBuffer(rtp_sender_handle, 1024*1024);
libPublisher.SetRTPSenderClockRate(rtp_sender_handle, session_des_.getPSRtpMapAttribute().getClockRate());
libPublisher.SetRTPSenderDestination(rtp_sender_handle, session_des_.getAddress(), session_des_.getMediaPort());
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(myContext);
gb28181_agent_.respondPlayInviteOK(device_id_,local_ip_addr, local_port);
gb28181_rtp_sender_handle_ = rtp_sender_handle;
}
private String device_id_;
private InvitePlaySessionDescription session_des_;
public Runnable set(String device_id, InvitePlaySessionDescription 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);
}
@Override
public void ntsOnAckPlay(String deviceId) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG,"ntsOnACKPlay, device_id:" +device_id_);
InitAndSetConfig();
libPublisher.SetGB28181RTPSender(publisherHandle, gb28181_rtp_sender_handle_, gb28181_rtp_payload_type_);
int startRet = libPublisher.StartGB28181MediaStream(publisherHandle);
if (startRet != 0) {
if (publisherHandle != 0) {
libPublisher.SmartPublisherClose(publisherHandle);
publisherHandle = 0;
}
destoryRTPSender();
return;
}
isGB28181StreamRunning = true;
}
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);
}
@Override
public void ntsOnByePlay(String deviceId)
{
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "ntsOnByePlay, stop GB28181 media stream, deviceId=" + device_id_);
stopGB28181Stream();
destoryRTPSender();
}
private String device_id_;
public Runnable set(String device_id) {
this.device_id_ = device_id;
return this;
}
}.set(deviceId),0);
}
@Override
public void ntsOnPlayDialogTerminated(String deviceId) {
/*
Play会话对应的对话终止, 一般不会出发这个回调,目前只有在响应了200K, 但在64*T1时间后还没收到ACK,才可能会出发
收到这个请做相关清理处理
*/
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "ntsOnPlayDialogTerminated, deviceId=" + device_id_);
stopGB28181Stream();
destoryRTPSender();
}
private String device_id_;
public Runnable set(String device_id) {
this.device_id_ = device_id;
return this;
}
}.set(deviceId),0);
}
//停止GB28181 媒体流
private void stopGB28181Stream() {
if(!isGB28181StreamRunning)
return;
if (libPublisher != null) {
libPublisher.StopGB28181MediaStream(publisherHandle);
}
if (publisherHandle != 0) {
if (libPublisher != null) {
libPublisher.SmartPublisherClose(publisherHandle);
publisherHandle = 0;
}
}
isGB28181StreamRunning = false;
}
private void destoryRTPSender() {
if (gb28181_rtp_sender_handle_ != 0) {
libPublisher.DestoryRTPSender(gb28181_rtp_sender_handle_);
gb28181_rtp_sender_handle_ = 0;
}
}
@Override
protected void onDestroy() {
Log.i(TAG, "activity destory!");
if (gb28181_agent_ != null ) {
if (gb28181_agent_.isRunning()) {
gb28181_agent_.terminateAllPlays(false);
gb28181_agent_.stop();
}
gb28181_agent_ = null;
}
stopGB28181Stream();
destoryRTPSender();
if (publisherHandle != 0) {
if (libPublisher != null) {
libPublisher.SmartPublisherClose(publisherHandle);
publisherHandle = 0;
}
}
super.onDestroy();
}
}
? ? ?音视频采集编码相关功能已经稳定运行多年了,GB28181接入用C++实现H264, H265打包成PS流,然后拆成RTP包发送即可,RTP传输同时支持UDP和TCP(这个根据Invite sdp来确定)。配合28181服务器测试,延时非常低,达到了预期的效果.?
? ?当前实现接口也支持多个设备(或者说是多个通道), 可以很方便的实现多个现有rtsp摄像头到GB28181的转换,另外考虑到安卓移动端的特性,也增加了对设备经纬度位置信息的上报.
? ?更多问题可以联系qq:?1130758427,? ?github
|