1. 用VLC模拟一个rtsp服务器
- 创建rtsp服务器
2. 接收rtsp数据
2. RTSP协议分析
1. 基本介绍
rtsp:实时流传输协议,面向直播、监控等领域,是tcp的上层应用协议,同时支持UDP,通常在使用中,命令通道使用tcp、数据通道使用UDP。 RTSP基于会话机制,通过会话可以来连接多个客户端 RTSP的状态机制:SETUP、PLAY/RECORD、PAUSE、TERDOWN RTSP URL:RTSP URL:rtsp://127.0.0.1:8554/test rtsp | rtspu:rtsp代表使用tcp、rtspu使用udp,默认端口8554 rtsp的链接过程; 鉴权的方式:
- Basic方式:username+password进行Base64编码后直接发送
- Digest方式:数字签名的方式
3. 大华和海康摄像机rtsp抓包
注意登录和配置方式 通过抓包来分析rtsp协议具体的过程和内容
3. 超时重连
本身命令通道tcp、数据通道使用udp 可以通过av_dice_set() 来设置参数
av_dict_set(&options, "rtsp_transport", "tcp", 0);
av_dict_set(&options, "stimeout", "1000000", 0);
stimeout:socket超时
1000000:单位us
超时时间:
- 建立链接的时间
- 建立连接后读取消息的超时时间:udp超时无效tcp才有效,超时无效会造成av_read_frame()阻塞的情况,需要自动重连
3.1 RTSP退出阻塞
AVFormatContext::interrupt_callback 中断回调
struct AVIOInterruptCB {
int (*callback)(void*);
void *opaque;
};
AVIOInterruptCB interrupt_callback;
每次read、connect等等待时都会调用interrupt_callback 中的callback 成员函数,return 0阻塞,return 1退出阻塞
int TimeoutCallback(void *)
{
auto *xf = (XFormat *)para;
cout << "此处时回调处理逻辑" << endl;
return 0/1;
}
fmt_ctx->interrupt_callback = {TimeoutCallback, this}
3.2 设置超时时间
可能发生的超时时间:
- 链接成功后等待读取数据时间
- 每次读取数据的间隔
因此可以在这两个过程中保存当时的时间戳,当阻塞等待时,当前时间距离保存的时间戳大于超时时间则说明已经超时
bool XFormat::set_c(AVFormatContext *c)
{
...
last_time_ = NowMs();
...
}
bool XFormat::Read(AVPacket *pkt)
{
...
last_time_ = NowMs();
}
bool XFormat::IsTimeout()
{
if(NowMs() - last_time_ > timeout_ms)
{
return true;
}
return false;
}
int TimeoutCallback(void *para)
{
auto *xf = (XFormat *)para;
if(xf->IsTimeout())
{
return 1;
}
return 0;
}
3.2 超时重连
在链接失败(即保存fmt_ctx 失败)和超时时都更新一下is_connected_ 成员的状态,即设置其为false 当每次读取数据时,都判断一下是否处于链接状态,若已经断开链接,则重新XDemuxTask::Open() 链接
void XDemuxTask::Main()
{
...
while(!is_exit())
{
if(!demux_.Read())
{
if(!demux_.is_connected())
{
Open();
}
}
}
}
注意在Open() 中调用XDemux::Open() 时,avformat_open_input() 也会阻塞,通过设置参数来定义阻塞时间
av_dict_set(&dict, "stimeout", "10000000", 0);
auto re = avformat_open_input(&c, url, nullptr, &dict);
在未连接时,一直等待链接
XDemuxTask xdt;
while (!xdt.Open("rtsp://127.0.0.1:8554/test", 1000))
{
continue;
}
4. 责任链模式
4.1 责任链代码
- 解封装线程和解码线程是责任链上的节点,节点之间无需关注处理的数据从何处而来到何处而去,只要调用接口接收数据处理好后再调用接口传出去即可
- 节点类之间都继承一个Chain类,来保证有相同的链中接口,如
Next()、Do() 等 - 节点类之间的关系是关联关系,需要再主线程中用户自己设置责任链上的上下节点关系。
class XChain
{
public:
virtual void Do(AVPacket* pkt) {}
virtual void Next(AVPacket* pkt)
{
if (next_)
next_->Do(pkt);
}
void set_next(XThread* xt)
{
std::unique_lock<std::mutex> lock(mtx_);
next_ = xt;
}
protected:
std::mutex mtx_;
private:
XThread *next_ = nullptr;
};
class XDemuxTask :
public XChain
{
public:
bool Operating(char *url)
{
...
Next(packet);
}
private:
XDemux demux_;
};
class XDecodeTask :
public XChain
{
public:
void Do(AVPacket* pkt) override
{
packets.push(pkt);
}
bool Operating(AVPacket *pkt)
{
...
Next(packet);
}
private:
XDecode decode_;
list<AVPacket *>pkt;
};
|