GB/T28181-2016基于RTP的视音频数据封装和技术实现

首先我们先回顾下相关技术规范,看看基于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封装后实时传输,达到随看随传的目的。


国内免备案VPS301跳转服务器国内免备案服务器域名被墙跳转301,绕过信息安全中心不放违反法律法规内容!(北京免备案 镇江免备案 江苏免备案 辽宁免备案vps 山东联通免备案
 
在线咨询