技术背景

最近不少开发者找到我们,他们在做智能家居等传统行业时,希望实现在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);}}};

总结

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

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

  1. android端获取Tomcat服务器端json数据并通过listview显示

    大体描述: 大体意思是用eclipse ee创建一个Javaweb项目,该项目能从MySQL数据库中获取user表的数据,将数据封装成json格式,将此项目发布到本地Tomcat服务器,在androi ...

  2. Unity实现在Android端获取Android手机的唯一ID(设备号)(亲测Android11可用)

    Unity实现在Android端获取Android手机的唯一ID(设备号)(亲测Android11可用) 备注:测试版本Unity2020,理论上Unity2018以上都可用,未做测试 - 文章初衷 ...

  3. uni-app Android端获取设备已连接的WiFi IP地址

    1.manifest.json 在APP权限配置或源码中,添加需要的权限: android.permission.ACCESS_WIFI_STATE android.permission.INTERN ...

  4. 云豹app直播源码Android 端获取相册图片功能的具体实现教程

    app直播源码开发时,关于动态功能的实现,通常采用类似于朋友圈式图文+视频模式,这就需要通过访问手机相册获取用户图片进行下一步操作,接下来就请跟随小编一起,从app直播源码角度探究云豹直播系统在这个功 ...

  5. Android端获取步数

    参考:https://www.cnblogs.com/areful/p/13738596.html 目前没有找到前台服务之外的准确方法, 使用前台服务自行处理计数 调用系统已有接口,获取两个时间点数量 ...

  6. 海康大华等安防摄像机采用通用RTSP协议流转RTMP推送至Web无插件播放展示的流程方法

    行业现状 中国互联网化的进程已经越来越快了,各个行业都在进行着互联网化的改造,流媒体.音视频,作为跑在互联网上最大量级的数据类型,其从编码方式到传输协议到终端兼容都成为各家标准抢占的高点,RTMP.H ...

  7. Android 内置RTSP/RTMP服务器,实现局域网内视频推流与播放

    1. 背景 工作中有一个需求,在同一个局域网内, 需要将Android平板端(车机)上的摄像头上的画面,实时传输到手机上进行播放. 对于这个需求,我们想到了用RTSP/RTMP进行推流,然后在手机端拉 ...

  8. Android流媒体开发之路二:NDK C++开发Android端RTMP直播推流程序

    经过一番折腾,成功把RTMP直播推流代码,通过NDK交叉编译的方式,移植到了Android下,从而实现了Android端采集摄像头和麦克缝数据,然后进行h264视频编码和aac音频编码,并发送到RTM ...

  9. rtsp实时流通过rtmp推送到服务端

    rtsp实时流通过rtmp推送到服务端 很多朋友都会问到rtsp如何通过rtmp协议推送到服务端,正好前段时间开发了这个功能写在这里,和大家分享下. 首先我想说的是:ffmpeg可以实现这个功能.ff ...

最新文章

  1. java 中策略模式_JAVA中的策略模式
  2. selenium--driver.switchTo()
  3. 借花献佛!docker讲解视频
  4. 双剑合璧:邮件客户端与邮件系统
  5. 如何解决win10+VS2017+WDK环境下编译C++程序提示error LNK1104无法打开文件*.lib的问题
  6. 我看windows mobile数据同步方案
  7. Python3.x字符串替换方法replace()、maketrans()和translate()
  8. C#LeetCode刷题之#628-三个数的最大乘积( Maximum Product of Three Numbers)
  9. [CTO札记]雅虎主页改版使用户停留时间增加20% -
  10. windows 下杀掉占用端口的程序
  11. 写给小白的区块链科普文
  12. gps面积测量仪手机版下载安装_GPS面积测量仪手机版下载
  13. 经典古诗词名句 mysql_经典古诗词名句摘抄50句
  14. 靶点c语言,降脂治疗靶点:LDL-C是最好的吗?(上)
  15. Python爬虫|爬取喜马拉雅音频
  16. HashMap在JDK1.7版本头插法实现解析
  17. Excel如何批量添加同一批注
  18. 学考高考照片批量重命名,将以姓名命名改为以身份证+姓名命名
  19. PAT Basic level 1062 最简分数 (20分)
  20. Susan角点检测python实现 (边缘检测、角点检测、重心计算、非极大值抑制)

热门文章

  1. JQuery调用iframe子页面函数/对象的方法
  2. python变量和字符_Python变量和字符串
  3. julia fit 函数_带有Julia中示例的flipsign()函数
  4. Java LocalDateTime类| 带示例的getMinute()方法
  5. c语言指针访问 静态变量_使用C中的指针访问变量的值
  6. 链接服务器访问接口返回了消息没有活动事务,因为链接服务器 SQLEHR 的 OLE DB 访问接口 SQLNCLI10 无法启动分布式事务。...
  7. c语言long的格式字符串,时间字符串和long类型之间的转换
  8. 最牛逼的 Java 项目实战,没有之一!
  9. 第 1-6 课:玩转时间 + 面试题
  10. 关于CentOS-6的默认带的mysql启动和安装问题