技术选型
RTSP转RTMP推送到流媒体服务器,说起来技术实现不难,简单来说,获取RTSP流后,拿到未经解码的H.264/H.265和audio数据,重新打包RTMP发送出去即可。需要注意的是,大多RTSP转RTMP模块,需要长时间运行,所以,需要有好多错误处理和自动重连机制,确保转发模块的稳定性。以下是两个可选的技术方案:
方案1:FFMPEG命令转发
ffmpeg -i rtsp://[摄像头地址]/[流媒体地址] -c:v libx264 -preset veryfast -maxrate 3000k -bufsize 6000k -pix_fmt yuv420p -g 50 -c:a aac -b:a 160k -ac 2 -ar 44100 -f flv rtmp://[服务器地址]/[应用名称]/[流密钥]
rtsp://[摄像头地址]/[流媒体地址]是摄像头的RTSP流地址。-c:v libx264指定视频编码器为libx264。-preset veryfast设置编码速度为非常快,以牺牲一些压缩效率换取更快的编码速度。-maxrate和-bufsize设置最大码率和缓冲区大小。-pix_fmt yuv420p设置像素格式为YUV420P,这是RTMP兼容的格式。-g 50设置关键帧间隔为50帧。-c:a aac指定音频编码器为AAC。-b:a、-ac和-ar分别设置音频比特率、声道数和采样率。-f flv指定输出格式为FLV,RTMP流通常以FLV格式封装。rtmp://[服务器地址]/[应用名称]/[流密钥]是目标RTMP服务器的推送地址。
上述命令中的参数可能需要根据实际情况进行调整。
方案2:SmartRelaySDK
大牛直播SDK发布的RTSP转RTMP推送模块(SmartRelaySDK)C#的界面如下:
技术设计:
**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服务器。
上述是C#的基础demo,如果对C++比较熟悉,也可以直接用C++的,大牛直播SDK的RTSP转RTMP推送模块,通过配置xml的形式,程序启动后,从configure.xml读取相关的参数,实现一键拉流转发。
常规的参数配置,比如推拉流的rtsp rtmp url,如果需要自采集audio,设置采集的audio类型,比如rtsp自带audio、麦克风、扬声器或麦克风扬声器混音。
<?xml version="1.0" encoding="utf-8" ?>
<StreamRelays>
<Relay>
<id>0</id>
<AudioOption>4</AudioOption>
<PullUrl>rtsp://admin:daniulive12345@192.168.0.120:554/h264/ch1/main/av_stream</PullUrl>
<PushUrl>rtmp://192.168.0.103:1935/hls/stream00</PushUrl>
</Relay>
<Relay>
<id>1</id>
<AudioOption>1</AudioOption>
<PullUrl>rtsp://admin:admin123456@192.168.0.121:554/cam/realmonitor?channel=1<![CDATA[&]]>subtype=0</PullUrl>
<PushUrl>rtmp://192.168.0.103:1935/hls/stream01</PushUrl>
</Relay>
<Relay>
<id>2</id>
<AudioOption>3</AudioOption>
<PullUrl>rtsp://admin:daniulive12345@192.168.0.120:554/h264/ch1/main/av_stream</PullUrl>
<PushUrl>rtmp://192.168.0.103:1935/hls/stream02</PushUrl>
</Relay>
<Relay>
<id>3</id>
<AudioOption>3</AudioOption>
<PullUrl>rtsp://admin:admin123456@192.168.0.121:554/cam/realmonitor?channel=1<![CDATA[&]]>subtype=0</PullUrl>
<PushUrl>rtmp://192.168.0.103:1935/hls/stream03</PushUrl>
</Relay>
<Relay>
<id>4</id>
<AudioOption>4</AudioOption>
<PullUrl>rtsp://admin:daniulive12345@192.168.0.120:554/h264/ch1/main/av_stream</PullUrl>
<PushUrl>rtmp://192.168.0.103:1935/hls/stream04</PushUrl>
</Relay>
<Relay>
<id>5</id>
<AudioOption>1</AudioOption>
<PullUrl>rtsp://admin:admin123456@192.168.0.121:554/cam/realmonitor?channel=1<![CDATA[&]]>subtype=0</PullUrl>
<PushUrl>rtmp://192.168.0.103:1935/hls/stream05</PushUrl>
</Relay>
<Relay>
<id>6</id>
<AudioOption>4</AudioOption>
<PullUrl>rtsp://admin:daniulive12345@192.168.0.120:554/h264/ch1/main/av_stream</PullUrl>
<PushUrl>rtmp://192.168.0.103:1935/hls/stream06</PushUrl>
</Relay>
<Relay>
<id>7</id>
<AudioOption>2</AudioOption>
<PullUrl>rtsp://admin:admin123456@192.168.0.121:554/cam/realmonitor?channel=1<![CDATA[&]]>subtype=0</PullUrl>
<PushUrl>rtmp://192.168.0.103:1935/hls/stream07</PushUrl>
</Relay>
</StreamRelays>
封装代码如下:
/* * nt_relay_wrapper.cs.cs
* nt_relay_wrapper.cs
*
* WebSite: https://daniusdk.com
* WeChat: xinsheng120
*
* Created by DaniuLive on 2017/11/14.
* Copyright © 2014~2024 DaniuLive. All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SmartRelayDemo
{
class nt_relay_wrapper
{
int relay_index_;
nt_player_wrapper player_wrapper_;
nt_publisher_wrapper publisher_wrapper_;
UInt32 video_option_ = (UInt32)NT.NTSmartPublisherDefine.NT_PB_E_VIDEO_OPTION.NT_PB_E_VIDEO_OPTION_ENCODED_DATA;
UInt32 audio_option_ = (UInt32)NT.NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_ENCODED_DATA;
public nt_player_wrapper GetPlayerWrapper() { return player_wrapper_; }
public nt_publisher_wrapper GetPublisherWrapper() { return publisher_wrapper_; }
public nt_relay_wrapper(int index, System.Windows.Forms.Control render_wnd, System.ComponentModel.ISynchronizeInvoke sync_invoke)
{
relay_index_ = index;
player_wrapper_ = new nt_player_wrapper(index, render_wnd, sync_invoke);
publisher_wrapper_ = new nt_publisher_wrapper(index, render_wnd, sync_invoke);
}
~nt_relay_wrapper() { }
private void OnVideoDataHandle(IntPtr handle, IntPtr user_data,
UInt32 video_codec_id, IntPtr data, UInt32 size,
IntPtr info, IntPtr reserve)
{
if (publisher_wrapper_.is_rtmp_publishing())
{
publisher_wrapper_.OnVideoDataHandle(handle, user_data, video_codec_id, data, size, info, reserve);
}
}
private void OnAudioDataHandle(IntPtr handle, IntPtr user_data,
UInt32 audio_codec_id, IntPtr data, UInt32 size,
IntPtr info, IntPtr reserve)
{
if (publisher_wrapper_.is_rtmp_publishing())
{
publisher_wrapper_.OnAudioDataHandle(handle, user_data, audio_codec_id, data, size, info, reserve);
}
}
public void StartPull(String url)
{
if (!player_wrapper_.is_pulling())
{
player_wrapper_.SetBuffer(0);
if (!player_wrapper_.StartPull(url, false))
return;
player_wrapper_.EventOnVideoDataHandle += new nt_player_wrapper.DelOnVideoDataHandle(OnVideoDataHandle);
if (audio_option_ == (UInt32)NT.NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_ENCODED_DATA)
{
player_wrapper_.EventOnAudioDataHandle += new nt_player_wrapper.DelOnAudioDataHandle(OnAudioDataHandle);
}
}
}
public void StopPull()
{
player_wrapper_.StopPull();
}
public void StartPlayer(String url, bool is_rtsp_tcp_mode, bool is_mute)
{
player_wrapper_.SetBuffer(0);
if (!player_wrapper_.StartPlay(url, is_rtsp_tcp_mode, is_mute))
return;
}
public void StopPlayer()
{
player_wrapper_.StopPlay();
}
public void PlayerDispose()
{
player_wrapper_.Dispose();
}
public void SetPusherOption(UInt32 video_option, UInt32 audio_option)
{
video_option_ = video_option;
audio_option_ = audio_option;
}
public void StartPublisher(String url)
{
if (!publisher_wrapper_.OpenPublisherHandle(video_option_, audio_option_))
return;
if (url.Length < 8)
{
publisher_wrapper_.try_close_handle();
return;
}
if (!publisher_wrapper_.StartPublisher(url))
{
return;
}
}
public void StopPublisher()
{
publisher_wrapper_.StopPublisher();
}
public void PublisherDispose()
{
publisher_wrapper_.Dispose();
}
}
}
总结
RTSP转RTMP模块设计,可以用ffmpeg直接命令行转发,也可以用方案二的非常成熟的转发设计,ffmpeg转发,需要有一定的代码基础,有问题的话,bug修复需要对底层逻辑非常了解才行,方案二,技术成熟,二次开发难度不大,很同意集成到自己现有系统,感兴趣的开发者,可以单独跟我沟通交流。