GB28181中SSRC的使用和语音广播流程浅析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GB28181中SSRC的使用和语音广播流程浅析相关的知识,希望对你有一定的参考价值。

今天主要聊聊GB28181中,SSRC的作用,从我们之前跟第三方厂商的对接来看,好多厂商对SSRC的处理,并不符合规范。

举个典型的操作:语音广播时带的SSRC和发送RTP包时的SSRC并不一致,然后厂商一开始给出来的结论是,不一致也不影响使用,实则按照规范来看,SSRC还是至关重要的,想想看,如果SSRC不重要的话,SDP携带的SSRC的意义在哪里?如果接入端,不对SSRC做判断,假设有多台设备向android端GB28181设备接入设备(如执法记录仪、智能头盔等)发送语音广播RTP包,如何过滤哪个设备发过来的数据?再比如,第三方恶意冲击系统,给监听的端口乱发RTP包,如何规避?

咱们先来仔细看看GB/T28181-2016规范里面,是怎么描述SSRC的使用的:

SSRC值由媒体流发送设备所在的SIP监控域产生,作为媒体流的标识使用。点播域内设备、点播外域设备媒体流SSRC的处理方式分别说明如下:

a) 点播域内设备媒体流SSRC处理方式

点播域内设备媒体流时,SSRC值由本域监控系统产生并通过Invite请求发送给设备使用,设备在回复的200 OK消息中携带此值,设备在发送的媒体流中使用此值作为RTP的SSRC值。流程图见图 F.1。

GB28181中SSRC的使用和语音广播流程浅析_GB28181语音对讲

b) 点播外域设备媒体流SSRC处理方式

点播外域设备媒体流时,SSRC由被点播域产生并在被点播域回复的200 OK SDP消息体中携带,被点播域发送的RTP码流使用该值作为SSRC值。流程图见图 F.2。

GB28181中SSRC的使用和语音广播流程浅析_gb28181接入_02

注5:错误响应补充说明

当设备收到无法满足的SDP时,向发送的Invite请求方发送488错误响应消息;当设备不能满足更多的呼叫请求时,向发送的Invite请求方发送486错误响应消息。

以下就以Android平台GB28181设备接入模块,语音广播这块为例:

当收到GB28181平台端的语音广播请求后,客户端做出响应,并在ntsOnNotifyBroadcastCommand()回调做出相应的处理,调用respondBroadcastCommand()回复平台端,并使能GB28181语音按钮。

/*
* MainActivity.java
* GitHub: https://github.com/daniulive/SmarterStreaming
*/
@Override
public void ntsOnNotifyBroadcastCommand(String fromUserName, String fromUserNameAtDomain, String sn, String sourceID, String targetID)
handler_.postDelayed(new Runnable()
@Override
public void run()
Log.i(TAG, "ntsOnNotifyBroadcastCommand, fromUserName:"+ from_user_name_ + ", fromUserNameAtDomain:"+ from_user_name_at_domain_
+ ", SN:" + sn_ + ", sourceID:" + source_id_ + ", targetID:" + target_id_);

if (gb28181_agent_ != null )
gb28181_agent_.respondBroadcastCommand(from_user_name_, from_user_name_at_domain_,sn_,source_id_, target_id_, true);
btnGB28181AudioBroadcast.setText("收到GB28181语音广播通知");



private String from_user_name_;
private String from_user_name_at_domain_;
private String sn_;
private String source_id_;
private String target_id_;

public Runnable set(String from_user_name, String from_user_name_at_domain, String sn, String source_id, String target_id)
this.from_user_name_ = from_user_name;
this.from_user_name_at_domain_ = from_user_name_at_domain;
this.sn_ = sn;
this.source_id_ = source_id;
this.target_id_ = target_id;
return this;


.set(fromUserName, fromUserNameAtDomain, sn, sourceID, targetID),0);

然后,在ntsOnAudioBroadcast()回调处理语音广播,创建RTP链路接收数据。

@Override
public void ntsOnAudioBroadcast(String commandFromUserName, String commandFromUserNameAtDomain, String sourceID, String targetID)
handler_.postDelayed(new Runnable()
@Override
public void run()
Log.i(TAG, "ntsOnAudioBroadcastPlay, fromFromUserName:" + command_from_user_name_
+ " FromUserNameAtDomain:" + command_from_user_name_at_domain_
+ " sourceID:" + source_id_ + ", targetID:" + target_id_);

stopAudioPlayer();
destoryRTPReceiver();

if (gb28181_agent_ != null )
String local_ip_addr = IPAddrUtils.getIpAddress(context_);

boolean is_tcp = true; // 考虑到跨网段, 默认用TCP传输rtp包
rtp_receiver_handle_ = lib_player_.CreateRTPReceiver(0);
if (rtp_receiver_handle_ != 0 )
lib_player_.SetRTPReceiverTransportProtocol(rtp_receiver_handle_, is_tcp?1:0);
lib_player_.SetRTPReceiverIPAddressType(rtp_receiver_handle_, 0);

if (0 == lib_player_.CreateRTPReceiverSession(rtp_receiver_handle_, 0) )
int local_port = lib_player_.GetRTPReceiverLocalPort(rtp_receiver_handle_);
boolean ret = gb28181_agent_.inviteAudioBroadcast(command_from_user_name_,command_from_user_name_at_domain_,
source_id_, target_id_, "IP4", local_ip_addr, local_port, is_tcp?"TCP/RTP/AVP":"RTP/AVP");

if (!ret )
destoryRTPReceiver();
btnGB28181AudioBroadcast.setText("GB28181语音广播");

else
btnGB28181AudioBroadcast.setText("GB28181语音广播呼叫中");

else
destoryRTPReceiver();
btnGB28181AudioBroadcast.setText("GB28181语音广播");





private String command_from_user_name_;
private String command_from_user_name_at_domain_;
private String source_id_;
private String target_id_;

public Runnable set(String command_from_user_name, String command_from_user_name_at_domain, String source_id, String target_id)
this.command_from_user_name_ = command_from_user_name;
this.command_from_user_name_at_domain_ = command_from_user_name_at_domain;
this.source_id_ = source_id;
this.target_id_ = target_id;
return this;


.set(commandFromUserName, commandFromUserNameAtDomain, sourceID, targetID),0);

如有异常或timeout,处理相关回调:

@Override
public void ntsOnInviteAudioBroadcastException(String sourceID, String targetID, String errorInfo)
handler_.postDelayed(new Runnable()
@Override
public void run()
Log.i(TAG, "ntsOnInviteAudioBroadcastException, sourceID:" + source_id_ + ", targetID:" + target_id_);

destoryRTPReceiver();
btnGB28181AudioBroadcast.setText("GB28181语音广播");


private String source_id_;
private String target_id_;

public Runnable set(String source_id, String target_id)
this.source_id_ = source_id;
this.target_id_ = target_id;
return this;


.set(sourceID, targetID),0);


@Override
public void ntsOnInviteAudioBroadcastTimeout(String sourceID, String targetID)
handler_.postDelayed(new Runnable()
@Override
public void run()
Log.i(TAG, "ntsOnInviteAudioBroadcastTimeout, sourceID:" + source_id_ + ", targetID:" + target_id_);

destoryRTPReceiver();
btnGB28181AudioBroadcast.setText("GB28181语音广播");


private String source_id_;
private String target_id_;

public Runnable set(String source_id, String target_id)
this.source_id_ = source_id;
this.target_id_ = target_id;
return this;


.set(sourceID, targetID),0);

ntsOnInviteAudioBroadcastResponse()回调处理如下:

@Override
public void ntsOnInviteAudioBroadcastResponse(String sourceID, String targetID, int statusCode, PlaySessionDescription sessionDescription)
handler_.postDelayed(new Runnable()
@Override
public void run()
Log.i(TAG, "ntsOnInviteAudioBroadcastResponse, statusCode:" + status_code_ +" sourceID:" + source_id_ + ", targetID:" + target_id_);

boolean is_need_destory_rtp = true;

if (gb28181_agent_ != null )
boolean is_need_bye = 200==status_code_;

if (200 == status_code_ && session_description_ != null && rtp_receiver_handle_ != 0 )
MediaSessionDescription audio_des = session_description_.getAudioDescription();

SDPRtpMapAttribute audio_attr = null;
if (audio_des != null && audio_des.getRtpMapAttributes() != null && !audio_des.getRtpMapAttributes().isEmpty() )
audio_attr = audio_des.getRtpMapAttributes().get(0);

if ( audio_des != null && audio_attr != null )
lib_player_.SetRTPReceiverSSRC(rtp_receiver_handle_, audio_des.getSSRC());

lib_player_.SetRTPReceiverPayloadType(rtp_receiver_handle_, audio_attr.getPayloadType(),
audio_attr.getEncodingName(), 2, audio_attr.getClockRate());

// 如果是PCMA, SDK会默认填 采样率8000, 通道1, 其他音频编码需要手动填入
// lib_player_.SetRTPReceiverAudiosamplingRate(rtp_receiver_handle_, 8000);
// lib_player_.SetRTPReceiverAudioChannels(rtp_receiver_handle_, 1);

lib_player_.SetRTPReceiverRemoteAddress(rtp_receiver_handle_, audio_des.getAddress(), audio_des.getPort());
lib_player_.InitRTPReceiver(rtp_receiver_handle_);

if (startAudioPlay())
is_need_bye = false;
is_need_destory_rtp = false;

gb_broadcast_source_id_ = source_id_;
gb_broadcast_target_id_ = target_id_;
btnGB28181AudioBroadcast.setText("终止GB28181语音广播");
btnGB28181AudioBroadcast.setEnabled(true);



else
btnGB28181AudioBroadcast.setText("GB28181语音广播");


if (is_need_bye)
gb28181_agent_.byeAudioBroadcast(source_id_, target_id_);


if (is_need_destory_rtp)
destoryRTPReceiver();


private String source_id_;
private String target_id_;
private int status_code_;
private PlaySessionDescription session_description_;

public Runnable set(String source_id, String target_id, int status_code, PlaySessionDescription session_description)
this.source_id_ = source_id;
this.target_id_ = target_id;
this.status_code_ = status_code;
this.session_description_ = session_description;
return this;


.set(sourceID, targetID, statusCode, sessionDescription),0);

ntsOnByeAudioBroadcast()回调处理如下:

@Override
public void ntsOnByeAudioBroadcast(String sourceID, String targetID)
handler_.postDelayed(new Runnable()
@Override
public void run()
Log.i(TAG, "ntsOnByeAudioBroadcast sourceID:" + source_id_ + " targetID:" + target_id_);

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

stopAudioPlayer();
destoryRTPReceiver();


private String source_id_;
private String target_id_;

public Runnable set(String source_id, String target_id)
this.source_id_ = source_id;
this.target_id_ = target_id;
return this;


.set(sourceID, targetID),0);

ntsOnTerminateAudioBroadcast()回调处理如下:

@Override
public void ntsOnTerminateAudioBroadcast(String sourceID, String targetID)
handler_.postDelayed(new Runnable()
@Override
public void run()
Log.i(TAG, "ntsOnTerminateAudioBroadcast sourceID:" + source_id_ + " targetID:" + target_id_);

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

stopAudioPlayer();
destoryRTPReceiver();


private String source_id_;
private String target_id_;

public Runnable set(String source_id, String target_id)
this.source_id_ = source_id;
this.target_id_ = target_id;
return this;


.set(sourceID, targetID),0);

以上是GB28181关于SSRC和语音广播的一点经验,感兴趣的开发者可酌情参考。

以上是关于GB28181中SSRC的使用和语音广播流程浅析的主要内容,如果未能解决你的问题,请参考以下文章

GB28181语音对讲

Android平台GB28181设备接入端对接编码前后音视频源类型浅析

GB28181 媒体服务器流中的识别组包策略

GB28181 媒体服务器流中的识别组包策略

GB28181 媒体服务器流中的识别组包策略

GB28181系统设计(脚本化流程高级技巧)