如何实现Android平台GB28181设备对接Camera2数据

简介: 在写如何实现Android平台GB28181设备对接Camera2数据说明之前,我在前两年的blog就有针对camera2的RTMP直播推送模块做过技术分享:

技术背景

在写如何实现Android平台GB28181设备对接Camera2数据说明之前,我在前两年的blog就有针对camera2的RTMP直播推送模块做过技术分享:


在Google 推出Android 5.0的时候, Android Camera API 版本升级到了API2(android.hardware.camera2), 之前使用的API1(android.hardware.camera)就被标为 Deprecated 了。


Camera API2相较于API1有很大不同, 并且API2是为了配合HAL3进行使用的, API2有很多API1不支持的特性, 比如:


  1. 先进的API架构;
  2. 可以获取更多的帧(预览/拍照)信息以及手动控制每一帧的参数;
  3. 对Camera的控制更加完全(比如支持调整focus distance, 剪裁预览/拍照图片);
  4. 支持更多图片格式(yuv/raw)以及高速连拍等。

7e5b46c59fcf4c3b9e2294b75af101c0.png

Camera2 API调用基础流程

  1. 通过context.getSystemService(Context.CAMERA_SERVICE) 获取CameraManager;
  2. 调用CameraManager .open()方法在回调中得到CameraDevice;
  3. 通过CameraDevice.createCaptureSession() 在回调中获取CameraCaptureSession;
  4. 构建CaptureRequest, 有三种模式可选 预览/拍照/录像.;
  5. 通过 CameraCaptureSession发送CaptureRequest, capture表示只发一次请求, setRepeatingRequest表示不断发送请求;
  6. 拍照数据可以在ImageReader.OnImageAvailableListener回调中获取, CaptureCallback中则可获取拍照实际的参数和Camera当前状态。


本次更新,系在Android平台camera2 RTMP推送的基础上,继续支持Android平台GB28181设备和语音广播接入,此外,添加了基于层结构设计的动态水印(动态水印的场景应用特别实在传统行业,重要性不言而喻。包含实时文字水印、图片水印),camera2的技术优越性不再赘述,无图无真相:

f0be546a2706422c898ab741c398019a.jpg

新的demo增加了动态水印设置、轻量级RTSP服务、实时录像、快照等。

技术实现

先说camera2的数据采集:

private class OnImageAvailableListenerImpl implements ImageReader.OnImageAvailableListener {
  @Override
  public void onImageAvailable(ImageReader reader) {
    Image image = reader.acquireLatestImage();
    if ( image != null )
    {
      if ( camera2Listener != null )
      {
        camera2Listener.onCameraImageData(image);
      }
      image.close();
    }
  }
}

获取到的数据,投递到SmartPublisher Jni层:

@Override
public void onCameraImageData(Image image) {
  Rect crop_rect = image.getCropRect();
  if (isPushingRtmp || isRTSPPublisherRunning || isGB28181StreamRunning || isRecording) {
    if (libPublisher != null) {
      Image.Plane[] planes = image.getPlanes();
      int w = image.getWidth(), h = image.getHeight();
      int y_offset = 0, u_offset = 0, v_offset = 0;
      if (!crop_rect.isEmpty()) {
        w = crop_rect.width();
        h = crop_rect.height();
        y_offset += crop_rect.top * planes[0].getRowStride() + crop_rect.left * planes[0].getPixelStride();
        u_offset += (crop_rect.top / 2) * planes[1].getRowStride() + (crop_rect.left / 2) * planes[1].getPixelStride();
        v_offset += (crop_rect.top / 2) * planes[2].getRowStride() + (crop_rect.left / 2) * planes[2].getPixelStride();
        ;
        // Log.i(TAG, "crop w:" + w + " h:" + h + " y_offset:"+ y_offset + " u_offset:" + u_offset + " v_offset:" + v_offset);
      }
      int scale_w = 0, scale_h = 0, scale_filter_mode = 0;
      scale_filter_mode = 3;
      libPublisher.PostLayerImageYUV420888ByteBuffer(publisherHandle, 0, 0, 0,
                                                     planes[0].getBuffer(), y_offset, planes[0].getRowStride(),
                                                     planes[1].getBuffer(), u_offset, planes[1].getRowStride(),
                                                     planes[2].getBuffer(), v_offset, planes[2].getRowStride(), planes[1].getPixelStride(),
                                                     w, h, 0, 0,
                                                     scale_w, scale_h, scale_filter_mode, cameraImageRotationDegree_);
    }
  }
}

PostLayerImageYUV420888ByteBuffer()接口设计如下:

/**
   * 投递层YUV420888图像, 专门为android.media.Image的android.graphics.ImageFormat.YUV_420_888格式提供的接口
   *
   * @param index: 层索引, 必须大于等于0
   *
   * @param left: 层叠加的左上角坐标, 对于第0层的话传0
   *
   * @param top: 层叠加的左上角坐标, 对于第0层的话传0
   *
   * @param y_plane: 对应android.media.Image.Plane[0].getBuffer()
   *
   * @param y_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
   *
   * @param y_row_stride: 对应android.media.Image.Plane[0].getRowStride()
   *
   * @param u_plane: android.media.Image.Plane[1].getBuffer()
   *
   * @param u_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
   *
   * @param u_row_stride: android.media.Image.Plane[1].getRowStride()
   *
   * @param v_plane: 对应android.media.Image.Plane[2].getBuffer()
   *
   * @param v_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
   *
   * @param v_row_stride: 对应android.media.Image.Plane[2].getRowStride()
   *
   * @param uv_pixel_stride: 对应android.media.Image.Plane[1].getPixelStride()
   *
   * @param width: width, 必须大于1, 且必须是偶数
   *
   * @param height: height, 必须大于1, 且必须是偶数
   *
   * @param  is_vertical_flip: 是否垂直翻转, 0不翻转, 1翻转
   *
   * @param  is_horizontal_flip:是否水平翻转, 0不翻转, 1翻转
   *
   * @param  scale_width: 缩放宽,必须是偶数, 0或负数不缩放
   *
   * @param  scale_height: 缩放高, 必须是偶数, 0或负数不缩放
   *
   * @param  scale_filter_mode: 缩放质量, 传0使用默认速度,可选等级范围是:[1,3],值越大缩放质量越好, 但速度越慢
   *
   * @param  rotation_degree: 顺时针旋转, 必须是0, 90, 180, 270, 注意:旋转是在缩放, 垂直/水品反转之后再做, 请留意顺序
   *
   * @return {0} if successful
   */
  public native int PostLayerImageYUV420888ByteBuffer(long handle, int index, int left, int top,
                             ByteBuffer y_plane, int y_offset, int y_row_stride,
                               ByteBuffer u_plane, int u_offset, int u_row_stride,
                             ByteBuffer v_plane, int v_offset, int v_row_stride, int uv_pixel_stride,
                             int width, int height, int is_vertical_flip,  int is_horizontal_flip,
                             int scale_width,  int scale_height, int scale_filter_mode,
                               int rotation_degree);

动态水印-文字水印:

private int postText1Layer(int index, int left, int top, int video_w, int video_h) {
  if (video_w < 1 || video_h < 1)
    return 0;
  Bitmap text_bitmap = makeTextBitmap("文本水印一", getFontSize(video_w)+8,
                                      Color.argb(255, 200, 250, 0),
                                      false, 0,false);
  if (null == text_bitmap)
    return 0;
  ByteBuffer buffer = ByteBuffer.allocateDirect(text_bitmap.getByteCount());
  text_bitmap.copyPixelsToBuffer(buffer);
  libPublisher.PostLayerImageRGBA8888ByteBuffer(handle_, index, left, top, buffer, 0,
                                                text_bitmap.getRowBytes(), text_bitmap.getWidth(), text_bitmap.getHeight(),
                                                0, 0, 0, 0, 0,0);
  int ret = text_bitmap.getHeight();
  text_bitmap.recycle();
  return ret;
}

动态水印-图片水印:

private Bitmap getAssetsBitmap() {
  Bitmap bitmap = null;
  try {
    InputStream s = getAssets().open("tca.png");
    bitmap = BitmapFactory.decodeStream(s);
    s.close();
  } catch (Exception e) {
    e.printStackTrace();
  }
  return bitmap;
}
private int postPictureLayer(int index, int left, int top, int video_w, int video_h) {
  if (video_w < 1 || video_h < 1)
    return  0;
  Bitmap bitmap = getAssetsBitmap();
  if (null == bitmap) {
    Log.e(TAG, "postPitcureLayer getAssetsBitmap is null");
    return 0;
  }
  if (bitmap.getConfig() != Bitmap.Config.ARGB_8888) {
    Log.e(TAG, "postPitcureLayer config is not ARGB_8888, config:" + Bitmap.Config.ARGB_8888);
    return 0;
  }
  ByteBuffer buffer = ByteBuffer.allocateDirect(bitmap.getByteCount());
  bitmap.copyPixelsToBuffer(buffer);
  final int w = bitmap.getWidth();
  final int h = bitmap.getHeight();
  if ( w < 2 || h < 2 )
    return 0;
  int scale_w = 0, scale_h = 0, scale_filter_mode = 0;
  final float r_w = video_w - left; // 有可能负数
  final float r_h = video_h - top; // 有可能负数
  if (w > r_w || h > r_h) {
    float s_w = w;
    float s_h = h;
    // 0.85的10次方是0.19687, 缩放到0.2倍差不多了
    for ( int i = 0; i < 10; ++i)  {
      s_w *= 0.85f;
      s_h *= 0.85f;
      if (s_w < r_w && s_h < r_h )
        break;
    }
    if (s_w > r_w || s_h > r_h)
      return 0;
    // 如果小于16就算了,太小看也看不见
    if (s_w < 16.0f || s_h < 16.0f)
      return  0;
    scale_w = align((int)(s_w + 0.5f), 2);
    scale_h = align( (int)(s_h + 0.5f), 2);
    scale_filter_mode = 3;
  }
  /*
            if ( scale_w > 0 && scale_h > 0)
                Log.i(TAG, "postTextLayer scale_w:" + scale_w + ", scale_h:" + scale_h + " w:" + w + ", h:" + h) ; */
  libPublisher.PostLayerImageRGBA8888ByteBuffer(handle_, index, left, top, buffer, 0, bitmap.getRowBytes(), w, h,
                                                0, 0, scale_w, scale_h, scale_filter_mode,0);
  int ret = scale_h > 0 ? scale_h : bitmap.getHeight();
  bitmap.recycle();
  return ret;
}

动态水印控制:

public void startPost(long handle, int w, int h, boolean is_text, boolean is_pitcure) {
  this.is_exit_ = false;
  this.handle_ = handle;
  updateVideoSize(w, h);
  is_text_ = is_text;
  is_picture_ = is_pitcure;
  Log.i(TAG, "LayerPostThread.startPost w:" + w + ", h:" + h + ", is_text:" + is_text_ + ", is_pitcure:" + is_picture_);
  try {
    this.start();
  } catch (Exception e) {
    e.printStackTrace();
  }
}
public void enableText(boolean is_text) {
  is_text_ = is_text;
  clear_flag_ = true;
  if (handle_ != 0) {
    libPublisher.EnableLayer(handle_, timestamp_index_, is_text_?1:0);
    libPublisher.EnableLayer(handle_, text1_index_, is_text_?1:0);
    libPublisher.EnableLayer(handle_, text2_index_, is_text_?1:0);
  }
}
public void enablePicture(boolean is_picture) {
  is_picture_ = is_picture;
  clear_flag_ = true;
  if (handle_ != 0) {
    libPublisher.EnableLayer(handle_, picture_index_, is_picture_?1:0);
  }
}
public void stopPost() {
  this.is_exit_ = true;
  try {
    this.join(1000);
  }
  catch (Exception e) {
    e.printStackTrace();
  }
  handle_ = 0;
}
private LayerPostThread layer_post_thread_ = null;
private void startLayerPostThread() {
  if (null == layer_post_thread_) {
    layer_post_thread_ = new LayerPostThread();
    int degree = cameraImageRotationDegree_;
    if (90 == degree || 270 == degree)
      layer_post_thread_.startPost(publisherHandle, video_height_, video_width_, isHasTextWatermark(), isHasPictureWatermark());
    else
      layer_post_thread_.startPost(publisherHandle, video_width_, video_height_, isHasTextWatermark(), isHasPictureWatermark());
  }
}
private void stopLayerPostThread() {
  if (layer_post_thread_ != null) {
    layer_post_thread_.stopPost();
    layer_post_thread_ = null;
  }
}

实时录像、快照之类不再赘述,gb28181的,其实和camera的部分一样:

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_.addAudioBroadcastListener(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("34020000001380000001", "安卓测试设备", 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");
      stopAudioPlayer();
      destoryRTPReceiver();
      if (gb_broadcast_source_id_ != null && gb_broadcast_target_id_ != null && gb28181_agent_ != null)
        gb28181_agent_.byeAudioBroadcast(gb_broadcast_source_id_, gb_broadcast_target_id_);
      gb_broadcast_source_id_ = null;
      gb_broadcast_target_id_ = null;
      btnGB28181AudioBroadcast.setText("GB28181语音广播");
      btnGB28181AudioBroadcast.setEnabled(false);
      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 (!isRTSPPublisherRunning && !isPushingRtmp && !isRecording) {
        InitAndSetConfig();
      }
      libPublisher.SetGB28181RTPSender(publisherHandle, gb28181_rtp_sender_handle_, gb28181_rtp_payload_type_, gb28181_rtp_encoding_name_);
      int startRet = libPublisher.StartGB28181MediaStream(publisherHandle);
      if (startRet != 0) {
        if (!isRTSPPublisherRunning && !isPushingRtmp  && !isRecording) {
          if (publisherHandle != 0) {
            libPublisher.SmartPublisherClose(publisherHandle);
            publisherHandle = 0;
          }
        }
        destoryRTPSender();
        Log.e(TAG, "Failed to start GB28181 service..");
        return;
      }
      if (!isRTSPPublisherRunning && !isPushingRtmp && !isRecording) {
        CheckInitAudioRecorder();
      }
      startLayerPostThread();
      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 ntsOnNotifyBroadcastCommand(String fromUserName, String fromUserNameAtDomain, String sn, String sourceID, String targetID) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG, "ntsOnNotifyBroadcastCommand, fromUserName:"+ from_user_name_ + ", fromUserNameAtDomain:"+ from_user_name_at_domain_
            + ", SN:" + sn_ + ", sourceID:" + source_id_ + ", targetID:" + target_id_);
      if (gb28181_agent_ != null ) {
        gb28181_agent_.respondBroadcastCommand(from_user_name_, from_user_name_at_domain_,sn_,source_id_, target_id_, true);
        btnGB28181AudioBroadcast.setText("收到GB28181语音广播通知");
      }
    }
    private String from_user_name_;
    private String from_user_name_at_domain_;
    private String sn_;
    private String source_id_;
    private String target_id_;
    public Runnable set(String from_user_name, String from_user_name_at_domain, String sn, String source_id, String target_id) {
      this.from_user_name_ = from_user_name;
      this.from_user_name_at_domain_ = from_user_name_at_domain;
      this.sn_ = sn;
      this.source_id_ = source_id;
      this.target_id_ = target_id;
      return this;
    }
  }.set(fromUserName, fromUserNameAtDomain, sn, sourceID, targetID),0);
}

语音广播相关:

@Override
public void ntsOnAudioBroadcast(String commandFromUserName, String commandFromUserNameAtDomain, String sourceID, String targetID) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG, "ntsOnAudioBroadcastPlay, fromFromUserName:" + command_from_user_name_
            + " FromUserNameAtDomain:" + command_from_user_name_at_domain_
            + " sourceID:" + source_id_ + ", targetID:" + target_id_);
      stopAudioPlayer();
      destoryRTPReceiver();
      if (gb28181_agent_ != null ) {
        String local_ip_addr = IPAddrUtils.getIpAddress(context_);
        boolean is_tcp = true; // 考虑到跨网段, 默认用TCP传输rtp包
        rtp_receiver_handle_ = lib_player_.CreateRTPReceiver(0);
        if (rtp_receiver_handle_ != 0 ) {
          lib_player_.SetRTPReceiverTransportProtocol(rtp_receiver_handle_, is_tcp?1:0);
          lib_player_.SetRTPReceiverIPAddressType(rtp_receiver_handle_, 0);
          if (0 == lib_player_.CreateRTPReceiverSession(rtp_receiver_handle_, 0) ) {
            int local_port = lib_player_.GetRTPReceiverLocalPort(rtp_receiver_handle_);
            boolean ret = gb28181_agent_.inviteAudioBroadcast(command_from_user_name_,command_from_user_name_at_domain_,
                                                              source_id_, target_id_, "IP4", local_ip_addr, local_port, is_tcp?"TCP/RTP/AVP":"RTP/AVP");
            if (!ret ) {
              destoryRTPReceiver();
              btnGB28181AudioBroadcast.setText("GB28181语音广播");
            }
            else {
              btnGB28181AudioBroadcast.setText("GB28181语音广播呼叫中");
            }
          } else {
            destoryRTPReceiver();
            btnGB28181AudioBroadcast.setText("GB28181语音广播");
          }
        }
      }
    }
    private String command_from_user_name_;
    private String command_from_user_name_at_domain_;
    private String source_id_;
    private String target_id_;
    public Runnable set(String command_from_user_name, String command_from_user_name_at_domain, String source_id, String target_id) {
      this.command_from_user_name_ = command_from_user_name;
      this.command_from_user_name_at_domain_ = command_from_user_name_at_domain;
      this.source_id_ = source_id;
      this.target_id_ = target_id;
      return this;
    }
  }.set(commandFromUserName, commandFromUserNameAtDomain, sourceID, targetID),0);
}
@Override
public void ntsOnInviteAudioBroadcastException(String sourceID, String targetID, String errorInfo) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG, "ntsOnInviteAudioBroadcastException, sourceID:" + source_id_ + ", targetID:" + target_id_);
      destoryRTPReceiver();
      btnGB28181AudioBroadcast.setText("GB28181语音广播");
    }
    private String source_id_;
    private String target_id_;
    public Runnable set(String source_id, String target_id) {
      this.source_id_ = source_id;
      this.target_id_ = target_id;
      return this;
    }
  }.set(sourceID, targetID),0);
}
@Override
public void ntsOnInviteAudioBroadcastTimeout(String sourceID, String targetID) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG, "ntsOnInviteAudioBroadcastTimeout, sourceID:" + source_id_ + ", targetID:" + target_id_);
      destoryRTPReceiver();
      btnGB28181AudioBroadcast.setText("GB28181语音广播");
    }
    private String source_id_;
    private String target_id_;
    public Runnable set(String source_id, String target_id) {
      this.source_id_ = source_id;
      this.target_id_ = target_id;
      return this;
    }
  }.set(sourceID, targetID),0);
}

我们demo实现的横竖屏切换时,自动切换分辨率,有些国标平台,对分辨率切换支持的并不友好,这点可以根据实际情况调整,比如固定横竖屏。


总的来说,camera2对焦等各个方面,确实优于camera,替换升级也是大势所趋。

相关文章
|
5天前
|
Java Android开发
Android Mediatek 应用层重置USB设备功能
Android Mediatek 应用层重置USB设备功能
11 0
|
5天前
|
Android开发
Android Mediatek USB 核心驱动中增加设备 PID/VID 检查
Android Mediatek USB 核心驱动中增加设备 PID/VID 检查
3 0
|
11天前
|
Android开发 开发者
Android网络和数据交互: 请解释Android中的AsyncTask的作用。
Android&#39;s AsyncTask simplifies asynchronous tasks for brief background work, bridging UI and worker threads. It involves execute() for starting tasks, doInBackground() for background execution, publishProgress() for progress updates, and onPostExecute() for returning results to the main thread.
10 0
|
11天前
|
网络协议 安全 API
Android网络和数据交互: 什么是HTTP和HTTPS?在Android中如何进行网络请求?
HTTP和HTTPS是网络数据传输协议,HTTP基于TCP/IP,简单快速,HTTPS则是加密的HTTP,确保数据安全。在Android中,过去常用HttpURLConnection和HttpClient,但HttpClient自Android 6.0起被移除。现在推荐使用支持TLS、流式上传下载、超时配置等特性的HttpsURLConnection进行网络请求。
10 0
|
14天前
|
编解码 人工智能 测试技术
安卓适配性策略:确保应用在不同设备上的兼容性
【4月更文挑战第13天】本文探讨了提升安卓应用兼容性的策略,包括理解平台碎片化、设计响应式UI(使用dp单位,考虑横竖屏)、利用Android SDK的兼容工具(支持库、资源限定符)、编写兼容性代码(运行时权限、设备特性检查)以及优化性能以适应低端设备。适配性是安卓开发的关键,通过这些方法可确保应用在多样化设备上提供一致体验。未来,自动化测试和AI将助力应对设备碎片化挑战。
|
25天前
|
XML Java Android开发
Android每点击一次按钮就添加一条数据
Android每点击一次按钮就添加一条数据
24 1
|
1月前
|
运维 监控 Java
应用研发平台EMAS产品常见问题之安卓构建版本失败如何解决
应用研发平台EMAS(Enterprise Mobile Application Service)是阿里云提供的一个全栈移动应用开发平台,集成了应用开发、测试、部署、监控和运营服务;本合集旨在总结EMAS产品在应用开发和运维过程中的常见问题及解决方案,助力开发者和企业高效解决技术难题,加速移动应用的上线和稳定运行。
|
1月前
|
运维 监控 Android开发
应用研发平台EMAS常见问题之安卓push的离线转通知目前无法收到如何解决
应用研发平台EMAS(Enterprise Mobile Application Service)是阿里云提供的一个全栈移动应用开发平台,集成了应用开发、测试、部署、监控和运营服务;本合集旨在总结EMAS产品在应用开发和运维过程中的常见问题及解决方案,助力开发者和企业高效解决技术难题,加速移动应用的上线和稳定运行。
25 1
|
1月前
|
Shell 开发工具 Android开发
ADB 下载、安装及使用教程:让你更好地管理 Android 设备
ADB 下载、安装及使用教程:让你更好地管理 Android 设备
516 2
|
1月前
|
存储 Android开发 C++
【Android 从入门到出门】第五章:使用DataStore存储数据和测试
【Android 从入门到出门】第五章:使用DataStore存储数据和测试
37 3