android studio官网,android系统
终极管理员 知识笔记 40阅读
为什么要开发Android平台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添加相关权限
在做Android平台GB28181接入模块之前我们在RTMP推送播放、RTSP轻量级服务、转发、播放这块已经有很多年的经验这意味着我们不需要重复造轮子已有屏幕、摄像头或编码前目前支持的有YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565等数据类型或编码后H.264/HEVC数据只需要实现GB28181的信令交互和媒体处理即可实现不具备国标音视频能力的 Android终端通过平台注册接入到现有的GB/T28181—2016服务。
GB28181设备对接1. 导入GB28181的相关库和依赖。

<uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE ></uses-permission><uses-permission android:nameandroid.permission.INTERNET ></uses-permission><uses-permission android:nameandroid.permission.MOUNT_UNMOUNT_FILESYSTEMS /><uses-permission android:nameandroid.permission.MODIFY_AUDIO_SETTINGS /><uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION></uses-permission><uses-permission android:nameandroid.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-namestrings.xml做以下修改 <string nameapp_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 { /* *收到sDownload的文件下载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 offsetpacket偏移量 * 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 { /* *收到sDownload的文件下载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() { /****相关代码**/ } }}
标签: