如何实现Android视音频数据对接到GB28181平台(SmartGBD)

简介: 如何实现Android视音频数据对接到GB28181平台(SmartGBD)

 为什么要开发Android平台GB28181?

在做Android平台GB28181接入模块之前,我们在RTMP推送播放、RTSP轻量级服务、转发、播放这块,已经有很多年的经验,这意味着,我们不需要重复造轮子,已有屏幕、摄像头或编码前(目前支持的有YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565等数据类型)或编码后(H.264/HEVC)数据,只需要实现GB28181的信令交互,和媒体处理,即可实现不具备国标音视频能力的 Android终端,通过平台注册接入到现有的GB/T28181—2016服务。

GB28181设备对接

image.gif

1. 导入GB28181的相关库和依赖。

系统要求

    • SDK支持Android 5.1以上版本;
    • 支持的CPU架构:armv7, arm64, x86, x86_64。

    准备工作

      • 确保SmartPublisherJniV2.java放到com.daniulive.smartpublisher包名下(可在其他包名下调用);
      • 如需集成语音广播、语音对讲功能,确保SmartPlayerJniV2.java放到com.daniulive.smartplayer包名下(可在其他包名下调用);
      • smartavengine.jar和smartgbsipagent.jar加入到工程;
      • 拷贝libSmartPublisher.so和libSmartPlayer.so(如需语音广播或语音对讲)到工程;
      • AndroidManifast.xml添加相关权限:
      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" ></uses-permission>
      <uses-permission android:name="android.permission.INTERNET" ></uses-permission>
      <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
      <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
      <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
      <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>


      • Load相关so:
      static {  
          System.loadLibrary("SmartPublisher");
          System.loadLibrary("SmartPlayer");
      }

      image.gif

        • build.gradle配置32/64位库:
        splits {
            abi {
                enable true        reset()
                // Specifies a list of ABIs that Gradle should create APKs for        include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' //select ABIs to build APKs for        // Specify that we do not want to also generate a universal APK that includes all ABIs
                universalApk true    }
        }

        image.gif

          • 如需集成到自己系统测试,请用大牛直播SDK的app name,授权版按照授权app name正常使用即可;
          • 如何改app-name,strings.xml做以下修改:
          <string name="app_name">SmartPublisherSDKDemo</string>

          image.gif

          2. 配置SIP服务器:设定GB28181设备需要连接的SIP服务器地址、端口、用户凭证等信息。

          GBSIPAgentgb28181_agent_=null;
          privateintgb28181_sip_local_port_base_=5060;
          privateStringgb28181_sip_server_id_="34020000002000000001";
          privateStringgb28181_sip_domain_="3402000000";
          privateStringgb28181_sip_server_addr_="192.168.0.108";
          privateintgb28181_sip_server_port_=15060;
          privateStringgb28181_sip_user_agent_filed_=null; // "NT GB UserAgent V1.7";privateStringgb28181_sip_username_="34020000011310000039";
          privateStringgb28181_sip_password_="12345678";
          privateintgb28181_reg_expired_=3600; // 注册有效期时间最小3600秒privateintgb28181_heartbeat_interval_=20; // 心跳间隔GB28181默认是60, 目前调整到20秒privateintgb28181_heartbeat_count_=3; // 心跳间隔3次失败,表示和服务器断开了privateintgb28181_sip_trans_protocol_=0; // 0表示信令用UDP传输, 1表示信令用TCP传输

          image.gif

          3. 注册设备:通过SIP协议实现设备的注册,将设备注册到SIP服务器上。

          @OverridepublicvoidntsRegisterOK(StringdateString) {
          Log.i(TAG, "ntsRegisterOK Date: "+ (dateString!=null?dateString : ""));
              }
          @OverridepublicvoidntsRegisterTimeout() {
          Log.e(TAG, "ntsRegisterTimeout");
              }
          @OverridepublicvoidntsRegisterTransportError(StringerrorInfo) {
          Log.e(TAG, "ntsRegisterTransportError error:"+ (errorInfo!=null?errorInfo :""));
              }

          image.gif

          4. 响应呼叫:当有呼叫请求时,通过SIP协议接收呼叫请求,并进行相应的处理(如接听、拒绝等)。

          @OverridepublicvoidntsOnInvitePlay(StringdeviceId, SessionDescriptionsession_des) {
          handler_.postDelayed(newRunnable() {
          @Overridepublicvoidrun() {
          // 先振铃响应下gb28181_agent_.respondPlayInvite(180, device_id_);
          MediaSessionDescriptionvideo_des=null;
          SDPRtpMapAttributeps_rtpmap_attr=null;
          // 28181 视频使用PS打包Vector<MediaSessionDescription>video_des_list=session_des_.getVideoPSDescriptions();
          if (video_des_list!=null&&!video_des_list.isEmpty()) {
          for(MediaSessionDescriptionm : video_des_list) {
          if (m!=null&&m.isValidAddressType() &&m.isHasAddress() ) {
          video_des=m;
          ps_rtpmap_attr=video_des.getPSRtpMapAttribute();
          break;
                                  }
                              }
                          }
          if (null==video_des) {
          gb28181_agent_.respondPlayInvite(488, device_id_);
          Log.i(TAG, "ntsOnInvitePlay get video description is null, response 488, device_id:"+device_id_);
          return;
                          }
          if (null==ps_rtpmap_attr) {
          gb28181_agent_.respondPlayInvite(488, device_id_);
          Log.i(TAG, "ntsOnInvitePlay get ps rtp map attribute is null, response 488, device_id:"+device_id_);
          return;
                          }
          Log.i(TAG,"ntsOnInvitePlay, 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());
          longrtp_sender_handle=libPublisher.CreateRTPSender(0);
          if ( rtp_sender_handle==0 ) {
          gb28181_agent_.respondPlayInvite(488, device_id_);
          Log.i(TAG, "ntsOnInvitePlay 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();
                          ...
          if (!gb28181_agent_.respondPlayInviteOK(device_id_,local_video_des) ) {
          libPublisher.DestoryRTPSender(rtp_sender_handle);
          Log.e(TAG, "ntsOnInvitePlay call respondPlayInviteOK failed.");
          return;
                          }
          gb28181_rtp_sender_handle_=rtp_sender_handle;
                      }
          privateStringdevice_id_;
          privateSessionDescriptionsession_des_;
          publicRunnableset(Stringdevice_id, SessionDescriptionsession_des) {
          this.device_id_=device_id;
          this.session_des_=session_des;
          returnthis;
                      }
                  }.set(deviceId, session_des),0);
              }
          @OverridepublicvoidntsOnCancelPlay(StringdeviceId) {
          // 这里取消Play会话handler_.postDelayed(newRunnable() {
          @Overridepublicvoidrun() {
          Log.i(TAG, "ntsOnCancelPlay, deviceId="+device_id_);
          destoryRTPSender();
                      }
          privateStringdevice_id_;
          publicRunnableset(Stringdevice_id) {
          this.device_id_=device_id;
          returnthis;
                      }
                  }.set(deviceId),0);
              }

          image.gif

          5. 视频流传输:通过SIP协议实现GB28181设备之间的视频流传输,使用相关的音视频编解码技术将视频数据进行传输。

          @OverridepublicvoidntsOnAckPlay(StringdeviceId) {
          handler_.postDelayed(newRunnable() {
          @Overridepublicvoidrun() {
          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_);
          //libPublisher.SetGBTCPConnectTimeout(publisherHandle, 10*60*1000);//libPublisher.SetGBInitialTCPReconnectInterval(publisherHandle, 1000);//libPublisher.SetGBInitialTCPMaxReconnectAttempts(publisherHandle, 3);intstartRet=libPublisher.StartGB28181MediaStream(publisherHandle);
          if (startRet!=0) {
          if (!isRTSPPublisherRunning&&!isPushingRtmp&&!isRecording) {
          if (publisherHandle!=0) {
          longhandle=publisherHandle;
          publisherHandle=0;
          libPublisher.SmartPublisherClose(handle);
                                  }
                              }
          destoryRTPSender();
          Log.e(TAG, "Failed to start GB28181 service..");
          return;
                          }
          if (!isRTSPPublisherRunning&&!isPushingRtmp&&!isRecording) {
          CheckInitAudioRecorder();
                          }
          startLayerPostThread();
          isGB28181StreamRunning=true;
                      }
          privateStringdevice_id_;
          publicRunnableset(Stringdevice_id) {
          this.device_id_=device_id;
          returnthis;
                      }
                  }.set(deviceId),0);
              }

          image.gif

          6. 语音广播或语音对讲:通过SIP协议实现设备之间的语音对讲功能,使得设备之间可以进行双向的语音通话。

          @OverridepublicvoidntsOnAudioBroadcast(StringcommandFromUserName, StringcommandFromUserNameAtDomain, StringsourceID, StringtargetID) {
          handler_.postDelayed(newRunnable() {
          @Overridepublicvoidrun() {
          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 ) {
          Stringlocal_ip_addr=IPAddrUtils.getIpAddress(context_);
          booleanis_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) ) {
          intlocal_port=lib_player_.GetRTPReceiverLocalPort(rtp_receiver_handle_);
          booleanret=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语音广播");
                                  }
                              }
                          }
                      }
          privateStringcommand_from_user_name_;
          privateStringcommand_from_user_name_at_domain_;
          privateStringsource_id_;
          privateStringtarget_id_;
          publicRunnableset(Stringcommand_from_user_name, Stringcommand_from_user_name_at_domain, Stringsource_id, Stringtarget_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;
          returnthis;
                      }
                  }.set(commandFromUserName, commandFromUserNameAtDomain, sourceID, targetID),0);
              }

          image.gif

          7. 视音频录制与历史视音频下载回放:实现对视频流的录制和下载回放功能,可以将实时视频数据进行录制保存,并可以进行下载、回放操作。

          信令接口设计:

          /*** Author: daniusdk.com*/packagecom.gb.ntsignalling;
          publicinterfaceGBSIPAgent {
          voidaddDownloadListener(GBSIPAgentDownloadListenerdownloadListener);
          voidremoveDownloadListener(GBSIPAgentDownloadListenerremoveListener);
          /**响应Invite Download 200 OK*/booleanrespondDownloadInviteOK(longid, StringdeviceId, StringstartTime, StringstopTime, MediaSessionDescriptionlocalMediaDescription);
          /**响应Invite Download 其他状态码*/booleanrespondDownloadInvite(intstatusCode, longid, StringdeviceId, StringstartTime, StringstopTime);
          /** 媒体流发送者在文件下载结束后发Message消息通知SIP服务器回文件已发送完成* notifyType 必须是"121“*/booleannotifyDownloadMediaStatus(longid, StringdeviceId, StringstartTime, StringstopTime, StringnotifyType);
          /**终止Download会话*/voidterminateDownload(longid, StringdeviceId, StringstartTime, StringstopTime, booleanisSendBYE);
          /**终止所有Download会话*/voidterminateAllDownloads(booleanisSendBYE);
          }

          image.gif

          历史视音频下载listener设计:

          /*** Author: daniusdk.com*/packagecom.gb.ntsignalling;
          publicinterfaceGBSIPAgentDownloadListener {
          /**收到s=Download的文件下载Invite*/voidntsOnInviteDownload(longid, StringdeviceId, SessionDescriptionsessionDescription);
          /**发送Download invite response 异常*/voidntsOnDownloadInviteResponseException(longid, StringdeviceId, StringstartTime, StringstopTime, intstatusCode, StringerrorInfo);
          /** 收到CANCEL Download INVITE请求*/voidntsOnCancelDownload(longid, StringdeviceId, StringstartTime, StringstopTime);
          /** 收到Ack*/voidntsOnAckDownload(longid, StringdeviceId, StringstartTime, StringstopTime);
          /** 更改下载速度*/voidntsOnDownloadMANSRTSPScaleCommand(longid, StringdeviceId, StringstartTime, StringstopTime, doublescale);
          /** 收到Bye*/voidntsOnByeDownload(longid, StringdeviceId, StringstartTime, StringstopTime);
          /** 不是在收到BYE Message情况下, 终止Download*/voidntsOnTerminateDownload(longid, StringdeviceId, StringstartTime, StringstopTime);
          /** Download会话对应的对话终止, 一般不会触发这个回调,目前只有在响应了200K, 但在64*T1时间后还没收到ACK,才可能会出发收到这个, 请做相关清理处理*/voidntsOnDownloadDialogTerminated(longid, StringdeviceId, StringstartTime, StringstopTime);
          }

          image.gif

          底层jni接口设计:

          /*** SmartPublisherJniV2.java* Author: daniusdk.com*/packagecom.daniulive.smartpublisher;
          publicclassSmartPublisherJniV2 {
          /*** Open publisher(启动推送实例)** @param ctx: get by this.getApplicationContext()* * @param audio_opt:* if 0: 不推送音频* if 1: 推送编码前音频(PCM)* if 2: 推送编码后音频(aac/pcma/pcmu/speex).* * @param video_opt:* if 0: 不推送视频* if 1: 推送编码前视频(NV12/I420/RGBA8888等格式)* if 2: 推送编码后视频(AVC/HEVC)* if 3: 层叠加模式** <pre>This function must be called firstly.</pre>** @return the handle of publisher instance*/publicnativelongSmartPublisherOpen(Objectctx, intaudio_opt, intvideo_opt,  intwidth, intheight);
          /*** 设置流类型* @param type: 0:表示 live 流, 1:表示 on-demand 流, SDK默认为0(live流)* 注意: 流类型设置当前仅对GB28181媒体流有效* @return {0} if successful*/publicnativeintSetStreamType(longhandle, inttype);
          /*** 投递视频 on demand包, 当前只用于GB28181推送, 注意ByteBuffer对象必须是DirectBuffer** @param codec_id: 编码id, 当前支持H264和H265, 1:H264, 2:H265** @param packet: 视频数据, 包格式请参考H264/H265 Annex B Byte stream format, 例如:*                0x00000001 nal_unit 0x00000001 ...*                H264 IDR: 0x00000001 sps 0x00000001 pps 0x00000001 IDR_nal_unit .... 或 0x00000001 IDR_nal_unit ....*                H265 IDR: 0x00000001 vps 0x00000001 sps 0x00000001 pps 0x00000001 IDR_nal_unit .... 或 0x00000001 IDR_nal_unit ....** @param offset: 偏移量* @param size: packet size* @param pts_us: 时间戳, 单位微秒* @param is_pts_discontinuity: 是否时间戳间断,0:未间断,1:间断* @param is_key: 是否是关键帧, 0:非关键帧, 1:关键帧* @param codec_specific_data: 可选参数,可传null, 对于H264关键帧包, 如果packet不含sps和pps, 可传0x00000001 sps 0x00000001 pps*                    ,对于H265关键帧包, 如果packet不含vps,sps和pps, 可传0x00000001 vps 0x00000001 sps 0x00000001 pps* @param codec_specific_data_size: codec_specific_data size* @param width: 图像宽, 可传0* @param height: 图像高, 可传0** @return {0} if successful*/publicnativeintPostVideoOnDemandPacketByteBuffer(longhandle, intcodec_id,
          ByteBufferpacket, intoffset, intsize, longpts_us, intis_pts_discontinuity, intis_key,
          byte[] codec_specific_data, intcodec_specific_data_size,
          intwidth, intheight);
          /*** 投递音频on demand包, 当前只用于GB28181推送, 注意ByteBuffer对象必须是DirectBuffer** @param codec_id: 编码id, 当前支持PCMA和AAC, 65536:PCMA, 65538:AAC* @param packet: 音频数据* @param offset:packet偏移量* @param size: packet size* @param pts_us: 时间戳, 单位微秒* @param is_pts_discontinuity: 是否时间戳间断,0:未间断,1:间断* @param codec_specific_data: 如果是AAC的话,需要传 Audio Specific Configuration* @param codec_specific_data_size: codec_specific_data size* @param sample_rate: 采样率* @param channels: 通道数** @return {0} if successful*/publicnativeintPostAudioOnDemandPacketByteBuffer(longhandle, intcodec_id,
          ByteBufferpacket, intoffset, intsize, longpts_us, intis_pts_discontinuity,
          byte[] codec_specific_data, intcodec_specific_data_size,
          intsample_rate, intchannels);
          /*** 启动 GB28181 媒体流** @return {0} if successful*/publicnativeintStartGB28181MediaStream(longhandle);
          /*** 停止 GB28181 媒体流** @return {0} if successful*/publicnativeintStopGB28181MediaStream(longhandle);
          /*** 关闭推送实例,结束时必须调用close接口释放资源** @return {0} if successful*/publicnativeintSmartPublisherClose(longhandle);
          }

          image.gif

          上次处理逻辑

          RecordDownloadListenerImpl实现如下:

          /*** RecordDownloadListenerImpl.java* Author: daniusdk.com*/packagecom.daniulive.smartpublisher;
          publicclassRecordDownloadListenerImplimplementscom.gb.ntsignalling.GBSIPAgentDownloadListener {
          /**收到s=Download的文件下载Invite*/@OverridepublicvoidntsOnInviteDownload(longid, StringdeviceId, SessionDescriptionsdp) {
          if (!post_task(newOnInviteTask(this.context_, this.is_exit_, this.senders_map_, deviceId, sdp, id))) {
          Log.e(TAG, "ntsOnInviteDownload post_task failed, "+RecordSender.make_print_tuple(id, deviceId, sdp.getTime().getStartTime(),  sdp.getTime().getStopTime()));
          // 这里不发488, 等待事务超时也可以的GBSIPAgentagent=this.context_.get_agent();
          if (agent!=null)
          agent.respondDownloadInvite(488, id, deviceId, sdp.getTime().getStartTime(), sdp.getTime().getStopTime());
                  }
              }
          /** 收到CANCEL Download INVITE请求*/@OverridepublicvoidntsOnCancelDownload(longid, StringdeviceId, StringstartTime, StringstopTime) {
          Log.i(TAG, "ntsOnCancelDownload, "+RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
          RecordSendersender=senders_map_.remove(id);
          if (null==sender)
          return;
          StopDisposeTasktask=newStopDisposeTask(sender);
          if (!post_task(task))
          task.run();
              }
          /** 收到Ack*/@OverridepublicvoidntsOnAckDownload(longid, StringdeviceId, StringstartTime, StringstopTime) {
          Log.i(TAG, "ntsOnAckDownload, "+RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
          RecordSendersender=senders_map_.get(id);
          if (null==sender) {
          Log.e(TAG, "ntsOnAckDownload get sender is null, "+RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
          GBSIPAgentagent=this.context_.get_agent();
          if (agent!=null)
          agent.terminateDownload(id, deviceId, startTime, stopTime, false);
          return;
                  }
          StartTasktask=newStartTask(sender, this.senders_map_);
          if (!post_task(task))
          task.run();
              }
          /** 收到Bye*/@OverridepublicvoidntsOnByeDownload(longid, StringdeviceId, StringstartTime, StringstopTime) {
          Log.i(TAG, "ntsOnByeDownload, "+RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
          RecordSendersender=this.senders_map_.remove(id);
          if (null==sender)
          return;
          StopDisposeTasktask=newStopDisposeTask(sender);
          if (!post_task(task))
          task.run();
              }
          /** 更改下载速度*/@OverridepublicvoidntsOnDownloadMANSRTSPScaleCommand(longid, StringdeviceId, StringstartTime, StringstopTime, doublescale) {
          if (scale<0.01) {
          Log.e(TAG, "ntsOnDownloadMANSRTSPScaleCommand invalid scale:"+scale+" "+RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
          return;
                  }
          RecordSendersender=this.senders_map_.get(id);
          if (null==sender) {
          Log.e(TAG, "ntsOnDownloadMANSRTSPScaleCommand can not get sender, scale:"+scale+" "+RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
          return;
                  }
          sender.set_speed(scale);
          Log.i(TAG, "ntsOnDownloadMANSRTSPScaleCommand, scale:"+scale+" "+RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
              }
          }

          image.gif

          文件发送相关处理代码如下:

          /*** RecordSender.java* Author: daniusdk.com*/packagecom.daniulive.smartpublisher;
          publicclassRecordSender {
          publicvoidset_speed(doublespeed) {
          intpercent_speed= (int)(speed*100);
          this.percent_speed_.set(percent_speed);
              }
          publicvoidset_file_description(RecordFileDescriptiondesc) {
          this.file_description_=desc;
              }
          publicstaticStringmake_print_tuple(longid, Stringdevice_id, Stringstart_time, Stringstop_time) {
          StringBuildersb=newStringBuilder(96);
          sb.append("[id:").append(id);
          sb.append(", device:"+device_id);
          sb.append(", t=").append(start_time).append(" ").append(start_time);
          sb.append("]");
          returnsb.toString();
              }
          publicbooleanstart() {
          SendThreadcurrent_thread=thread_.get();
          if (current_thread!=null) {
          if (current_thread.is_exit()) {
          Log.e(TAG, "start, the thread already exists and has exited, return false, "+get_print_tuple());
          returnfalse;
                      }
          Log.i(TAG, "start, the thread already exists and has exited, return true, "+get_print_tuple());
          returntrue;
                  }
          SendThreadthread=newSendThread();
          if (!thread_.compareAndSet(null, thread)) {
          Log.i(TAG, "start, call compareAndSet return false, the thread already exists, return true, "+get_print_tuple());
          returntrue;
                  }
          try {
          Log.i(TAG, "start thread, "+get_print_tuple());
          thread.start();
                  }catch (Exceptione) {
          thread_.compareAndSet(thread, null);
          Log.e(TAG, "start e:", e);
          returnfalse;
                  }
          returntrue;
              }
          publicvoidstop() {
          SendThreadcurrent_thread=thread_.get();
          if (current_thread!=null&&!current_thread.is_exit()) {
          current_thread.exit();
          Log.i(TAG, "stop, exit thread "+get_print_tuple());
                  }
              }
          privatebooleaninit_native_sender(StackDisposabledisposables) {
          if(native_handle_!=0) {
          Log.e(TAG, "init_native_sender, native_handle_ is not 0, "+get_print_tuple());
          returnfalse;
                  }
          if (null==this.media_info_||!this.media_info_.is_has_track() ) {
          Log.e(TAG, "init_native_sender, there is no track, "+get_print_tuple());
          returnfalse;
                  }
          if (0==rtp_handle_) {
          Log.e(TAG, "init_native_sender, rtp_handle_ is 0, "+get_print_tuple());
          returnfalse;
                  }
          if (null==lib_publisher_){
          Log.e(TAG, "init_native_sender, lib_publisher_ is null, "+get_print_tuple());
          returnfalse;
                  }
          Contextcontext=this.context_.get_context();
          if (null==context) {
          Log.e(TAG, "init_native_sender, context is null, "+get_print_tuple());
          returnfalse;
                  }
          longhandle=lib_publisher_.SmartPublisherOpen(context, media_info_.is_has_audio_track()?2:0, media_info_.is_has_video_track()?2:0, 0, 0);
          if (0==handle) {
          Log.e(TAG, "init_native_sender, call SmartPublisherOpen failed, "+get_print_tuple());
          returnfalse;
                  }
          NativeSenderDisposablenative_disposable=newNativeSenderDisposable(lib_publisher_, handle);
          lib_publisher_.SetStreamType(handle, 1);
          List<MediaTrack>tracks=media_info_.get_tracks();
          for (MediaTracki : tracks) {
          if (i.is_video())
          lib_publisher_.SetEncodedVideoCodecId(handle, i.codec_id(), i.csd_set(), i.csd_set() !=null?i.csd_set().length : 0);
          elseif(i.is_audio())
          lib_publisher_.SetEncodedAudioCodecId(handle, i.codec_id(), i.csd_set(), i.csd_set() !=null?i.csd_set().length : 0);
                  }
          lib_publisher_.SetGB28181RTPSender(handle, rtp_handle_, rtp_payload_type_, rtp_encoding_name_);
          intret=lib_publisher_.StartGB28181MediaStream(handle);
          if (ret!=0) {
          Log.e(TAG, "init_native_sender, call StartGB28181MediaStream failed, "+get_print_tuple());
          native_disposable.dispose();
          returnfalse;
                  }
          native_disposable.is_need_call_stop(true);
          disposables.push(native_disposable);
          native_handle_=handle;
          returntrue;
              }
          privatebooleanpost_media_packet(MediaPacketpacket) {
          /*Log.i(TAG, "post "+ MediaTrack.get_media_type_string(packet.media_type()) + " " +MediaTrack.get_codec_id_string(packet.codec_id()) + " packet, pts:" + out_point_3(packet.pts_us()/1000.0) +"ms, key:"+ (packet.is_key()?1:0) + ", size:" + packet.size()); */if (null==lib_publisher_||0==native_handle_||!packet.is_has_data())
          returnfalse;
          if (packet.is_audio()) {
          if (packet.is_aac()) {
          if (packet.is_has_codec_specific_data_set())
          return0==lib_publisher_.PostAudioOnDemandPacketByteBuffer(native_handle_, packet.codec_id(), packet.data(), 0, packet.size(),
          packet.pts_us(), 0, packet.codec_specific_data_set(), packet.codec_specific_data_set_size(), 0, 0);
                      }
                  }elseif (packet.is_video()) {
          if (packet.is_avc() ||packet.is_hevc()) {
          return0==lib_publisher_.PostVideoOnDemandPacketByteBuffer(native_handle_, packet.codec_id(), packet.data(),
          0, packet.size(), packet.pts_us(), 0, packet.is_key()?1:0,
          packet.codec_specific_data_set(), packet.codec_specific_data_set_size(), 0, 0);
                      }
                  }
          returnfalse;
              }
          privatevoidrelease_packets(Deque<MediaPacket>packets) {
          while (!packets.isEmpty())
          packets.removeFirst().release_buffer();
              }
          privatestaticStringout_point_3(doublev) { returnString.format("%.3f", v); }
          publicstaticStringto_mega_bytes_string(longbytes) {
          doublemb=bytes/(1024*1024.0);
          returnout_point_3(mb);
              }
          privateclassSendThreadextendsThread {
          @Overridepublicvoidrun() {
          /****相关代码**/        }
              }
          }

          image.gif

          相关文章
          |
          2月前
          |
          Android开发
          安卓SO层开发 -- 编译指定平台的SO文件
          安卓SO层开发 -- 编译指定平台的SO文件
          31 0
          |
          7天前
          |
          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.
          9 0
          |
          7天前
          |
          网络协议 安全 API
          Android网络和数据交互: 什么是HTTP和HTTPS?在Android中如何进行网络请求?
          HTTP和HTTPS是网络数据传输协议,HTTP基于TCP/IP,简单快速,HTTPS则是加密的HTTP,确保数据安全。在Android中,过去常用HttpURLConnection和HttpClient,但HttpClient自Android 6.0起被移除。现在推荐使用支持TLS、流式上传下载、超时配置等特性的HttpsURLConnection进行网络请求。
          8 0
          |
          21天前
          |
          XML Java Android开发
          Android每点击一次按钮就添加一条数据
          Android每点击一次按钮就添加一条数据
          22 1
          |
          1月前
          |
          运维 监控 Java
          应用研发平台EMAS产品常见问题之安卓构建版本失败如何解决
          应用研发平台EMAS(Enterprise Mobile Application Service)是阿里云提供的一个全栈移动应用开发平台,集成了应用开发、测试、部署、监控和运营服务;本合集旨在总结EMAS产品在应用开发和运维过程中的常见问题及解决方案,助力开发者和企业高效解决技术难题,加速移动应用的上线和稳定运行。
          |
          1月前
          |
          运维 监控 Android开发
          应用研发平台EMAS常见问题之安卓push的离线转通知目前无法收到如何解决
          应用研发平台EMAS(Enterprise Mobile Application Service)是阿里云提供的一个全栈移动应用开发平台,集成了应用开发、测试、部署、监控和运营服务;本合集旨在总结EMAS产品在应用开发和运维过程中的常见问题及解决方案,助力开发者和企业高效解决技术难题,加速移动应用的上线和稳定运行。
          25 1
          |
          1月前
          |
          存储 Android开发 C++
          【Android 从入门到出门】第五章:使用DataStore存储数据和测试
          【Android 从入门到出门】第五章:使用DataStore存储数据和测试
          31 3
          |
          2月前
          |
          JavaScript Java 数据安全/隐私保护
          安卓逆向 -- POST数据解密
          安卓逆向 -- POST数据解密
          25 2
          |
          3月前
          |
          开发工具 Android开发
          Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?
          Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?
          |
          2天前
          |
          Linux 编译器 Android开发
          FFmpeg开发笔记(九)Linux交叉编译Android的x265库
          在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
          22 1
          FFmpeg开发笔记(九)Linux交叉编译Android的x265库