技术背景
最近不少开发者找到我们,他们在做智能家居等传统行业时,希望实现在Android板件拉取本地的RTSP或RTMP流,然后对外推送RTMP出去,亦或内部启个轻量级RTSP服务,提供个对外对接的媒介URL,简单来说,设计架构图如下:
基于上诉诉求,我们以大牛直播SDK (官方)Android端的 SmartRelayDemoV2 工程为例,大概介绍下相关实现。
整体设计
1. 拉流:通过RTSP|RTMP直播播放SDK的数据回调接口,拿到音视频数据;
2. 转推:通过RTMP直播推送SDK的编码后数据输入接口,把回调上来的数据,传给RTMP直播推送模块,实现RTSP|RTMP数据流到RTMP服务器的转发;
3. 录像:如果需要录像,借助RTSP|RTMP直播播放SDK,拉到音视频数据后,直接存储MP4文件即可;
4. 快照:如果需要实时快照,拉流后,解码调用播放端快照接口,生成快照,因为快照涉及到video数据解码,如无必要,可不必开启,不然会额外消耗性能。
5. 拉流预览:如需预览拉流数据,只要调用播放端的播放接口,即可实现拉流数据预览;
6. 数据转AAC后转发:考虑到好多监控设备出来的音频可能是PCMA/PCMU的,如需要更通用的音频格式,可以转AAC后,在通过RTMP推送;
7. 转推RTMP实时静音:只需要在传audio数据的地方,加个判断即可;
8. 拉流速度反馈:通过RTSP播放端的实时码率反馈event,拿到实时带宽占用即可;
9. 整体网络状态反馈:考虑到有些摄像头可能会临时或异常关闭,RTMP服务器亦是,可以通过推拉流的event回调状态,查看那整体网络情况,如此界定:是拉不到流,还是推不到RTMP服务器;
10. 数据注入轻量级RTSP服务:拉流的数据,注入轻量级RTSP服务,对外提供RTSP URL。
先上图
Demo主要实现了以下几个功能点展示:
1. 设置RTMP、RTSP拉流的URL;
2. 设置转推RTMP的URL;
3. 实时播放|录像过程中,实时静音、实施快照;
4. 实时播放;
5. 实时录像;
6. 拉取的流数据,实时转推,对应“开始推流”;
7. 拉取的流数据,注入轻量级RTSP服务,启动服务后,发布RTSP流,对外提供可访问的RTSP URL。
注意:以上播放、录像、转推RTMP、注入轻量级RTSP服务四者是可单独工作,也可随时启动或停止相关功能,互不影响。
相关代码实现
开始拉流
拉流的目的,主要是启动数据回调,注意:拉流并不是直接播放出来窗口,只是拿数据,如果需要本地预览拉流数据,可以点击“开始播放”。
注意:“开始推流”和“发布RTSP流”之前,一定要先“开始拉流”,拿到音视频数据。
private boolean StartPull() { if ( isPulling ) return false; if (!OpenPullHandle()) return false; libPlayer.SmartPlayerSetAudioDataCallback(playerHandle, new PlayerAudioDataCallback()); libPlayer.SmartPlayerSetVideoDataCallback(playerHandle, new PlayerVideoDataCallback()); int is_pull_trans_code = 1; libPlayer.SmartPlayerSetPullStreamAudioTranscodeAAC(playerHandle, is_pull_trans_code); int startRet = libPlayer.SmartPlayerStartPullStream(playerHandle); if (startRet != 0) { Log.e(TAG, "Failed to start pull stream!"); if(!isPlaying && !isRecording && isPushing && !isRTSPPublisherRunning) { libPlayer.SmartPlayerClose(playerHandle); playerHandle = 0; } return false; } isPulling = true; return true; }
这里调到OpenPullHandle()封装,其实就是启动调研Player的Open()接口,获取到player handle,然后设置一下基础数据接口,比如event callback,buffer time,TCP/UDP模式、拉流的URL等;
private boolean OpenPullHandle() { if (playerHandle != 0) { return true; } playbackUrl = "rtsp://admin:daniulive12345@192.168.0.120:554/h265/ch1/main/av_stream"; if (playbackUrl == null) { Log.e(TAG, "playback URL with NULL..."); return false; } playerHandle = libPlayer.SmartPlayerOpen(myContext); if (playerHandle == 0) { Log.e(TAG, "playerHandle is nil.."); return false; } libPlayer.SetSmartPlayerEventCallbackV2(playerHandle, new EventHandePlayerV2()); libPlayer.SmartPlayerSetBuffer(playerHandle, playBuffer); // set report download speed // libPlayer.SmartPlayerSetReportDownloadSpeed(playerHandle, 1, 5); //设置RTSP超时时间 int rtsp_timeout = 12; libPlayer.SmartPlayerSetRTSPTimeout(playerHandle, rtsp_timeout); //设置RTSP TCP/UDP模式自动切换 int is_auto_switch_tcp_udp = 1; libPlayer.SmartPlayerSetRTSPAutoSwitchTcpUdp(playerHandle, is_auto_switch_tcp_udp); libPlayer.SmartPlayerSaveImageFlag(playerHandle, 1); // It only used when playback RTSP stream.. //libPlayer.SmartPlayerSetRTSPTcpMode(playerHandle, 1); libPlayer.SmartPlayerSetUrl(playerHandle, playbackUrl); return true; }
停止拉流
private void StopPull() { if ( !isPulling ) return; libPlayer.SmartPlayerStopPullStream(playerHandle); if ( !isPlaying && !isRecording && !isPushing && !isRTSPPublisherRunning) { libPlayer.SmartPlayerClose(playerHandle); playerHandle = 0; } isPulling = false; }
开始播放
private boolean StartPlay() { if (!OpenPullHandle()) return false; // 如果第二个参数设置为null,则播放纯音频 libPlayer.SmartPlayerSetSurface(playerHandle, sSurfaceView); libPlayer.SmartPlayerSetRenderScaleMode(playerHandle, 1); // External Render test // libPlayer.SmartPlayerSetExternalRender(playerHandle, new // RGBAExternalRender()); // libPlayer.SmartPlayerSetExternalRender(playerHandle, new // I420ExternalRender()); libPlayer.SmartPlayerSetFastStartup(playerHandle, isFastStartup ? 1 : 0); libPlayer.SmartPlayerSetAudioOutputType(playerHandle, 1); if (isMute) { libPlayer.SmartPlayerSetMute(playerHandle, isMute ? 1 : 0); } if (isHardwareDecoder) { int isSupportHevcHwDecoder = libPlayer.SetSmartPlayerVideoHevcHWDecoder(playerHandle, 1); int isSupportH264HwDecoder = libPlayer .SetSmartPlayerVideoHWDecoder(playerHandle, 1); Log.i(TAG, "isSupportH264HwDecoder: " + isSupportH264HwDecoder + ", isSupportHevcHwDecoder: " + isSupportHevcHwDecoder); } libPlayer.SmartPlayerSetLowLatencyMode(playerHandle, isLowLatency ? 1 : 0); libPlayer.SmartPlayerSetRotation(playerHandle, rotate_degrees); int iPlaybackRet = libPlayer .SmartPlayerStartPlay(playerHandle); if (iPlaybackRet != 0) { Log.e(TAG, "StartPlay failed!"); if ( !isPulling && !isRecording && !isPushing && !isRTSPPublisherRunning) { libPlayer.SmartPlayerClose(playerHandle); playerHandle = 0; } return false; } isPlaying = true; return true; }
停止播放
private void StopPlay() { if ( !isPlaying ) return; isPlaying = false; libPlayer.SmartPlayerStopPlay(playerHandle); if ( !isPulling && !isRecording && !isPushing && !isRTSPPublisherRunning) { libPlayer.SmartPlayerClose(playerHandle); playerHandle = 0; } }
开始录像
private boolean StartRecorder() { if (!OpenPullHandle()) return false; ConfigRecorderFuntion(); int iRecRet = libPlayer .SmartPlayerStartRecorder(playerHandle); if (iRecRet != 0) { Log.e(TAG, "StartRecorder failed!"); if ( !isPulling &&!isPlaying && !isPushing && !isRTSPPublisherRunning) { libPlayer.SmartPlayerClose(playerHandle); playerHandle = 0; } return false; } isRecording = true; return true; }
停止录像
private void StopRecorder() { if ( !isRecording ) return; isRecording = false; libPlayer.SmartPlayerStopRecorder(playerHandle); if ( !isPlaying && !isPulling && !isPushing && !isRTSPPublisherRunning) { libPlayer.SmartPlayerClose(playerHandle); playerHandle = 0; } }
开始推流
private boolean StartPush() { if (isPushing) return false; relayStreamUrl = "rtmp://192.168.0.211:1935/hls/stream1"; if (relayStreamUrl == null) { Log.e(TAG, "StartPush URL is null..."); return false; } if (!OpenPushHandle()) return false; if ( libPublisher.SmartPublisherSetURL(publisherHandle, relayStreamUrl) != 0 ) { Log.e(TAG, "StartPush failed!"); } int startRet = libPublisher.SmartPublisherStartPublisher(publisherHandle); if( startRet != 0) { Log.e(TAG, "Failed to call StartPublisher!"); if(isRTSPPublisherRunning) { libPublisher.SmartPublisherClose(publisherHandle); publisherHandle = 0; } return false; } isPushing = true; return true; }
开始推流调到了OpenPushHandle()封装,具体代码如下:
private boolean OpenPushHandle() { if(publisherHandle != 0) { return true; } int audio_opt = 2; int video_opt = 2; int videoWidth = 640; int videoHeight = 480; publisherHandle = libPublisher.SmartPublisherOpen(myContext, audio_opt, video_opt, videoWidth, videoHeight); if (publisherHandle == 0 ) { Log.e(TAG, "OpenPushHandle failed!"); return false; } Log.i(TAG, "publisherHandle=" + publisherHandle); libPublisher.SetSmartPublisherEventCallbackV2(publisherHandle, new EventHandePublisherV2()); return true; }
停止推流
public void StopPush() { if (!isPushing) return; isPushing = false; libPublisher.SmartPublisherStopPublisher(publisherHandle); if(!isRTSPPublisherRunning && !isRTSPServiceRunning) { libPublisher.SmartPublisherClose(publisherHandle); publisherHandle = 0; } }
启动RTSP服务
//启动/停止RTSP服务 class ButtonRtspServiceListener implements OnClickListener { public void onClick(View v) { if (isRTSPServiceRunning) { stopRtspService(); btnRtspService.setText("启动RTSP服务"); btnRtspPublisher.setEnabled(false); isRTSPServiceRunning = false; return; } if(!OpenPushHandle()) { return; } Log.i(TAG, "onClick start rtsp service.."); rtsp_handle_ = libPublisher.OpenRtspServer(0); if (rtsp_handle_ == 0) { Log.e(TAG, "创建rtsp server实例失败! 请检查SDK有效性"); } else { int port = 8554; if (libPublisher.SetRtspServerPort(rtsp_handle_, port) != 0) { libPublisher.CloseRtspServer(rtsp_handle_); rtsp_handle_ = 0; Log.e(TAG, "创建rtsp server端口失败! 请检查端口是否重复或者端口不在范围内!"); } //String user_name = "admin"; //String password = "12345"; //libPublisher.SetRtspServerUserNamePassword(rtsp_handle_, user_name, password); //一般来说单播网络设备支持的好,wifi组播很多路由器不支持,默认单播模式;如需使用组播模式,确保设备支持后,打开注释代码测试即可 /* boolean is_enable_multicast = true; if(is_enable_multicast) { int is_multicast = 1; libPublisher.SetRtspServerMulticast(rtsp_handle_, is_multicast); boolean is_enable_ssm_multicast = true; String multicast_address = ""; if(is_enable_ssm_multicast) { multicast_address = MakeSSMMulticastAddress(); } else { multicast_address = MakeMulticastAddress(); } Log.i(TAG, "is_enable_ssm_multicast:" + is_enable_ssm_multicast + " multiAddr: " + multicast_address); libPublisher.SetRtspServerMulticastAddress(rtsp_handle_, multicast_address); } */ 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服务 private void stopRtspService() { if(!isRTSPServiceRunning) return; if (libPublisher != null && rtsp_handle_ != 0) { libPublisher.StopRtspServer(rtsp_handle_); libPublisher.CloseRtspServer(rtsp_handle_); rtsp_handle_ = 0; } if(!isPushing) { libPublisher.SmartPublisherClose(publisherHandle); publisherHandle = 0; } isRTSPServiceRunning = false; }
开始发布RTSP流
private boolean StartRtspStream() { if (isRTSPPublisherRunning) return false; String rtsp_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流接口失败!"); if (!isPushing) { libPublisher.SmartPublisherClose(publisherHandle); publisherHandle = 0; } return false; } isRTSPPublisherRunning = true; return true; }
停止发布RTSP流
//停止发布RTSP流 private void stopRtspPublisher() { if(!isRTSPPublisherRunning) return; if (libPublisher != null) { libPublisher.StopRtspStream(publisherHandle); } if (!isPushing && !isRTSPServiceRunning) { libPublisher.SmartPublisherClose(publisherHandle); publisherHandle = 0; } isRTSPPublisherRunning = false; }
获取RTSP连接会话数
//当前RTSP会话数弹出框 private void PopRtspSessionNumberDialog(int session_numbers) { final EditText inputUrlTxt = new EditText(this); inputUrlTxt.setFocusable(true); inputUrlTxt.setEnabled(false); String session_numbers_tag = "RTSP服务当前客户会话数: " + session_numbers; inputUrlTxt.setText(session_numbers_tag); AlertDialog.Builder builderUrl = new AlertDialog.Builder(this); builderUrl .setTitle("内置RTSP服务") .setView(inputUrlTxt).setNegativeButton("确定", null); builderUrl.show(); } //获取RTSP会话数 class ButtonGetRtspSessionNumbersListener implements OnClickListener { public void onClick(View v) { if (libPublisher != null && rtsp_handle_ != 0) { int session_numbers = libPublisher.GetRtspServerClientSessionNumbers(rtsp_handle_); Log.i(TAG, "GetRtspSessionNumbers: " + session_numbers); PopRtspSessionNumberDialog(session_numbers); } } };
总结
以上是大概的流程,感兴趣的开发者可自行参考。