wordpress幻燈片怎么建運(yùn)營seo是什么意思
技術(shù)背景
在Android上實(shí)現(xiàn)RTSP服務(wù)器確實(shí)是一個不太常見的需求,因?yàn)锳ndroid平臺主要是為客戶端應(yīng)用設(shè)計的。在一些內(nèi)網(wǎng)場景下,我們更希望把安卓終端或開發(fā)板,作為一個IPC(網(wǎng)絡(luò)攝像機(jī))一樣,對外提供個拉流的rtsp url,然后把攝像頭麥克風(fēng)甚至屏幕采集的數(shù)據(jù),共享出去,輕量級RTSP的設(shè)計理念脫穎而出。
輕量級RTSP服務(wù)設(shè)計初衷,就是避免用戶單獨(dú)部署RTSP或者RTMP服務(wù),實(shí)現(xiàn)本地的音視頻數(shù)據(jù)(如攝像頭、麥克風(fēng)),編碼后,匯聚到內(nèi)置RTSP服務(wù),對外提供可供拉流的RTSP URL,輕量級RTSP服務(wù),適用于內(nèi)網(wǎng)環(huán)境下,對并發(fā)要求不高的場景,支持H.264/H.265,支持RTSP鑒權(quán)、單播、組播模式,考慮到單個服務(wù)承載能力,我們支持同時創(chuàng)建多個RTSP服務(wù),并支持獲取當(dāng)前RTSP服務(wù)會話連接數(shù)。
如何實(shí)現(xiàn)RTSP服務(wù)器
如果你找不到合適的庫,或者需要更高級的功能,你可以考慮編寫自己的RTSP服務(wù)器:
- 了解RTSP協(xié)議:首先,你需要深入了解RTSP協(xié)議的工作原理,包括其消息格式、會話管理和流控制機(jī)制。
- 網(wǎng)絡(luò)編程:在Android上,你可以使用Java的Socket API來處理網(wǎng)絡(luò)通信,也可以直接底層實(shí)現(xiàn),然后對上提供jni的接口。你需要編寫代碼來監(jiān)聽端口、接收RTSP請求、解析請求并發(fā)送響應(yīng)。
- 媒體處理:RTSP服務(wù)器需要能夠捕獲、編碼和傳輸媒體數(shù)據(jù)。你可以使用Android的Camera或Camera2的API來捕獲視頻,并使用FFmpeg或Android的MediaCodec API來實(shí)現(xiàn)音視頻數(shù)據(jù)編碼。
- 多線程和并發(fā):RTSP服務(wù)器需要處理多個并發(fā)客戶端連接。你可以使用Java的線程或并發(fā)API來管理這些連接。
功能設(shè)計
一個完整的RTSP服務(wù),需要設(shè)計的功能如下:
- ??[視頻格式]H.264/H.265(Android H.265硬編碼);
- ?[音頻格式]G.711 A律、AAC;
- 協(xié)議:RTSP;
- ?[音量調(diào)節(jié)]Android平臺采集端支持實(shí)時音量調(diào)節(jié);
- ?[H.264硬編碼]支持H.264特定機(jī)型硬編碼;
- ?[H.265硬編碼]支持H.265特定機(jī)型硬編碼;
- [音視頻]支持純音頻/純視頻/音視頻;
- [攝像頭]支持采集過程中,前后攝像頭實(shí)時切換;
- 支持幀率、關(guān)鍵幀間隔(GOP)、碼率(bit-rate)設(shè)置;
- [實(shí)時水印]支持動態(tài)文字水印、png水印;
- [實(shí)時快照]支持實(shí)時快照;
- [降噪]支持環(huán)境音、手機(jī)干擾等引起的噪音降噪處理、自動增益、VAD檢測;
- [外部編碼前視頻數(shù)據(jù)對接]支持YUV數(shù)據(jù)對接;
- [外部編碼前音頻數(shù)據(jù)對接]支持PCM對接;
- [外部編碼后視頻數(shù)據(jù)對接]支持外部H.264、H.265數(shù)據(jù)對接;
- [外部編碼后音頻數(shù)據(jù)對接]外部AAC數(shù)據(jù)對接;
- [擴(kuò)展錄像功能]支持和錄像SDK組合使用,錄像相關(guān)功能。?
- 支持RTSP端口設(shè)置;
- 支持RTSP鑒權(quán)用戶名、密碼設(shè)置;
- 支持獲取當(dāng)前RTSP服務(wù)會話連接數(shù);
- 支持Android?5.1及以上版本。
接口設(shè)計
Android內(nèi)置輕量級RTSP服務(wù)SDK接口詳解 | ||
調(diào)用描述 | 接口 | 接口描述 |
SmartRTSPServerSDK | ||
初始化RTSP Server | InitRtspServer | Init rtsp server(和UnInitRtspServer配對使用,即便是啟動多個RTSP服務(wù),也只需調(diào)用一次InitRtspServer,請確保在OpenRtspServer之前調(diào)用) |
創(chuàng)建一個rtsp server | OpenRtspServer | 創(chuàng)建一個rtsp server,返回rtsp server句柄 |
設(shè)置端口 | SetRtspServerPort | 設(shè)置rtsp server 監(jiān)聽端口, 在StartRtspServer之前必須要設(shè)置端口 |
設(shè)置鑒權(quán)用戶名、密碼 | SetRtspServerUserNamePassword | 設(shè)置rtsp server 鑒權(quán)用戶名和密碼, 這個可以不設(shè)置,只有需要鑒權(quán)的再設(shè)置 |
獲取rtsp server當(dāng)前會話數(shù) | GetRtspServerClientSessionNumbers | 獲取rtsp server當(dāng)前的客戶會話數(shù), 這個接口必須在StartRtspServer之后再調(diào)用 |
啟動rtsp server | StartRtspServer | 啟動rtsp server |
停止rtsp server | StopRtspServer | 停止rtsp server |
關(guān)閉rtsp server | CloseRtspServer | 關(guān)閉rtsp server |
UnInit rtsp server | UnInitRtspServer | UnInit rtsp server(和InitRtspServer配對使用,即便是啟動多個RTSP服務(wù),也只需調(diào)用一次UnInitRtspServer) |
SmartRTSPServerSDK供Publisher調(diào)用的接口 | ||
設(shè)置rtsp的流名稱 | SetRtspStreamName | 設(shè)置rtsp的流名稱 |
給要發(fā)布的rtsp流設(shè)置rtsp server | AddRtspStreamServer | 給要發(fā)布的rtsp流設(shè)置rtsp server, 一個流可以發(fā)布到多個rtsp server上,rtsp server的創(chuàng)建啟動請參考OpenRtspServer和StartRtspServer接口 |
清除設(shè)置的rtsp server | ClearRtspStreamServer | 清除設(shè)置的rtsp server |
啟動rtsp流 | StartRtspStream | 啟動rtsp流 |
停止rtsp流 | StopRtspStream | 停止rtsp流 |
調(diào)用邏輯
以大牛直播SDK的Android平臺Camera2對接為例,先初始化RTSP Server,啟動RTSP服務(wù),然后發(fā)布RTSP流即可,如果需要停止RTSP服務(wù),那么先停止RTSP流,再停止RTSP服務(wù)即可:
/** MainActivity.java* Author: daniusdk.com* WeChat: xinsheng120*/
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);...context_ = this.getApplicationContext();libPublisher = new SmartPublisherJniV2();libPublisher.InitRtspServer(context_); //和UnInitRtspServer配對使用,即便是啟動多個RTSP服務(wù),也只需調(diào)用一次InitRtspServer,請確保在OpenRtspServer之前調(diào)用
}
啟動、停止RTSP服務(wù):
//啟動/停止RTSP服務(wù)
class ButtonRtspServiceListener implements View.OnClickListener {public void onClick(View v) {if (isRTSPServiceRunning) {stopRtspService();btnRtspService.setText("啟動RTSP服務(wù)");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, "創(chuàng)建rtsp server實(shí)例失敗! 請檢查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端口失敗! 請檢查端口是否重復(fù)或者端口不在范圍內(nèi)!");}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失敗! 請檢查設(shè)置的端口是否被占用!");}btnRtspService.setText("停止RTSP服務(wù)");btnRtspPublisher.setEnabled(true);isRTSPServiceRunning = true;}}
}
stopRtspService()實(shí)現(xiàn)如下:
//停止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;}
}
發(fā)布、停止RTSP流:
//發(fā)布/停止RTSP流
class ButtonRtspPublisherListener implements View.OnClickListener {public void onClick(View v) {if (stream_publisher_.is_rtsp_publishing()) {stopRtspPublisher();btnRtspPublisher.setText("發(fā)布RTSP流");btnGetRtspSessionNumbers.setEnabled(false);btnRtspService.setEnabled(true);return;}Log.i(TAG, "onClick start rtsp publisher..");InitAndSetConfig();String rtsp_stream_name = "stream1";stream_publisher_.SetRtspStreamName(rtsp_stream_name);stream_publisher_.ClearRtspStreamServer();stream_publisher_.AddRtspStreamServer(rtsp_handle_);if (!stream_publisher_.StartRtspStream()) {stream_publisher_.try_release();Log.e(TAG, "調(diào)用發(fā)布rtsp流接口失敗!");return;}startAudioRecorder();startLayerPostThread();btnRtspPublisher.setText("停止RTSP流");btnGetRtspSessionNumbers.setEnabled(true);btnRtspService.setEnabled(false);}
}
stopRtspPublisher()實(shí)現(xiàn)如下:
//停止發(fā)布RTSP流
private void stopRtspPublisher() {stream_publisher_.StopRtspStream();stream_publisher_.try_release();if (!stream_publisher_.is_publishing())stopAudioRecorder();
}
其中,InitAndSetConfig()實(shí)現(xiàn)如下,通過調(diào)研SmartPublisherOpen()接口,生成推送實(shí)例句柄。
/** MainActivity.java* Author: daniusdk.com*/
private void InitAndSetConfig() {if (null == libPublisher)return;if (!stream_publisher_.empty())return;Log.i(TAG, "InitAndSetConfig video width: " + video_width_ + ", height" + video_height_ + " imageRotationDegree:" + cameraImageRotationDegree_);int audio_opt = 1;long handle = libPublisher.SmartPublisherOpen(context_, audio_opt, 3, video_width_, video_height_);if (0==handle) {Log.e(TAG, "sdk open failed!");return;}Log.i(TAG, "publisherHandle=" + handle);int fps = 25;int gop = fps * 3;initialize_publisher(libPublisher, handle, video_width_, video_height_, fps, gop);stream_publisher_.set(libPublisher, handle);
}
對應(yīng)的initialize_publisher()實(shí)現(xiàn)如下,設(shè)置軟硬編碼、幀率、關(guān)鍵幀間隔等。
private boolean initialize_publisher(SmartPublisherJniV2 lib_publisher, long handle, int width, int height, int fps, int gop) {if (null == lib_publisher) {Log.e(TAG, "initialize_publisher lib_publisher is null");return false;}if (0 == handle) {Log.e(TAG, "initialize_publisher handle is 0");return false;}if (videoEncodeType == 1) {int kbps = LibPublisherWrapper.estimate_video_hardware_kbps(width, height, fps, true);Log.i(TAG, "h264HWKbps: " + kbps);int isSupportH264HWEncoder = lib_publisher.SetSmartPublisherVideoHWEncoder(handle, kbps);if (isSupportH264HWEncoder == 0) {lib_publisher.SetNativeMediaNDK(handle, 0);lib_publisher.SetVideoHWEncoderBitrateMode(handle, 1); // 0:CQ, 1:VBR, 2:CBRlib_publisher.SetVideoHWEncoderQuality(handle, 39);lib_publisher.SetAVCHWEncoderProfile(handle, 0x08); // 0x01: Baseline, 0x02: Main, 0x08: High// lib_publisher.SetAVCHWEncoderLevel(handle, 0x200); // Level 3.1// lib_publisher.SetAVCHWEncoderLevel(handle, 0x400); // Level 3.2// lib_publisher.SetAVCHWEncoderLevel(handle, 0x800); // Level 4lib_publisher.SetAVCHWEncoderLevel(handle, 0x1000); // Level 4.1 多數(shù)情況下,這個夠用了//lib_publisher.SetAVCHWEncoderLevel(handle, 0x2000); // Level 4.2// lib_publisher.SetVideoHWEncoderMaxBitrate(handle, ((long)h264HWKbps)*1300);Log.i(TAG, "Great, it supports h.264 hardware encoder!");}} else if (videoEncodeType == 2) {int kbps = LibPublisherWrapper.estimate_video_hardware_kbps(width, height, fps, false);Log.i(TAG, "hevcHWKbps: " + kbps);int isSupportHevcHWEncoder = lib_publisher.SetSmartPublisherVideoHevcHWEncoder(handle, kbps);if (isSupportHevcHWEncoder == 0) {lib_publisher.SetNativeMediaNDK(handle, 0);lib_publisher.SetVideoHWEncoderBitrateMode(handle, 1); // 0:CQ, 1:VBR, 2:CBRlib_publisher.SetVideoHWEncoderQuality(handle, 39);// libPublisher.SetVideoHWEncoderMaxBitrate(handle, ((long)hevcHWKbps)*1200);Log.i(TAG, "Great, it supports hevc hardware encoder!");}}boolean is_sw_vbr_mode = true;//H.264 software encoderif (is_sw_vbr_mode) {int is_enable_vbr = 1;int video_quality = LibPublisherWrapper.estimate_video_software_quality(width, height, true);int vbr_max_kbps = LibPublisherWrapper.estimate_video_vbr_max_kbps(width, height, fps);lib_publisher.SmartPublisherSetSwVBRMode(handle, is_enable_vbr, video_quality, vbr_max_kbps);}if (is_pcma_) {lib_publisher.SmartPublisherSetAudioCodecType(handle, 3);} else {lib_publisher.SmartPublisherSetAudioCodecType(handle, 1);}lib_publisher.SetSmartPublisherEventCallbackV2(handle, new EventHandlerPublisherV2().set(handler_, record_executor_));lib_publisher.SmartPublisherSetSWVideoEncoderProfile(handle, 3);lib_publisher.SmartPublisherSetSWVideoEncoderSpeed(handle, 2);lib_publisher.SmartPublisherSetGopInterval(handle, gop);lib_publisher.SmartPublisherSetFPS(handle, fps);// lib_publisher.SmartPublisherSetSWVideoBitRate(handle, 600, 1200);boolean is_noise_suppression = true;lib_publisher.SmartPublisherSetNoiseSuppression(handle, is_noise_suppression ? 1 : 0);boolean is_agc = false;lib_publisher.SmartPublisherSetAGC(handle, is_agc ? 1 : 0);int echo_cancel_delay = 0;lib_publisher.SmartPublisherSetEchoCancellation(handle, 1, echo_cancel_delay);return true;
}
發(fā)布RTSP流成功后,會回調(diào)上來可供拉流的RTSP URL:
private static class EventHandlerPublisherV2 implements NTSmartEventCallbackV2 {@Overridepublic void onNTSmartEventCallbackV2(long handle, int id, long param1, long param2, String param3, String param4, Object param5) {switch (id) {...case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL:publisher_event = "RTSP服務(wù)URL: " + param3;break;}}
}
獲取RTSP Session會話數(shù):
//獲取RTSP會話數(shù)
class ButtonGetRtspSessionNumbersListener implements View.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);}}
}//當(dāng)前RTSP會話數(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)前客戶會話數(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();
}
onDestroy() 的時候,調(diào)研UnInitRtspServer()即可:
@Override
protected void onDestroy() {Log.i(TAG, "activity destory!");stopAudioRecorder();stopRtspPublisher();stopRtspService();isRTSPServiceRunning = false;stream_publisher_.release();if (libPublisher != null)libPublisher.UnInitRtspServer(); //如已啟用內(nèi)置服務(wù)功能(InitRtspServer),調(diào)用UnInitRtspServer, 注意,即便是啟動多個RTSP服務(wù),也只需調(diào)用UnInitRtspServer一次stopLayerPostThread();if (camera2Helper != null) {camera2Helper.release();}super.onDestroy();
}
總結(jié)
Android上實(shí)現(xiàn)RTSP服務(wù)器是一個極具挑戰(zhàn)的任務(wù),功能設(shè)計這塊,除了需要支持接編碼前音視頻數(shù)據(jù)外,還需要支持對接編碼后音視頻數(shù)據(jù),并實(shí)現(xiàn)本地錄像、快照等功能組合使用。需要注意的是,就像海康、大華的攝像頭一樣,對外的并發(fā),一般限于4-8個,Android設(shè)備的性能一般來說,可能不足以處理高負(fù)載的RTSP服務(wù)器,但是小并發(fā)模式下,能穩(wěn)定的運(yùn)行,就達(dá)到設(shè)計初衷了,感興趣的開發(fā)者,可以單獨(dú)跟單獨(dú)探討。