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

          相关文章
          |
          28天前
          |
          开发框架 前端开发 Android开发
          Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
          本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
          114 4
          |
          2月前
          |
          Java Android开发 Swift
          安卓与iOS开发对比:平台选择对项目成功的影响
          【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
          121 1
          |
          3月前
          |
          IDE Android开发 iOS开发
          探索Android与iOS开发的差异:平台选择对项目成功的影响
          【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
          |
          16天前
          |
          IDE 开发工具 Android开发
          移动应用开发之旅:探索Android和iOS平台
          在这篇文章中,我们将深入探讨移动应用开发的两个主要平台——Android和iOS。我们将了解它们的操作系统、开发环境和工具,并通过代码示例展示如何在这两个平台上创建一个简单的“Hello World”应用。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧,帮助你更好地理解和掌握移动应用开发。
          40 17
          |
          2月前
          |
          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开发知识可参考相关书籍。
          111 0
          FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
          |
          3月前
          |
          监控 Android开发 iOS开发
          深入探索安卓与iOS的系统架构差异:理解两大移动平台的技术根基在移动技术日新月异的今天,安卓和iOS作为市场上最为流行的两个操作系统,各自拥有独特的技术特性和庞大的用户基础。本文将深入探讨这两个平台的系统架构差异,揭示它们如何支撑起各自的生态系统,并影响着全球数亿用户的使用体验。
          本文通过对比分析安卓和iOS的系统架构,揭示了这两个平台在设计理念、安全性、用户体验和技术生态上的根本区别。不同于常规的技术综述,本文以深入浅出的方式,带领读者理解这些差异是如何影响应用开发、用户选择和市场趋势的。通过梳理历史脉络和未来展望,本文旨在为开发者、用户以及行业分析师提供有价值的见解,帮助大家更好地把握移动技术发展的脉络。
          116 6
          |
          3月前
          |
          开发工具 Android开发 iOS开发
          安卓与iOS开发环境对比:选择适合你的平台
          【9月更文挑战第26天】在移动应用开发的广阔天地中,安卓和iOS是两大巨头。它们各自拥有独特的优势和挑战,影响着开发者的选择和决策。本文将深入探讨这两个平台的开发环境,帮助你理解它们的核心差异,并指导你根据个人或项目需求做出明智的选择。无论你是初学者还是资深开发者,了解这些平台的异同都至关重要。让我们一起探索,找到最适合你的那片开发天地。
          |
          2月前
          |
          存储 大数据 数据库
          Android经典面试题之Intent传递数据大小为什么限制是1M?
          在 Android 中,使用 Intent 传递数据时存在约 1MB 的大小限制,这是由于 Binder 机制的事务缓冲区限制、Intent 的设计初衷以及内存消耗和性能问题所致。推荐使用文件存储、SharedPreferences、数据库存储或 ContentProvider 等方式传递大数据。
          89 0
          |
          3月前
          |
          Android开发 开发者
          Android平台无纸化同屏如何实现实时录像功能
          Android平台无纸化同屏,如果需要本地录像的话,实现难度不大,只要复用之前开发的录像模块的就可以,对我们来说,同屏采集这块,只是数据源不同而已,如果是自采集的其他数据,我们一样可以编码录像。
          |
          3月前
          |
          安全 API 开发工具
          Android平台RTMP推送|轻量级RTSP服务如何实现麦克风|扬声器声音采集切换
          Android平台扬声器播放声音的采集,在无纸化同屏等场景下,意义很大,早期低版本的Android设备,是没法直接采集扬声器audio的(从Android 10开始支持),所以,如果需要采集扬声器audio,需要先做系统版本判断,添加相应的权限。
          下一篇
          DataWorks