如何实现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);
      }
    }
  };

总结

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

相关文章
|
10月前
|
编解码 物联网 开发工具
Android平台内网RTSP网关和轻量级RTSP服务的区别和联系
我们在对接轻量级RTSP服务的时候,遇到客户这样的使用场景:客户是用于车载自组网环境,确保多辆车之间可以相互看到对方的实时视频,以期可以了解到前方路况等关注的信息。
115 0
|
10月前
|
数据采集 前端开发 Android开发
Android平台RTMP推送或GB28181设备接入端如何实现采集audio音量放大?
我们在做Android平台RTMP推送和GB28181设备对接的时候,遇到这样的问题,有的设备,麦克风采集出来的audio,音量过高或过低,特别是有些设备,采集到的麦克风声音过低,导致播放端听不清前端采集的audio,这时候,就需要针对采集到的audio,做音量放大处理。
|
10月前
|
编解码 监控 网络协议
Android平台音视频推送选RTMP还是GB28181?
早在2015年,我们发布了RTMP直播推送模块,那时候音视频直播这块场景需求,还不像现在这么普遍,我们做这块的初衷,主要是为了实现移动单兵应急指挥系统的低延迟音视频数据传输。好多开发者可能会疑惑,走RTMP怎么可能低延迟?网上看到的RTMP推拉流延迟,总归要2-3秒起,如果是自己实现框架,RTMP推拉流逻辑自己实现的话,延迟确实可以控制在毫秒级,这个已无需赘述。
|
2月前
|
监控 Unix 应用服务中间件
Android-音视频学习系列-(八)基于-Nginx-搭建(rtmp、http)直播服务器
Android-音视频学习系列-(八)基于-Nginx-搭建(rtmp、http)直播服务器
|
10月前
|
编解码 开发工具 Android开发
Android平台如何实现外部RTSP|RTMP流注入轻量级RTSP服务模块(内网RTSP网关)
今天分享的是外部RTSP或RTMP流,拉取后注入到本地轻量级RTSP服务模块,供内网小并发场景下使用,这里我们叫做内网RTSP网关模块。
104 0
|
2月前
|
开发工具 Android开发
Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?
Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?
|
2月前
|
数据采集 编解码 图形学
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
176 0
|
10月前
|
开发工具 Android开发 开发者
Android平台轻量级RTSP服务模块如何实现一个服务发布多路RTSP流?
Android平台轻量级RTSP服务模块如何实现一个服务发布多路RTSP流?
247 0
|
10月前
|
编解码 网络协议 Android开发
Android平台RTMP|RTSP直播播放器功能进阶探讨
很多开发者在跟我聊天的时候,经常问我,为什么一个RTMP或RTSP播放器,你们需要设计那么多的接口,真的有必要吗?带着这样的疑惑,我们今天聊聊Android平台RTMP、RTSP播放器常规功能,如软硬解码设置、实时音量调节、实时快照、实时录像、视频view翻转和旋转、画面填充模式设定、解码后YUV、RGB数据回调等:
137 0
|
10月前
|
编解码 Android开发
Android平台GB28181设备接入、RTMP推送模块如何实现高效率的视频编码
我们在做Android平台RTMP推送、轻量级RTSP服务和GB28181设备接入模块的时候,有一个点是逃不掉的:如何高效率的实现视频数据编码?
159 0