当前位置: 首页 > article >正文

如何在Android上实现RTSP服务器

技术背景

在Android上实现RTSP服务器确实是一个不太常见的需求,因为Android平台主要是为客户端应用设计的。在一些内网场景下,我们更希望把安卓终端或开发板,作为一个IPC(网络摄像机)一样,对外提供个拉流的rtsp url,然后把摄像头麦克风甚至屏幕采集的数据,共享出去,轻量级RTSP的设计理念脱颖而出。

轻量级RTSP服务设计初衷,就是避免用户单独部署RTSP或者RTMP服务,实现本地的音视频数据(如摄像头、麦克风),编码后,汇聚到内置RTSP服务,对外提供可供拉流的RTSP URL,轻量级RTSP服务,适用于内网环境下,对并发要求不高的场景,支持H.264/H.265,支持RTSP鉴权、单播、组播模式,考虑到单个服务承载能力,我们支持同时创建多个RTSP服务,并支持获取当前RTSP服务会话连接数。

如何实现RTSP服务器

如果你找不到合适的库,或者需要更高级的功能,你可以考虑编写自己的RTSP服务器:

  • 了解RTSP协议:首先,你需要深入了解RTSP协议的工作原理,包括其消息格式、会话管理和流控制机制。
  • 网络编程:在Android上,你可以使用Java的Socket API来处理网络通信,也可以直接底层实现,然后对上提供jni的接口。你需要编写代码来监听端口、接收RTSP请求、解析请求并发送响应。
  • 媒体处理:RTSP服务器需要能够捕获、编码和传输媒体数据。你可以使用Android的Camera或Camera2的API来捕获视频,并使用FFmpeg或Android的MediaCodec API来实现音视频数据编码。
  • 多线程和并发:RTSP服务器需要处理多个并发客户端连接。你可以使用Java的线程或并发API来管理这些连接。

功能设计

一个完整的RTSP服务,需要设计的功能如下:

  •  ​[视频格式]H.264/H.265(Android H.265硬编码);
  •  [音频格式]G.711 A律、AAC;
  • 协议:RTSP;
  •  [音量调节]Android平台采集端支持实时音量调节;
  •  [H.264硬编码]支持H.264特定机型硬编码;
  •  [H.265硬编码]支持H.265特定机型硬编码;
  • [音视频]支持纯音频/纯视频/音视频;
  • [摄像头]支持采集过程中,前后摄像头实时切换;
  • 支持帧率、关键帧间隔(GOP)、码率(bit-rate)设置;
  • [实时水印]支持动态文字水印、png水印;
  • [实时快照]支持实时快照;
  • [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;
  • [外部编码前视频数据对接]支持YUV数据对接;
  • [外部编码前音频数据对接]支持PCM对接;
  • [外部编码后视频数据对接]支持外部H.264、H.265数据对接;
  • [外部编码后音频数据对接]外部AAC数据对接;
  • [扩展录像功能]支持和录像SDK组合使用,录像相关功能。​
  • 支持RTSP端口设置;
  • 支持RTSP鉴权用户名、密码设置;
  • 支持获取当前RTSP服务会话连接数;
  • 支持Android 5.1及以上版本。

接口设计

Android内置轻量级RTSP服务SDK接口详解

调用描述

接口

接口描述

SmartRTSPServerSDK

初始化RTSP Server

InitRtspServer

Init rtsp server(和UnInitRtspServer配对使用,即便是启动多个RTSP服务,也只需调用一次InitRtspServer,请确保在OpenRtspServer之前调用)

创建一个rtsp server

OpenRtspServer

创建一个rtsp server,返回rtsp server句柄

设置端口

SetRtspServerPort

设置rtsp server 监听端口, 在StartRtspServer之前必须要设置端口

设置鉴权用户名、密码

SetRtspServerUserNamePassword

设置rtsp server 鉴权用户名和密码, 这个可以不设置,只有需要鉴权的再设置

获取rtsp server当前会话数

GetRtspServerClientSessionNumbers

获取rtsp server当前的客户会话数, 这个接口必须在StartRtspServer之后再调用

启动rtsp server

StartRtspServer

启动rtsp server

停止rtsp server

StopRtspServer

停止rtsp server

关闭rtsp server

CloseRtspServer

关闭rtsp server

UnInit rtsp server

UnInitRtspServer

UnInit rtsp server(和InitRtspServer配对使用,即便是启动多个RTSP服务,也只需调用一次UnInitRtspServer)

SmartRTSPServerSDK供Publisher调用的接口

设置rtsp的流名称

SetRtspStreamName

设置rtsp的流名称

给要发布的rtsp流设置rtsp server

AddRtspStreamServer

给要发布的rtsp流设置rtsp server, 一个流可以发布到多个rtsp server上,rtsp server的创建启动请参考OpenRtspServer和StartRtspServer接口

清除设置的rtsp server

ClearRtspStreamServer

清除设置的rtsp server

启动rtsp流

StartRtspStream

启动rtsp流

停止rtsp流

StopRtspStream

停止rtsp流

调用逻辑

以大牛直播SDK的Android平台Camera2对接为例,先初始化RTSP Server,启动RTSP服务,然后发布RTSP流即可,如果需要停止RTSP服务,那么先停止RTSP流,再停止RTSP服务即可:

/*
 * 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服务,也只需调用一次InitRtspServer,请确保在OpenRtspServer之前调用
}

启动、停止RTSP服务:

//启动/停止RTSP服务
class ButtonRtspServiceListener implements View.OnClickListener {
	public void onClick(View v) {
		if (isRTSPServiceRunning) {
			stopRtspService();

			btnRtspService.setText("启动RTSP服务");
			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, "创建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端口失败! 请检查端口是否重复或者端口不在范围内!");
			}

			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;
		}
	}
}

stopRtspService()实现如下:

//停止RTSP服务
private void stopRtspService() {
	if(!isRTSPServiceRunning)
	{
		return;
	}
	if (libPublisher != null && rtsp_handle_ != 0) {
		libPublisher.StopRtspServer(rtsp_handle_);
		libPublisher.CloseRtspServer(rtsp_handle_);
		rtsp_handle_ = 0;
	}
}

发布、停止RTSP流:

//发布/停止RTSP流
class ButtonRtspPublisherListener implements View.OnClickListener {
	public void onClick(View v) {
		if (stream_publisher_.is_rtsp_publishing()) {
			stopRtspPublisher();

			btnRtspPublisher.setText("发布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, "调用发布rtsp流接口失败!");
			return;
		}

		startAudioRecorder();
		startLayerPostThread();

		btnRtspPublisher.setText("停止RTSP流");
		btnGetRtspSessionNumbers.setEnabled(true);
		btnRtspService.setEnabled(false);
	}
}

stopRtspPublisher()实现如下:

//停止发布RTSP流
private void stopRtspPublisher() {
	stream_publisher_.StopRtspStream();
	stream_publisher_.try_release();

	if (!stream_publisher_.is_publishing())
		stopAudioRecorder();
}

其中,InitAndSetConfig()实现如下,通过调研SmartPublisherOpen()接口,生成推送实例句柄。

/*
 * 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);
}

对应的initialize_publisher()实现如下,设置软硬编码、帧率、关键帧间隔等。

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 多数情况下,这个够用了
			//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;
}

发布RTSP流成功后,会回调上来可供拉流的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服务URL: " + param3;
				break;
		}
	}
}

获取RTSP Session会话数:

//获取RTSP会话数
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);
		}
	}
}

//当前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();
}

onDestroy() 的时候,调研UnInitRtspServer()即可:

@Override
protected void onDestroy() {
	Log.i(TAG, "activity destory!");

	stopAudioRecorder();

	stopRtspPublisher();
	stopRtspService();
	isRTSPServiceRunning = false;

	stream_publisher_.release();

	if (libPublisher != null)
		libPublisher.UnInitRtspServer();      //如已启用内置服务功能(InitRtspServer),调用UnInitRtspServer, 注意,即便是启动多个RTSP服务,也只需调用UnInitRtspServer一次

	stopLayerPostThread();

	if (camera2Helper != null) {
		camera2Helper.release();
	}

	super.onDestroy();
}

总结

Android上实现RTSP服务器是一个极具挑战的任务,功能设计这块,除了需要支持接编码前音视频数据外,还需要支持对接编码后音视频数据,并实现本地录像、快照等功能组合使用。需要注意的是,就像海康、大华的摄像头一样,对外的并发,一般限于4-8个,Android设备的性能一般来说,可能不足以处理高负载的RTSP服务器,但是小并发模式下,能稳定的运行,就达到设计初衷了,感兴趣的开发者,可以单独跟单独探讨。


http://www.kler.cn/a/310909.html

相关文章:

  • 计算机网络(3)网络拓扑和IP地址,MAC地址,端口地址详解
  • NAT网络工作原理和NAT类型
  • JAVA题目笔记(十五)经典算法题
  • Scala入门基础(17.1)Set集习题
  • 单元测试、集成测试、系统测试有什么区别
  • 【大数据技术基础 | 实验十】Hive实验:部署Hive
  • 2024/9/19 408大题专训之五段式指令流水线题型总结
  • fastson与jackson入门
  • Windows本地制作java证书(与jeecgboot配置本地证书ssl问题)
  • 基于vue框架的宠物领养管理系统88v55(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
  • Python 数学建模——傅里叶变换时间序列分析
  • LeetCode 算法笔记-第 04 章 基础算法篇
  • Docker vs. containerd 深度剖析容器运行时
  • 【kafka-01】kafka安装和基本核心概念
  • CSP-J算法基础 树状结构与二叉树
  • C++笔记21•C++11•
  • PyRosetta Task介绍及示例代码
  • nginx基础篇(一)
  • 算法:双指针题目练习
  • MATLAB图像处理
  • CISP备考题库(八)
  • 术语“in law”(在分布上)
  • oracle表的类型
  • 当 PC 端和移动端共用一个域名时,避免 CDN 缓存页面混乱(nginx)
  • 基于MATLAB/Simulink的模型降阶方法介绍
  • Unity射击游戏开发教程:(36)敌人关卡生成器的设计和开发