GB28181-2022相对2016版"基于TCP协议的视音频媒体传输要求"调整
2023-03-07 09:15:15 时间
规范解读
GB28181-2022针对“基于TCP协议的视音频媒体传输”实时点播、历史视频回放与下载中,TCP媒体传输重连机制,做了说明。
修改后的“基于TCP协议的视音频媒体传输要求”如下:
实时视频点播、历史视频回放与下载的TCP媒体传输应支持基于RTP封装的视音频PS流,封装格式参照IETF RFC 4571。
流媒体服务器宜同时支持作为TCP媒体流传输服务端和客户端。在默认情况下,前端设备向流媒体服务器发送媒体流时,前端设备应作为TCP媒体流传输客户端,流媒体服务器作为TCP媒体流传输服务端;同级或跨级流媒体服务器间基于TCP协议传输视频流时,媒体流的接收方宜作为TCP媒体流传输服务端。
媒体流的发送方和接收方可扩展SDP参数进行TCP媒体流传输服务端和客户端的协商,协商机制应符合附录G及IETF RFC 4571的定义。
实时视频点播、历史视频回放与下载的TCP媒体传输在建立TCP连接时应支持重连机制。首次TCP连接失败,TCP媒体流传输客户端应间隔一段时间进行重连,重连间隔应不小于l s,重连次数应不小于3次。
代码实现
本文以大牛直播SDK实现的Andorid平台GB28181设备接入模块为例,收到Invite处理如下,其中SetRTPSenderTransportProtocol()设置TCP/UDP传输模式:
ntsOnInvitePlay()处理代码如下:
// Author: daniusdk.com
@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);
}
收到Ack后:
// Author: daniusdk.com
@Override
public void ntsOnAckPlay(String deviceId) {
handler_.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG,"ntsOnACKPlay, device_id:" +device_id_);
if (!isRTSPPublisherRunning && !isPushingRtmp && !isRecording) {
InitAndSetConfig();
}
libPublisher.SetGB28181RTPSender(publisherHandle, gb28181_rtp_sender_handle_, gb28181_rtp_payload_type_, gb28181_rtp_encoding_name_);
int startRet = libPublisher.StartGB28181MediaStream(publisherHandle);
if (startRet != 0) {
if (!isRTSPPublisherRunning && !isPushingRtmp && !isRecording) {
if (publisherHandle != 0) {
libPublisher.SmartPublisherClose(publisherHandle);
publisherHandle = 0;
}
}
destoryRTPSender();
Log.e(TAG, "Failed to start GB28181 service..");
return;
}
if (!isRTSPPublisherRunning && !isPushingRtmp && !isRecording) {
CheckInitAudioRecorder();
}
startLayerPostThread();
isGB28181StreamRunning = true;
}
private String device_id_;
public Runnable set(String device_id) {
this.device_id_ = device_id;
return this;
}
}.set(deviceId),0);
}
总结
TCP媒体传输重连机制,非常必要,实际上在2022出来之前,我们也已经做了很好的重连处理,GB28181-2022对此专门做了详细的解释说明,具体实现难度不大,感兴趣的开发者可以酌情参考。
相关文章
- 2022-12-23:portainer是docker的web可视化工具。如果根据docker部署去写yaml,默认local是
- 2022-12-24:给定一个字符串s,其中都是英文小写字母,如果s中的子串含有的每种字符都是偶数个,那么这样的子串就是达标子串
- 2022-12-25:etcd可以完全替代zookeeper,原因是k8s用的etcd,不用担心不成熟。请问etcd部署在k3s
- 2022-12-26:有一个数组包含0、1、2三种值,有m次修改机会,第一种将所有连通的1变为0,修改次数-1,第二种将所有连通
- 2022-12-27:etcd是无界面的,不好看,joinsunsoft/etcdv3-browser是etcd的web可视化工
- 2022-12-28:有n个黑白棋子,它们的一面是黑色,一面是白色,它们被排成一行,位置0~n-1上。一开始所有的棋子都是黑色向
- 2022-12-29:nsq是go语言写的消息队列。请问k3s部署nsq,yaml如何写?
- 2022-12-30:某天小美进入了一个迷宫探险,根据地图所示,这个迷宫里有无数个房间序号分别为1、2、3、...入口房间的序号
- 2022-12-31:以下go语言代码输出什么?A:1 1;B:-1 1;C:-1 -1;D:编译错误。 package mai
- 2023-01-01:remix-ide是浏览器的ide,官方已经提供地址,但是需要连接外网。如果是内网,需要自己在服务器里搭建
- 2023-01-02:某天,小美在玩一款游戏,游戏开始时,有n台机器,每台机器都有一个能量水平,分别为a1、a2、…、an,小美
- 2023-01-04:有三个题库A、B、C,每个题库均有n道题目,且题目都是从1到n进行编号每个题目都有一个难度值题库A中第i个
- 2023-01-06:给定一个只由小写字母组成的字符串str,长度为N,给定一个只由0、1组成的数组arr,长度为N,arr[i
- 2023-01-08:小红定义一个仅有r、e、d三种字符的字符串中,如果仅有一个长度不小于2的回文子串,那么这个字符串定义为"好
- 2023-01-09:以下go语言代码输出什么?A:+Inf; B:zero; C:something else; D:does
- 2023-01-10:智能机器人要坐专用电梯把货物送到指定地点,整栋楼只有一部电梯,并且由于容量限制智能机器人只能放下一件货物,
- 2023-01-11:体育馆的人流量。编写一个 SQL 查询以找出每行的人数大于或等于 100 且 id 连续的三行或更多行记录
- 2023-01-12:一个n*n的二维数组中,只有0和1两种值,当你决定在某个位置操作一次,那么该位置的行和列整体都会变成1,不
- WebStorm2023年激活码,安装教程WebStorm项目创建
- 2023-01-14:给定一个二维数组map,代表一个餐厅,其中只有0、1两种值map[i][j] == 0 表示(i,j)位置