首先我们先回顾下相关技术规范,看看基于RTP的音视频数据PS封装。
C.1 基于RTP的视音频数据PS封装
基于RTP的 PS封装首先按照ISO/IEC13818-1:2000将视音频流封装成PS包,再将PS包以负载的方式封装成 RTP包。
进行PS封装时,应将每个视频帧封装为一个PS包,且每个关键帧的PS包中应包含系统头(System Header)和 PSM(ProgramStream Map),系统头和 PSM放置于PS包头之后、第一个PES包之前。
其中 PESV为视频 PES包,PESA 为音频PES包;视频非关键帧的PS包结构中一般不包含系统头和 PSM。PS包中各部分的具体数据结构参见ISO/IEC 13818-1:2000中的相关描述。
图C.1典型的视频关键帧PS包结构系统头应包含对PS包中码流种类的描述,其中视频和音频的流ID(stream_id)取值如下:
a) 视频流ID:0xE0;
b) 音频流ID:0xC0。
针对本文档规定的几种视音频格式,PSM 中流类型(stream_type)的取值如下:
a) MPEG-4视频流:0x10;
b) H.264视频流:0x1B;
c) SVAC视频流:0x80;
d) G.711音频流:0x90;
e) G.722.1音频流:0x92;
f) G.723.1音频流:0x93;
g) G.729音频流:0x99;
h) SVAC音频流:0x9B。
PS包封装的其他具体技术规范详见ISO/IEC13818-1:2000。
PS包的 RTP封装格式参照IETFRFC2250,RTP的主要参数设置如下:
a) 负载类型(payloadtype):96;
b) 编码名称(encodingname):PS;
c) 时钟频率(clockrate):90kHz;
d) SDP描述中“m”字段的“media”项:video。
C.2 基于 RTP的视音频基本流封装
该方式直接将视音频数据以负载的方式封装成 RTP包。
C.2.1 MPEG-4视频流的 RTP封装
MPEG-4视频流的 RTP封装格式应符合IETFRFC3016协议中的相关规定。 MPEG-4视频流 RTP包的负载类型(PayloadType)标识号选定:从IETFRFC3551—2003表5 中的动态范围(96~127)中选择,建议定为97。
C.2.2 H.264视频流的 RTP封装
H.264的 RTP载荷格式应符合IETFRFC3984中的相关规定。
H.264视频流RTP包的负载类型(PayloadType)标识号选定:从IETFRFC3551—2003表5中的动态范围(96~127)中选择,建议定为98。
C.2.3 SVAC视频流的 RTP封装
SVAC视频流的 RTP载荷格式可参照IETFRFC3984中的相关规定。
SVAC视频流 RTP包的负载类型(Payload Type)标志号选定,从IETF RFC 3551-2003表5中的动态范围(96~127)中选择,建议定为99。
C.2.4 音频流的 RTP封装
语音比特流宜采用标准的 RTP协议进行打包。
在一个RTP包中,音频载荷数据应为整数个音频编码帧,且时间长度在20ms~180ms之间。
音频载荷数据的 RTP封装参数如下:
a) G.711的主要参数
G.711A律语音编码 RTP包的负载类型(PayloadType)的参数规定如下(见IETFRFC3551— 2003中的表4):
负载类型(PT):8;
编码名称(encodingname):PCMA;
时钟频率(clockrate):8kHz;
通道数:1;
SDP描述中“m”字段的“media”项:audio。
b) SVAC音频的主要参数
SVAC语音编码 RTP包的负载类型(PayloadType)的参数规定如下:
负载类型(PT):20;
编码名称(encodingname):SVACA;
时钟频率(clockrate):8kHz;
通道数:1;
SDP描述中“m”字段的“media”项:audio。
c) G.723.1的主要参数
G.723.1语音编码 RTP包的负载类型(PayloadType)的参数规定参照IETFRFC3551—2003 表4中的 G.723,具体如下:
负载类型(PT):4;
编码名称(encodingname):G723;
时钟频率(clockrate):8kHz;
通道数:1;
SDP描述中“m”字段的“media”项:audio。
d) G.729的主要参数
G.729语音编码 RTP 包的负载类型(PayloadType)的参数规定如下(见IETFRFC3551— 2003中的表4):
负载类型(PT):18;
编码名称(encodingname):G729;
时钟频率(clockrate):8kHz;
通道数:1;
SDP描述中“m”字段的“media”项:audio。
e) G.722.1的主要参数
G.722.1语音编码 RTP包的负载类型(PayloadType)的参数规定参照IETFRFC3551—2003 表4中 G.722,具体如下:
负载类型(PT):9;
编码名称(encodingname):G722;
时钟频率(clockrate):8kHz;
通道数:1;
SDP描述中“m”字段的“media”项:audio。
技术实现
本文以Android平台为例,介绍下Android平台GB28181接入模块设计。实现不具备国标音视频能力的Android终端,通过平台注册接入到现有的GB/T28181—2016服务,可用于如智能监控、智慧零售、智慧教育、远程办公、生产运输、智慧交通、车载或执法记录仪等场景。Android终端除支持常规的音视频数据接入外,还可以支持移动设备位置(MobilePosition)订阅和通知、语音广播和语音对讲。
功能设计如下:
[视频格式]H.264/H.265(Android H.265硬编码);
[音频格式]G.711 A律、AAC;
[音量调节]Android平台采集端支持实时音量调节;
[H.264硬编码]支持H.264特定机型硬编码;
[H.265硬编码]支持H.265特定机型硬编码;
[软硬编码参数配置]支持gop间隔、帧率、bit-rate设置;
[软编码参数配置]支持软编码profile、软编码速度、可变码率设置;
支持纯视频、音视频PS打包传输;
支持RTP OVER UDP和RTP OVER TCP被动模式;
支持信令通道网络传输协议TCP/UDP设置;
支持注册、注销,支持注册刷新及注册有效期设置;
支持设备目录查询应答;
支持心跳机制,支持心跳间隔、心跳检测次数设置;
支持移动设备位置(MobilePosition)订阅和通知;
支持国标GB/T28181—2016平台接入;
支持语音广播及语音对讲;
[实时水印]支持动态文字水印、png水印;
[镜像]Android平台支持前置摄像头实时镜像功能;
[实时静音]支持实时静音/取消静音;
[实时快照]支持实时快照;
[降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;
[外部编码前视频数据对接]支持YUV数据对接;
[外部编码前音频数据对接]支持PCM对接;
[外部编码后视频数据对接]支持外部H.264数据对接;
[外部编码后音频数据对接]外部AAC数据对接;
支持录像录像相关功能。
Android平台GB28181设备接入端,在收到平台端的invite请求和ack确认后,完成基础的信令交互,进入媒体数据发送阶段:
收到Invite后,开始创建RTP Sender,并完成相关的参数设定:
/*
* CameraPublishActivity.java
* Github: https://github.com/daniulive/SmarterStreaming
*/
@Override
public void ntsOnInvitePlay(String deviceId, PlaySessionDescription session_des) {
handler_.postDelayed(new Runnable() {
@Override
public void run() {
MediaSessionDescription video_des = session_des_.getVideoDescription();
SDPRtpMapAttribute ps_rtpmap_attr = video_des.getPSRtpMapAttribute();
Log.i(TAG,"ntsInviteReceived, 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());
// 可以先给信令服务器发送临时振铃响应
//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_ = 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_);
gb28181_agent_.respondPlayInviteOK(device_id_,local_ip_addr, local_port);
gb28181_rtp_sender_handle_ = rtp_sender_handle;
}
private String device_id_;
private PlaySessionDescription session_des_;
public Runnable set(String device_id, PlaySessionDescription session_des) {
this.device_id_ = device_id;
this.session_des_ = session_des;
return this;
}
}.set(deviceId, session_des),0);
}
Ack后,开始发送数据:
@Override
public void ntsOnAckPlay(String deviceId) {
handler_.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG,"ntsOnACKPlay, device_id:" +device_id_);
if (!isRecording && !isRTSPPublisherRunning && !isPushingRtmp) {
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 (!isRecording && !isRTSPPublisherRunning && !isPushingRtmp ) {
if (publisherHandle != 0) {
libPublisher.SmartPublisherClose(publisherHandle);
publisherHandle = 0;
}
}
destoryRTPSender();
Log.e(TAG, "Failed to start GB28181 service..");
return;
}
if (!isRecording && !isRTSPPublisherRunning && !isPushingRtmp) {
if (pushType == 0 || pushType == 1) {
CheckInitAudioRecorder(); //enable pure video publisher..
}
}
startLayerPostThread();
isGB28181StreamRunning = true;
}
private String device_id_;
public Runnable set(String device_id) {
this.device_id_ = device_id;
return this;
}
}.set(deviceId),0);
}
通过信令和媒体数据交互分离,设备注册后,心跳机制保持在线状态,无需音视频数据编码,平台端如果需要查看实时媒体数据,发起invite请求,采集音视频数据,编码并实现RTP的视音频数据PS封装后实时传输,达到随看随传的目的。