技术背景
好多开发者希望了解,Android平台下,如何实现RTSP拉流播放的同时,可以按需转发到RTMP服务,在介绍这块技术实现之前,我们先看都有哪些场景使用这块技术。
安防监控领域:
- 远程监控与视频查看:传统的安防监控摄像头大多采用 RTSP 协议传输视频流,但是如果需要在公网环境下或者通过互联网进行远程实时监控查看,直接使用 RTSP 会受到很多限制,比如内网穿透问题等。将 RTSP 流转为 RTMP 流后,可以推送到支持 RTMP 协议的流媒体服务器,用户就可以通过各种终端设备(如手机、电脑、平板等)上的播放器来实时查看监控视频,方便安保人员、业主等随时随地了解监控区域的情况。
- 监控系统集成与扩展:一些大型的安防监控系统可能需要将多个不同来源的监控视频流进行整合和统一管理。通过将 RTSP 转 RTMP,可以将原本格式各异的监控视频流转换为统一的 RTMP 格式,便于系统进行集中处理、存储和分发,也方便与其他安防子系统(如门禁系统、报警系统等)进行联动和集成。
视频直播领域:
- 企业级直播活动:企业在举办线上会议、产品发布会、培训课程等直播活动时,可能会使用到各种不同类型的视频采集设备,其中部分设备可能输出 RTSP 流。为了保证直播的顺利进行和在不同终端上的稳定播放,需要将这些 RTSP 流转换为 RTMP 流,然后推送到企业自己的直播服务器或者第三方直播平台。
视频会议领域:
- 跨平台视频会议:在一些视频会议系统中,不同的参会设备和终端可能支持不同的视频传输协议。如果 Android 设备作为会议的参与方,其摄像头采集的视频是 RTSP 格式,而视频会议系统的服务器要求接收 RTMP 格式的视频流,那么就需要在 Android 设备上进行协议转换,以确保能够顺利地将视频信号传输到视频会议系统中,实现跨平台的视频会议通信。
- 多方视频会议集成:对于一些大型的多方视频会议平台,需要集成各种不同来源的视频流。如果其中有来自 Android 设备的 RTSP 流,将其转换为 RTMP 流后可以更好地与其他来源的视频流进行整合和管理,提高视频会议的质量和稳定性。
智能硬件与物联网领域:
- 智能家居监控:智能家居系统中的智能摄像头等设备通常会输出 RTSP 流,以便用户在家庭内网中查看实时视频。但是,如果用户想要通过远程方式在手机上查看家中的实时情况,或者将智能家居系统中的视频数据与其他智能设备或云服务进行交互和共享,就需要将 RTSP 流转换为 RTMP 流,以便在互联网环境下进行传输和处理。
- 工业物联网监控:在工业领域,一些工业监控摄像头或传感器设备可能会输出 RTSP 流,用于实时监测工业生产过程中的各种情况。通过将这些 RTSP 流转换为 RTMP 流,可以实现远程监控和管理,提高工业生产的效率和安全性,并且方便企业对生产数据进行集中分析和处理。
技术实现
Android平台RTSP转RTMP推送使用场景介绍完之后,我们介绍下,如何实现RTSP拉流并转RTMP推送,我们可以把这块,分成两个阶段:
RTSP 拉流
- 可以考虑使用RTSP播放器,来发送 RTSP 请求,接收 RTSP 服务器的响应。
- 解析 RTSP 协议的消息,包括 SETUP、PLAY 等命令,以建立连接并开始接收视频数据。如果只是转发RTMP服务,不做播放的话,可以把拉流后的H.264/H.265/AAC/PCMA/PCMU数据,直接回调上来,不做解码。
RTMP 转发
- 了解 RTMP 协议的格式和规范,包括消息类型、头信息、数据块等。
- 使用网络编程将视频数据按照 RTMP 协议的格式封装并发送到 RTMP 服务器。这可能需要手动构建 RTMP 消息,包括连接请求、创建流、发布流等步骤。
废话不多说,以大牛直播SDK的SmartRelayStream为例,为了便于播放和拉流、RTMP转推分离,我们把之前的RTSP播放和RTMP推送模块,做了二次封装。
逻辑实现如下:
1. 拉流:通过RTSP直播播放SDK的数据回调接口,拿到音视频数据;
2. 转推:通过RTMP直播推送SDK的编码后数据输入接口,把回调上来的数据,传给RTMP直播推送模块,实现RTSP数据流到RTMP服务器的转发;
3. 录像:如果需要录像,借助RTSP直播播放SDK,拉到音视频数据后,直接存储MP4文件即可;
4. 快照:如果需要实时快照,拉流后,解码调用播放端快照接口,生成快照,因为快照涉及到video数据解码,如无必要,可不必开启,不然会额外消耗性能。
5. 拉流预览:如需预览拉流数据,只要调用播放端的播放接口,即可实现拉流数据预览;
6. 数据转AAC后转发:考虑到好多监控设备出来的音频可能是PCMA/PCMU的,如需要更通用的音频格式,可以转AAC后,在通过RTMP推送;
7. 转推RTMP实时静音:只需要在传audio数据的地方,加个判断即可;
8. 拉流速度反馈:通过RTSP播放端的实时码率反馈event,拿到实时带宽占用即可;
9. 整体网络状态反馈:考虑到有些摄像头可能会临时或异常关闭,RTMP服务器亦是,可以通过推拉流的event回调状态,查看那整体网络情况,如此界定:是拉不到流,还是推不到RTMP服务器。
先说RTSP拉流:
/* SmartRelayDemo.java
* Created by daniusdk.com
* WeChat:xinsheng120
*/
class ButtonPullListener implements View.OnClickListener {
public void onClick(View v) {
if (stream_player_.is_pulling()) {
Log.i(TAG, "Stop Pull..");
boolean iRet = stream_player_.StopPull();
if (!iRet) {
Log.e(TAG, "Call StopPull failed..");
return;
}
stream_player_.try_release();
btnPullStream.setText("开始拉流");
} else {
Log.i(TAG, "Start playback stream++");
if (!stream_player_.OpenPlayerHandle(playback_url_, play_buffer_, is_using_tcp_))
return;
if(audio_opt_ == 2)
{
libPlayer.SmartPlayerSetAudioDataCallback(stream_player_.get(), new PlayerAudioDataCallback(stream_publisher_));
}
if(video_opt_ == 2)
{
libPlayer.SmartPlayerSetVideoDataCallback(stream_player_.get(), new PlayerVideoDataCallback(stream_publisher_));
}
int is_pull_trans_code = 1;
boolean iPlaybackRet = stream_player_.StartPull(is_pull_trans_code);
if (!iPlaybackRet) {
Log.e(TAG, "Call StartPlayer failed..");
return;
}
btnPullStream.setText("停止拉流");
}
}
}
如果拉取的RTSP流需要录像:
class ButtonPullRecorderListener implements View.OnClickListener {
public void onClick(View v) {
if (stream_player_.is_recording()) {
Log.i(TAG, "Stop recorder1..");
boolean iRet = stream_player_.StopRecorder();
if (!iRet) {
Log.e(TAG, "Call StopRecorder failed..");
return;
}
stream_player_.try_release();
btnPullRecorder.setText("拉流端开始录像");
} else {
Log.i(TAG, "Start recorder stream1++");
if (!stream_player_.OpenPlayerHandle(playback_url_, play_buffer_, is_using_tcp_))
return;
stream_player_.ConfigRecorderParam(recDir, 500, 1, 1, 1);
boolean iRecRet = stream_player_.StartRecorder();
if (!iRecRet) {
Log.e(TAG, "Call StartRecorder failed..");
return;
}
btnPullRecorder.setText("停止录像");
}
}
}
如果拉流端需要预览播放:
class ButtonPlayListener implements View.OnClickListener {
public void onClick(View v) {
if (stream_player_.is_playing()) {
Log.i(TAG, "Stop player1..");
boolean iRet = stream_player_.StopPlayer();
if (!iRet) {
Log.e(TAG, "Call StopPlayer failed..");
return;
}
stream_player_.try_release();
btnStartStopPlayback.setText("开始播放");
SetViewVisibility(surface_view_);
} else {
Log.i(TAG, "Start playback stream++");
if(!stream_player_.OpenPlayerHandle(playback_url_, play_buffer_, is_using_tcp_))
return;
stream_player_.SetView(surface_view_);
boolean iPlaybackRet = stream_player_.StartPlayer(is_hardware_decoder_, is_enable_hardware_render_mode_, is_mute_);
if (!iPlaybackRet) {
Log.e(TAG, "Call StartPlayer failed..");
return;
}
btnStartStopPlayback.setText("停止播放");
}
}
}
再说转推RTMP:
class ButtonPushRtmpListener implements View.OnClickListener {
public void onClick(View v) {
if (stream_publisher_.is_rtmp_publishing()) {
stopPush();
btnRTMPPusher.setText("推送RTMP");
return;
}
Log.i(TAG, "onClick start push rtmp..");
PusherInitAndSetConfig();
String rtmp_pusher_url = "rtmp://192.168.0.104:1935/hls/stream1";
//String rtmp_pusher_url = relayStreamUrl;
if (!stream_publisher_.SetURL(rtmp_pusher_url))
Log.e(TAG, "Failed to set publish stream URL..");
boolean start_ret = stream_publisher_.StartPublisher();
if (!start_ret) {
stream_publisher_.try_release();
Log.e(TAG, "Failed to start push stream..");
return;
}
startAudioRecorder();
btnRTMPPusher.setText("停止推送");
}
}
总结
实现 Android 上拉取 RTSP 流转发为 RTMP 流,可以通过使用强大的多媒体框架如 FFmpeg,或者利用专门的第三方库,也可以尝试自己实现协议转换,但这需要深入的技术知识和大量的编程工作。利用大牛直播SDK的SmartRelayModule,可以轻松实现几乎无延迟的RTSP到RTMP的转发。