如何实现Android端获取RTSP|RTMP流转推RTMP

技术背景

最近不少开发者找到我们,他们在做智能家居等传统行业时,希望实现在Android板件拉取本地的RTSP或RTMP流,然后对外推送RTMP出去,亦或内部启个轻量级RTSP服务,提供个对外对接的媒介URL,简单来说,设计架构图如下:

基于上诉诉求,我们以大牛直播SDK (官方)Android端的 SmartRelayDemoV2 工程为例,大概介绍下相关实现。

整体设计

1. 拉流:通过RTSP|RTMP直播播放SDK的数据回调接口,拿到音视频数据;

2. 转推:通过RTMP直播推送SDK的编码后数据输入接口,把回调上来的数据,传给RTMP直播推送模块,实现RTSP|RTMP数据流到RTMP服务器的转发;

3. 录像:如果需要录像,借助RTSP|RTMP直播播放SDK,拉到音视频数据后,直接存储MP4文件即可;

4. 快照:如果需要实时快照,拉流后,解码调用播放端快照接口,生成快照,因为快照涉及到video数据解码,如无必要,可不必开启,不然会额外消耗性能。

5. 拉流预览:如需预览拉流数据,只要调用播放端的播放接口,即可实现拉流数据预览;

6. 数据转AAC后转发:考虑到好多监控设备出来的音频可能是PCMA/PCMU的,如需要更通用的音频格式,可以转AAC后,在通过RTMP推送;

7. 转推RTMP实时静音:只需要在传audio数据的地方,加个判断即可;

8. 拉流速度反馈:通过RTSP播放端的实时码率反馈event,拿到实时带宽占用即可;

9. 整体网络状态反馈:考虑到有些摄像头可能会临时或异常关闭,RTMP服务器亦是,可以通过推拉流的event回调状态,查看那整体网络情况,如此界定:是拉不到流,还是推不到RTMP服务器;

10. 数据注入轻量级RTSP服务:拉流的数据,注入轻量级RTSP服务,对外提供RTSP URL。

先上图

Demo主要实现了以下几个功能点展示:

1. 设置RTMP、RTSP拉流的URL;

2. 设置转推RTMP的URL;

3. 实时播放|录像过程中,实时静音、实施快照;

4. 实时播放;

5. 实时录像;

6. 拉取的流数据,实时转推,对应“开始推流”;

7. 拉取的流数据,注入轻量级RTSP服务,启动服务后,发布RTSP流,对外提供可访问的RTSP URL。

注意:以上播放、录像、转推RTMP、注入轻量级RTSP服务四者是可单独工作,也可随时启动或停止相关功能,互不影响。

相关代码实现

开始拉流

拉流的目的,主要是启动数据回调,注意:拉流并不是直接播放出来窗口,只是拿数据,如果需要本地预览拉流数据,可以点击“开始播放”。

注意:“开始推流”和“发布RTSP流”之前,一定要先“开始拉流”,拿到音视频数据。

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

这里调到OpenPullHandle()封装,其实就是启动调研Player的Open()接口,获取到player handle,然后设置一下基础数据接口,比如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);//设置RTSP超时时间int rtsp_timeout = 12;libPlayer.SmartPlayerSetRTSPTimeout(playerHandle, rtsp_timeout);//设置RTSP TCP/UDP模式自动切换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;// 如果第二个参数设置为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;}

开始推流调到了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;}}

启动RTSP服务

	//启动/停止RTSP服务class ButtonRtspServiceListener implements OnClickListener {public void onClick(View v) {if (isRTSPServiceRunning) {stopRtspService();btnRtspService.setText("启动RTSP服务");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, "创建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端口失败! 请检查端口是否重复或者端口不在范围内!");}//String user_name = "admin";//String password = "12345";//libPublisher.SetRtspServerUserNamePassword(rtsp_handle_, user_name, password);//一般来说单播网络设备支持的好,wifi组播很多路由器不支持,默认单播模式;如需使用组播模式,确保设备支持后,打开注释代码测试即可/*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, "启动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服务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;}

开始发布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, "调用发布rtsp流接口失败!");if (!isPushing){libPublisher.SmartPublisherClose(publisherHandle);publisherHandle = 0;}return false;}isRTSPPublisherRunning = true;return true;}

停止发布RTSP流

	//停止发布RTSP流private void stopRtspPublisher(){if(!isRTSPPublisherRunning)return;if (libPublisher != null) {libPublisher.StopRtspStream(publisherHandle);}if (!isPushing && !isRTSPServiceRunning){libPublisher.SmartPublisherClose(publisherHandle);publisherHandle = 0;}isRTSPPublisherRunning = false;}

获取RTSP连接会话数

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

总结

以上是大概的流程,感兴趣的开发者可自行参考。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QT实现低延迟的RTSP、RTMP播放器

好多开发者在QT环境下实现RTMP或RTSP播放时,首先考虑到的是集成VLC,集成后,却发现VLC在延迟、断网重连、稳定性等各个方面不尽人意,无法满足上线环境需求。本文以调用大牛直播SDK(官方)的Windows平台播放端…

Android对接实现内网无纸化会议|智慧教室|实时同屏功能

背景 本文主要讲的是基于Android平台实现RTMP的技术方案设计,基础架构图如下: 组网注意事项 1. 组网:无线组网,需要好的AP模块才能撑得住大的并发流量,推送端到AP,最好是有线网链接; 2. 服务…

Windows平台RTMP推送|轻量级RTSP服务实现本地摄像头|屏幕|叠加数据预览

背景 大家在做Windows平台RTMP推送或轻量级RTSP服务的时候,不管是采集屏幕还是采集摄像头,亦或屏幕摄像头的叠加模式,总会有这样的诉求,采集到的数据,希望能本地看看具体采集的数据或者图像实际效果,也就是…

Windows平台RTMP|RTSP播放器为什么要兼容GDI绘制

为什么要支持GDI 先说结论,Windows平台播放渲染这块,一般来说99%以上的机器都是支持D3D的,实现GDI模式绘制,除了为了好的兼容性外,在远程连接的场景下,D3D创建不成功,需要使用GDI模式。 简单来…

Android平台实现Unity3D下RTMP推送

像Unity3D下的RTMP或RTSP播放器一样,好多开发者苦于在Unity环境下,如何高效率低延迟的把数据采集并编码实时推送到流媒体服务器,实现Unity场景下的低延迟推拉流方案。 关于屏幕采集,有两种方案: 1. 直接封装Android原…

Windows平台实现Unity下窗体|摄像头|屏幕采集推送

技术背景 随着Unity3D的应用范围越来越广,越来越多的行业开始基于Unity3D开发产品,如传统行业中虚拟仿真教育、航空工业、室内设计、城市规划、工业仿真等领域。 基于此,好多开发者苦于在Unity环境下,没有低延迟的推拉流解决方案…

数据推送选择GB28181、RTSP还是RTMP?

GB/T28181 国标GB/T28181协议全称《安全防范视频监控联网系统信息传输、交换、控制技术要求》,是一个定义视频联网传输和设备控制标准的白皮书,由公安部科技信息化局提出,该标准规定了城市监控报警联网系统中信息传输、交换、控制的互联结构…

Android平台基于RTMP或RTSP的一对一音视频互动技术方案探讨

背景 随着智能门禁等物联网产品的普及,越来越多的开发者对音视频互动体验提出了更高的要求。目前市面上大多一对一互动都是基于WebRTC,优点不再赘述,我们这里先说说可能需要面临的问题:WebRTC的服务器部署非常复杂,可…

Android前端音视频数据接入GB28181平台意义

技术背景 在我们研发Android平台GB28181前端音视频接入模块之前,业内听到最多的是,如何用Android或者Windows端,在没有国标IPC设备的前提下,模拟GB28181的信令和媒体流交互流程,实现GB28181整体方案的测试&#xff1f…

std::atomic和std::mutex区别

​std::atomic介绍​ ​模板类std::atomic是C11提供的原子操作类型&#xff0c;头文件 #include<atomic>。​在多线程调用下&#xff0c;利用std::atomic可实现数据结构的无锁设计。​​ ​和互斥量的不同之处在于&#xff0c;std::atomic原子操作&#xff0c;主要是保…

C++ std::remove/std::remove_if/erase用法探讨

​std::remove 不会改变输入vector/string的长度。其过程相当于去除指定的字符&#xff0c;剩余字符往前靠。后面的和原始字符保持一致。​ 需要注意的是&#xff0c;remove函数是通过覆盖移去的&#xff0c;如果容器最后一个值刚好是需要删除的&#xff0c;则它无法覆盖掉容器…

再谈NULL和nullptr(C++11)区别

在谈NULL和nullptr区别之前&#xff0c;我们先看段代码&#xff1a; #include "stdafx.h" #include <iostream>using namespace std; void func(void *p) {cout << "p is pointer " << p << endl; } void func(int num) {cout &l…

C++11新特性探索:原始字符串字面值(raw string literal)

原始字符串字面值(raw string literal)是C11引入的新特性。 原始字符串简单来说&#xff0c;“原生的、不加处理的”&#xff0c;字符表示的就是自己&#xff08;所见即所得&#xff09;&#xff0c;引号、斜杠无需 “\” 转义&#xff0c;比如常用的目录表示&#xff0c;引入…

Android国标接入终端实现GB28181实时位置(MobilePosition)上报

技术背景 在实现本文提到的Android平台国标GB28181接入终端的实时位置上报之前&#xff0c;之前已经完成了Android终端GB28181常规功能接入&#xff0c;采集到实时音视频数据&#xff0c;编码PS打包后&#xff0c;按需传到GB28281服务平台&#xff0c;媒体流支持最新GB28181-2…

基于RTMP的智慧数字人|AI数字人传输技术方案探讨

技术背景 随着智慧数字人、AI数字人的兴起&#xff0c;越来越多的公司着手构建​全息、真实感数字角色等技术合成的数字仿真人虚拟形象&#xff0c;通过“虚拟形象语音交互&#xff08;T-T-S、ASR&#xff09;自然语言理解&#xff08;NLU&#xff09;深度学习”&#xff0c;构…

​GB28181心跳机制探讨和技术实现

​GB/T 28181-2016心跳机制​ ​通过周期性的状态信息报送&#xff0c;实现注册服务器与源设备之间的状态检测即心跳机制。 ​ ​心跳发送方、接收方需统一配置“心跳间隔”参数&#xff0c;按照“心跳间隔”定时发送心跳消息&#xff0c;默认心跳间隔60s。心跳发送方、接收方…

Unity3D下Linux平台播放RTSP或RTMP流

背景 尽管Windows平台有诸多优势&#xff0c;Linux平台的发展还是势不可挡&#xff0c;特别实在传统行业&#xff0c;然而Linux生态构建&#xff0c;总是差点意思&#xff0c;特别是有些常用的组件&#xff0c;本文基于已有的Linux平台RTSP、RTMP播放模块&#xff0c;构建Unit…

Unity3D平台实现全景实时RTMP|RTSP流渲染

好多开发者的使用场景&#xff0c;需要在Windows特别是Android平台实现Unity3D的全景实时视频渲染&#xff0c;本文以Windows平台为例&#xff0c;简单介绍下具体实现&#xff1a; 如果是RTSP或RTMP流数据&#xff0c;实际上难点&#xff0c;主要在于拉取RTSP或RTMP流&#xf…

C++17新特性之std::string_view

std::string_view系C17标准发布后新增的内容&#xff0c;类成员变量包含两个部分&#xff1a;字符串指针和字符串长度&#xff0c;相比std::string, std::string_view涵盖了std::string的所有只读接口。如果生成的std::string无需进行修改操作&#xff0c;可以把std::string转换…

Android平台实现RTSP|RTMP转GB28181网关接入

背景 在事先Android平台RTSP、RTMP转GB28181网关之前&#xff0c;我们已经实现了Android平台GB28181的接入&#xff0c;可实现Android平台采集到的音视频数据&#xff0c;编码后&#xff0c;打包按需发到GB28181服务平台。此外&#xff0c;拉流端&#xff0c;我们已经有了成熟…