最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android上實(shí)現(xiàn)RTSP服務(wù)器的方法

 更新時(shí)間:2024年11月05日 08:58:27   作者:音視頻牛哥  
在Android平臺(tái)實(shí)現(xiàn)RTSP服務(wù)器是一項(xiàng)挑戰(zhàn)性任務(wù),旨在無需部署獨(dú)立的RTSP/RTMP服務(wù),通過內(nèi)置輕量級RTSP服務(wù),實(shí)現(xiàn)本地音視頻數(shù)據(jù)的對外共享,本文介紹Android上實(shí)現(xiàn)RTSP服務(wù)器的方法,感興趣的朋友一起看看吧

技術(shù)背景

在Android上實(shí)現(xiàn)RTSP服務(wù)器確實(shí)是一個(gè)不太常見的需求,因?yàn)锳ndroid平臺(tái)主要是為客戶端應(yīng)用設(shè)計(jì)的。在一些內(nèi)網(wǎng)場景下,我們更希望把安卓終端或開發(fā)板,作為一個(gè)IPC(網(wǎng)絡(luò)攝像機(jī))一樣,對外提供個(gè)拉流的rtsp url,然后把攝像頭麥克風(fēng)甚至屏幕采集的數(shù)據(jù),共享出去,輕量級RTSP的設(shè)計(jì)理念脫穎而出。

輕量級RTSP服務(wù)設(shè)計(jì)初衷,就是避免用戶單獨(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)、單播、組播模式,考慮到單個(gè)服務(wù)承載能力,我們支持同時(shí)創(chuàng)建多個(gè)RTSP服務(wù),并支持獲取當(dāng)前RTSP服務(wù)會(huì)話連接數(shù)。

如何實(shí)現(xiàn)RTSP服務(wù)器

如果你找不到合適的庫,或者需要更高級的功能,你可以考慮編寫自己的RTSP服務(wù)器:

  • 了解RTSP協(xié)議:首先,你需要深入了解RTSP協(xié)議的工作原理,包括其消息格式、會(huì)話管理和流控制機(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ù)器需要處理多個(gè)并發(fā)客戶端連接。你可以使用Java的線程或并發(fā)API來管理這些連接。

功能設(shè)計(jì)

一個(gè)完整的RTSP服務(wù),需要設(shè)計(jì)的功能如下:

  •  ?[視頻格式]H.264/H.265(Android H.265硬編碼);
  •  [音頻格式]G.711 A律、AAC;
  • 協(xié)議:RTSP;
  •  [音量調(diào)節(jié)]Android平臺(tái)采集端支持實(shí)時(shí)音量調(diào)節(jié);
  •  [H.264硬編碼]支持H.264特定機(jī)型硬編碼;
  •  [H.265硬編碼]支持H.265特定機(jī)型硬編碼;
  • [音視頻]支持純音頻/純視頻/音視頻;
  • [攝像頭]支持采集過程中,前后攝像頭實(shí)時(shí)切換;
  • 支持幀率、關(guān)鍵幀間隔(GOP)、碼率(bit-rate)設(shè)置;
  • [實(shí)時(shí)水印]支持動(dòng)態(tài)文字水印、png水??;
  • [實(shí)時(shí)快照]支持實(shí)時(shí)快照;
  • [降噪]支持環(huán)境音、手機(jī)干擾等引起的噪音降噪處理、自動(dòng)增益、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ù)會(huì)話連接數(shù);
  • 支持Android 5.1及以上版本。

接口設(shè)計(jì)

Android內(nèi)置輕量級RTSP服務(wù)SDK接口詳解

調(diào)用描述

接口

接口描述

SmartRTSPServerSDK

初始化RTSP Server

InitRtspServer

Init rtsp server(和UnInitRtspServer配對使用,即便是啟動(dòng)多個(gè)RTSP服務(wù),也只需調(diào)用一次InitRtspServer,請確保在OpenRtspServer之前調(diào)用)

創(chuàng)建一個(gè)rtsp server

OpenRtspServer

創(chuàng)建一個(gè)rtsp server,返回rtsp server句柄

設(shè)置端口

SetRtspServerPort

設(shè)置rtsp server 監(jiān)聽端口, 在StartRtspServer之前必須要設(shè)置端口

設(shè)置鑒權(quán)用戶名、密碼

SetRtspServerUserNamePassword

設(shè)置rtsp server 鑒權(quán)用戶名和密碼, 這個(gè)可以不設(shè)置,只有需要鑒權(quán)的再設(shè)置

獲取rtsp server當(dāng)前會(huì)話數(shù)

GetRtspServerClientSessionNumbers

獲取rtsp server當(dāng)前的客戶會(huì)話數(shù), 這個(gè)接口必須在StartRtspServer之后再調(diào)用

啟動(dòng)rtsp server

StartRtspServer

啟動(dòng)rtsp server

停止rtsp server

StopRtspServer

停止rtsp server

關(guān)閉rtsp server

CloseRtspServer

關(guān)閉rtsp server

UnInit rtsp server

UnInitRtspServer

UnInit rtsp server(和InitRtspServer配對使用,即便是啟動(dòng)多個(gè)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, 一個(gè)流可以發(fā)布到多個(gè)rtsp server上,rtsp server的創(chuàng)建啟動(dòng)請參考OpenRtspServer和StartRtspServer接口

清除設(shè)置的rtsp server

ClearRtspStreamServer

清除設(shè)置的rtsp server

啟動(dòng)rtsp流

StartRtspStream

啟動(dòng)rtsp流

停止rtsp流

StopRtspStream

停止rtsp流

調(diào)用邏輯

以大牛直播SDK的Android平臺(tái)Camera2對接為例,先初始化RTSP Server,啟動(dòng)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配對使用,即便是啟動(dòng)多個(gè)RTSP服務(wù),也只需調(diào)用一次InitRtspServer,請確保在OpenRtspServer之前調(diào)用
}

啟動(dòng)、停止RTSP服務(wù):

//啟動(dòng)/停止RTSP服務(wù)
class ButtonRtspServiceListener implements View.OnClickListener {
	public void onClick(View v) {
		if (isRTSPServiceRunning) {
			stopRtspService();
			btnRtspService.setText("啟動(dòng)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, "啟動(dòng)rtsp server 成功!");
			} else {
				libPublisher.CloseRtspServer(rtsp_handle_);
				rtsp_handle_ = 0;
				Log.e(TAG, "啟動(dòng)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:CBR
			lib_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 4
			lib_publisher.SetAVCHWEncoderLevel(handle, 0x1000); // Level 4.1 多數(shù)情況下,這個(gè)夠用了
			//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:CBR
			lib_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 encoder
	if (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流成功后,會(huì)回調(diào)上來可供拉流的RTSP URL:

private static class EventHandlerPublisherV2 implements NTSmartEventCallbackV2 {
	@Override
	public 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會(huì)話數(shù):

//獲取RTSP會(huì)話數(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會(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();
}

onDestroy() 的時(shí)候,調(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, 注意,即便是啟動(dòng)多個(gè)RTSP服務(wù),也只需調(diào)用UnInitRtspServer一次
	stopLayerPostThread();
	if (camera2Helper != null) {
		camera2Helper.release();
	}
	super.onDestroy();
}

總結(jié)

Android上實(shí)現(xiàn)RTSP服務(wù)器是一個(gè)極具挑戰(zhàn)的任務(wù),功能設(shè)計(jì)這塊,除了需要支持接編碼前音視頻數(shù)據(jù)外,還需要支持對接編碼后音視頻數(shù)據(jù),并實(shí)現(xiàn)本地錄像、快照等功能組合使用。需要注意的是,就像海康、大華的攝像頭一樣,對外的并發(fā),一般限于4-8個(gè),Android設(shè)備的性能一般來說,可能不足以處理高負(fù)載的RTSP服務(wù)器,但是小并發(fā)模式下,能穩(wěn)定的運(yùn)行,就達(dá)到設(shè)計(jì)初衷了,感興趣的開發(fā)者,可以單獨(dú)跟單獨(dú)探討。

到此這篇關(guān)于如何在Android上實(shí)現(xiàn)RTSP服務(wù)器的文章就介紹到這了,更多相關(guān)Android RTSP服務(wù)器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

荔波县| 黄浦区| 额尔古纳市| 廊坊市| 咸丰县| 巴彦县| 利川市| 革吉县| 奇台县| 进贤县| 罗平县| 杂多县| 乐平市| 丰顺县| 理塘县| 盐津县| 阿合奇县| 深泽县| 寻乌县| 萍乡市| 永春县| 华亭县| 浙江省| 武清区| 定西市| 安阳县| 绥中县| 胶州市| 孟州市| 皋兰县| 公安县| 留坝县| 洪江市| 德州市| 磐安县| 青海省| 白银市| 西林县| 武夷山市| 镇江市| 宁津县|