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

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

技术背景

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

20210402174457333.png

基于上诉诉求,我们以大牛直播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。

先上图

20210402173929283.jpg

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

总结

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

相关文章
|
5月前
|
开发工具 Android开发 开发者
Android平台如何不推RTMP|不发布RTSP流|不实时录像|不回传GB28181数据时实时快照?
本文介绍了一种在Android平台上实现实时截图快照的方法,尤其适用于无需依赖系统接口的情况,如在RTMP推送、RTSP服务或GB28181设备接入等场景下进行截图。通过底层模块(libSmartPublisher.so)实现了截图功能,封装了`SnapShotImpl.java`类来管理截图流程。此外,提供了关键代码片段展示初始化SDK实例、执行截图、以及在Activity销毁时释放资源的过程。此方案还考虑到了快照数据的灵活处理需求,符合GB/T28181-2022的技术规范。对于寻求更灵活快照机制的开发者来说,这是一个值得参考的设计思路。
|
4月前
|
Web App开发 网络协议 Android开发
Android平台一对一音视频通话方案大比拼:WebRTC VS RTMP VS RTSP,谁才是王者?
【9月更文挑战第4天】本文详细对比了在Android平台上实现一对一音视频通话时常用的WebRTC、RTMP及RTSP三种技术方案。从技术原理、性能表现与开发难度等方面进行了深入分析,并提供了示例代码。WebRTC适合追求低延迟和高质量的场景,但开发成本较高;RTMP和RTSP则在简化开发流程的同时仍能保持较好的传输效果,适用于不同需求的应用场景。
215 1
|
4月前
|
安全 API 开发工具
Android平台RTMP推送|轻量级RTSP服务如何实现麦克风|扬声器声音采集切换
Android平台扬声器播放声音的采集,在无纸化同屏等场景下,意义很大,早期低版本的Android设备,是没法直接采集扬声器audio的(从Android 10开始支持),所以,如果需要采集扬声器audio,需要先做系统版本判断,添加相应的权限。
|
4月前
|
编解码 开发工具 Android开发
Android平台实现屏幕录制(屏幕投影)|音频播放采集|麦克风采集并推送RTMP或轻量级RTSP服务
Android平台屏幕采集、音频播放声音采集、麦克风采集编码打包推送到RTMP和轻量级RTSP服务的相关技术实现,做成高稳定低延迟的同屏系统,还需要有配套好的RTMP、RTSP直播播放器
|
5月前
|
图形学 Android开发 iOS开发
穿越数字洪流,揭秘Unity3d中的视频魔法!Windows、Android和iOS如何征服RTSP与RTMP的终极指南!
【8月更文挑战第15天】在数字媒体的海洋中,实时视频流是连接世界的桥梁。对于那些渴望在Unity3d中搭建这座桥梁的开发者来说,本文将揭示如何在Windows、Android和iOS平台上征服RTSP与RTMP的秘密。我们将深入探讨这两种协议的特性,以及在不同平台上实现流畅播放的技巧。无论你是追求稳定性的RTSP拥趸,还是低延迟的RTMP忠实粉丝,这里都有你需要的答案。让我们一起穿越数字洪流,探索Unity3d中视频魔法的世界吧!
86 2
|
5月前
|
编解码 开发工具 Android开发
Android平台RTMP直播推送模块技术接入说明
大牛直播SDK跨平台RTMP直播推送模块,始于2015年,支持Windows、Linux(x64_64架构|aarch64)、Android、iOS平台,支持采集推送摄像头、屏幕、麦克风、扬声器、编码前、编码后数据对接,功能强大,性能优异,配合大牛直播SDK的SmartPlayer播放器,轻松实现毫秒级的延迟体验,满足大多数行业的使用场景。RTMP直播推送模块数据源,支持编码前、编码后数据对接
|
5月前
|
Web App开发 网络协议 Android开发
### 惊天对决!Android平台一对一音视频通话方案大比拼:WebRTC VS RTMP VS RTSP,谁才是王者?
【8月更文挑战第14天】随着移动互联网的发展,实时音视频通信已成为移动应用的关键部分。本文对比分析了Android平台上WebRTC、RTMP与RTSP三种主流技术方案。WebRTC提供端到端加密与直接数据传输,适于高质量低延迟通信;RTMP适用于直播场景,但需服务器中转;RTSP支持实时流播放,但在复杂网络下稳定性不及WebRTC。三种方案各有优劣,WebRTC功能强大但集成复杂,RTMP和RTSP实现较简单但需额外编码支持。本文还提供了示例代码以帮助开发者更好地理解和应用这些技术。
166 0
|
Android开发
Android RTMP直播推流方案选择
1. 技术科普: RTMP是Real Time Messaging Protocol(实时消息传输协议)的首字母缩写。
2534 0
|
1月前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
55 19
|
1月前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
60 14