从Meida Server 到live555客户端
之前零零碎碎写了
live555 MediaServer
以及关于RTSP 流程的处理
live555 处理 请求消息 一 “OPTIONS”
live555 处理 请求消息 二 “DESCRIBE”
live555 处理 请求消息 三 “SETUP”
live555 处理 请求消息 四 “PLAY”
live555 处理 请求消息 四 “PLAY” [续]
以及每个部分涉及到的知识,如何读取.mkv文件一帧,用NALU 封装,发送给客户端过程分析
当时只是关注细节,导致没有从架构分析,专门解剖live555 (也是对自己的live555总结)当然,live555 也有很多需要优化,一步一步来解决
这一章,从功能划分解剖 live555 RTSPClient.时至今日,我依然记得前辈讲过,有刨根问底的精神,当啃完后,就会豁然开朗
最后有彩蛋……
testRTSPClient
看 main入口,代码功能就划分为
1.初始化RTSPClient 环境变量
2.打开连接MediaServer RTSP服务器,自动触发RTSP 流程,以及传输RTP传输,甚至RTCP控制
3.第二步接收返回的消息,通过Scheduler 轮询处理 定时事件和socket,所以需要开启Scheduler轮询(单线程处理)
char eventLoopWatchVariable = 0;
int main(int argc, char** argv) {
// Begin by setting up our usage environment:
TaskScheduler* scheduler = BasicTaskScheduler::createNew();
UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);
// Open and start streaming :
openURL(*env, "test", "rtsp://10.0.2.15/ss1.mkv");
// All subsequent activity takes place within the event loop:
env->taskScheduler().doEventLoop(&eventLoopWatchVariable);
return 0;
}
首先wireshark抓一个rtsp 流程的包
在步骤2 会触发 RTSP DESCRIPE 、SETUP、PLAY 请求,少了OPTION 请求,不过没关系,OPTION 只是为了确认服务器所支持的请求有哪一些
在SETUP会触发 RTP接收socket 建立 ,PLAY请求完成会接收RTP包
分析环境初始化
- 创建 scheduler 实例对象
TaskScheduler* scheduler = BasicTaskScheduler::createNew();
并且初始化变量,为轮询准备的变量
maxSchedulerGranularity = 10000/*microseconds*/ //最大调度间隔
fMaxNumSockets = 0 //最大socket 数目
fDummySocketNum = -1 //dummysocket 的初始化值
/*清除并初始化 保存socket 句柄的fd_set 结构体*/
FD_ZERO(&fReadSet);
FD_ZERO(&fWriteSet);
FD_ZERO(&fExceptionSet);
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;
#define FD_SETSIZE 64
以及超时scheduler相关的初始化
然后初始化live555 使用环境
UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);
将*scheduler 放在env 实例中,方便轮询使用
初始化,使用轮寻需要相关参数
# define RESULT_MSG_BUFFER_MAX 1000 //消息的最大buffer
fCurBufferSize = 0; //清除当前消息buffer大小
fResultMsgBuffer[fCurBufferSize] = '\0'; //清空 消息buffer
轮询 事件
char eventLoopWatchVariable = 0;
env->taskScheduler().doEventLoop(&eventLoopWatchVariable);
void BasicTaskScheduler0::doEventLoop(char volatile* watchVariable) {
// Repeatedly loop, handling readble sockets and timed events:
while (1) {
if (watchVariable != NULL && *watchVariable != 0) break;
SingleStep();
}
}
就是不断的处理 可读socket 传过来消息和定时事件
void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
fd_set readSet = fReadSet; // make a copy for this select() call
fd_set writeSet = fWriteSet; // ditto
fd_set exceptionSet = fExceptionSet; // ditto
//....
// Call the handler function for one readable socket:
HandlerIterator iter(*fHandlers);
HandlerDescriptor* handler;
//....
while ((handler = iter.next()) != NULL) {
int sock = handler->socketNum; // alias
int resultConditionSet = 0;
if (FD_ISSET(sock, &readSet) && FD_ISSET(sock, &fReadSet)/*sanity check*/) resultConditionSet |= SOCKET_READABLE;
if (FD_ISSET(sock, &writeSet) && FD_ISSET(sock, &fWriteSet)/*sanity check*/) resultConditionSet |= SOCKET_WRITABLE;
if (FD_ISSET(sock, &exceptionSet) && FD_ISSET(sock, &fExceptionSet)/*sanity check*/) resultConditionSet |= SOCKET_EXCEPTION;
if ((resultConditionSet&handler->conditionSet) != 0 && handler->handlerProc != NULL) {
fLastHandledSocketNum = sock;
// Note: we set "fLastHandledSocketNum" before calling the handler,
// in case the handler calls "doEventLoop()" reentrantly.
(*handler->handlerProc)(handler->clientData, resultConditionSet);
break;
}
}
//定时处理
}
关于HandlerIterator 迭代器原理 查看:live555 谈一谈 HandlerIterator \ HandlerDescriptor \HandlerSet 构成迭代器
最后,轮询处理接收socket 消息
(*handler->handlerProc)(handler->clientData, resultConditionSet);
openURL 触发RTSP流程
为当前RTSP客户端创建对象并且发送DESCRIBE请求
void openURL(UsageEnvironment& env, char const* progName, char const* rtspURL) {
RTSPClient* rtspClient = ourRTSPClient::createNew(env, rtspURL, RTSP_CLIENT_VERBOSITY_LEVEL, progName);
rtspClient->sendDescribeCommand(continueAfterDESCRIBE);
}
发送Describe请求
sendRequest(new RequestRecord(++fCSeq, "DESCRIBE", responseHandler));
在后面可以看出其他请求也是sendRequest
1. 打开创建socket 并且连接
2. send 发送组成的请求消息
unsigned RTSPClient::sendRequest(RequestRecord* request) {
char* cmd = NULL;
do {
Boolean connectionIsPending = False;
//....
// we need to open a connection
int connectResult = openConnection(); //success return 1
// Construct and send the command:
cmd = new char[cmdSize];
sprintf(cmd, cmdFmt,
request->commandName(), cmdURL, protocolStr,
request->cseq(),
authenticatorStr,
fUserAgentHeaderStr,
extraHeaders,
contentLengthHeader,
contentStr);
}
send(fOutputSocketNum, cmd, strlen(cmd), 0)
//......
} while (0);
return 0;
}
openConnection负责
1.创建socket
2.连接服务器
3.将socket 句柄对应信息,放入使用环境的scheduler 轮询,对接收消息进行回调处理
int RTSPClient::openConnection() {
do {
// Set up a connection to the server. Begin by parsing the URL:
// We don't yet have a TCP socket (or we used to have one, but it got closed). Set it up now.
fInputSocketNum = setupStreamSocket(envir(), 0);
if (fOutputSocketNum < 0) fOutputSocketNum = fInputSocketNum;
// Connect to the remote endpoint:
int connectResult = connectToServer(fInputSocketNum, destPortNum);
// The connection succeeded. Arrange to handle responses to requests sent on it:
envir().taskScheduler().setBackgroundHandling(
fInputSocketNum
,SOCKET_READABLE|SOCKET_EXCEPTION
,(TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler
, this);
}
return connectResult;
} while (0);
return -1;
}
上面将socket conditionSet handlerProc丢给 Scheduler处理,等待轮询处理,前提通过HandlerSet 调用 assignHandler 封装给HandlerDescriptor 然后交由事件轮询处理
void BasicTaskScheduler::setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData) {
//....
fHandlers->assignHandler(socketNum, conditionSet, handlerProc, clientData);
}
在轮询过程中才进行回调函数处理
(*handler->handlerProc)(handler->clientData, resultConditionSet);
说明了轮询处理过程,因此DESCRIPE请求 这里对应的
handlerProc->incomingDataHandler
clientData ->OurRTSPClient
resultConditionSet->SOCKET_READABLE|SOCKET_EXCEPTION
//以及socket句柄都存放在handler 对应是HandlerDescriptor
这里简单认识下HandlerDescriptor、HandlerSet、HandlerIterator
HandlerDescriptor 用来存放句柄相关信息,用双向链表查找
HandlerIterator 用来封装HandlerDescriptor ,遍历HandlerIterator其实就是查找HandlerDescriptor的节点
HandlerSet 用来将对应socket conditionSet 等处理回调函数信息封装给HandlerDescriptor
最后看一下回调函数incomingDataHandler 作用:
读取服务器返回消息,处理返回消息
void RTSPClient::incomingDataHandler1() {
int bytesRead = readSocket(envir(), fInputSocketNum, (unsigned char*)&fResponseBuffer[fResponseBytesAlreadySeen], fResponseBufferBytesLeft, dummy);
handleResponseBytes(bytesRead);
}
handleResponseBytes 后面会继续后续 SETUP PLAY等请求
总结 live555 事件RTSPClient 事件轮询过程
事件节点解释:
事件轮询 :SingleStep() 的过程
请求消息: 第一次是DESCRIBE 请求消息sendDescribeCommand ,后面依次发送其他请求
事件接收:通过envir().taskScheduler().setBackgroundHandling将事件传递给轮询
事件封装:在HandlerSet中通过assignHandler,将socket/conditionSet/handlerProc等信息封装在HandlerDescriptor 组成的双向链表中
回调:handlerProc函数回调,处理句柄所返回的消息,成功的话进行下一步请求