4

Android平台实现mp4文件实时推送RTMP|轻量级RTSP服务|GB28181平台

 1 year ago
source link: https://blog.51cto.com/daniusdk/5724465
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Android平台实现mp4文件实时推送RTMP|轻量级RTSP服务|GB28181平台

推荐 原创

好多开发者有这样的诉求,想把本地录制的MP4文件,以实时流数据的形式,推送到RTMP服务器,注入轻量级RTSP服务,或者对接到GB28181平台,这块前几年我们就有对接。

本次以MediaExtractor为例,先利用MediaExtractor,把mp4文件的音视频数据分离,然后调用我们publisher模块,实现编码后的数据对接到RTMP服务器、轻量级RTSP服务或GB28181平台即可,废话不多说,上代码,由于实例代码比较简单,不再赘述用法:

/*
* SmartPublisherActivity.java
* Github: https://github.com/daniulive/SmarterStreaming
*/
private void InitMediaExtractor(){
File mFile = new File("/storage/emulated/0/","2022.mp4");

if (!mFile.exists()){
Log.e(TAG, "mp4文件不存在");
return;
}

MediaExtractor mediaExtractor = new MediaExtractor();
try {
mediaExtractor.setDataSource(mFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}

int count = mediaExtractor.getTrackCount();//获取轨道数量
Log.e(TAG, "轨道数量 = "+count);

for (int i = 0; i < count; i++)
{
MediaFormat trackFormat = mediaExtractor.getTrackFormat(i);
String mineType = trackFormat.getString(MediaFormat.KEY_MIME);
Log.e(TAG, i + "编号通道格式 = " + mineType);

//视频信道
if (mineType.startsWith("video/")) {
video_track_index = i;
is_has_video = true;

try {
video_media_extractor.setDataSource(mFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}

if(mineType.equals("video/avc"))
{
video_codec_id = 1;
}
else if(mineType.equals("video/hevc"))
{
video_codec_id = 2;
}

int width = trackFormat.getInteger(MediaFormat.KEY_WIDTH);
int height = trackFormat.getInteger(MediaFormat.KEY_HEIGHT);
long duration = trackFormat.getLong(MediaFormat.KEY_DURATION);//总时间
int video_fps = trackFormat.getInteger(MediaFormat.KEY_FRAME_RATE);//帧率
max_sample_size = trackFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);//获取视频缓存输出的最大大小

Log.e(TAG, "video width " + width + ", height: " + height + ", duration: " + duration + ", max_sample_size: " + max_sample_size + ", fps: " + video_fps);
}

//音频信道
if (mineType.startsWith("audio/")) {
audio_track_index = i;
is_has_audio = true;

try {
audio_media_extractor.setDataSource(mFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}


if(mineType.equals("audio/mp4a-latm"))
{
audio_codec_id = 0x10002;
}

audio_sample_rate = trackFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);//获取采样率
int audioTrackBitrate = trackFormat.getInteger(MediaFormat.KEY_BIT_RATE); //获取比特率
int channels = trackFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); //获取声道数量

Log.e(TAG, "mp4 audio_sample_rate " + audio_sample_rate + ", audioTrackBitrate: " + audioTrackBitrate + ", channels: "

视频数据处理,先切到视频信道,然后调用readSampleData(),读取到video数据后,判断是不是关键帧,是关键帧的话,带上sps pps(如果是h265 带上vps sps pps),一般来说,比如无人机等设备回调,大多都贴心的实现了关键帧前携带sps pps,也有的设备是单独发sps pps,所以,对接的时候,可以先把数据打印出来看看,具体问题具体分析即可,获取video数据后,通过SmartPublisherPostVideoEncodedData()投递到底层:

//切换到视频信道
if(IsVpsSpsPps(video_header_checker_buffer, video_codec_id))
{
is_key_frame = true;
}

if ( isPushing || isRTSPPublisherRunning || isGB28181StreamRunning) {
libPublisher.SmartPublisherPostVideoEncodedData(publisherHandle, video_codec_id, byteBuffer, video_sample_size, is_key_frame?1:0, cur_sample_time, cur_sample_time);
}

audio也是类似的流程,audio有一点,需要先拿到audio param info,然后,调用readSampleData()获取到audio数据,调用SmartPublisherPostAudioEncodedData()投递出去即可。

byte[] audio_param_info = GetAudioParamInfo();
ByteBuffer parameter_info = ByteBuffer.allocateDirect(2);
parameter_info.put(audio_param_info);

int parameter_info_size = 2;

audio_media_extractor.selectTrack(audio_track_index);
int audio_sample_size = audio_media_extractor.readSampleData(byteBuffer, 0);

if(audio_sample_size < 0)
{
Log.i(TAG, "audio reach the end..");
break;
}

long cur_sample_time = audio_media_extractor.getSampleTime()/1000;

if ( isPushing || isRTSPPublisherRunning || isGB28181StreamRunning) {
libPublisher.SmartPublisherPostAudioEncodedData(publisherHandle, audio_codec_id, byteBuffer, audio_sample_size, 0, cur_sample_time, parameter_info, parameter_info_size);
}

数据投递讲完后,就是推送模块接口的处理,获取到的数据,是可以对接到RTMP推送模块,或者轻量级RTSP服务亦或GB28181设备接入模块,这些模块,都可以在一个实例内完成,所以,我们先调用OpenPushHandle()完成publisherHandle生成,并设置event callback。

private boolean OpenPushHandle()
{
if(publisherHandle != 0)
{
return true;
}

int audio_opt = 2;
int video_opt = 2;

int videoWidth = 640;
int videoHeight = 480;

publisherHandle = libPublisher.SmartPublisherOpen(context_, audio_opt, video_opt,
videoWidth, videoHeight);

if (publisherHandle == 0 )
{
Log.e(TAG, "OpenPushHandle failed!");
return false;
}

Log.i(TAG, "publisherHandle=" + publisherHandle);

libPublisher.SetSmartPublisherEventCallbackV2(publisherHandle, new EventHandePublisherV2());

return true;
}

RTMP推送相关处理:

private boolean StartPush()
{
if (isPushing)
return false;

//relayStreamUrl = "rtmp://192.168.1.77/hls/stream1";

if (relayStreamUrl == null) {
Log.e(TAG, "StartPush URL is null...");
return false;
}

if (!OpenPushHandle())
return false;

if ( libPublisher.SmartPublisherSetURL(publisherHandle, relayStreamUrl) != 0 )
{
Log.e(TAG, "StartPush failed!");
}

int startRet = libPublisher.SmartPublisherStartPublisher(publisherHandle);
if( startRet != 0)
{
Log.e(TAG, "Failed to call StartPublisher!");

if(!isRTSPPublisherRunning && !isGB28181StreamRunning)
{
libPublisher.SmartPublisherClose(publisherHandle);
publisherHandle = 0;
}

return false;
}

isPushing = true;

return true;
}

public void StopPush()
{
if (!isPushing)
return;

isPushing = false;

libPublisher.SmartPublisherStopPublisher(publisherHandle);

if(!isRTSPPublisherRunning && !isRTSPServiceRunning && !isGB28181StreamRunning)
{
libPublisher.SmartPublisherClose(publisherHandle);
publisherHandle = 0;
}
}

轻量级RTSP服务相关处理:

//启动/停止RTSP服务
class ButtonRtspServiceListener implements OnClickListener {
public void onClick(View v) {
if (isRTSPServiceRunning) {
stopRtspService();

btnRtspService.setText("启动RTSP服务");
btnRtspPublisher.setEnabled(false);

isRTSPServiceRunning = false;
return;
}

if(!OpenPushHandle())
{
return;
}

Log.i(TAG, "onClick start rtsp service..");

rtsp_handle_ = libPublisher.OpenRtspServer(0);

if (rtsp_handle_ == 0) {
Log.e(TAG, "创建rtsp server实例失败! 请检查SDK有效性");
} else {
int port = 8554;
if (libPublisher.SetRtspServerPort(rtsp_handle_, port) != 0) {
libPublisher.CloseRtspServer(rtsp_handle_);
rtsp_handle_ = 0;
Log.e(TAG, "创建rtsp server端口失败! 请检查端口是否重复或者端口不在范围内!");
}


if (libPublisher.StartRtspServer(rtsp_handle_, 0) == 0) {
Log.i(TAG, "启动rtsp server 成功!");
} else {
libPublisher.CloseRtspServer(rtsp_handle_);
rtsp_handle_ = 0;
Log.e(TAG, "启动rtsp server失败! 请检查设置的端口是否被占用!");
}

btnRtspService.setText("停止RTSP服务");
btnRtspPublisher.setEnabled(true);

isRTSPServiceRunning = true;
}
}
}

//发布/停止RTSP流
class ButtonRtspPublisherListener implements OnClickListener {
public void onClick(View v) {
if (isRTSPPublisherRunning) {
stopRtspPublisher();

btnRtspPublisher.setText("发布RTSP流");
btnGetRtspSessionNumbers.setEnabled(false);
btnRtspService.setEnabled(true);
}
else
{
Log.i(TAG, "onClick start rtsp publisher..");

boolean startRet = StartRtspStream();

if (!startRet) {
Log.e(TAG, "Failed to call StartRtspStream().");
return;
}

btnRtspPublisher.setText("停止RTSP流");
btnGetRtspSessionNumbers.setEnabled(true);
btnRtspService.setEnabled(false);
}
}
};

//当前RTSP会话数弹出框
private void PopRtspSessionNumberDialog(int {
final EditText inputUrlTxt = new EditText(this);
inputUrlTxt.setFocusable(true);
inputUrlTxt.setEnabled(false);

String session_numbers_tag = "RTSP服务当前客户会话数: " + session_numbers;
inputUrlTxt.setText(session_numbers_tag);

AlertDialog.Builder builderUrl = new AlertDialog.Builder(this);
builderUrl
.setTitle("内置RTSP服务")
.setView(inputUrlTxt).setNegativeButton("确定", null);
builderUrl.show();
}

//获取RTSP会话数
class ButtonGetRtspSessionNumbersListener implements OnClickListener {
public void onClick(View v) {
if (libPublisher != null && rtsp_handle_ != 0) {
int session_numbers = libPublisher.GetRtspServerClientSessionNumbers(rtsp_handle_);

Log.i(TAG, "GetRtspSessionNumbers: "

GB28181设备对接相关处理:

class ButtonGB28181AgentListener implements OnClickListener {
public void onClick(View v) {
stopGB28181Stream();
destoryRTPSender();

if (null == gb28181_agent_ ) {
if( !initGB28181Agent() )
return;
}

if (gb28181_agent_.isRunning()) {
gb28181_agent_.terminateAllPlays(true);// 目前测试下来,发送BYE之后,有些服务器会立即发送INVITE,是否发送BYE根据实际情况看
gb28181_agent_.stop();
btnGB28181Agent.setText("启动GB28181");
}
else {
if ( gb28181_agent_.start() ) {
btnGB28181Agent.setText("停止GB28181");
}
}
}
}

//停止GB28181 媒体流
private void stopGB28181Stream() {
if(!isGB28181StreamRunning)
return;

if (libPublisher != null) {
libPublisher.StopGB28181MediaStream(publisherHandle);
}

if (!isRecording && !isRTSPPublisherRunning && !isPushing) {
if (publisherHandle != 0) {
if (libPublisher != null) {
libPublisher.SmartPublisherClose(publisherHandle);
publisherHandle = 0;
}
}
}

isGB28181StreamRunning = false;
}
private boolean initGB28181Agent() {
if ( gb28181_agent_ != null )
return true;

getLocation(context_);

String local_ip_addr = IPAddrUtils.getIpAddress(context_);
Log.i(TAG, "initGB28181Agent local ip addr: " + local_ip_addr);

if ( local_ip_addr == null || local_ip_addr.isEmpty() ) {
Log.e(TAG, "initGB28181Agent local ip is empty");
return false;
}

gb28181_agent_ = GBSIPAgentFactory.getInstance().create();
if ( gb28181_agent_ == null ) {
Log.e(TAG, "initGB28181Agent create agent failed");
return false;
}

gb28181_agent_.addListener(this);
gb28181_agent_.addPlayListener(this);
gb28181_agent_.addDeviceControlListener(this);

// 必填信息
gb28181_agent_.setLocalAddress(local_ip_addr);
gb28181_agent_.setServerParameter(gb28181_sip_server_addr_, gb28181_sip_server_port_, gb28181_sip_server_id_, gb28181_sip_domain_);
gb28181_agent_.setUserInfo(gb28181_sip_username_, gb28181_sip_password_);

// 可选参数
gb28181_agent_.setUserAgent(gb28181_sip_user_agent_filed_);
gb28181_agent_.setTransportProtocol(gb28181_sip_trans_protocol_==0?"UDP":"TCP");

// GB28181配置
gb28181_agent_.config(gb28181_reg_expired_, gb28181_heartbeat_interval_, gb28181_heartbeat_count_);

com.gb.ntsignalling.Device gb_device = new com.gb.ntsignalling.Device("34020000001310000001", "安卓测试设备", Build.MANUFACTURER, Build.MODEL,
"宇宙","火星1","火星", true);

if (mLongitude != null && mLatitude != null) {
com.gb.ntsignalling.DevicePosition device_pos = new com.gb.ntsignalling.DevicePosition();

device_pos.setTime(mLocationTime);
device_pos.setLongitude(mLongitude);
device_pos.setLatitude(mLatitude);
gb_device.setPosition(device_pos);

gb_device.setSupportMobilePosition(true); // 设置支持移动位置上报
}

gb28181_agent_.addDevice(gb_device);

/*
com.gb28181.ntsignalling.Device gb_device1 = new com.gb28181.ntsignalling.Device("34020000001380000002", "安卓测试设备2", Build.MANUFACTURER, Build.MODEL,
"宇宙","火星1","火星", true);

if (mLongitude != null && mLatitude != null) {
com.gb28181.ntsignalling.DevicePosition device_pos = new com.gb28181.ntsignalling.DevicePosition();

device_pos.setTime(mLocationTime);
device_pos.setLongitude(mLongitude);
device_pos.setLatitude(mLatitude);
gb_device1.setPosition(device_pos);

gb_device1.setSupportMobilePosition(true);
}

gb28181_agent_.addDevice(gb_device1);


*/

if (!gb28181_agent_.createSipStack()) {
gb28181_agent_ = null;
Log.e(TAG, "initGB28181Agent gb28181_agent_.createSipStack failed.");
return false;
}

boolean is_bind_local_port_ok = false;

// 最多尝试5000个端口
int try_end_port = gb28181_sip_local_port_base_ + 5000;
try_end_port = try_end_port > 65536 ?65536: try_end_port;

for (int i = gb28181_sip_local_port_base_; i < try_end_port; ++i) {
if (gb28181_agent_.bindLocalPort(i)) {
is_bind_local_port_ok = true;
break;
}
}

if (!is_bind_local_port_ok) {
gb28181_agent_.releaseSipStack();
gb28181_agent_ = null;
Log.e(TAG, "initGB28181Agent gb28181_agent_.bindLocalPort failed.");
return false;
}

if (!gb28181_agent_.initialize()) {
gb28181_agent_.unBindLocalPort();
gb28181_agent_.releaseSipStack();
gb28181_agent_ = null;
Log.e(TAG, "initGB28181Agent gb28181_agent_.initialize failed.");
return false;
}

return true;
}

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

@Override
public void ntsOnHeartBeatException(int {
Log.e(TAG, "ntsOnHeartBeatException heart beat timeout count reached, count:" + exceptionCount +
", exception info:" + (lastExceptionInfo != null ? lastExceptionInfo : ""));

// 停止信令, 然后重启
handler_.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "gb28281_heart_beart_timeout");

stopGB28181Stream();
destoryRTPSender();

if (gb28181_agent_ != null) {
gb28181_agent_.terminateAllPlays(true);

Log.i(TAG, "gb28281_heart_beart_timeout sip stop");
gb28181_agent_.stop();

String local_ip_addr = IPAddrUtils.getIpAddress(context_);
if (local_ip_addr != null && !local_ip_addr.isEmpty()) {
Log.i(TAG, "gb28281_heart_beart_timeout get local ip addr: " + local_ip_addr);
gb28181_agent_.setLocalAddress(local_ip_addr);
}

Log.i(TAG, "gb28281_heart_beart_timeout sip start");
gb28181_agent_.start();
}
}

}, 0);
}

@Override
public void ntsOnInvitePlay(String deviceId, PlaySessionDescription session_des) {
handler_.postDelayed(new Runnable() {
@Override
public void run() {
MediaSessionDescription video_des = session_des_.getVideoDescription();
SDPRtpMapAttribute ps_rtpmap_attr = video_des.getPSRtpMapAttribute();

Log.i(TAG,"ntsInviteReceived, 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());

// 可以先给信令服务器发送临时振铃响应
//sip_stack_android.respondPlayInvite(180, device_id_);

long rtp_sender_handle = libPublisher.CreateRTPSender(0);
if ( rtp_sender_handle == 0 ) {
gb28181_agent_.respondPlayInvite(488, device_id_);
Log.i(TAG, "ntsInviteReceived 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();

libPublisher.SetRTPSenderTransportProtocol(rtp_sender_handle, video_des.isRTPOverUDP()?0:1);
libPublisher.SetRTPSenderIPAddressType(rtp_sender_handle, video_des.isIPv4()?0:1);
libPublisher.SetRTPSenderLocalPort(rtp_sender_handle, 0);
libPublisher.SetRTPSenderSSRC(rtp_sender_handle, video_des.getSSRC());
libPublisher.SetRTPSenderSocketSendBuffer(rtp_sender_handle, 2*1024*1024); // 设置到2M
libPublisher.SetRTPSenderClockRate(rtp_sender_handle, ps_rtpmap_attr.getClockRate());
libPublisher.SetRTPSenderDestination(rtp_sender_handle, video_des.getAddress(), video_des.getPort());

if ( libPublisher.InitRTPSender(rtp_sender_handle) != 0 ) {
gb28181_agent_.respondPlayInvite(488, device_id_);
libPublisher.DestoryRTPSender(rtp_sender_handle);
return;
}

int local_port = libPublisher.GetRTPSenderLocalPort(rtp_sender_handle);
if (local_port == 0) {
gb28181_agent_.respondPlayInvite(488, device_id_);
libPublisher.DestoryRTPSender(rtp_sender_handle);
return;
}

Log.i(TAG,"get local_port:" + local_port);

String local_ip_addr = IPAddrUtils.getIpAddress(context_);
gb28181_agent_.respondPlayInviteOK(device_id_,local_ip_addr, local_port);

gb28181_rtp_sender_handle_ = rtp_sender_handle;
}

private String device_id_;
private PlaySessionDescription session_des_;

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

@Override
public void ntsOnAckPlay(String deviceId) {
handler_.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG,"ntsOnACKPlay, device_id:" +device_id_);

if (!isRecording && !isRTSPPublisherRunning && !isPushing) {
OpenPushHandle();
}

libPublisher.SetGB28181RTPSender(publisherHandle, gb28181_rtp_sender_handle_, gb28181_rtp_payload_type_, gb28181_rtp_encoding_name_);
int startRet = libPublisher.StartGB28181MediaStream(publisherHandle);
if (startRet != 0) {

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

destoryRTPSender();

Log.e(TAG, "Failed to start GB28181 service..");
return;
}

isGB28181StreamRunning = true;
}

private String device_id_;

public Runnable set(String device_id) {
this.device_id_ = device_id;
return this;
}

}.set(deviceId),0);
}

@Override
public void ntsOnPlayInviteResponseException(String deviceId, int {
// 这里要释放掉响应的资源
Log.i(TAG, "ntsOnPlayInviteResponseException, deviceId=" + deviceId + " statusCode=" +statusCode
+ " errorInfo:" + errorInfo);

handler_.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "ntsOnPlayInviteResponseException, deviceId=" + device_id_);

destoryRTPSender();
}

private String device_id_;

public Runnable set(String device_id) {
this.device_id_ = device_id;
return this;
}

}.set(deviceId),0);
}

@Override
public void ntsOnByePlay(String deviceId) {
handler_.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "ntsOnByePlay, stop GB28181 media stream, deviceId=" + device_id_);

stopGB28181Stream();
destoryRTPSender();
}

private String device_id_;

public Runnable set(String device_id) {
this.device_id_ = device_id;
return this;
}

}.set(deviceId),0);
}

@Override
public void ntsOnTerminatePlay(String deviceId) {
handler_.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "ntsOnTerminatePlay, stop GB28181 media stream, deviceId=" + device_id_);

stopGB28181Stream();
destoryRTPSender();
}

private String device_id_;

public Runnable set(String device_id) {
this.device_id_ = device_id;
return this;
}

}.set(deviceId),0);
}

@Override
public void ntsOnPlayDialogTerminated(String deviceId) {
/*
Play会话对应的对话终止, 一般不会出发这个回调,目前只有在响应了200K, 但在64*T1时间后还没收到ACK,才可能会出发
收到这个请做相关清理处理
*/
handler_.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "ntsOnPlayDialogTerminated, deviceId=" + device_id_);

stopGB28181Stream();
destoryRTPSender();
}

private String device_id_;

public Runnable set(String device_id) {
this.device_id_ = device_id;
return this;
}

}.set(deviceId),0);
}

@Override
public void ntsOnDevicePositionRequest(String deviceId, int {
handler_.postDelayed(new Runnable() {
@Override
public void run() {
getLocation(context_);

Log.v(TAG, "ntsOnDevicePositionRequest, deviceId:" + this.device_id_ + ", Longitude:" + mLongitude
+ ", Latitude:" + mLatitude + ", Time:" + mLocationTime);


if (mLongitude != null && mLatitude != null) {
com.gb.ntsignalling.DevicePosition device_pos = new com.gb.ntsignalling.DevicePosition();

device_pos.setTime(mLocationTime);
device_pos.setLongitude(mLongitude);
device_pos.setLatitude(mLatitude);

if (gb28181_agent_ != null ) {
gb28181_agent_.updateDevicePosition(device_id_, device_pos);
}
}
}

private String device_id_;
private int interval_;

public Runnable set(String device_id, int {
this.device_id_ = device_id;
this.interval_ = interval;
return this;
}

}.set(deviceId, interval),0);
}

@Override
public void ntsOnDeviceControlTeleBootCommand(String deviceId, String teleBootValue) {
handler_.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "ntsOnDeviceControlTeleBootCommand device_id:" + device_id_ + " tele_boot_value:" + tele_boot_value_);

stopGB28181Stream();
destoryRTPSender();

if (gb28181_agent_ != null ) {
gb28181_agent_.terminateAllPlays(true);
gb28181_agent_.stop();
}

// 发送注销消息后,等待2000毫秒, 再释放资源
handler_.postDelayed(new Runnable() {
@Override
public void run() {
if (gb28181_agent_ != null ) {
Log.i(TAG, " gb28181_agent_.unInitialize++");
gb28181_agent_.unInitialize();
gb28181_agent_.unBindLocalPort();
gb28181_agent_.releaseSipStack();
Log.i(TAG, " gb28181_agent_.unInitialize--");

gb28181_agent_ = null;
}

// 200毫秒后再重启
handler_.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "restart gb sip agent.");

if (null==gb28181_agent_) {
if (!initGB28181Agent()) {
Log.e(TAG, "init gb sip agent failed.");
return;
}
}

if (!gb28181_agent_.isRunning()) {
if ( !gb28181_agent_.start() ) {
Log.e(TAG, "restart gb sip agent failed.");
}
}
}

},200);

}

},2000);
}

private String device_id_;
private String tele_boot_value_;

public Runnable set(String device_id, String tele_boot_value) {
this.device_id_ = device_id;
this.tele_boot_value_ = tele_boot_value;
return this;
}

}.set(deviceId, teleBootValue),0);
}

以上就是大概流程,需要注意的是,本地MP4文件作为实时数据发送的时候,需要注意时间戳的问题,简单来说,确保“1分钟的数据,按照时间戳间隔,1分钟均匀的发出去”。

  • 打赏
  • 收藏
  • 评论
  • 分享
  • 举报

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK