Android平台实现RTSP拉流转发至轻量级RTSP服务

技术背景

我们在做Android平台RTSP转发模块的时候,有公司提出来这样的技术需求,他们希望拉取外部RTSP摄像头的流,然后提供个轻量级RTSP服务,让内网其他终端过来拉流。实际上,这块,大牛直播SDK前几年就已经实现。

技术实现

拉流的话,很好理解,其实就是播放端,把未解码的数据,直接回调上来,如果需要预览,直接底层绘制即可。单纯的数据回调,对性能消耗不大。

回调上来的数据,可以作为轻量级RTSP服务的数据源(投递编码后数据),推送端,只要启动RTSP服务,然后发布RTSP流即可。

先说拉流,开始拉流、停止拉流实现:

/*
 * SmartPlayer.java
 * Author: daniusdk.com
 */
private boolean StartPull()
{
	if ( isPulling )
		return false;

	if(!isPlaying)
	{
		if (!OpenPullHandle())
			return false;
	}

	libPlayer.SmartPlayerSetAudioDataCallback(player_handle_, new PlayerAudioDataCallback(stream_publisher_));
	libPlayer.SmartPlayerSetVideoDataCallback(player_handle_, new PlayerVideoDataCallback(stream_publisher_));

	int is_pull_trans_code  = 1;
	libPlayer.SmartPlayerSetPullStreamAudioTranscodeAAC(player_handle_, is_pull_trans_code);

	int startRet = libPlayer.SmartPlayerStartPullStream(player_handle_);

	if (startRet != 0) {
		Log.e(TAG, "Failed to start pull stream!");

		if(!isPlaying)
		{
			releasePlayerHandle();
		}

		return false;
	}

	isPulling = true;
	return true;
}

private void StopPull()
{
	if ( !isPulling )
		return;

	isPulling = false;

	if (null == libPlayer || 0 == player_handle_)
		return;

	libPlayer.SmartPlayerStopPullStream(player_handle_);

	if ( !isPlaying)
	{
		releasePlayerHandle();
	}
}

音频回调处理:

class PlayerAudioDataCallback implements NTAudioDataCallback
{
	private WeakReference<LibPublisherWrapper> publisher_;
	private int audio_buffer_size = 0;
	private int param_info_size = 0;

	private ByteBuffer audio_buffer_ = null;
	private ByteBuffer parameter_info_ = null;

	public PlayerAudioDataCallback(LibPublisherWrapper publisher) {
		if (publisher != null)
			publisher_ = new WeakReference<>(publisher);
	}

	@Override
	public ByteBuffer getAudioByteBuffer(int size)
	{
		//Log.i("getAudioByteBuffer", "size: " + size);

		if( size < 1 )
		{
			return null;
		}

		if ( size <= audio_buffer_size && audio_buffer_ != null )
		{
			return audio_buffer_;
		}

		audio_buffer_size = size + 512;
		audio_buffer_size = (audio_buffer_size+0xf) & (~0xf);

		audio_buffer_ = ByteBuffer.allocateDirect(audio_buffer_size);

		// Log.i("getAudioByteBuffer", "size: " + size + " buffer_size:" + audio_buffer_size);

		return audio_buffer_;
	}

	@Override
	public ByteBuffer getAudioParameterInfo(int size)
	{
		//Log.i("getAudioParameterInfo", "size: " + size);

		if(size < 1)
		{
			return null;
		}

		if ( size <= param_info_size &&  parameter_info_ != null )
		{
			return  parameter_info_;
		}

		param_info_size = size + 32;
		param_info_size = (param_info_size+0xf) & (~0xf);

		parameter_info_ = ByteBuffer.allocateDirect(param_info_size);

		//Log.i("getAudioParameterInfo", "size: " + size + " buffer_size:" + param_info_size);

		return parameter_info_;
	}

	public void onAudioDataCallback(int ret, int audio_codec_id, int sample_size, int is_key_frame, long timestamp, int sample_rate, int channel, int parameter_info_size, long reserve)
	{
		//Log.i("onAudioDataCallback", "ret: " + ret + ", audio_codec_id: " + audio_codec_id + ", sample_size: " + sample_size + ", timestamp: " + timestamp +
		//		",sample_rate:" + sample_rate);

		if ( audio_buffer_ == null)
			return;

		LibPublisherWrapper publisher = publisher_.get();
		if (null == publisher)
			return;

		if (!publisher.is_publishing())
			return;

		audio_buffer_.rewind();

		publisher.PostAudioEncodedData(audio_codec_id, audio_buffer_, sample_size, is_key_frame, timestamp, parameter_info_, parameter_info_size);
	}
}

视频回调数据:

class PlayerVideoDataCallback implements NTVideoDataCallback
{
	private WeakReference<LibPublisherWrapper> publisher_;

	private int video_buffer_size = 0;
	private ByteBuffer video_buffer_ = null;

	public PlayerVideoDataCallback(LibPublisherWrapper publisher) {
		if (publisher != null)
			publisher_ = new WeakReference<>(publisher);
	}

	@Override
	public ByteBuffer getVideoByteBuffer(int size)
	{
		//Log.i("getVideoByteBuffer", "size: " + size);

		if( size < 1 )
		{
			return null;
		}

		if ( size <= video_buffer_size &&  video_buffer_ != null )
		{
			return  video_buffer_;
		}

		video_buffer_size = size + 1024;
		video_buffer_size = (video_buffer_size+0xf) & (~0xf);

		video_buffer_ = ByteBuffer.allocateDirect(video_buffer_size);

		// Log.i("getVideoByteBuffer", "size: " + size + " buffer_size:" + video_buffer_size);

		return video_buffer_;
	}

	public void onVideoDataCallback(int ret, int video_codec_id, int sample_size, int is_key_frame, long timestamp, int width, int height, long presentation_timestamp)
	{
		//Log.i("onVideoDataCallback", "ret: " + ret + ", video_codec_id: " + video_codec_id + ", sample_size: " + sample_size + ", is_key_frame: "+ is_key_frame +  ", timestamp: " + timestamp +
		//		",presentation_timestamp:" + presentation_timestamp);

		if ( video_buffer_ == null)
			return;

		LibPublisherWrapper publisher = publisher_.get();
		if (null == publisher)
			return;

		if (!publisher.is_publishing())
			return;

		video_buffer_.rewind();

		publisher.PostVideoEncodedData(video_codec_id, video_buffer_, sample_size, is_key_frame, timestamp, presentation_timestamp);
	}
}

启动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 = 28554;
			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;
		}
	}
}

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

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

获取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外部拉流,不需要解码,配合大牛直播SDK的SmartPlayer播放器,延迟和直连的,差别不大,整体毫秒级,延迟非常低,巡检或监控类场景,都可以达到相应的技术指标。如果需要二次水印,也可以回调解码后的yuv或rgb数据,推送端添加二次文字或图片水印后,编码输出,这种在一些合成类场景,比如智慧煤矿、管廊隧道等行业,非常适用,感兴趣的开发者,可以单独跟我探讨。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/783321.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python网络编程-TCP/IP

链路层 帧组成&#xff08;按顺序&#xff09;&#xff1a; 目标MAC&#xff1a;6B 源MAC&#xff1a;6B 类型&#xff1a;2B 数据&#xff1a;46B-1500B CRC&#xff1a;4B 其中&#xff0c;源MAC为主机网卡地址&#xff0c;类型为来源网络层的数据类型&#xff0c;ipv…

基于Java+SpringMvc+Vue技术智慧校园系统设计与实现--60页及以上论文参考

博主介绍&#xff1a;硕士研究生&#xff0c;专注于信息化技术领域开发与管理&#xff0c;会使用java、标准c/c等开发语言&#xff0c;以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年&#xff0c;拥有近12年的管理工作经验&#xff0c;拥有较丰富的技术架…

Python 爬虫 tiktok API接口获取tiktok用户关注列表

此接口可获取tiktok用户关注列表。若有需要&#xff0c;请点击文末链接联系我们。 详细采集页面如下https://www.tiktok.com/quanap_official 请求API http://api.xxxx.com/tt/user/following?user_id7252644648840381445&count10&offset0&tokentest 请求参数 返…

雅虎财经世媒讯全球软文发稿对于企业宣发的意义

在当今信息爆炸的时代&#xff0c;企业宣传和品牌推广的方式变得多种多样&#xff0c;其中软文发稿成为了一种颇受欢迎的宣传手段。雅虎作为全球知名的门户网站之一&#xff0c;拥有广泛的用户基础和强大的影响力&#xff0c;通过雅虎进行软文发稿&#xff0c;不仅可以有效提升…

移远BC28_opencpu方案_开发环境搭建

OPEN CPU 代码采用的是 Python 脚本写的 scons 自动化构建工具。从构建这个角度说&#xff0c;它与 GNU make 是同一类的工具。它是一种改进&#xff0c;并跨平台的 gnu make 替代工具&#xff0c;其集成功能类似于 autoconf/automake。 这里给出简单安装方式

WAIC | 2024年世界人工智能大会“数学与人工智能”学术会议成功举办!

由斯梅尔数学与计算研究院&#xff08;Smale Institue of Mathematics & Computation&#xff09;主办的2024年世界人工智能大会(WAIC)“数学与人工智能”学术会议7月4日在上海世博中心圆满落幕&#xff01;作为全球性高级别学术研讨会&#xff0c;此次会议由华院计算技术&…

如何通过ip地址判断网络类别

在计算机网络中&#xff0c;IP地址不仅是设备在网络中的唯一标识&#xff0c;同时也隐含了网络类别的信息。了解如何根据IP地址判断网络类别&#xff0c;对于网络管理员、系统工程师以及网络爱好者来说都是一项基本技能。本文将详细介绍如何通过IP地址判断网络类别。 一、IP地址…

伦敦银交易平台价格的突破成不成功?这点很重要!

在伦敦银交易中&#xff0c;当银价出现突破的时候&#xff0c;也正是引起很多投资者关注的时候。一旦银价出现突破&#xff0c;很可能是新行情的开端。但是做过突破交易&#xff0c;有相关经验的朋友会发现&#xff0c;自己在伦敦银交易平台做突破的时候&#xff0c;也并不是每…

等保2.0中,云计算平台如何做到数据的分类和加密?

在信息化浪潮的激荡中&#xff0c;云计算平台已然成为企业智慧运作的心脏&#xff0c;承载着海量的数据资产。随着中国国家网络安全等级保护制度迈入2.0时代&#xff0c;对云计算平台的数据安全提出了更为严苛的要求。在这一背景下&#xff0c;如何巧妙地编织数据的分类之网&am…

MySQL的慢sql

什么是慢sql 每执行一次sql&#xff0c;数据库除了会返回执行结果以外&#xff0c;还会返回sql执行耗时&#xff0c;以mysql数据库为例&#xff0c;当我们开启了慢sql监控开关后&#xff0c;默认配置下&#xff0c;当sql的执行时间大于10s&#xff0c;会被记录到慢sql的日志文件…

【AI资讯】可以媲美GPT-SoVITS的低显存开源文本转语音模型Fish Speech

Fish Speech是一款由fishaudio开发的全新文本转语音工具&#xff0c;支持中英日三种语言&#xff0c;语音处理接近人类水平&#xff0c;使用Flash-Attn算法处理大规模数据&#xff0c;提供高效、准确、稳定的TTS体验。 Fish Audio

【MySQL】MySQL连接池原理与简易网站数据流动是如何进行

MySQL连接池原理与简易网站数据流动是如何进行 1.MySQL连接池原理2.简易网站数据流动是如何进行 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f60…

Avalonia 常用控件四 Text Controls

1、AutoCompleteBox <StackPanel Margin"20"><TextBlock Margin"0 5">选择一种动物</TextBlock><AutoCompleteBox x:Name"animals" FilterMode"StartsWith"/><!--AutoCompleteBox:Items:要匹配的项目列表。…

如何检查 Windows 版本?这几种方法都可以查看

设置界面查看 要想查看电脑安装的 Windows 版本我们可以在设置界面进行查看&#xff0c;打开设置界面之后点击系统。 接下来在左边框中往下滑动&#xff0c;点击关于选项&#xff0c;然后在右边框中往下滑动找到 Windows 规格模块&#xff0c;在这里就可以看见安装的 Windows …

windows sshkeygen 多平台添加配置

文章目录 .ssh目录生成新的ssh配置添加公钥到仓库验证 .ssh目录 windows下一般为&#xff1a;C:\Users\15237.ssh &#xff0c;其中“15237”为当前登录用户 生成新的ssh .ssh目录下打开“Git Bash Here”&#xff08;如果没有&#xff0c;先安装 Git 软件&#xff09; 执行…

学会python——用python生成一个验证码(python实例二十)

目录 1.认识Python 2.环境与工具 2.1 python环境 2.2 Visual Studio Code编译 3.生成验证码 3.1 代码构思 3.2 代码实例 3.3 运行如果 4.总结 1.认识Python Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具有很强的可读性&…

无人直播怎么玩,一文带你了解AI小姐姐自动换装玩法

最近经常有小伙伴问我 就是像这种&#xff0c;一刷礼物&#xff0c;小姐姐就换装的视频到底该怎么做 今天就来教大家 如何来制作这种直播视频 第一步&#xff1a;搭建OBS 1、设置屏幕分辨率&#xff1a; 背景&#xff1a;因为一般初始状态&#xff0c;屏幕是横屏的&#xf…

从零开始的python学习生活1

python函数的对返回值 本来多个return是不行的 这种语法就能接受多个返回值 def hanshu():return 1,"hello",True x,y,z hanshu() print(x) print(y) print(z)函数的多种传参方式 提前说明白了顺序就无所谓了 关键字传递一个传递参数&#xff0c;一个传递键值…

04-Haproxy搭建Web群集

理论讲解 Haproxy 是目前比较流行的一种群集调度工具&#xff0c;同类群集调度工具有很多&#xff0c;如LVS 和Nginx。相比较而言&#xff0c;LVS 性能最好&#xff0c;但是搭建相对复杂:Nginx的upstream模块支持群集功能&#xff0c;但是对群集节点健康检查功能不强&#xff…

人员定位系统于不同场景的实际应用

人员定位系统的应用&#xff0c;尽管还没有做到大范围的普及&#xff0c;但是这一系统在不同企业&#xff0c;不同单位的实际应用效果还是很好的&#xff0c;所以人员定位系统也应用于不同场景当中了&#xff0c;那么&#xff0c;本文就来讲讲这一系统在不同场景的实际应用。 人…