RTSP协议探究
RTSP播放器可广泛应用于对延迟要求比较高的场景下,比如协同操控相关的智能机器人或无人机、实时视频监控、远程视频会议、网络电视等。通过控制信令实现对流媒体数据的远程控制和传输管理。
一、协议基础
- 定义与目的:RTSP建立并控制一个或多个时间同步流,尽管交错(interleaving)媒体流和控制流是可行的,但RTSP通常不直接参与数据传送,而是作为“网络远程控制”功能存在。
- 标准规范:遵循IETF RFC 2326等规范,其语法和操作参考了HTTP/1.1。
- 字符集与编码:采用ISO 10646字符集,使用UTF-8编码。
二、协议特性
- 有状态协议:与HTTP的无状态特性不同,RTSP是一个有状态的协议,服务器需要维护关于客户端会话的状态信息。
- 可扩展性:RTSP支持新方法和参数的添加,具有良好的可扩展性。
- 多服务器兼容:呈现中的各个媒体流可以分布在不同服务器上,客户端会自动与不同服务器建立多个并行的控制会话。
- 安全性:RTSP重用了Web安全机制,如HTTP的授权机制(basic、digest等)均可直接使用。
三、协议功能
- 播放控制:客户端可以通过RTSP命令控制媒体流的播放,如播放、暂停、停止、快进和快退等。
- 媒体描述:RTSP支持通过DESCRIBE方法从服务器获取媒体流的描述信息,通常使用SDP(Session Description Protocol)格式。
- 会话管理:RTSP支持通过SETUP方法建立会话并准备传输,以及通过TEARDOWN方法结束会话并释放资源。
四、传输机制
- 传输层协议:RTSP通常基于TCP协议进行交互,默认端口为554。同时,RTSP也可以基于HTTP进行隧道传输(RTSP-over-HTTP tunneling),此时默认端口为8080。
- 数据流传输:实际的流媒体数据(如音频、视频流)通常通过RTP/RTCP协议进行传输。RTP负责数据传输,RTCP负责传输控制信息。
五、RTSP请求与响应
- 请求方法:RTSP定义了多种请求方法,包括OPTIONS、DESCRIBE、SETUP、PLAY、PAUSE、TEARDOWN等。这些方法用于实现不同的功能,如查询服务器支持的方法、获取媒体描述、建立会话、控制媒体播放等。
- 响应状态码:RTSP的响应状态码与HTTP类似,用于表示请求的成功或失败。
六、应用场景
- 实时流媒体传输:RTSP广泛应用于实时流媒体传输场景,如视频会议、网络摄像头直播、视频点播系统等。
- 远程控制:RTSP允许客户端远程控制媒体服务器上的流媒体播放,提供了灵活的媒体控制功能。
如何实现RTSP播放器
本文以大牛直播SDK的Windows平台RTSP直播播放器为例,大概介绍下,如何集成RTSP直播播放能力。
- 大牛直播SDK提供C++/C#两套接口,对外提供32/64位debug/release库,C++和C#接口一一对应,C#接口比C++接口增加前缀NT_PB_;
- WIN-PlayerSDK-CPP-Demo:播放端SDK对应的C++接口的demo;
- WIN-PlayerSDK-CSharp-Demo:播放端SDK对应的C#接口的demo;
- 播放端SDK支持Win7及以上系统;
- demo基于VS2013开发。
集成说明
C++头文件:
- [类型定义]nt_type_define.h
- [Log定义]smart_log.h
- [Log定义]smart_log_define.h
- [base code定义]nt_base_code_define.h
- [player接口]smart_player_define.h
- [player参数定义]smart_player_sdk.h
C#头文件:
- [base code定义]nt_base_code_define.cs
- [player接口]smart_player_define.cs
- [player参数定义]smart_player_sdk.cs
相关Lib:
- SmartLog.dll
- SmartLog.lib
- SmartPlayerSDK.dll
- SmartPlayerSDK.lib
- avcodec-56.dll
- avdevice-56.dll
- avfilter-5.dll
- avformat-56.dll
- avutil-54.dll
- postproc-53.dll
- swresample-1.dll
- swscale-3.dll
集成步骤:
1. 把lib目录下debug/release库拷贝到需要集成的工程对应的debug或release目录下(确保32位/64位库debug/release目录一一对应);
lib目录如下:
- 32位debug库:debug
- 32位release库:release
- 64位debug库:x64\debug
- 64位release库:x64\release
2. 相关cs头文件,加入需要集成的工程;
3. 在需要集成的工程,右键->Properties->Application->Assembly name,大牛直播SDK按照APP名称授权,未授权版本,此处请改成“SmartPlayer”,如需授权,可直接联系商务;
4. 正式授权版,需要在Init()接口调用之前添加设置license的代码(相关Key和CID请根据正式授权版邮件说明填写):
接口调用时序(以C#为例)
设置授权license
C#的SDK,请在在NT.NTSmartPlayerSDK.NT_SP_Init之前添加下面的代码:
NT.NTSmartPlayerSDK.NT_SP_SetSDKClientKey("xxxxxxxxxx", "xxxxxxxxxx", 0, IntPtr.Zero);
UInt32 isInited = NT.NTSmartPlayerSDK.NT_SP_Init(0, IntPtr.Zero);
if (isInited != 0)
{
MessageBox.Show("调用NT_SP_Init失败..");
return;
}
C++的SDK,请在player_api_.Init之前添加下面的代码:
NT_SP_SetSDKClientKey(NT_SP_SetSDKClientKey("xxxxxxxxxx", "xxxxxxxxxx", 0, nullptr);
if ( NT_ERC_OK != player_api_.Init(0, NULL) )
{
return FALSE;
}
设置日志存放路径
需要在player_api_.Init之前添加下面的代码:
// 设置日志路径(请确保目录存在)
String log_path = "D:\\playerlog";
NTSmartLog.NT_SL_SetPath(log_path);
如目录存在,并具备文件写入权限,关闭应用程序后,相关文件夹下会有smart_sdk.log生成。
初始化SDK
NT_SP_Init:SDK初始化,多实例播放,此接口仅需调用一次即可。
特定机型硬解码检测
如系统用于特定机型环境下,特别是多路播放场景,需用到硬解码的话,可以用以下两组接口检测系统是否支持硬解。
注:在软解性能满足系统需求的前提下,一般建议优先使用软解。
/*
* 检查是否支持H264硬解码
* 如果支持的话返回NT_ERC_OK
*/
[DllImport(@"SmartPlayerSDK.dll")]
public static extern UInt32 NT_SP_IsSupportH264HardwareDecoder();
/*
* 检查是否支持H265硬解码
* 如果支持的话返回NT_ERC_OK
*/
[DllImport(@"SmartPlayerSDK.dll")]
public static extern UInt32 NT_SP_IsSupportH265HardwareDecoder();
如需使用硬解码,调用如下接口即可:
NTSmartPlayerSDK.NT_SP_SetH264HardwareDecoder(player_handle_, is_support_h264_hardware_decoder_ ? 1 : 0, 0);
NTSmartPlayerSDK.NT_SP_SetH265HardwareDecoder(player_handle_, is_support_h265_hardware_decoder_ ? 1 : 0, 0);
Open生成播放实例
NT_SP_Open:每调用一次Open接口,对应一个播放实例,如需播放多实例,对应多个player handler。
if (player_handle_ == IntPtr.Zero)
{
player_handle_ = new IntPtr();
UInt32 ret_open = NTSmartPlayerSDK.NT_SP_Open(out player_handle_, IntPtr.Zero, 0, IntPtr.Zero);
if (ret_open != 0)
{
player_handle_ = IntPtr.Zero;
MessageBox.Show("调用NT_SP_Open失败..");
return;
}
}
设置回调事件
- NT_SP_SetEventCallBack:用于回调网络链接状态、buffer状态(开始、buffer比例、结束)、实时带宽等,对应EventID如下:
/*事件ID*/
public enum NT_SP_E_EVENT_ID : uint
{
NT_SP_E_EVENT_ID_BASE = NTBaseCodeDefine.NT_EVENT_ID_SMART_PLAYER_SDK,
NT_SP_E_EVENT_ID_CONNECTING = NT_SP_E_EVENT_ID_BASE | 0x2, /*连接中*/
NT_SP_E_EVENT_ID_CONNECTION_FAILED = NT_SP_E_EVENT_ID_BASE | 0x3, /*连接失败*/
NT_SP_E_EVENT_ID_CONNECTED = NT_SP_E_EVENT_ID_BASE | 0x4, /*已连接*/
NT_SP_E_EVENT_ID_DISCONNECTED = NT_SP_E_EVENT_ID_BASE | 0x5, /*断开连接*/
NT_SP_E_EVENT_ID_NO_MEDIADATA_RECEIVED = NT_SP_E_EVENT_ID_BASE | 0x8, /*收不到RTMP数据*/
NT_SP_E_EVENT_ID_RTSP_STATUS_CODE = NT_SP_E_EVENT_ID_BASE | 0xB, /*rtsp status code上报, 目前只上报401, param1表示status code*/
/* 接下来请从0x81开始*/
NT_SP_E_EVENT_ID_START_BUFFERING = NT_SP_E_EVENT_ID_BASE | 0x81, /*开始缓冲*/
NT_SP_E_EVENT_ID_BUFFERING = NT_SP_E_EVENT_ID_BASE | 0x82, /*缓冲中, param1 表示百分比进度*/
NT_SP_E_EVENT_ID_STOP_BUFFERING = NT_SP_E_EVENT_ID_BASE | 0x83, /*停止缓冲*/
NT_SP_E_EVENT_ID_DOWNLOAD_SPEED = NT_SP_E_EVENT_ID_BASE | 0x91, /*下载速度, param1表示下载速度,单位是(Byte/s)*/
NT_SP_E_EVENT_ID_PLAYBACK_REACH_EOS = NT_SP_E_EVENT_ID_BASE | 0xa1, /*播放结束, 直播流没有这个事件,点播流才有*/
NT_SP_E_EVENT_ID_RECORDER_REACH_EOS = NT_SP_E_EVENT_ID_BASE | 0xa2, /*录像结束, 直播流没有这个事件, 点播流才有*/
NT_SP_E_EVENT_ID_PULLSTREAM_REACH_EOS = NT_SP_E_EVENT_ID_BASE | 0xa3, /*拉流结束, 直播流没有这个事件,点播流才有*/
NT_SP_E_EVENT_ID_DURATION = NT_SP_E_EVENT_ID_BASE | 0xa8, /*视频时长,如果是直播,则不上报,如果是点播的话, 若能从视频源获取视频时长的话,则上报, param1表示视频时长,单位是毫秒(ms)*/
}
- NT_SP_SetVideoSizeCallBack:设置视频分辨率回调,如流数据携带视频数据,SDK会回上来视频宽高信息:
//video resolution callback
video_size_call_back_ = new SP_SDKVideoSizeCallBack(SP_SDKVideoSizeHandle);
NTSmartPlayerSDK.NT_SP_SetVideoSizeCallBack(player_handle_, IntPtr.Zero, video_size_call_back_);
注意:视频宽高回上来或绘制窗口发生变化时,记得调用NT_SP_OnWindowSize()更新,如不调用可能会引起视频模糊。
private void PlaybackWindowResized(Int32 width,Int32 height)
{
width_=width;
height_=height;
int left=playWnd.Left;
int top=playWnd.Top;
textBox_resolution.Text=width+"*"+height;
if(player_handle_==IntPtr.Zero)
{
return;
}
NTSmartPlayerSDK.NT_SP_OnWindowSize(player_handle_,playWnd.Width,playWnd.Height);
}
- NT_SP_SetVideoFrameCallBack:设置YUV/RGB32数据回调,可用于对接第三方视频分析,或自行绘制等,如系统不支持D3D绘制,可设置回调数据,上层GDI模式绘制:
/*定义视频帧图像格式*/
public enum NT_SP_E_VIDEO_FRAME_FORMAT : uint
{
NT_SP_E_VIDEO_FRAME_FORMAT_RGB32 = 1, // 32位的rgb格式, r, g, b各占8, 另外一个字节保留, 内存字节格式为: bb gg rr xx, 主要是和windows位图匹配, 在小端模式下,按DWORD类型操作,最高位是xx, 依次是rr, gg, bb
NT_SP_E_VIDEO_FRAME_FORMAT_ARGB = 2, // 32位的argb格式,内存字节格式是: bb gg rr aa 这种类型,和windows位图匹配
NT_SP_E_VIDEO_FRAME_FROMAT_I420 = 3, // YUV420格式, 三个分量保存在三个面上
}
- NT_SP_SetVideoFrameCallBackV2:设置YUV/RGB32数据回调,与NT_SP_SetVideoFrameCallBack接口的不同在于,吐出来的视频数据, 可以指定宽高;
- NT_SP_SetRenderVideoFrameTimestampCallBack:设置绘制视频帧时,视频帧时间戳回调,一般播放器无时间戳回调需求的话,无需设置:
//video timestamp callback
video_frame_ts_callback_ = new SP_SDKRenderVideoFrameTimestampCallBack(SP_SDKRenderVideoFrameTimestampCallBack);
NTSmartPlayerSDK.NT_SP_SetRenderVideoFrameTimestampCallBack(player_handle_, IntPtr.Zero, video_frame_ts_callback_);
- NT_SP_SetAudioPCMFrameCallBack:设置音频PCM帧回调, 吐PCM数据出来,目前每帧大小是10ms,一般播放器无使用需求的话,无需设置;
- NT_SP_SetUserDataCallBack:设置用户数据回调,此接口需要和推送端SDK配套使用,用于返回推送端设定的实时用户数据(如时间戳、经纬度等各种扩展指令或信息),如只是单纯使用播放SDK,无需设置;
- NT_SP_SetSEIDataCallBack:设置视频SEI数据回调,如只是单纯使用播放SDK,不需要额外处理扩展SEI数据的话,无需设置。
D3DRender检测
目前,几乎很少存在不支持D3D绘制的情况,考虑到系统通用性,我们在播放之前,先做检测,具体调用接口如下:
/*
* handle: 播放句柄
* hwnd: 这个要传入真正用来绘制的窗口句柄
* is_support: 如果支持的话 *is_support 为1, 不支持的话为0
* 接口调用成功返回NT_ERC_OK
*/
[DllImport(@"SmartPlayerSDK.dll")]
public static extern UInt32 NT_SP_IsSupportD3DRender(IntPtr handle, IntPtr hwnd, ref Int32 is_support);
对于不支持D3D绘制的情况下,设置回调YUV数据,上层直接用GDI模式绘制,注意:GDI绘制效率偏低。
Int32 in_support_d3d_render = 0;
if (NT.NTBaseCodeDefine.NT_ERC_OK == NTSmartPlayerSDK.NT_SP_IsSupportD3DRender(player_handle_, playWnd.Handle, ref in_support_d3d_render))
{
if (1 == in_support_d3d_render)
{
is_support_d3d_render = true;
}
}
if (is_support_d3d_render)
{
is_gdi_render_ = false;
// 支持d3d绘制的话,就用D3D绘制
NTSmartPlayerSDK.NT_SP_SetRenderWindow(player_handle_, playWnd.Handle);
if (btn_check_render_scale_mode.Checked)
{
NTSmartPlayerSDK.NT_SP_SetRenderScaleMode(player_handle_, 1);
}
else
{
NTSmartPlayerSDK.NT_SP_SetRenderScaleMode(player_handle_, 0);
}
}
else
{
is_gdi_render_ = true;
playWnd.Visible = false;
// 不支持D3D就让播放器吐出数据来,用GDI绘制
//video frame callback (YUV/RGB)
//format请参见 NT_SP_E_VIDEO_FRAME_FORMAT,如需回调YUV,请设置为 NT_SP_E_VIDEO_FRAME_FROMAT_I420
video_frame_call_back_ = new SP_SDKVideoFrameCallBack(SetVideoFrameCallBack);
NTSmartPlayerSDK.NT_SP_SetVideoFrameCallBack(player_handle_, (Int32)NT.NTSmartPlayerDefine.NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FORMAT_RGB32, IntPtr.Zero, video_frame_call_back_);
}
设置播放URL
NT_SP_SetURL:支持rtsp/rtmp/本地FLV文件(全路径)。
设置回调PCM
NT_SP_SetIsOutputAudioDevice:设置是否播放出声音,这个和静音接口是有区别的,这个接口的主要目的是为了用户设置了外部PCM回调接口后,又不想让SDK播放出声音时使用。
RTMP/RTSP播放参数设置
具体可参照Demo源码里面InitCommonSDKParam():
播放前可选设置接口
- NT_SP_SetBuffer:设置视频播放缓冲buffer大小,单位:毫秒;
- NT_SP_SetRTSPTcpMode:设置RTSP TCP 模式, 1为TCP, 0为UDP, 此接口仅RTSP有效;
- NT_SP_SetRtspTimeout:设置RTSP超时时间, timeout单位为秒,必须大于0;
- NT_SP_SetRtspAutoSwitchTcpUdp:对于RTSP来说,有些可能支持rtp over udp方式,有些可能支持使用rtp over tcp方式. 为了方便使用,有些场景下可以开启自动尝试切换开关, 打开后如果udp无法播放,sdk会自动尝试tcp, 如果tcp方式播放不了,sdk会自动尝试udp, is_auto_switch_tcp_udp: 如果设置1的话, sdk将在tcp和udp之间尝试切换播放,如果设置为0,则不尝试切换;
- NT_SP_SetFastStartup:设置秒开, 1为秒开, 0为不秒开,此接口用于如RTMP服务器缓存GOP时,酌情使用;
- NT_SP_SetLowLatencyMode:设置低延时播放模式,默认是正常播放模式,mode: 1为低延时模式, 0为正常模式,低延迟模式下,可能会导致音视频不同步,或视频帧不均匀;
- NT_SP_SetReportDownloadSpeed:设置下载速度上报, 默认不上报下载速度;
* is_report: 上报开关, 1: 表上报. 0: 表示不上报. 其他值无效.
* report_interval: 上报时间间隔(上报频率),单位是秒,最小值是1秒1次. 如果小于1且设置了上报,将调用失败
* 注意:如果设置上报的话,请设置SetEventCallBack, 然后在回调函数里面处理这个事件.
* 上报事件是:NT_SP_E_EVENT_ID_DOWNLOAD_SPEED
- NT_SP_GetDownloadSpeed:主动获取下载速度,speed: 返回下载速度,单位是Byte/s;
- NT_SP_SetParam:万能接口, 设置参数, 大多数问题, 这些接口都能解决;
- NT_SP_GetParam:万能接口, 得到参数, 大多数问题,这些接口都能解决;
播放前后可实时调用的接口
- NT_SP_SetMute:播放过程中,实时静音、取消静音,可播放之前调用,亦或播放过程中实时调用;
- NT_SP_SetAudioVolume:不同于实时静音接口,此接口可以更细粒度的控制音量,默认范围[0,100],其中0是静音,100是最大音量, 默认是100;
- NT_SP_SetOnlyDecodeVideoKeyFrame:多窗口播放场景下,部分窗口可能只需要播放关键帧,如有类似场景需求,可用此接口;
- NT_SP_SetRotation:设置视频View旋转,顺时针旋转,degress: 设置0, 90, 180, 270度有效,其他值无效,注意:除了0度,其他角度播放会耗费更多CPU;
- NT_SP_SetFlipVertical:设置视频View上下反转(垂直反转);
- NT_SP_SetFlipHorizontal:设置视频View水平反转;
- NT_SP_SetRenderScaleMode:设置视频画面的填充模式,如填充整个绘制窗口、等比例填充绘制窗口,如不设置,默认填充整个绘制窗口;
开始播放
NT_SP_StartPlay
开始播放RTMP或RTSP流数据。
RTMP/RTSP拉流端录像
- NT_SP_SetRecorderDirectory:设置录像目录
- NT_SP_SetRecorderFileMaxSize:设置单个文件最大大小
- NT_SP_SetRecorderFileNameRuler:设置录像文件名生成规则
- NT_SP_SetRecorderCallBack:设置录像回调接口
- NT_SP_SetRecorderAudioTranscodeAAC:设置录像时音频转AAC编码的开关, aac比较通用,sdk增加其他音频编码(比如speex, pcmu, pcma等)转aac的功能
- NT_SP_SetRecorderVideo:设置是否录视频,默认的话,如果视频源有视频就录,没有就没得录, 但有些场景下可能不想录制视频,只想录音频,所以增加个开关
- NT_SP_SetRecorderAudio:设置是否录音频,默认的话,如果视频源有音频就录,没有就没得录, 但有些场景下可能不想录制音频,只想录视频,所以增加个开关
- NT_SP_StartRecorder:启动录像
- NT_SP_StopRecorder:停止录像
实时快照
NT_SP_CaptureImage
用于播放端实时截取当前播放图片,图片以PNG形式保存至本地。
String name = capture_image_path_ + "\\" + DateTime.Now.ToString("hh-mm-ss") + ".png";
byte[] buffer1 = Encoding.Default.GetBytes(name);
byte[] buffer2 = Encoding.Convert(Encoding.Default, Encoding.UTF8, buffer1, 0, buffer1.Length);
byte[] buffer3 = new byte[buffer2.Length + 1];
buffer3[buffer2.Length] = 0;
Array.Copy(buffer2, buffer3, buffer2.Length);
IntPtr file_name_ptr = Marshal.AllocHGlobal(buffer3.Length);
Marshal.Copy(buffer3, 0, file_name_ptr, buffer3.Length);
capture_image_call_back_ = new SP_SDKCaptureImageCallBack(SDKCaptureImageCallBack);
UInt32 ret = NTSmartPlayerSDK.NT_SP_CaptureImage(player_handle_, file_name_ptr, IntPtr.Zero, capture_image_call_back_);
Marshal.FreeHGlobal(file_name_ptr);
if (NT.NTBaseCodeDefine.NT_ERC_OK == ret)
{
// 发送截图请求成功
}
else if ((UInt32)NT.NTSmartPlayerDefine.SP_E_ERROR_CODE.NT_ERC_SP_TOO_MANY_CAPTURE_IMAGE_REQUESTS == ret)
{
// 通知用户延时
MessageBox.Show("Too many capture image requests!");
}
else
{
// 其他失败
}
快速切换URL
NT_SP_SwitchURL
快速切换URL,用于不用析构整个player实例的前提下,实时切换播放的URL。
用户数据回调
NT_SP_SetUserDataCallBack
设置用户数据回调,用于接收扩展SEI模块发送的用户数据信息,如不是配合我们扩展SEI发送DK,此接口无需调用。
SEI数据回调
NT_SP_SetSEIDataCallBack
设置视频sei数据回调,用于接收SEI数据回调,如流数据不存在SEI或不准备处理SEI数据,此接口无需调用。
停止播放
NT_SP_StopPlay
停止播放RTMP或RTSP流数据。
关闭播放实例
NT_SP_Close
调用Close接口后,player handler置空。
if ( player_handle_ != IntPtr.Zero)
{
NTSmartPlayerSDK.NT_SP_Close(player_handle_);
player_handle_ = IntPtr.Zero;
}
Uninit
NT_SP_UnInit
UnInit() 是SDK最后一个调用的接口,多实例环境下,只需要调用一次即可。