如何实现Android端获取RTSP|RTMP流转推RTMP
技術背景
最近不少開發者找到我們,他們在做智能家居等傳統行業時,希望實現在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);}}};總結
以上是大概的流程,感興趣的開發者可自行參考。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的如何实现Android端获取RTSP|RTMP流转推RTMP的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微软推行 Windows 10 更新政策
- 下一篇: 如何实现Android平台GB28181