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

2023-10-26 12:45

本文主要是介绍如何实现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设备对接

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 truereset()// Specifies a list of ABIs that Gradle should create APKs forinclude '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 ABIsuniversalApk 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服务器上。

    @Overridepublic void ntsRegisterOK(String dateString) {Log.i(TAG, "ntsRegisterOK Date: " + (dateString!= null? dateString : ""));}@Overridepublic void ntsRegisterTimeout() {Log.e(TAG, "ntsRegisterTimeout");}@Overridepublic void ntsRegisterTransportError(String errorInfo) {Log.e(TAG, "ntsRegisterTransportError error:" + (errorInfo != null?errorInfo :""));}

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

    @Overridepublic void ntsOnInvitePlay(String deviceId, SessionDescription session_des) {handler_.postDelayed(new Runnable() {@Overridepublic 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);}@Overridepublic void ntsOnCancelPlay(String deviceId) {// 这里取消Play会话handler_.postDelayed(new Runnable() {@Overridepublic 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设备之间的视频流传输,使用相关的音视频编解码技术将视频数据进行传输。

    @Overridepublic void ntsOnAckPlay(String deviceId) {handler_.postDelayed(new Runnable() {@Overridepublic 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协议实现设备之间的语音对讲功能,使得设备之间可以进行双向的语音通话。

    @Overridepublic void ntsOnAudioBroadcast(String commandFromUserName, String commandFromUserNameAtDomain, String sourceID, String targetID) {handler_.postDelayed(new Runnable() {@Overridepublic 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*/@Overridepublic 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请求*/@Overridepublic 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*/@Overridepublic 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*/@Overridepublic 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();}/** 更改下载速度*/@Overridepublic 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 {@Overridepublic void run() {/****相关代码**/}}
}

这篇关于如何实现Android视音频数据对接到GB28181平台(SmartGBD)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/285618

相关文章

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听