如何实现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,替换升级也是大势所趋。

相关文章
|
1月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
109 1
|
2月前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
27天前
|
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开发知识可参考相关书籍。
75 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
1月前
|
存储 大数据 数据库
Android经典面试题之Intent传递数据大小为什么限制是1M?
在 Android 中,使用 Intent 传递数据时存在约 1MB 的大小限制,这是由于 Binder 机制的事务缓冲区限制、Intent 的设计初衷以及内存消耗和性能问题所致。推荐使用文件存储、SharedPreferences、数据库存储或 ContentProvider 等方式传递大数据。
54 0
|
2月前
|
开发工具 Android开发 iOS开发
安卓与iOS开发环境对比:选择适合你的平台
【9月更文挑战第26天】在移动应用开发的广阔天地中,安卓和iOS是两大巨头。它们各自拥有独特的优势和挑战,影响着开发者的选择和决策。本文将深入探讨这两个平台的开发环境,帮助你理解它们的核心差异,并指导你根据个人或项目需求做出明智的选择。无论你是初学者还是资深开发者,了解这些平台的异同都至关重要。让我们一起探索,找到最适合你的那片开发天地。
|
2月前
|
Android开发 开发者
Android平台无纸化同屏如何实现实时录像功能
Android平台无纸化同屏,如果需要本地录像的话,实现难度不大,只要复用之前开发的录像模块的就可以,对我们来说,同屏采集这块,只是数据源不同而已,如果是自采集的其他数据,我们一样可以编码录像。
|
2天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
4天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
6天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
【10月更文挑战第35天】在数字化时代,安卓应用的开发成为了一个热门话题。本文旨在通过浅显易懂的语言,带领初学者了解安卓开发的基础知识,同时为有一定经验的开发者提供进阶技巧。我们将一起探讨如何从零开始构建第一个安卓应用,并逐步深入到性能优化和高级功能的实现。无论你是编程新手还是希望提升技能的开发者,这篇文章都将为你提供有价值的指导和灵感。
|
4天前
|
存储 API 开发工具
探索安卓开发:从基础到进阶
【10月更文挑战第37天】在这篇文章中,我们将一起探索安卓开发的奥秘。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和建议。我们将从安卓开发的基础开始,逐步深入到更复杂的主题,如自定义组件、性能优化等。最后,我们将通过一个代码示例来展示如何实现一个简单的安卓应用。让我们一起开始吧!