Android平台实现mp4文件实时推送RTMP|轻量级RTSP服务|GB28181平台

简介: 好多开发者有这样的诉求,想把本地录制的MP4文件,以实时流数据的形式,推送到RTMP服务器,注入轻量级RTSP服务,或者对接到GB28181平台,这块前几年我们就有对接。

好多开发者有这样的诉求,想把本地录制的MP4文件,以实时流数据的形式,推送到RTMP服务器,注入轻量级RTSP服务,或者对接到GB28181平台,这块前几年我们就有对接。


本次以MediaExtractor为例,先利用MediaExtractor,把mp4文件的音视频数据分离,然后调用我们publisher模块,实现编码后的数据对接到RTMP服务器、轻量级RTSP服务或GB28181平台即可,废话不多说,上代码,由于实例代码比较简单,不再赘述用法:

/*
 * SmartPublisherActivity.java
 * Github: https://github.com/daniulive/SmarterStreaming
 */ 
private void InitMediaExtractor(){
    File mFile = new File("/storage/emulated/0/","2022.mp4");
    if (!mFile.exists()){
      Log.e(TAG, "mp4文件不存在");
      return;
    }
    MediaExtractor mediaExtractor = new MediaExtractor();
    try {
      mediaExtractor.setDataSource(mFile.getAbsolutePath());
    } catch (IOException e) {
      e.printStackTrace();
    }
    int count = mediaExtractor.getTrackCount();//获取轨道数量
    Log.e(TAG, "轨道数量 = "+count);
    for (int i = 0; i < count; i++)
    {
      MediaFormat trackFormat = mediaExtractor.getTrackFormat(i);
      String mineType = trackFormat.getString(MediaFormat.KEY_MIME);
      Log.e(TAG, i + "编号通道格式 = " + mineType);
      //视频信道
      if (mineType.startsWith("video/")) {
        video_track_index = i;
        is_has_video = true;
        try {
          video_media_extractor.setDataSource(mFile.getAbsolutePath());
        } catch (IOException e) {
          e.printStackTrace();
        }
        if(mineType.equals("video/avc"))
        {
          video_codec_id = 1;
        }
        else if(mineType.equals("video/hevc"))
        {
          video_codec_id = 2;
        }
        int width = trackFormat.getInteger(MediaFormat.KEY_WIDTH);
        int height = trackFormat.getInteger(MediaFormat.KEY_HEIGHT);
        long duration = trackFormat.getLong(MediaFormat.KEY_DURATION);//总时间
        int video_fps = trackFormat.getInteger(MediaFormat.KEY_FRAME_RATE);//帧率
        max_sample_size = trackFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);//获取视频缓存输出的最大大小
        Log.e(TAG, "video width " + width + ", height: " + height + ", duration: " + duration + ", max_sample_size: " + max_sample_size + ", fps: " + video_fps);
      }
      //音频信道
      if (mineType.startsWith("audio/")) {
        audio_track_index = i;
        is_has_audio = true;
        try {
          audio_media_extractor.setDataSource(mFile.getAbsolutePath());
        } catch (IOException e) {
          e.printStackTrace();
        }
        if(mineType.equals("audio/mp4a-latm"))
        {
          audio_codec_id = 0x10002;
        }
        audio_sample_rate = trackFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);//获取采样率
        int audioTrackBitrate = trackFormat.getInteger(MediaFormat.KEY_BIT_RATE);      //获取比特率
        int channels = trackFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);      //获取声道数量
        Log.e(TAG, "mp4 audio_sample_rate " + audio_sample_rate + ", audioTrackBitrate: " + audioTrackBitrate + ", channels: " + channels);
      }
    }
  }

视频数据处理,先切到视频信道,然后调用readSampleData(),读取到video数据后,判断是不是关键帧,是关键帧的话,带上sps pps(如果是h265 带上vps sps pps),一般来说,比如无人机等设备回调,大多都贴心的实现了关键帧前携带sps pps,也有的设备是单独发sps pps,所以,对接的时候,可以先把数据打印出来看看,具体问题具体分析即可,获取video数据后,通过SmartPublisherPostVideoEncodedData()投递到底层:

//切换到视频信道
video_media_extractor.selectTrack(video_track_index);
if(IsVpsSpsPps(video_header_checker_buffer, video_codec_id))
{
  is_key_frame = true;
}
if ( isPushing || isRTSPPublisherRunning || isGB28181StreamRunning) {
  libPublisher.SmartPublisherPostVideoEncodedData(publisherHandle, video_codec_id, byteBuffer, video_sample_size, is_key_frame?1:0, cur_sample_time, cur_sample_time);
}

audio也是类似的流程,audio有一点,需要先拿到audio param info,然后,调用readSampleData()获取到audio数据,调用SmartPublisherPostAudioEncodedData()投递出去即可。

byte[] audio_param_info = GetAudioParamInfo();
ByteBuffer parameter_info = ByteBuffer.allocateDirect(2);
parameter_info.put(audio_param_info);
int parameter_info_size = 2;
audio_media_extractor.selectTrack(audio_track_index);


int audio_sample_size = audio_media_extractor.readSampleData(byteBuffer, 0);
if(audio_sample_size < 0)
{
  Log.i(TAG, "audio reach the end..");
  break;
}
long cur_sample_time = audio_media_extractor.getSampleTime()/1000;
if ( isPushing || isRTSPPublisherRunning || isGB28181StreamRunning) {
  libPublisher.SmartPublisherPostAudioEncodedData(publisherHandle, audio_codec_id, byteBuffer, audio_sample_size, 0, cur_sample_time, parameter_info, parameter_info_size);
}

数据投递讲完后,就是推送模块接口的处理,获取到的数据,是可以对接到RTMP推送模块,或者轻量级RTSP服务亦或GB28181设备接入模块,这些模块,都可以在一个实例内完成,所以,我们先调用OpenPushHandle()完成publisherHandle生成,并设置event callback。

  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(context_, 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;
  }

RTMP推送相关处理:

  private boolean StartPush()
  {
    if (isPushing)
      return false;
    //relayStreamUrl = "rtmp://192.168.1.77/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 && !isGB28181StreamRunning)
      {
        libPublisher.SmartPublisherClose(publisherHandle);
        publisherHandle = 0;
      }
      return false;
    }
    isPushing = true;
    return true;
  }
  public void StopPush()
  {
    if (!isPushing)
      return;
    isPushing = false;
    libPublisher.SmartPublisherStopPublisher(publisherHandle);
    if(!isRTSPPublisherRunning && !isRTSPServiceRunning && !isGB28181StreamRunning)
    {
      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端口失败! 请检查端口是否重复或者端口不在范围内!");
        }
        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流
  class ButtonRtspPublisherListener implements OnClickListener {
    public void onClick(View v) {
      if (isRTSPPublisherRunning) {
        stopRtspPublisher();
        btnRtspPublisher.setText("发布RTSP流");
        btnGetRtspSessionNumbers.setEnabled(false);
        btnRtspService.setEnabled(true);
      }
      else
      {
        Log.i(TAG, "onClick start rtsp publisher..");
        boolean startRet = StartRtspStream();
        if (!startRet) {
          Log.e(TAG, "Failed to call StartRtspStream().");
          return;
        }
        btnRtspPublisher.setText("停止RTSP流");
        btnGetRtspSessionNumbers.setEnabled(true);
        btnRtspService.setEnabled(false);
      }
    }
  };
  //当前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);
      }
    }
  };

GB28181设备对接相关处理:

  class ButtonGB28181AgentListener implements OnClickListener {
    public void onClick(View v) {
      stopGB28181Stream();
      destoryRTPSender();
      if (null == gb28181_agent_ ) {
        if( !initGB28181Agent() )
          return;
      }
      if (gb28181_agent_.isRunning()) {
        gb28181_agent_.terminateAllPlays(true);// 目前测试下来,发送BYE之后,有些服务器会立即发送INVITE,是否发送BYE根据实际情况看
        gb28181_agent_.stop();
        btnGB28181Agent.setText("启动GB28181");
      }
      else {
        if ( gb28181_agent_.start() ) {
          btnGB28181Agent.setText("停止GB28181");
        }
      }
    }
  }
  //停止GB28181 媒体流
  private void stopGB28181Stream() {
    if(!isGB28181StreamRunning)
      return;
    if (libPublisher != null) {
      libPublisher.StopGB28181MediaStream(publisherHandle);
    }
    if (!isRecording && !isRTSPPublisherRunning && !isPushing) {
      if (publisherHandle != 0) {
        if (libPublisher != null) {
          libPublisher.SmartPublisherClose(publisherHandle);
          publisherHandle = 0;
        }
      }
    }
    isGB28181StreamRunning = false;
  }
  private boolean initGB28181Agent() {
    if ( gb28181_agent_ != null )
      return  true;
    getLocation(context_);
    String local_ip_addr = IPAddrUtils.getIpAddress(context_);
    Log.i(TAG, "initGB28181Agent local ip addr: " + local_ip_addr);
    if ( local_ip_addr == null || local_ip_addr.isEmpty() ) {
      Log.e(TAG, "initGB28181Agent local ip is empty");
      return  false;
    }
    gb28181_agent_ = GBSIPAgentFactory.getInstance().create();
    if ( gb28181_agent_ == null ) {
      Log.e(TAG, "initGB28181Agent create agent failed");
      return false;
    }
    gb28181_agent_.addListener(this);
    gb28181_agent_.addPlayListener(this);
    gb28181_agent_.addDeviceControlListener(this);
    // 必填信息
    gb28181_agent_.setLocalAddress(local_ip_addr);
    gb28181_agent_.setServerParameter(gb28181_sip_server_addr_, gb28181_sip_server_port_, gb28181_sip_server_id_, gb28181_sip_domain_);
    gb28181_agent_.setUserInfo(gb28181_sip_username_, gb28181_sip_password_);
    // 可选参数
    gb28181_agent_.setUserAgent(gb28181_sip_user_agent_filed_);
    gb28181_agent_.setTransportProtocol(gb28181_sip_trans_protocol_==0?"UDP":"TCP");
    // GB28181配置
    gb28181_agent_.config(gb28181_reg_expired_, gb28181_heartbeat_interval_, gb28181_heartbeat_count_);
    com.gb.ntsignalling.Device gb_device = new com.gb.ntsignalling.Device("34020000001310000001", "安卓测试设备", Build.MANUFACTURER, Build.MODEL,
        "宇宙","火星1","火星", true);
    if (mLongitude != null && mLatitude != null) {
      com.gb.ntsignalling.DevicePosition device_pos = new com.gb.ntsignalling.DevicePosition();
      device_pos.setTime(mLocationTime);
      device_pos.setLongitude(mLongitude);
      device_pos.setLatitude(mLatitude);
      gb_device.setPosition(device_pos);
      gb_device.setSupportMobilePosition(true); // 设置支持移动位置上报
    }
    gb28181_agent_.addDevice(gb_device);
/*
        com.gb28181.ntsignalling.Device gb_device1 = new com.gb28181.ntsignalling.Device("34020000001380000002", "安卓测试设备2", Build.MANUFACTURER, Build.MODEL,
                "宇宙","火星1","火星", true);
        if (mLongitude != null && mLatitude != null) {
            com.gb28181.ntsignalling.DevicePosition device_pos = new com.gb28181.ntsignalling.DevicePosition();
            device_pos.setTime(mLocationTime);
            device_pos.setLongitude(mLongitude);
            device_pos.setLatitude(mLatitude);
            gb_device1.setPosition(device_pos);
            gb_device1.setSupportMobilePosition(true);
        }
        gb28181_agent_.addDevice(gb_device1);
 */
    if (!gb28181_agent_.createSipStack()) {
      gb28181_agent_ = null;
      Log.e(TAG, "initGB28181Agent gb28181_agent_.createSipStack failed.");
      return  false;
    }
    boolean is_bind_local_port_ok = false;
    // 最多尝试5000个端口
    int try_end_port = gb28181_sip_local_port_base_ + 5000;
    try_end_port = try_end_port > 65536 ?65536: try_end_port;
    for (int i = gb28181_sip_local_port_base_; i < try_end_port; ++i) {
      if (gb28181_agent_.bindLocalPort(i)) {
        is_bind_local_port_ok = true;
        break;
      }
    }
    if (!is_bind_local_port_ok) {
      gb28181_agent_.releaseSipStack();
      gb28181_agent_ = null;
      Log.e(TAG, "initGB28181Agent gb28181_agent_.bindLocalPort failed.");
      return  false;
    }
    if (!gb28181_agent_.initialize()) {
      gb28181_agent_.unBindLocalPort();
      gb28181_agent_.releaseSipStack();
      gb28181_agent_ = null;
      Log.e(TAG, "initGB28181Agent gb28181_agent_.initialize failed.");
      return  false;
    }
    return true;
  }
  @Override
  public void ntsRegisterOK(String dateString) {
    Log.i(TAG, "ntsRegisterOK Date: " + (dateString!= null? dateString : ""));
  }
  @Override
  public void ntsRegisterTimeout() {
    Log.e(TAG, "ntsRegisterTimeout");
  }
  @Override
  public void ntsRegisterTransportError(String errorInfo) {
    Log.e(TAG, "ntsRegisterTransportError error:" + (errorInfo != null?errorInfo :""));
  }
  @Override
  public void ntsOnHeartBeatException(int exceptionCount,  String lastExceptionInfo) {
    Log.e(TAG, "ntsOnHeartBeatException heart beat timeout count reached, count:" + exceptionCount +
        ", exception info:" + (lastExceptionInfo != null ? lastExceptionInfo : ""));
    // 停止信令, 然后重启
    handler_.postDelayed(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "gb28281_heart_beart_timeout");
        stopGB28181Stream();
        destoryRTPSender();
        if (gb28181_agent_ != null) {
          gb28181_agent_.terminateAllPlays(true);
          Log.i(TAG, "gb28281_heart_beart_timeout sip stop");
          gb28181_agent_.stop();
          String local_ip_addr = IPAddrUtils.getIpAddress(context_);
          if (local_ip_addr != null && !local_ip_addr.isEmpty()) {
            Log.i(TAG, "gb28281_heart_beart_timeout get local ip addr: " + local_ip_addr);
            gb28181_agent_.setLocalAddress(local_ip_addr);
          }
          Log.i(TAG, "gb28281_heart_beart_timeout sip start");
          gb28181_agent_.start();
        }
      }
    }, 0);
  }
  @Override
  public void ntsOnInvitePlay(String deviceId, PlaySessionDescription session_des) {
    handler_.postDelayed(new Runnable() {
      @Override
      public void run() {
        MediaSessionDescription video_des = session_des_.getVideoDescription();
        SDPRtpMapAttribute ps_rtpmap_attr = video_des.getPSRtpMapAttribute();
        Log.i(TAG,"ntsInviteReceived, device_id:" +device_id_+", is_tcp:" + video_des.isRTPOverTCP()
            + " rtp_port:" + video_des.getPort() + " ssrc:" + video_des.getSSRC()
            + " address_type:" + video_des.getAddressType() + " address:" + video_des.getAddress());
        // 可以先给信令服务器发送临时振铃响应
        //sip_stack_android.respondPlayInvite(180, device_id_);
        long rtp_sender_handle = libPublisher.CreateRTPSender(0);
        if ( rtp_sender_handle == 0 ) {
          gb28181_agent_.respondPlayInvite(488, device_id_);
          Log.i(TAG, "ntsInviteReceived CreateRTPSender failed, response 488, device_id:" + device_id_);
          return;
        }
        gb28181_rtp_payload_type_  = ps_rtpmap_attr.getPayloadType();
        gb28181_rtp_encoding_name_ =  ps_rtpmap_attr.getEncodingName();
        libPublisher.SetRTPSenderTransportProtocol(rtp_sender_handle, video_des.isRTPOverUDP()?0:1);
        libPublisher.SetRTPSenderIPAddressType(rtp_sender_handle, video_des.isIPv4()?0:1);
        libPublisher.SetRTPSenderLocalPort(rtp_sender_handle, 0);
        libPublisher.SetRTPSenderSSRC(rtp_sender_handle, video_des.getSSRC());
        libPublisher.SetRTPSenderSocketSendBuffer(rtp_sender_handle, 2*1024*1024); // 设置到2M
        libPublisher.SetRTPSenderClockRate(rtp_sender_handle, ps_rtpmap_attr.getClockRate());
        libPublisher.SetRTPSenderDestination(rtp_sender_handle, video_des.getAddress(), video_des.getPort());
        if ( libPublisher.InitRTPSender(rtp_sender_handle) != 0 ) {
          gb28181_agent_.respondPlayInvite(488, device_id_);
          libPublisher.DestoryRTPSender(rtp_sender_handle);
          return;
        }
        int local_port = libPublisher.GetRTPSenderLocalPort(rtp_sender_handle);
        if (local_port == 0) {
          gb28181_agent_.respondPlayInvite(488, device_id_);
          libPublisher.DestoryRTPSender(rtp_sender_handle);
          return;
        }
        Log.i(TAG,"get local_port:" + local_port);
        String local_ip_addr = IPAddrUtils.getIpAddress(context_);
        gb28181_agent_.respondPlayInviteOK(device_id_,local_ip_addr, local_port);
        gb28181_rtp_sender_handle_ = rtp_sender_handle;
      }
      private String device_id_;
      private PlaySessionDescription session_des_;
      public Runnable set(String device_id, PlaySessionDescription session_des) {
        this.device_id_ = device_id;
        this.session_des_ = session_des;
        return this;
      }
    }.set(deviceId, session_des),0);
  }
  @Override
  public void ntsOnCancelPlay(String deviceId) {
    // 这里取消Play会话
    handler_.postDelayed(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "ntsOnCancelPlay, deviceId=" + device_id_);
        destoryRTPSender();
      }
      private String device_id_;
      public Runnable set(String device_id) {
        this.device_id_ = device_id;
        return this;
      }
    }.set(deviceId),0);
  }
  @Override
  public void ntsOnAckPlay(String deviceId) {
    handler_.postDelayed(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG,"ntsOnACKPlay, device_id:" +device_id_);
        if (!isRecording && !isRTSPPublisherRunning && !isPushing) {
          OpenPushHandle();
        }
        libPublisher.SetGB28181RTPSender(publisherHandle, gb28181_rtp_sender_handle_, gb28181_rtp_payload_type_, gb28181_rtp_encoding_name_);
        int startRet = libPublisher.StartGB28181MediaStream(publisherHandle);
        if (startRet != 0) {
          if (!isRecording && !isRTSPPublisherRunning && !isPushing) {
            if (publisherHandle != 0) {
              libPublisher.SmartPublisherClose(publisherHandle);
              publisherHandle = 0;
            }
          }
          destoryRTPSender();
          Log.e(TAG, "Failed to start GB28181 service..");
          return;
        }
        isGB28181StreamRunning = true;
      }
      private String device_id_;
      public Runnable set(String device_id) {
        this.device_id_ = device_id;
        return this;
      }
    }.set(deviceId),0);
  }
  @Override
  public void ntsOnPlayInviteResponseException(String deviceId, int statusCode, String errorInfo) {
    // 这里要释放掉响应的资源
    Log.i(TAG, "ntsOnPlayInviteResponseException, deviceId=" + deviceId + " statusCode=" +statusCode
        + " errorInfo:" + errorInfo);
    handler_.postDelayed(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "ntsOnPlayInviteResponseException, deviceId=" + device_id_);
        destoryRTPSender();
      }
      private String device_id_;
      public Runnable set(String device_id) {
        this.device_id_ = device_id;
        return this;
      }
    }.set(deviceId),0);
  }
  @Override
  public void ntsOnByePlay(String deviceId) {
    handler_.postDelayed(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "ntsOnByePlay, stop GB28181 media stream, deviceId=" + device_id_);
        stopGB28181Stream();
        destoryRTPSender();
      }
      private String device_id_;
      public Runnable set(String device_id) {
        this.device_id_ = device_id;
        return this;
      }
    }.set(deviceId),0);
  }
  @Override
  public void ntsOnTerminatePlay(String deviceId) {
    handler_.postDelayed(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "ntsOnTerminatePlay, stop GB28181 media stream, deviceId=" + device_id_);
        stopGB28181Stream();
        destoryRTPSender();
      }
      private String device_id_;
      public Runnable set(String device_id) {
        this.device_id_ = device_id;
        return this;
      }
    }.set(deviceId),0);
  }
  @Override
  public void ntsOnPlayDialogTerminated(String deviceId) {
        /*
        Play会话对应的对话终止, 一般不会出发这个回调,目前只有在响应了200K, 但在64*T1时间后还没收到ACK,才可能会出发
        收到这个请做相关清理处理
        */
    handler_.postDelayed(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "ntsOnPlayDialogTerminated, deviceId=" + device_id_);
        stopGB28181Stream();
        destoryRTPSender();
      }
      private String device_id_;
      public Runnable set(String device_id) {
        this.device_id_ = device_id;
        return this;
      }
    }.set(deviceId),0);
  }
  @Override
  public void ntsOnDevicePositionRequest(String deviceId, int interval) {
    handler_.postDelayed(new Runnable() {
      @Override
      public void run() {
        getLocation(context_);
        Log.v(TAG, "ntsOnDevicePositionRequest, deviceId:" + this.device_id_ + ", Longitude:" + mLongitude
            + ", Latitude:" + mLatitude + ", Time:" + mLocationTime);
        if (mLongitude != null && mLatitude != null) {
          com.gb.ntsignalling.DevicePosition device_pos = new com.gb.ntsignalling.DevicePosition();
          device_pos.setTime(mLocationTime);
          device_pos.setLongitude(mLongitude);
          device_pos.setLatitude(mLatitude);
          if (gb28181_agent_ != null ) {
            gb28181_agent_.updateDevicePosition(device_id_, device_pos);
          }
        }
      }
      private String device_id_;
      private int interval_;
      public Runnable set(String device_id, int interval) {
        this.device_id_ = device_id;
        this.interval_ = interval;
        return this;
      }
    }.set(deviceId, interval),0);
  }
  @Override
  public void ntsOnDeviceControlTeleBootCommand(String deviceId, String teleBootValue) {
    handler_.postDelayed(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "ntsOnDeviceControlTeleBootCommand device_id:" + device_id_ + " tele_boot_value:" + tele_boot_value_);
        stopGB28181Stream();
        destoryRTPSender();
        if (gb28181_agent_ != null ) {
          gb28181_agent_.terminateAllPlays(true);
          gb28181_agent_.stop();
        }
        // 发送注销消息后,等待2000毫秒, 再释放资源
        handler_.postDelayed(new Runnable() {
          @Override
          public void run() {
            if (gb28181_agent_ != null ) {
              Log.i(TAG, " gb28181_agent_.unInitialize++");
              gb28181_agent_.unInitialize();
              gb28181_agent_.unBindLocalPort();
              gb28181_agent_.releaseSipStack();
              Log.i(TAG, " gb28181_agent_.unInitialize--");
              gb28181_agent_ = null;
            }
            // 200毫秒后再重启
            handler_.postDelayed(new Runnable() {
              @Override
              public void run() {
                Log.i(TAG, "restart gb sip agent.");
                if (null==gb28181_agent_) {
                  if (!initGB28181Agent()) {
                    Log.e(TAG, "init gb sip agent failed.");
                    return;
                  }
                }
                if (!gb28181_agent_.isRunning()) {
                  if ( !gb28181_agent_.start() ) {
                    Log.e(TAG, "restart gb sip agent failed.");
                  }
                }
              }
            },200);
          }
        },2000);
      }
      private String device_id_;
      private String tele_boot_value_;
      public Runnable set(String device_id, String tele_boot_value) {
        this.device_id_ = device_id;
        this.tele_boot_value_ = tele_boot_value;
        return this;
      }
    }.set(deviceId, teleBootValue),0);
  }

以上就是大概流程,需要注意的是,本地MP4文件作为实时数据发送的时候,需要注意时间戳的问题,简单来说,确保“1分钟的数据,按照时间戳间隔,1分钟均匀的发出去”。

相关文章
|
1月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
110 1
|
1月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
83 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
6天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
7天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
9天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
【10月更文挑战第35天】在数字化时代,安卓应用的开发成为了一个热门话题。本文旨在通过浅显易懂的语言,带领初学者了解安卓开发的基础知识,同时为有一定经验的开发者提供进阶技巧。我们将一起探讨如何从零开始构建第一个安卓应用,并逐步深入到性能优化和高级功能的实现。无论你是编程新手还是希望提升技能的开发者,这篇文章都将为你提供有价值的指导和灵感。
|
7天前
|
存储 API 开发工具
探索安卓开发:从基础到进阶
【10月更文挑战第37天】在这篇文章中,我们将一起探索安卓开发的奥秘。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和建议。我们将从安卓开发的基础开始,逐步深入到更复杂的主题,如自定义组件、性能优化等。最后,我们将通过一个代码示例来展示如何实现一个简单的安卓应用。让我们一起开始吧!
|
8天前
|
存储 XML JSON
探索安卓开发:从新手到专家的旅程
【10月更文挑战第36天】在这篇文章中,我们将一起踏上一段激动人心的旅程,从零基础开始,逐步深入安卓开发的奥秘。无论你是编程新手,还是希望扩展技能的老手,这里都有适合你的知识宝藏等待发掘。通过实际的代码示例和深入浅出的解释,我们将解锁安卓开发的关键技能,让你能够构建自己的应用程序,甚至贡献于开源社区。准备好了吗?让我们开始吧!
21 2
|
9天前
|
Android开发
布谷语音软件开发:android端语音软件搭建开发教程
语音软件搭建android端语音软件开发教程!
|
17天前
|
编解码 Java Android开发
通义灵码:在安卓开发中提升工作效率的真实应用案例
本文介绍了通义灵码在安卓开发中的应用。作为一名97年的聋人开发者,我在2024年Google Gemma竞赛中获得了冠军,拿下了很多项目竞赛奖励,通义灵码成为我的得力助手。文章详细展示了如何安装通义灵码插件,并通过多个实例说明其在适配国际语言、多种分辨率、业务逻辑开发和编程语言转换等方面的应用,显著提高了开发效率和准确性。
|
16天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
28 5