如何实现Android端获取RTSP|RTMP流转推RTMP
技術(shù)背景
最近不少開發(fā)者找到我們,他們?cè)谧鲋悄芗揖拥葌鹘y(tǒng)行業(yè)時(shí),希望實(shí)現(xiàn)在Android板件拉取本地的RTSP或RTMP流,然后對(duì)外推送RTMP出去,亦或內(nèi)部啟個(gè)輕量級(jí)RTSP服務(wù),提供個(gè)對(duì)外對(duì)接的媒介URL,簡(jiǎn)單來說,設(shè)計(jì)架構(gòu)圖如下:
基于上訴訴求,我們以大牛直播SDK (官方)Android端的 SmartRelayDemoV2 工程為例,大概介紹下相關(guān)實(shí)現(xiàn)。
整體設(shè)計(jì)
1. 拉流:通過RTSP|RTMP直播播放SDK的數(shù)據(jù)回調(diào)接口,拿到音視頻數(shù)據(jù);
2. 轉(zhuǎn)推:通過RTMP直播推送SDK的編碼后數(shù)據(jù)輸入接口,把回調(diào)上來的數(shù)據(jù),傳給RTMP直播推送模塊,實(shí)現(xiàn)RTSP|RTMP數(shù)據(jù)流到RTMP服務(wù)器的轉(zhuǎn)發(fā);
3. 錄像:如果需要錄像,借助RTSP|RTMP直播播放SDK,拉到音視頻數(shù)據(jù)后,直接存儲(chǔ)MP4文件即可;
4. 快照:如果需要實(shí)時(shí)快照,拉流后,解碼調(diào)用播放端快照接口,生成快照,因?yàn)榭煺丈婕暗絭ideo數(shù)據(jù)解碼,如無必要,可不必開啟,不然會(huì)額外消耗性能。
5. 拉流預(yù)覽:如需預(yù)覽拉流數(shù)據(jù),只要調(diào)用播放端的播放接口,即可實(shí)現(xiàn)拉流數(shù)據(jù)預(yù)覽;
6. 數(shù)據(jù)轉(zhuǎn)AAC后轉(zhuǎn)發(fā):考慮到好多監(jiān)控設(shè)備出來的音頻可能是PCMA/PCMU的,如需要更通用的音頻格式,可以轉(zhuǎn)AAC后,在通過RTMP推送;
7. 轉(zhuǎn)推RTMP實(shí)時(shí)靜音:只需要在傳audio數(shù)據(jù)的地方,加個(gè)判斷即可;
8. 拉流速度反饋:通過RTSP播放端的實(shí)時(shí)碼率反饋event,拿到實(shí)時(shí)帶寬占用即可;
9. 整體網(wǎng)絡(luò)狀態(tài)反饋:考慮到有些攝像頭可能會(huì)臨時(shí)或異常關(guān)閉,RTMP服務(wù)器亦是,可以通過推拉流的event回調(diào)狀態(tài),查看那整體網(wǎng)絡(luò)情況,如此界定:是拉不到流,還是推不到RTMP服務(wù)器;
10. 數(shù)據(jù)注入輕量級(jí)RTSP服務(wù):拉流的數(shù)據(jù),注入輕量級(jí)RTSP服務(wù),對(duì)外提供RTSP URL。
先上圖
Demo主要實(shí)現(xiàn)了以下幾個(gè)功能點(diǎn)展示:
1. 設(shè)置RTMP、RTSP拉流的URL;
2. 設(shè)置轉(zhuǎn)推RTMP的URL;
3. 實(shí)時(shí)播放|錄像過程中,實(shí)時(shí)靜音、實(shí)施快照;
4. 實(shí)時(shí)播放;
5. 實(shí)時(shí)錄像;
6. 拉取的流數(shù)據(jù),實(shí)時(shí)轉(zhuǎn)推,對(duì)應(yīng)“開始推流”;
7. 拉取的流數(shù)據(jù),注入輕量級(jí)RTSP服務(wù),啟動(dòng)服務(wù)后,發(fā)布RTSP流,對(duì)外提供可訪問的RTSP URL。
注意:以上播放、錄像、轉(zhuǎn)推RTMP、注入輕量級(jí)RTSP服務(wù)四者是可單獨(dú)工作,也可隨時(shí)啟動(dòng)或停止相關(guān)功能,互不影響。
相關(guān)代碼實(shí)現(xiàn)
開始拉流
拉流的目的,主要是啟動(dòng)數(shù)據(jù)回調(diào),注意:拉流并不是直接播放出來窗口,只是拿數(shù)據(jù),如果需要本地預(yù)覽拉流數(shù)據(jù),可以點(diǎn)擊“開始播放”。
注意:“開始推流”和“發(fā)布RTSP流”之前,一定要先“開始拉流”,拿到音視頻數(shù)據(jù)。
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;}這里調(diào)到OpenPullHandle()封裝,其實(shí)就是啟動(dòng)調(diào)研Player的Open()接口,獲取到player handle,然后設(shè)置一下基礎(chǔ)數(shù)據(jù)接口,比如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);//設(shè)置RTSP超時(shí)時(shí)間int rtsp_timeout = 12;libPlayer.SmartPlayerSetRTSPTimeout(playerHandle, rtsp_timeout);//設(shè)置RTSP TCP/UDP模式自動(dòng)切換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;// 如果第二個(gè)參數(shù)設(shè)置為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;}開始推流調(diào)到了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;}}啟動(dòng)RTSP服務(wù)
//啟動(dòng)/停止RTSP服務(wù)class ButtonRtspServiceListener implements OnClickListener {public void onClick(View v) {if (isRTSPServiceRunning) {stopRtspService();btnRtspService.setText("啟動(dòng)RTSP服務(wù)");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, "創(chuàng)建rtsp server實(shí)例失敗! 請(qǐng)檢查SDK有效性");} else {int port = 8554;if (libPublisher.SetRtspServerPort(rtsp_handle_, port) != 0) {libPublisher.CloseRtspServer(rtsp_handle_);rtsp_handle_ = 0;Log.e(TAG, "創(chuàng)建rtsp server端口失敗! 請(qǐng)檢查端口是否重復(fù)或者端口不在范圍內(nèi)!");}//String user_name = "admin";//String password = "12345";//libPublisher.SetRtspServerUserNamePassword(rtsp_handle_, user_name, password);//一般來說單播網(wǎng)絡(luò)設(shè)備支持的好,wifi組播很多路由器不支持,默認(rèn)單播模式;如需使用組播模式,確保設(shè)備支持后,打開注釋代碼測(cè)試即可/*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, "啟動(dòng)rtsp server 成功!");} else {libPublisher.CloseRtspServer(rtsp_handle_);rtsp_handle_ = 0;Log.e(TAG, "啟動(dòng)rtsp server失敗! 請(qǐng)檢查設(shè)置的端口是否被占用!");}btnRtspService.setText("停止RTSP服務(wù)");btnRtspPublisher.setEnabled(true);isRTSPServiceRunning = true;}}}停止RTSP服務(wù)
//停止RTSP服務(wù)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;}開始發(fā)布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, "調(diào)用發(fā)布rtsp流接口失敗!");if (!isPushing){libPublisher.SmartPublisherClose(publisherHandle);publisherHandle = 0;}return false;}isRTSPPublisherRunning = true;return true;}停止發(fā)布RTSP流
//停止發(fā)布RTSP流private void stopRtspPublisher(){if(!isRTSPPublisherRunning)return;if (libPublisher != null) {libPublisher.StopRtspStream(publisherHandle);}if (!isPushing && !isRTSPServiceRunning){libPublisher.SmartPublisherClose(publisherHandle);publisherHandle = 0;}isRTSPPublisherRunning = false;}獲取RTSP連接會(huì)話數(shù)
//當(dāng)前RTSP會(huì)話數(shù)彈出框private void PopRtspSessionNumberDialog(int session_numbers) {final EditText inputUrlTxt = new EditText(this);inputUrlTxt.setFocusable(true);inputUrlTxt.setEnabled(false);String session_numbers_tag = "RTSP服務(wù)當(dāng)前客戶會(huì)話數(shù): " + session_numbers;inputUrlTxt.setText(session_numbers_tag);AlertDialog.Builder builderUrl = new AlertDialog.Builder(this);builderUrl.setTitle("內(nèi)置RTSP服務(wù)").setView(inputUrlTxt).setNegativeButton("確定", null);builderUrl.show();}//獲取RTSP會(huì)話數(shù)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);}}};總結(jié)
以上是大概的流程,感興趣的開發(fā)者可自行參考。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的如何实现Android端获取RTSP|RTMP流转推RTMP的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微软推行 Windows 10 更新政策
- 下一篇: DHCP租用信息导出方案