技术背景
GB28181的应用场景非常广泛,如公共安全、交通管理、企业安全、教育、医疗等众多领域,细分场景可用于如执法记录仪、智能安全帽、智能监控、智慧零售、智慧教育、远程办公、明厨亮灶、智慧交通、智慧工地、雪亮工程、平安乡村、生产运输、车载终端等:
- 公共安全:通过GB28181协议,用户可以实时监控特定区域的视频画面,从而提高公共安全水平。
- 交通管理:GB28181可用于交通监控系统,帮助交通部门实时监控道路交通情况,提高交通管理效率。
- 企业安全:GB28181可以用于构建企业视频监控系统,保护企业资产,提高安全工作效率。
- 教育:通过GB28181协议,用户可以进行远程视频会议和教学,为学生提供更为灵活的学习方式。
- 医疗:GB28181可以用于医疗领域的视频监控,提高医疗安全和管理效率。
技术实现
本文以Android平台GB28181设备接入模块为例,谈谈具体实现,还有如何对外输出RTSP流。
Android终端除支持常规的音视频数据接入外,还可以支持移动设备位置(MobilePosition)订阅和通知、语音广播和语音对讲、云台控制回调和预置位查询,支持对接数据类型如下:
- 编码前数据(目前支持的有YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565等数据类型);
- 编码后数据(如无人机等264/HEVC数据,或者本地解析的MP4音视频数据);
- 拉取RTSP或RTMP流并接入至GB28181平台(比如其他IPC的RTSP流,可通过Android平台GB28181接入到国标平台)。
技术设计架构图:
功能设计:
- [视频格式]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媒体流传输客户端);
- 支持信令通道网络传输协议TCP/UDP设置;
- 支持注册、注销,支持注册刷新及注册有效期设置;
- 支持设备目录查询应答;
- 支持心跳机制,支持心跳间隔、心跳检测次数设置;
- 支持移动设备位置(MobilePosition)订阅和通知;
- 支持语音广播;
- 支持语音对讲;
- 支持云台控制和预置位查询;
- [实时水印]支持动态文字水印、png水印;
- [镜像]Android平台支持前置摄像头实时镜像功能;
- [实时静音]支持实时静音/取消静音;
- [实时快照]支持实时快照;
- [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;
- [外部编码前视频数据对接]支持YUV数据对接;
- [外部编码前音频数据对接]支持PCM对接;
- [外部编码后视频数据对接]支持外部H.264数据对接;
- [外部编码后音频数据对接]外部AAC数据对接;
- [扩展录像功能]支持和录像模块组合使用,录像相关功能。
Android平台GB28181设备接入模块,除了上述的功能点外,我们遇到的诉求有,如何同时对外输出RTSP,供如内网平台预览播放?
这里就提到了轻量级RTSP服务,音视频数据源过来后,编码分别注入GB28181模块和轻量级RTSP服务模块,如果需要做到对外输出RTSP流,只需要启动RTSP服务,然后发布RTSP流即可,具体的操作如下:
编辑
启动、停止RTSP服务:
//启动/停止RTSP服务classButtonRtspServiceListenerimplementsView.OnClickListener { publicvoidonClick(Viewv) { if (isRTSPServiceRunning) { stopRtspService(); btnRtspService.setText("启动RTSP服务"); btnRtspPublisher.setEnabled(false); isRTSPServiceRunning=false; return; } Log.i(TAG, "onClick start rtsp service.."); rtsp_handle_=libPublisher.OpenRtspServer(0); if (rtsp_handle_==0) { Log.e(TAG, "创建rtsp server实例失败! 请联系 https://daniusdk.com 检查SDK有效性"); } else { intport=8554; if (libPublisher.SetRtspServerPort(rtsp_handle_, port) !=0) { libPublisher.CloseRtspServer(rtsp_handle_); rtsp_handle_=0; Log.e(TAG, "创建rtsp server端口失败! 请检查端口是否重复或者端口不在范围内!"); } if (libPublisher.StartRtspServer(rtsp_handle_, 0) ==0) { Log.i(TAG, "启动rtsp server 成功!"); } else { libPublisher.CloseRtspServer(rtsp_handle_); rtsp_handle_=0; Log.e(TAG, "启动rtsp server失败! 请检查设置的端口是否被占用!"); } btnRtspService.setText("停止RTSP服务"); btnRtspPublisher.setEnabled(true); isRTSPServiceRunning=true; } } }
发布、停止RTSP流:
//发布/停止RTSP流classButtonRtspPublisherListenerimplementsView.OnClickListener { publicvoidonClick(Viewv) { if (isRTSPPublisherRunning) { stopRtspPublisher(); btnRtspPublisher.setText("发布RTSP流"); btnGetRtspSessionNumbers.setEnabled(false); btnRtspService.setEnabled(true); return; } Log.i(TAG, "onClick start rtsp publisher.."); if (!isPushingRtmp&&!isGB28181StreamRunning&&!isRecording) { InitAndSetConfig(); } if (publisherHandle==0) { Log.e(TAG, "Start rtsp publisher, publisherHandle is null.."); return; } Stringrtsp_stream_name="stream1"; libPublisher.SetRtspStreamName(publisherHandle, rtsp_stream_name); libPublisher.ClearRtspStreamServer(publisherHandle); libPublisher.AddRtspStreamServer(publisherHandle, rtsp_handle_, 0); if (libPublisher.StartRtspStream(publisherHandle, 0) !=0) { Log.e(TAG, "调用发布rtsp流接口失败!"); return; } if (!isPushingRtmp&&!isGB28181StreamRunning&&!isRecording) { CheckInitAudioRecorder(); //enable pure video publisher.. } startLayerPostThread(); btnRtspPublisher.setText("停止RTSP流"); btnGetRtspSessionNumbers.setEnabled(true); btnRtspService.setEnabled(false); isRTSPPublisherRunning=true; } }
获取RTSP链接数:
//获取RTSP会话数classButtonGetRtspSessionNumbersListenerimplementsView.OnClickListener { publicvoidonClick(Viewv) { if (libPublisher!=null&&rtsp_handle_!=0) { intsession_numbers=libPublisher.GetRtspServerClientSessionNumbers(rtsp_handle_); Log.i(TAG, "GetRtspSessionNumbers: "+session_numbers); PopRtspSessionNumberDialog(session_numbers); } } }
获取回调上来的RTSP URL,对应的事件ID为EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL:
privatestaticclassEventHandlerPublisherV2implementsNTSmartEventCallbackV2 { publicvoidonNTSmartEventCallbackV2(longhandle, intid, longparam1, longparam2, Stringparam3, Stringparam4, Objectparam5) { Log.i(TAG, "EventHandeV2: handle="+handle+" id:"+id); Stringpublisher_event=""; switch (id) { ..... caseNTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RECORDER_START_NEW_FILE: publisher_event="开始一个新的录像文件 : "+param3; break; caseNTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_ONE_RECORDER_FILE_FINISHED: if (recorder_io_executor_!=null) { ExecutorServiceexecutor=recorder_io_executor_.get(); if (executor!=null) executor.execute(newRecordFileFinishedHandler().set(handle, param3, param1)); } publisher_event="已生成一个录像文件 : "+param3; break; caseNTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_SEND_DELAY: publisher_event="发送时延: "+param1+" 帧数:"+param2; break; caseNTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CAPTURE_IMAGE: publisher_event="快照: "+param1+" 路径:"+param3; if (param1==0) { publisher_event=publisher_event+"截取快照成功.."; } else { publisher_event=publisher_event+"截取快照失败.."; } break; caseNTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL: publisher_event="RTSP服务URL: "+param3; break; } Stringstr="当前回调状态:"+publisher_event; Log.i(TAG, str); if (handler_!=null) { android.os.Handlerhandler=handler_.get(); if (handler!=null) { Messagemessage=newMessage(); message.what=PUBLISHER_EVENT_MSG; message.obj=publisher_event; handler.sendMessage(message); } } } publicNTSmartEventCallbackV2set(android.os.Handlerhandler, ExecutorServicerecorder_io_executor) { this.handler_=newWeakReference<>(handler); this.recorder_io_executor_=newWeakReference<>(recorder_io_executor); returnthis; } privateWeakReference<android.os.Handler>handler_; privateWeakReference<ExecutorService>recorder_io_executor_; }
总结
GB28181设备接入模块同时输出RTSP流的话,需要注意的是,在一个实例里面完成,确保只编码一路音视频数据,然后分别打包注入两个模块,尽可能的降低设备性能消耗。