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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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: "

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

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

//切换到视频信道

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

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

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

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

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

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

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

数据投递讲完后,就是推送模块接口的处理,获取到的数据,是可以对接到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;

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

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;

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

轻量级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: "

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

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;

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

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以上是关于Android平台实现mp4文件实时推送RTMP|轻量级RTSP服务|GB28181平台的主要内容,如果未能解决你的问题,请参考以下文章

简单高效易用Windows/Linux/ARM/Android/iOS平台实现RTMP推送组件EasyRTMP-Android MediaCodec硬编码流程介绍

Go语学习笔记 - 实现将mp4通过rtmp推送流媒体服务

实现手机直播推送屏幕推送及录像功能RTMP推流组件之EasyRTMP-Android设置授权Key介绍

Android平台实现系统内录(捕获播放的音频)并推送RTMP服务技术方案探究

Windows平台Unity Camera场景实现轻量级RTSP服务和RTMP推送

Android平台音视频RTMP推送|GB28181对接之动态水印设计