rtps demo(linux是64位的,windows32位的):rtsp_demo.rar
1.首先服务器收到客户端连接请求,生产 一个RtspClientConnection对象,RtspClientConnection定义详见 从头写rtsp服务器-模块的划分
int rtsp::v_accept(netconnection * n) { netoperation::v_accept(n); printf("client accept\n\n\n"); RtspClientConnection * session = new RtspClientConnection(n, m_serveAddr); n->_setcontext((void*)session); return 0; }
2. 服务端收到客户端发来的消息,解析RTSP协议
int rtsp::v_read(netconnection * n) { netoperation::v_read(n); char ch = 0; n->_peek(&ch, sizeof(ch)); if (ch != '$'){ v_rtspRead(n); } else{ v_rtpRead(n); } return 0; } int rtsp::v_rtspRead(netconnection * n) { RtspClientConnection * clientConnection = (RtspClientConnection*)n->_context(); int nRead = n->_avaliableread(); //拷贝消息 std::string requst; n->_peek(requst, nRead); size_type pos = requst.find("\r\n\r\n"); //消息不完整 if (pos == std::string::npos){ return 0; } int msglen = (int)pos + 4; //取出该条消息 n->_pop(msglen); //消息过长 if (msglen < nRead){ requst[msglen] = 0; } std::string cmd; std::string urlPreSuffix; std::string urlSuffix; std::string strSessionId; uint cseq = 0; int ret = ParseRTSPRequestString(requst, cmd, urlPreSuffix, urlSuffix, cseq, strSessionId); printf("parseRTSPRequestString cmd %s\n", cmd.c_str()); //printf("[cmd:%s] C->S: %s\n\n", cmd.c_str(), requst.c_str()); std::string streamName; std::string trackId; std::string urlTotalSuffix = urlPreSuffix ; if (!urlTotalSuffix.empty()) { urlTotalSuffix.append("/"); } urlTotalSuffix.append(urlSuffix); if (urlSuffix.find(MediaSubSession::TrackFmt()) == std::string::npos) { streamName = urlTotalSuffix; } else { streamName = urlPreSuffix; trackId= urlSuffix; } if (cmd == "OPTIONS") { clientConnection->handle_options(cseq); } else if (cmd == "DESCRIBE") { clientConnection->handle_describle(streamName, cseq, requst); } else if (cmd == "SETUP") { clientConnection->handle_setup(strSessionId, streamName, trackId, cseq, requst); } else if (cmd == "PLAY" || cmd == "PAUSE" || cmd == "GET_PARAMETER" || cmd == "SET_PARAMETER" || cmd == "TEARDOWN") { clientConnection->handle_incmd(strSessionId, cmd, streamName, trackId, cseq, requst); } else if(cmd == "REGISTER" || cmd == "REGISTER_REMOTE") { clientConnection->handle_register(urlSuffix, requst, cmd == "REGISTER_REMOTE"); } else { clientConnection->handleCmd_notSupported(); } return 0; }
3. 下面是RtspClientConnection,rtps协议的处理就在这个类中实现。
1). OPTION实现如下,返回服务器所支持的方法
int RtspClientConnection::handle_options(uint seq){ std::string str; append(str, "RTSP/1.0 200 OK\r\n" "CSeq: %u\r\n" "%s" "Public: %s\r\n\r\n", seq, dateStr().c_str(), "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER"); m_n->_send(str.c_str(), str.length()); return 0; }
2)DESCRIBLE实现如下,主要是生成媒体对象,和媒体SDP, SDP客户端解码用到
int RtspClientConnection::handle_describle(
std::string & streamName,
uint seq,
std::string & fullRequestStr)
{
if (m_mediaSession){
if(m_mediaSession->StreamName() != streamName){
delete m_mediaSession;
m_mediaSession = CreateMediaSession(streamName);
}
}else{
m_mediaSession = CreateMediaSession(streamName);
}
if (!m_mediaSession){
handleCmd_notFound();
return -1;
}
MediaSession * session = m_mediaSession;
std::string sdp = session->GenerateSDPDescription(m_serveAddr);
//get the rtsp url
//rtsp://127.0.0.1/
std::string rtspUrl;
append(rtspUrl, "rtsp://%s:%u/%s",
m_serveAddr._ipstr(),
m_serveAddr._port() ,
session->StreamName().c_str());
std::string response = "RTSP/1.0 200 OK\r\n";
append(response, "CSeq: %u\r\n"
"%s"
"Content-Base: %s\r\n"
"Content-Type: application/sdp\r\n"
"Content-Length: %d\r\n\r\n"
"%s",
seq,
dateStr().c_str(),
rtspUrl.c_str(),
sdp.length(),
sdp.c_str());
// printf("S-C : %s\n", response.c_str());
m_n->_send(response.c_str(), response.length());
return 0;
}
3.SETUP实现如下,主要是建立会话,每一条流都会建立一次会话,生成会话id, 和服务器rtp,rtcp端口,创建用于rtp的UDP socke,这里rtp值实现了udp方式
int RtspClientConnection::handle_setup(std::string & sessionId,
std::string & streamName,
std::string & trackId,
uint seq,
std::string & fullRequestStr)
{
if (sessionId.empty()){
m_sessionId = "";
append(m_sessionId, "%08X", random_32());
}
else{
if(m_sessionId != sessionId){
handleCmd_sessionNotFound();
printf("session error\n");
return -1;
}
}
enum
{
RTP_UDP,
RTP_TCP,
RAW_UDP
};
//in case : with out no DESCRIBE
if (!m_mediaSession){
m_mediaSession = CreateMediaSession(streamName);
if(!m_mediaSession){
handleCmd_notFound();
return -1;
}
}
else{
if(!streamName.empty()){
if(m_mediaSession->StreamName() != streamName){
handleCmd_bad();
return -1;
}
}
}
MediaSession * session = m_mediaSession;
size_type npos = std::string::npos;
size_type t = fullRequestStr.find("Transport:");
if (t == npos){
printf("no Transport\n");
return -1;
}
t += 9;
while(fullRequestStr[++t] == ' ');
std::string strParse = fullRequestStr.substr(t, fullRequestStr.length() - t);
int transMode = RTP_UDP;
std::string strtransMode = "RTP/AVP";
std::string strDstAdder;
uint dstTTL = 255;
uint rtpPort = 0; //UDP
uint rtcpPort = 1; //UDP
uint rtpChannel = 0xff; //tcp
uint rtcpChannel = 0xff; //tcp
uint16 p1 = 0, p2 = 0;
uint ttl = 0, rtpId = 0, rtcpId =0;
size_type pos = 0;
size_type end = fullRequestStr.length() - (size_type)4; // /r/n/r/n
do
{
pos = fullRequestStr.find(';', t);
if (pos == npos)
{
pos = end;
}
std::string str = fullRequestStr.substr(t, pos - t);
t = pos + 1;
if (str == "RTP/AVP/TCP")
{
transMode = RTP_TCP;
}
else if (str == "RAW/RAW/UDP" || str == "MP2T/H2221/UDP")
{
transMode = RAW_UDP;
strtransMode = str;
}
else if (str.find("destination=") != npos)
{
strDstAdder = str.substr(12);
}
else if (sscanf(str.c_str(), "ttl%d", &ttl) == 1)
{
dstTTL = (int)ttl;
}
else if (sscanf(str.c_str(), "client_port=%hu-%hu", &p1, &p2) == 2)
{
rtpPort = p1;
rtcpPort = transMode == RAW_UDP ? 0 : p2;
}
else if (sscanf(str.c_str(), "client_port=%hu", &p1) == 1)
{
rtpPort = p1;
rtcpPort = transMode == RAW_UDP ? 0 : (p1 + 1);
}
else if (sscanf(str.c_str(), "interleaved=%u-%u", &rtpId, &rtcpId) == 2)
{
rtpChannel = rtpId;
rtcpChannel = rtcpId;
}
}while(pos != end);
if ((transMode == RTP_TCP && rtpChannel == 0xff) )
{
rtpChannel = 0;
rtcpChannel = 1;
}
// Next, check whether a "Range:" or "x-playNow:" header is present in the request.
// This isn't legal, but some clients do this to combine "SETUP" and "PLAY":
if (fullRequestStr.find("x-playNow:") != npos)
{
handleCmd_bad();
printf("no support x-playNow\n");
return -1;
}
//look for MediaSubSession add code
MediaSubSession * subsession = NULL;
if (trackId.find(MediaSubSession::TrackFmt()) != std::string::npos){
subsession = session->Lookup(trackId);
if(!subsession){
handleCmd_notFound();
return -1;
}
}else{
// Weird case: there was no track id in the URL.
// This works only if we have only one subsession:
if (session->SubSessionCount() > 1){
handleCmd_bad();
return -1;
}
}
netaddress clientAddr = m_n->_getclientaddr();
netaddress destAddr(strDstAdder.c_str());
if(strDstAdder.empty()){
destAddr._setip(clientAddr._ip());
}
uint serverRtpPort = 0;
uint serverRtcpPort = 0;
bool isMulticast = false;
subsession->GetStreamParam(
rtpPort,
rtcpPort,
rtpChannel,
rtcpChannel,
destAddr._ip(),
dstTTL,
isMulticast,
serverRtpPort,
serverRtcpPort,
m_serveAddr._ip());
// unicast
std::string response;
switch(transMode)
{
case RTP_UDP:
append(response,
"RTSP/1.0 200 OK\r\n"
"CSeq: %u\r\n"
"%s"
"Transport: RTP/AVP;unicast;source=%s;client_port=%d-%d;server_port=%d-%d\r\n"
"Session: %s\r\n\r\n",
seq, dateStr().c_str(),
m_serveAddr._ipstr(),
rtpPort, rtcpPort, serverRtpPort, serverRtcpPort,m_sessionId.c_str()
);
//destination=%s;
break;
case RAW_UDP:
append(response,
"RTSP/1.0 200 OK\r\n"
"CSeq: %u\r\n"
"%s"
"Transport: %s;unicast;destination=%s;source=%s;client_port=%d;server_port=%d\r\n"
"Session: %s\r\n\r\n",
seq, dateStr().c_str(),
clientAddr._ipstr(), m_serveAddr._ipstr());
printf("not support RAW_UDP\n");
//not support now
break;
case RTP_TCP:
append(response,
"RTSP/1.0 200 OK\r\n"
"CSeq: %u\r\n"
"%s"
"Transport: RTP/AVP/TCP;unicast;destination=%s;source=%s;interleaved=%d-%d\r\n"
"Session: %s\r\n\r\n",
seq, dateStr().c_str(),
clientAddr._ipstr(), m_serveAddr._ipstr());
//not support now
printf("not support RTP_TCP\n");
break;
}
//printf("S->C : %s\n", response.c_str());
m_n->_send(response.c_str(), response.length());
return 0;
}
4.PLAY实现如下,主要是读取文件,打成RTP包,注册定时器(定时投递任务到线程次发送)
int RtspClientConnection::handle_play(MediaSession * session,
MediaSubSession * subSession,
uint seq,
std::string & fullRequestStr)
{
std::string strScale;
//parse scale
size_type pos = fullRequestStr.find("Scale:");
if(pos != std::string::npos){
pos += 5;
while(fullRequestStr[++pos] == ' ');
float fscale = (float)atof(fullRequestStr.c_str() + pos);
append(strScale, "Scale: %f\r\n", fscale);
}
std::string rtspUrl;
MediaSubSession * sub = session->GetSubSession(0);
for (int i = 0; i < session->SubSessionCount(); i++){
MediaSubSession * temp = session->GetSubSession(i);
if (!subSession || temp == subSession){
sub = temp;
append(rtspUrl, "RTP-Info: rtsp://%s:%u/%s/%s;seq=%u;rtptime=%u",
m_serveAddr._ipstr(),
m_serveAddr._port() ,
session->StreamName().c_str(),
temp->GetTrackId().c_str(),
temp->SeqNo(),
temp->RtpTimestamp());
}
}
if (!sub){
//error
return -1;
}
std::string absstart, absend;
double startTime = 0, endTime = 0;
int result = parseRange(fullRequestStr, absstart, absend, startTime, endTime);
if (result < 0){
}
std::string response;
append(response,
"RTSP/1.0 200 OK\r\n"
"CSeq: %u\r\n"
"%s"
"%s"
"Session: %s\r\n"
"%s\r\n\r\n",
seq,
dateStr().c_str(),
strScale.c_str(),
m_sessionId.c_str(),
rtspUrl.c_str());
m_n->_send(response.c_str(), response.length());
sub->StartStream();
return 0;
}
至于PAUSE,GET_PARAMETER,SET_PARAMETER,TEARDOWN实现没有什么,这里就不在说了,下次讲一次 MediaSession 的实现