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

news2024/9/9 1:21:05

为什么要开发Android平台GB28181?

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

GB28181设备对接

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

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

    GBSIPAgent     gb28181_agent_             = null;
    private int    gb28181_sip_local_port_base_  = 5060;
    private String gb28181_sip_server_id_     = "34020000002000000001";
    private String gb28181_sip_domain_        =  "3402000000";
    private String gb28181_sip_server_addr_   = "192.168.0.108";
    private int    gb28181_sip_server_port_   = 15060;

    private String gb28181_sip_user_agent_filed_  = null; // "NT GB UserAgent V1.7";
    private String gb28181_sip_username_   = "34020000011310000039";
    private String gb28181_sip_password_   = "12345678";

    private int gb28181_reg_expired_           = 3600; // 注册有效期时间最小3600秒
    private int gb28181_heartbeat_interval_    = 20; // 心跳间隔GB28181默认是60, 目前调整到20秒
    private int gb28181_heartbeat_count_       = 3; // 心跳间隔3次失败,表示和服务器断开了
    private int gb28181_sip_trans_protocol_    = 0; // 0表示信令用UDP传输, 1表示信令用TCP传输

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

    @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 :""));
    }

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

    @Override
    public void ntsOnInvitePlay(String deviceId, SessionDescription session_des) {
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                // 先振铃响应下
                gb28181_agent_.respondPlayInvite(180, device_id_);

                MediaSessionDescription video_des = null;
                SDPRtpMapAttribute ps_rtpmap_attr = null;

                // 28181 视频使用PS打包
                Vector<MediaSessionDescription> video_des_list = session_des_.getVideoPSDescriptions();
                if (video_des_list != null && !video_des_list.isEmpty()) {
                    for(MediaSessionDescription m : 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());

                long rtp_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;
            }

            private String device_id_;
            private SessionDescription session_des_;

            public Runnable set(String device_id, SessionDescription 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);
    }

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

    @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_);

                //libPublisher.SetGBTCPConnectTimeout(publisherHandle, 10*60*1000);
                //libPublisher.SetGBInitialTCPReconnectInterval(publisherHandle, 1000);
                //libPublisher.SetGBInitialTCPMaxReconnectAttempts(publisherHandle, 3);

                int startRet = libPublisher.StartGB28181MediaStream(publisherHandle);
                if (startRet != 0) {

                    if (!isRTSPPublisherRunning && !isPushingRtmp  && !isRecording) {
                        if (publisherHandle != 0) {
                            long handle = publisherHandle;
                            publisherHandle = 0;
                            libPublisher.SmartPublisherClose(handle);
                        }
                    }

                    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);
    }

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

    @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);
    }

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

信令接口设计:


/**
* Author: daniusdk.com
*/
package com.gb.ntsignalling;
 
public interface GBSIPAgent {
    void addDownloadListener(GBSIPAgentDownloadListener downloadListener);
 
    void removeDownloadListener(GBSIPAgentDownloadListener removeListener);
 
    /*
    *响应Invite Download 200 OK
    */
    boolean respondDownloadInviteOK(long id, String deviceId, String startTime, String stopTime, MediaSessionDescription localMediaDescription);
 
    /*
    *响应Invite Download 其他状态码
    */
    boolean respondDownloadInvite(int statusCode, long id, String deviceId, String startTime, String stopTime);
 
    /*
    * 媒体流发送者在文件下载结束后发Message消息通知SIP服务器回文件已发送完成
    * notifyType 必须是"121“
     */
    boolean notifyDownloadMediaStatus(long id, String deviceId, String startTime, String stopTime, String notifyType);
 
    /*
     *终止Download会话
     */
    void terminateDownload(long id, String deviceId, String startTime, String stopTime, boolean isSendBYE);
 
    /*
     *终止所有Download会话
     */
    void terminateAllDownloads(boolean isSendBYE);
 
}

历史视音频下载listener设计:

/**
* Author: daniusdk.com
*/
package com.gb.ntsignalling;
 
public interface GBSIPAgentDownloadListener {
    /*
     *收到s=Download的文件下载Invite
     */
    void ntsOnInviteDownload(long id, String deviceId, SessionDescription sessionDescription);
 
    /*
     *发送Download invite response 异常
     */
    void ntsOnDownloadInviteResponseException(long id, String deviceId, String startTime, String stopTime, int statusCode, String errorInfo);
 
    /*
     * 收到CANCEL Download INVITE请求
     */
    void ntsOnCancelDownload(long id, String deviceId, String startTime, String stopTime);
 
    /*
     * 收到Ack
     */
    void ntsOnAckDownload(long id, String deviceId, String startTime, String stopTime);
 
    /*
    * 更改下载速度
     */
    void ntsOnDownloadMANSRTSPScaleCommand(long id, String deviceId, String startTime, String stopTime, double scale);
 
    /*
     * 收到Bye
     */
    void ntsOnByeDownload(long id, String deviceId, String startTime, String stopTime);
 
    /*
     * 不是在收到BYE Message情况下, 终止Download
     */
    void ntsOnTerminateDownload(long id, String deviceId, String startTime, String stopTime);
 
    /*
     * Download会话对应的对话终止, 一般不会触发这个回调,目前只有在响应了200K, 但在64*T1时间后还没收到ACK,才可能会出发
    收到这个, 请做相关清理处理
    */
    void ntsOnDownloadDialogTerminated(long id, String deviceId, String startTime, String stopTime);
}

底层jni接口设计:


/**
* SmartPublisherJniV2.java
* Author: daniusdk.com
*/
package com.daniulive.smartpublisher;
 
public class SmartPublisherJniV2 {
 
     /**
	 * 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
	 */
    public native long SmartPublisherOpen(Object ctx, int audio_opt, int video_opt,  int width, int height);
 
    
     /**
	 * 设置流类型
	 * @param type: 0:表示 live 流, 1:表示 on-demand 流, SDK默认为0(live流)
	 * 注意: 流类型设置当前仅对GB28181媒体流有效
	 * @return {0} if successful
	 */
    public native int SetStreamType(long handle, int type);
 
 
    /**
	 * 投递视频 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
	 */
	public native int PostVideoOnDemandPacketByteBuffer(long handle, int codec_id,
														ByteBuffer packet, int offset, int size, long pts_us, int is_pts_discontinuity, int is_key,
														byte[] codec_specific_data, int codec_specific_data_size,
									  					int width, int height);
 
	
     /**
	 * 投递音频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
	 */
	public native int PostAudioOnDemandPacketByteBuffer(long handle, int codec_id,
														ByteBuffer packet, int offset, int size, long pts_us, int is_pts_discontinuity,
														byte[] codec_specific_data, int codec_specific_data_size,
														int sample_rate, int channels);
 
	/**
	 * 启动 GB28181 媒体流
	 *
	 * @return {0} if successful
	 */
	public native int StartGB28181MediaStream(long handle);
 
 
    /**
	 * 停止 GB28181 媒体流
	 *
	 * @return {0} if successful
	 */
	public native int StopGB28181MediaStream(long handle);
 
    
	/**
     * 关闭推送实例,结束时必须调用close接口释放资源
	 *
	 * @return {0} if successful
	 */
    public native int SmartPublisherClose(long handle);
 
}

上次处理逻辑

RecordDownloadListenerImpl实现如下:

/**
* RecordDownloadListenerImpl.java
* Author: daniusdk.com
*/
package com.daniulive.smartpublisher;
 
public class RecordDownloadListenerImpl implements com.gb.ntsignalling.GBSIPAgentDownloadListener {
 
    /*
     *收到s=Download的文件下载Invite
     */
    @Override
    public void ntsOnInviteDownload(long id, String deviceId, SessionDescription sdp) {
        if (!post_task(new OnInviteTask(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, 等待事务超时也可以的
            GBSIPAgent agent = this.context_.get_agent();
            if (agent != null)
                agent.respondDownloadInvite(488, id, deviceId, sdp.getTime().getStartTime(), sdp.getTime().getStopTime());
        }
    }
 
    /*
     * 收到CANCEL Download INVITE请求
     */
    @Override
    public void ntsOnCancelDownload(long id, String deviceId, String startTime, String stopTime) {
        Log.i(TAG, "ntsOnCancelDownload, " + RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
 
        RecordSender sender = senders_map_.remove(id);
        if (null == sender)
            return;
 
        StopDisposeTask task = new StopDisposeTask(sender);
        if (!post_task(task))
            task.run();
    }
 
    /*
     * 收到Ack
     */
    @Override
    public void ntsOnAckDownload(long id, String deviceId, String startTime, String stopTime) {
        Log.i(TAG, "ntsOnAckDownload, "+ RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
 
        RecordSender sender = senders_map_.get(id);
        if (null == sender) {
            Log.e(TAG, "ntsOnAckDownload get sender is null, " + RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
 
            GBSIPAgent agent = this.context_.get_agent();
            if (agent != null)
                agent.terminateDownload(id, deviceId, startTime, stopTime, false);
 
            return;
        }
 
        StartTask task = new StartTask(sender, this.senders_map_);
        if (!post_task(task))
            task.run();
    }
 
    /*
     * 收到Bye
     */
    @Override
    public void ntsOnByeDownload(long id, String deviceId, String startTime, String stopTime) {
        Log.i(TAG, "ntsOnByeDownload, "+ RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
 
        RecordSender sender = this.senders_map_.remove(id);
        if (null == sender)
            return;
 
        StopDisposeTask task = new StopDisposeTask(sender);
        if (!post_task(task))
            task.run();
    }
 
    /*
     * 更改下载速度
     */
    @Override
    public void ntsOnDownloadMANSRTSPScaleCommand(long id, String deviceId, String startTime, String stopTime, double scale) {
        if (scale < 0.01) {
            Log.e(TAG, "ntsOnDownloadMANSRTSPScaleCommand invalid scale:" + scale  + " " + RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
            return;
        }
 
        RecordSender sender = 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));
    }
}

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

/**
* RecordSender.java
* Author: daniusdk.com
*/
package com.daniulive.smartpublisher;
 
public class RecordSender {
    public void set_speed(double speed) {
        int percent_speed = (int)(speed*100);
        this.percent_speed_.set(percent_speed);
    }
 
    public void set_file_description(RecordFileDescription desc) {
        this.file_description_ = desc;
    }
 
    public static String make_print_tuple(long id, String device_id, String start_time, String stop_time) {
        StringBuilder sb = new StringBuilder(96);
        sb.append("[id:").append(id);
        sb.append(", device:" + device_id);
        sb.append(", t=").append(start_time).append(" ").append(start_time);
        sb.append("]");
        return sb.toString();
    }
 
    public boolean start() {
        SendThread current_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());
                return false;
            }
 
            Log.i(TAG, "start, the thread already exists and has exited, return true, " + get_print_tuple());
            return true;
        }
 
        SendThread thread = new SendThread();
        if (!thread_.compareAndSet(null, thread)) {
            Log.i(TAG, "start, call compareAndSet return false, the thread already exists, return true, " + get_print_tuple());
            return true;
        }
 
        try {
            Log.i(TAG, "start thread, " + get_print_tuple());
            thread.start();
        }catch (Exception e) {
            thread_.compareAndSet(thread, null);
            Log.e(TAG, "start e:", e);
            return false;
        }
 
        return true;
    }
 
    public void stop() {
        SendThread current_thread = thread_.get();
        if (current_thread != null && !current_thread.is_exit()) {
            current_thread.exit();
            Log.i(TAG, "stop, exit thread " + get_print_tuple());
        }
    }
 
    private boolean init_native_sender(StackDisposable disposables) {
        if(native_handle_ !=0) {
            Log.e(TAG, "init_native_sender, native_handle_ is not 0, " + get_print_tuple());
            return false;
        }
 
        if (null == this.media_info_ || !this.media_info_.is_has_track() ) {
            Log.e(TAG, "init_native_sender, there is no track, " + get_print_tuple());
            return false;
        }
 
        if (0 == rtp_handle_) {
            Log.e(TAG, "init_native_sender, rtp_handle_ is 0, " + get_print_tuple());
            return false;
        }
 
        if (null == lib_publisher_){
            Log.e(TAG, "init_native_sender, lib_publisher_ is null, " + get_print_tuple());
            return false;
        }
 
        Context context = this.context_.get_context();
        if (null == context) {
            Log.e(TAG, "init_native_sender, context is null, " + get_print_tuple());
            return false;
        }
 
        long handle = 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());
            return false;
        }
 
        NativeSenderDisposable native_disposable = new NativeSenderDisposable(lib_publisher_, handle);
 
        lib_publisher_.SetStreamType(handle, 1);
 
        List<MediaTrack> tracks = media_info_.get_tracks();
        for (MediaTrack i : tracks) {
            if (i.is_video())
                lib_publisher_.SetEncodedVideoCodecId(handle, i.codec_id(), i.csd_set(), i.csd_set() != null? i.csd_set().length : 0);
            else if(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_);
 
        int ret = lib_publisher_.StartGB28181MediaStream(handle);
        if (ret != 0) {
            Log.e(TAG, "init_native_sender, call StartGB28181MediaStream failed, " + get_print_tuple());
            native_disposable.dispose();
            return false;
        }
 
        native_disposable.is_need_call_stop(true);
        disposables.push(native_disposable);
 
        native_handle_ = handle;
 
        return true;
    }
 
    private boolean post_media_packet(MediaPacket packet) {
        /*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())
            return false;
 
        if (packet.is_audio()) {
            if (packet.is_aac()) {
                if (packet.is_has_codec_specific_data_set())
                    return 0 == 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);
            }
        }else if (packet.is_video()) {
            if (packet.is_avc() || packet.is_hevc()) {
                return 0 == 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);
            }
        }
 
        return false;
    }
 
  
    private void release_packets(Deque<MediaPacket> packets) {
        while (!packets.isEmpty())
            packets.removeFirst().release_buffer();
    }
 
    private static String out_point_3(double v) { return String.format("%.3f", v); }
 
    public static String to_mega_bytes_string(long bytes) {
        double mb = bytes/(1024*1024.0);
        return out_point_3(mb);
    }
 
    private class SendThread extends Thread {
	
        @Override
        public void run() {
            /***
			*相关代码
			**/
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1135963.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Keil uVision 5 MDK版软件安装包下载及安装教程(最详细图文教程)

目录 一.简介 二.安装步骤 软件&#xff1a;Keil uvision5版本&#xff1a;MDKv518语言&#xff1a;中文/英文大小&#xff1a;377.01M安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU2.59GHz 内存4G(或更高&#xff09;下载通道①百度网盘丨64位下载链接…

【Linux】第三站:Linux基本指令(二)

文章目录 一、通配符 *二、man指令三、cp指令1.先给一个文件里面写入数据2. cp指令拷贝普通文件3.cp指令拷贝文件目录4.常用的选项总结 四、mv指令1.mv命令简介2.使用 五、一些插曲1.一些注意事项2.指令的本质3.再谈输出重定向4.追加重定向5.输入重定向 六、cat指令七、more指令…

liunx Centos-7.5上 rabbitmq安装

在安装rabbitmq中需要注意&#xff1a; 1、rabbitmq依赖于erlang&#xff0c;需要先安装erlang 2、erlang和rabbitmq版本有对应关系 可参考网页&#xff1a;https://www.rabbitmq.com/which-erlang.html 第一步&#xff0c;安装编译工具及库文件,如果服务器上已经有了&…

大事务导致数据库恢复时间长

背景 客户的一套系统从凌晨开始出现运行缓慢&#xff0c;重启SQL Server服务后一个主要的数据库一直处在正在恢复的状态&#xff0c;多次重启SQL Server服务和服务器无果后请我们协助处理。 现象 在SSMS中看到数据库是正在恢复的状态&#xff0c;而且不能被访问。 分析 启动SQ…

全球医疗产品畅销:跨境电商的生命科技

随着数字化时代的到来&#xff0c;跨境电商不仅仅是商业领域的一种革命性力量&#xff0c;更是生命科技行业的重要推动者。这一融合带来了全球医疗产品畅销的新时代&#xff0c;为世界各地的消费者带来了前所未有的便利和机会。本文将深入探讨这一跨境电商的生命科技领域&#…

C# 高级 <一>

一、 特性&#xff08;Attribute&#xff09; .NET 框架提供了三种预定义特性&#xff1a; AttributeUsageConditionalObsolete a. AttributeUsage 它描述了如何使用一个自定义特性类。它规定了特性可应用项目的类型。语法如下&#xff1a; [AttributeUsage(validon,AllowM…

万圣节倒计时:如何在TikTok上推动商品广告

万圣节是一个商机充足的季节&#xff0c;对于广告商和品牌来说&#xff0c;这是一个绝佳的机会。TikTok&#xff0c;作为一款全球短视频应用&#xff0c;提供了一个独特的平台&#xff0c;可以将商品广告巧妙地与万圣节元素结合&#xff0c;吸引年轻受众的注意力。本文将深入探…

想要完美录屏?这几款录屏大师,不容错过

在数字媒体和游戏产业的不断崛起下&#xff0c;屏幕录制技术变得越来越重要。无论是捕捉游戏精彩瞬间、创作教程视频&#xff0c;还是与朋友分享有趣时刻&#xff0c;录屏工具成为了当今数字生活不可或缺的一部分。本文将介绍三款好用的录屏大师&#xff0c;无论您是一位游戏爱…

Gartner:2024年优秀战略技术趋势

到2024年&#xff0c;人工智能将在企业中占据主导地位&#xff0c;而云服务、安全性和可持续性的影响力也将上升。 到2024年&#xff0c;人工智能将在企业中占据主导地位&#xff0c;而云服务、安全性和可持续性的影响力也将上升。 Gartner表示&#xff0c;人工智能和智能应用…

Ubuntu20.04下安装MySQL8环境

Ubuntu20.04下安装MySQL8环境 1.下载MySQL客户端和服务器2.配置MySQL3.测试MySQL4.设置MySQL服务开机自启动5.修改root密码MySQL数据库基本使用启动MySQL数据库服务重启MySQL数据库服务停止MySQL数据库服务查看MySQL运行状态设置MySQL服务开机自启动停止MySQL服务开机自启动MyS…

Unity的galgame形式对话系统工具

这段代码用于读取表格 using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using OfficeOpenXml; using System.IO; using UnityEngine.Networking; using UnityEngine.UI; using Random UnityEngine.Random;public class Plots…

[计算机提升] 命令、批处理介绍

1.12 命令、批处理 1.12.1 命令 在Windows系统中&#xff0c;命令通常指的是命令行指令&#xff08;或称为命令行命令&#xff09;&#xff0c;也就是通过命令提示符&#xff08;Command Prompt&#xff09;或者PowerShell运行的特定命令。这些命令可以让用户与操作系统进行交…

敏捷or瀑布: Salesforce项目应该选哪一个?

在项目管理方法和框架不断扩展的世界中&#xff0c;敏捷和瀑布是两种最著名的项目管理方法。它们在Salesforce实施中都很流行&#xff0c;但各自适合不同类型的项目。 选择正确的方法将直接影响到项目的预算、日程安排和有效执行&#xff0c;对Salesforce项目的成功至关重要。…

windows如何查看电脑IP地址

介绍两种查看电脑IP的方式 一、第一种方式 1、在电脑左下角搜索网络连接 2、鼠标右键你目前连接的网络&#xff08;wifi就点wifi 网线就点以太网&#xff09;&#xff1b;选择里面的状态。 3、点击详细信息&#xff0c;这里的IPv4地址就是你电脑的IP。 二、第二种 1、win…

LCR 146. 螺旋遍历二维数组

LCR 146. 螺旋遍历二维数组 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a; 原题链接&#xff1a; LCR 146. 螺旋遍历二维数组 https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/description/ 完成情况&#xff1a; 解…

记一次企业微信的(CorpID)和密钥(Secret)泄漏的利用案例

文章目录 一、介绍二、利用过程1、获取AccessToken2、获取企业微信接口IP段3、获取企业微信回调IP段4、通过部门ID,查看返回的ID5、通过部门ID,查看用户列表6、通过便利ID,发现用户信息泄露,可以进行提交报告7、通过添加接口,添加企业账号8、登陆企业账号进行测试三、参考…

2023年中国医疗器械供应链服务平台发展趋势分析:向国家高端化市场发力[图]

医疗器械供应链服务主要分为全流程供应链服务与院内SPD服务&#xff0c;同时全流程供应链服务主要分为市场、仓储物流与金融三大服务。在SPD数字化赋能下&#xff0c;大数据、云计算等技术支撑促进一站式数字化供应链业务协同平台&#xff0c;带动了整体医疗器械供应链服务的发…

某全球领先的晶圆代工企业:替代FTP实现大规模文件的高效传输

全球领先的集成电路晶圆代工企业之一 该企业不仅是全球领先的集成电路晶圆代工企业之一&#xff0c;也是中国内地技术最先进、配套最完善、规模最大、跨国经营的集成电路制造企业集团。主要业务是根据客户本身或第三者的集成电路设计&#xff0c;为客户制造集成电路芯片&#…

【Amazon】跨AWS账号级别迁移弹性IP地址操作流程

文章目录 一、实验背景二、适用场景三、实验目标四、实验说明五、实验步骤1. 登录AWS控制台[源账户]2. 启用弹性 IP 地址转移[源账户]3. 接受转移的弹性 IP 地址[目标账户]4. <可选>禁止转移弹性IP地址[源账户] 六、结果验证七、注意事项八、附录1. 多弹性IP地址多AWS账号…

SpringCloud Alibaba【二】nacos

nacos配置与使用 nacos初步使用nacos安装与配置创建命名空间 nacos使用与配置创建新项目作为父项目 创建nacos服务端项目pom.xmlapplication.yml启动类 创建nacos客户端项目pom.xml application.yml启动类 启动测试 nacos配置负载均衡改造生产者nacos-provider-projectcontroll…