openrtsp是live555里面作为rtsp客户端的一个例子程序。默认是接收rtsp流并保存为文件,里面对多种格式进行了处理。网上也有很多对openrtsp的分析的文章,但是个人感觉都不是太详细,一般都只有个大概的流程。这里再给这个过程捋一捋。
主要是一些重要的线路,细节部分就不予讨论。
在playCommon.cpp中是从main开始执行的。
开始是
TaskScheduler* scheduler = BasicTaskScheduler::createNew(); env = BasicUsageEnvironment::createNew(*scheduler);
做一些初始化参数的工作。然后是
ourClient = createClient(*env, streamURL, verbosityLevel, progName);
if (sendOptionsRequest) { // Begin by sending an "OPTIONS" command: getOptions(continueAfterOPTIONS); } else { continueAfterOPTIONS(NULL, 0, NULL); }
其实是调用openRtsp中定义的函数
void getOptions(RTSPClient::responseHandler* afterFunc) { ourRTSPClient->sendOptionsCommand(afterFunc, ourAuthenticator); }
unsigned RTSPClient::sendOptionsCommand(responseHandler* responseHandler, Authenticator* authenticator) { if (authenticator != NULL) fCurrentAuthenticator = *authenticator; return sendRequest(new RequestRecord(++fCSeq, "OPTIONS", responseHandler)); }
RTSPClient::RequestRecord::RequestRecord(unsigned cseq, char const* commandName, responseHandler* handler, MediaSession* session, MediaSubsession* subsession, u_int32_t booleanFlags, double start, double end, float scale, char const* contentStr) : fNext(NULL), fCSeq(cseq), fCommandName(commandName), fSession(session), fSubsession(subsession), fBooleanFlags(booleanFlags), fStart(start), fEnd(end), fScale(scale), fContentStr(strDup(contentStr)), fHandler(handler) { }
unsigned RTSPClient::sendRequest(RequestRecord* request) { char* cmd = NULL; do { Boolean connectionIsPending = False; if (!fRequestsAwaitingConnection.isEmpty()) { // A connection is currently pending (with at least one enqueued request). Enqueue this request also: connectionIsPending = True; } else if (fInputSocketNum < 0) { // we need to open a connection int connectResult = openConnection(); if (connectResult < 0) break; // an error occurred else if (connectResult == 0) { // A connection is pending connectionIsPending = True; } // else the connection succeeded. Continue sending the command.u } if (connectionIsPending) { fRequestsAwaitingConnection.enqueue(request); return request->cseq(); } // If requested (and we're not already doing it, or have done it), set up the special protocol for tunneling RTSP-over-HTTP: if (fTunnelOverHTTPPortNum != 0 && strcmp(request->commandName(), "GET") != 0 && fOutputSocketNum == fInputSocketNum) { if (!setupHTTPTunneling1()) break; fRequestsAwaitingHTTPTunneling.enqueue(request); return request->cseq(); } // Construct and send the command: // First, construct command-specific headers that we need: char* cmdURL = fBaseURL; // by default Boolean cmdURLWasAllocated = False; char const* protocolStr = "RTSP/1.0"; // by default char* extraHeaders = (char*)""; // by default Boolean extraHeadersWereAllocated = False; char* contentLengthHeader = (char*)""; // by default Boolean contentLengthHeaderWasAllocated = False; char const* contentStr = request->contentStr(); // by default if (contentStr == NULL) contentStr = ""; unsigned contentStrLen = strlen(contentStr); if (contentStrLen > 0) { char const* contentLengthHeaderFmt = "Content-Length: %d\r\n"; unsigned contentLengthHeaderSize = strlen(contentLengthHeaderFmt) + 20 /* max int len */; contentLengthHeader = new char[contentLengthHeaderSize]; sprintf(contentLengthHeader, contentLengthHeaderFmt, contentStrLen); contentLengthHeaderWasAllocated = True; } //根据commandName不同执行不同的程序段。 if (strcmp(request->commandName(), "DESCRIBE") == 0) { extraHeaders = (char*)"Accept: application/sdp\r\n"; } else if (strcmp(request->commandName(), "OPTIONS") == 0) { } else if (strcmp(request->commandName(), "ANNOUNCE") == 0) { extraHeaders = (char*)"Content-Type: application/sdp\r\n"; } else if (strcmp(request->commandName(), "SETUP") == 0) { MediaSubsession& subsession = *request->subsession(); Boolean streamUsingTCP = (request->booleanFlags()&0x1) != 0; Boolean streamOutgoing = (request->booleanFlags()&0x2) != 0; Boolean forceMulticastOnUnspecified = (request->booleanFlags()&0x4) != 0; char const *prefix, *separator, *suffix; constructSubsessionURL(subsession, prefix, separator, suffix); char const* transportFmt; if (strcmp(subsession.protocolName(), "UDP") == 0) { suffix = ""; transportFmt = "Transport: RAW/RAW/UDP%s%s%s=%d-%d\r\n"; } else { transportFmt = "Transport: RTP/AVP%s%s%s=%d-%d\r\n"; } cmdURL = new char[strlen(prefix) + strlen(separator) + strlen(suffix) + 1]; cmdURLWasAllocated = True; sprintf(cmdURL, "%s%s%s", prefix, separator, suffix); // Construct a "Transport:" header. char const* transportTypeStr; char const* modeStr = streamOutgoing ? ";mode=receive" : ""; // Note: I think the above is nonstandard, but DSS wants it this way char const* portTypeStr; portNumBits rtpNumber, rtcpNumber; if (streamUsingTCP) { // streaming over the RTSP connection transportTypeStr = "/TCP;unicast"; portTypeStr = ";interleaved"; rtpNumber = fTCPStreamIdCount++; rtcpNumber = fTCPStreamIdCount++; } else { // normal RTP streaming unsigned connectionAddress = subsession.connectionEndpointAddress(); Boolean requestMulticastStreaming = IsMulticastAddress(connectionAddress) || (connectionAddress == 0 && forceMulticastOnUnspecified); transportTypeStr = requestMulticastStreaming ? ";multicast" : ";unicast"; portTypeStr = ";client_port"; rtpNumber = subsession.clientPortNum(); if (rtpNumber == 0) { envir().setResultMsg("Client port number unknown\n"); delete[] cmdURL; break; } rtcpNumber = rtpNumber + 1; } unsigned transportSize = strlen(transportFmt) + strlen(transportTypeStr) + strlen(modeStr) + strlen(portTypeStr) + 2*5 /* max port len */; char* transportStr = new char[transportSize]; sprintf(transportStr, transportFmt, transportTypeStr, modeStr, portTypeStr, rtpNumber, rtcpNumber); // When sending more than one "SETUP" request, include a "Session:" header in the 2nd and later commands: char* sessionStr = createSessionString(fLastSessionId); // The "Transport:" and "Session:" (if present) headers make up the 'extra headers': extraHeaders = new char[transportSize + strlen(sessionStr)]; extraHeadersWereAllocated = True; sprintf(extraHeaders, "%s%s", transportStr, sessionStr); delete[] transportStr; delete[] sessionStr; } else if (strcmp(request->commandName(), "GET") == 0 || strcmp(request->commandName(), "POST") == 0) { // We will be sending a HTTP (not a RTSP) request. // Begin by re-parsing our RTSP URL, just to get the stream name, which we'll use as our 'cmdURL' in the subsequent request: char* username; char* password; NetAddress destAddress; portNumBits urlPortNum; if (!parseRTSPURL(envir(), fBaseURL, username, password, destAddress, urlPortNum, (char const**)&cmdURL)) break; if (cmdURL[0] == '\0') cmdURL = (char*)"/"; delete[] username; delete[] password; protocolStr = "HTTP/1.0"; if (strcmp(request->commandName(), "GET") == 0) { // Create a 'session cookie' string, using MD5: struct { struct timeval timestamp; unsigned counter; } seedData; gettimeofday(&seedData.timestamp, NULL); seedData.counter = ++fSessionCookieCounter; our_MD5Data((unsigned char*)(&seedData), sizeof seedData, fSessionCookie); // DSS seems to require that the 'session cookie' string be 22 bytes long: fSessionCookie[23] = '\0'; char const* const extraHeadersFmt = "x-sessioncookie: %s\r\n" "Accept: application/x-rtsp-tunnelled\r\n" "Pragma: no-cache\r\n" "Cache-Control: no-cache\r\n"; unsigned extraHeadersSize = strlen(extraHeadersFmt) + strlen(fSessionCookie); extraHeaders = new char[extraHeadersSize]; extraHeadersWereAllocated = True; sprintf(extraHeaders, extraHeadersFmt, fSessionCookie); } else { // "POST" char const* const extraHeadersFmt = "x-sessioncookie: %s\r\n" "Content-Type: application/x-rtsp-tunnelled\r\n" "Pragma: no-cache\r\n" "Cache-Control: no-cache\r\n" "Content-Length: 32767\r\n" "Expires: Sun, 9 Jan 1972 00:00:00 GMT\r\n"; unsigned extraHeadersSize = strlen(extraHeadersFmt) + strlen(fSessionCookie); extraHeaders = new char[extraHeadersSize]; extraHeadersWereAllocated = True; sprintf(extraHeaders, extraHeadersFmt, fSessionCookie); } } else { // "PLAY", "PAUSE", "TEARDOWN", "RECORD", "SET_PARAMETER", "GET_PARAMETER" // First, make sure that we have a RTSP session in progress if (fLastSessionId == NULL) { envir().setResultMsg("No RTSP session is currently in progress\n"); break; } char const* sessionId; float originalScale; if (request->session() != NULL) { // Session-level operation cmdURL = (char*)sessionURL(*request->session()); sessionId = fLastSessionId; originalScale = request->session()->scale(); } else { // Media-level operation char const *prefix, *separator, *suffix; constructSubsessionURL(*request->subsession(), prefix, separator, suffix); cmdURL = new char[strlen(prefix) + strlen(separator) + strlen(suffix) + 1]; cmdURLWasAllocated = True; sprintf(cmdURL, "%s%s%s", prefix, separator, suffix); sessionId = request->subsession()->sessionId(); originalScale = request->subsession()->scale(); } if (strcmp(request->commandName(), "PLAY") == 0) { // Create "Session:", "Scale:", and "Range:" headers; these make up the 'extra headers': char* sessionStr = createSessionString(sessionId); char* scaleStr = createScaleString(request->scale(), originalScale); char* rangeStr = createRangeString(request->start(), request->end()); extraHeaders = new char[strlen(sessionStr) + strlen(scaleStr) + strlen(rangeStr) + 1]; extraHeadersWereAllocated = True; sprintf(extraHeaders, "%s%s%s", sessionStr, scaleStr, rangeStr); delete[] sessionStr; delete[] scaleStr; delete[] rangeStr; } else { // Create a "Session:" header; this makes up our 'extra headers': extraHeaders = createSessionString(sessionId); extraHeadersWereAllocated = True; } } char* authenticatorStr = createAuthenticatorString(request->commandName(), fBaseURL); char const* const cmdFmt = "%s %s %s\r\n" "CSeq: %d\r\n" "%s" "%s" "%s" "%s" "\r\n" "%s"; unsigned cmdSize = strlen(cmdFmt) + strlen(request->commandName()) + strlen(cmdURL) + strlen(protocolStr) + 20 /* max int len */ + strlen(authenticatorStr) + fUserAgentHeaderStrLen + strlen(extraHeaders) + strlen(contentLengthHeader) + contentStrLen; cmd = new char[cmdSize]; sprintf(cmd, cmdFmt, request->commandName(), cmdURL, protocolStr, request->cseq(), authenticatorStr, fUserAgentHeaderStr, extraHeaders, contentLengthHeader, contentStr); delete[] authenticatorStr; if (cmdURLWasAllocated) delete[] cmdURL; if (extraHeadersWereAllocated) delete[] extraHeaders; if (contentLengthHeaderWasAllocated) delete[] contentLengthHeader; if (fVerbosityLevel >= 1) envir() << "Sending request: " << cmd << "\n"; if (fTunnelOverHTTPPortNum != 0 && strcmp(request->commandName(), "GET") != 0 && strcmp(request->commandName(), "POST") != 0) { // When we're tunneling RTSP-over-HTTP, we Base-64-encode the request before we send it. // (However, we don't do this for the HTTP "GET" and "POST" commands that we use to set up the tunnel.) char* origCmd = cmd; cmd = base64Encode(origCmd, strlen(cmd)); if (fVerbosityLevel >= 1) envir() << "\tThe request was base-64 encoded to: " << cmd << "\n\n"; delete[] origCmd; } //将命令发送出去,上面是对发送命令做一个拼接 if (send(fOutputSocketNum, cmd, strlen(cmd), 0) < 0) { char const* errFmt = "%s send() failed: "; unsigned const errLength = strlen(errFmt) + strlen(request->commandName()); char* err = new char[errLength]; sprintf(err, errFmt, request->commandName()); envir().setResultErrMsg(err); delete[] err; break; } // The command send succeeded, so enqueue the request record, so that its response (when it comes) can be handled: fRequestsAwaitingResponse.enqueue(request); delete[] cmd; return request->cseq(); } while (0); // An error occurred, so call the response handler immediately (indicating the error): delete[] cmd; handleRequestError(request); delete request; return 0; }
在sendReques中先判断fRequestsAwaitingConnection是否为isEmpty,如果不是就会openConnection()
在我们再来看看这个openConnection
int RTSPClient::openConnection() { do { // Set up a connection to the server. Begin by parsing the URL: char* username; char* password; NetAddress destAddress; portNumBits urlPortNum; char const* urlSuffix; if (!parseRTSPURL(envir(), fBaseURL, username, password, destAddress, urlPortNum, &urlSuffix)) break; portNumBits destPortNum = fTunnelOverHTTPPortNum == 0 ? urlPortNum : fTunnelOverHTTPPortNum; if (username != NULL || password != NULL) { fCurrentAuthenticator.setUsernameAndPassword(username, password); delete[] username; delete[] password; } // We don't yet have a TCP socket (or we used to have one, but it got closed). Set it up now. fInputSocketNum = fOutputSocketNum = setupStreamSocket(envir(), 0); if (fInputSocketNum < 0) break; // Connect to the remote endpoint: fServerAddress = *(netAddressBits*)(destAddress.data()); int connectResult = connectToServer(fInputSocketNum, destPortNum); if (connectResult < 0) break; else if (connectResult > 0) { // The connection succeeded. Arrange to handle responses to requests sent on it: envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE, (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this); } return connectResult; } while (0); resetTCPSockets(); return -1; }
这里面主要是
int setupStreamSocket(UsageEnvironment& env, Port port, Boolean makeNonBlocking) { if (!initializeWinsockIfNecessary()) { socketErr(env, "Failed to initialize 'winsock': "); return -1; } int newSocket = createSocket(SOCK_STREAM); if (newSocket < 0) { socketErr(env, "unable to create stream socket: "); return newSocket; } int reuseFlag = groupsockPriv(env)->reuseFlag; reclaimGroupsockPriv(env); if (setsockopt(newSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseFlag, sizeof reuseFlag) < 0) { socketErr(env, "setsockopt(SO_REUSEADDR) error: "); closeSocket(newSocket); return -1; } // SO_REUSEPORT doesn't really make sense for TCP sockets, so we // normally don't set them. However, if you really want to do this // #define REUSE_FOR_TCP #ifdef REUSE_FOR_TCP #if defined(__WIN32__) || defined(_WIN32) // Windoze doesn't properly handle SO_REUSEPORT #else #ifdef SO_REUSEPORT if (setsockopt(newSocket, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuseFlag, sizeof reuseFlag) < 0) { socketErr(env, "setsockopt(SO_REUSEPORT) error: "); closeSocket(newSocket); return -1; } #endif #endif #endif // Note: Windoze requires binding, even if the port number is 0 #if defined(__WIN32__) || defined(_WIN32) #else if (port.num() != 0 || ReceivingInterfaceAddr != INADDR_ANY) { #endif MAKE_SOCKADDR_IN(name, ReceivingInterfaceAddr, port.num()); if (bind(newSocket, (struct sockaddr*)&name, sizeof name) != 0) { char tmpBuffer[100]; sprintf(tmpBuffer, "bind() error (port number: %d): ", ntohs(port.num())); socketErr(env, tmpBuffer); closeSocket(newSocket); return -1; } #if defined(__WIN32__) || defined(_WIN32) #else } #endif if (makeNonBlocking) { if (!makeSocketNonBlocking(newSocket)) { socketErr(env, "failed to make non-blocking: "); closeSocket(newSocket); return -1; } } return newSocket; }
再看下面做做了connectToServer
int RTSPClient::connectToServer(int socketNum, portNumBits remotePortNum) { MAKE_SOCKADDR_IN(remoteName, fServerAddress, htons(remotePortNum)); if (fVerbosityLevel >= 1) { envir() << "Opening connection to " << AddressString(remoteName).val() << ", port " << remotePortNum << "...\n"; } if (connect(socketNum, (struct sockaddr*) &remoteName, sizeof remoteName) != 0) { int const err = envir().getErrno(); if (err == EINPROGRESS || err == EWOULDBLOCK) { // The connection is pending; we'll need to handle it later. Wait for our socket to be 'writable', or have an exception. envir().taskScheduler().setBackgroundHandling(socketNum, SOCKET_WRITABLE|SOCKET_EXCEPTION, (TaskScheduler::BackgroundHandlerProc*)&connectionHandler, this); return 0; } envir().setResultErrMsg("connect() failed: "); if (fVerbosityLevel >= 1) envir() << "..." << envir().getResultMsg() << "\n"; return -1; } if (fVerbosityLevel >= 1) envir() << "...local connection opened\n"; return 1; }
主要是tcp的connection操作。这里会执行if里面的,我们来看看这个setBackgroundHandling
void BasicTaskScheduler ::setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData) { if (socketNum < 0) return; FD_CLR((unsigned)socketNum, &fReadSet); FD_CLR((unsigned)socketNum, &fWriteSet); FD_CLR((unsigned)socketNum, &fExceptionSet); if (conditionSet == 0) { fHandlers->clearHandler(socketNum); if (socketNum+1 == fMaxNumSockets) { --fMaxNumSockets; } } else { fHandlers->assignHandler(socketNum, conditionSet, handlerProc, clientData); if (socketNum+1 > fMaxNumSockets) { fMaxNumSockets = socketNum+1; } if (conditionSet&SOCKET_READABLE) FD_SET((unsigned)socketNum, &fReadSet); if (conditionSet&SOCKET_WRITABLE) FD_SET((unsigned)socketNum, &fWriteSet); if (conditionSet&SOCKET_EXCEPTION) FD_SET((unsigned)socketNum, &fExceptionSet); } }
这里的assignHandler主要是将handlerProc加入到fHandlers中,并且将socketNum加入到这个节点中。
if (connectionIsPending) { fRequestsAwaitingConnection.enqueue(request); return request->cseq(); }
回到main主程序, env->taskScheduler().doEventLoop();
void BasicTaskScheduler0::doEventLoop(char* watchVariable) { // Repeatedly loop, handling readble sockets and timed events: while (1) { if (watchVariable != NULL && *watchVariable != 0) break; SingleStep(); } }
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
//计算select超时的时间 DelayInterval const& timeToDelay = fDelayQueue.timeToNextAlarm(); struct timeval tv_timeToDelay; tv_timeToDelay.tv_sec = timeToDelay.seconds(); tv_timeToDelay.tv_usec = timeToDelay.useconds(); // Very large "tv_sec" values cause select() to fail. // Don't make it any larger than 1 million seconds (11.5 days) const long MAX_TV_SEC = MILLION; if (tv_timeToDelay.tv_sec > MAX_TV_SEC) { tv_timeToDelay.tv_sec = MAX_TV_SEC; } // Also check our "maxDelayTime" parameter (if it's > 0): if (maxDelayTime > 0 && (tv_timeToDelay.tv_sec > (long)maxDelayTime/MILLION || (tv_timeToDelay.tv_sec == (long)maxDelayTime/MILLION && tv_timeToDelay.tv_usec > (long)maxDelayTime%MILLION))) { tv_timeToDelay.tv_sec = maxDelayTime/MILLION; tv_timeToDelay.tv_usec = maxDelayTime%MILLION; } int selectResult = select(fMaxNumSockets, &readSet, &writeSet, &exceptionSet, &tv_timeToDelay); if (selectResult < 0) { #if defined(__WIN32__) || defined(_WIN32) int err = WSAGetLastError(); // For some unknown reason, select() in Windoze sometimes fails with WSAEINVAL if // it was called with no entries set in "readSet". If this happens, ignore it: if (err == WSAEINVAL && readSet.fd_count == 0) { err = EINTR; // To stop this from happening again, create a dummy socket: int dummySocketNum = socket(AF_INET, SOCK_DGRAM, 0); FD_SET((unsigned)dummySocketNum, &fReadSet); } if (err != EINTR) { #else if (errno != EINTR && errno != EAGAIN) { #endif // Unexpected error - treat this as fatal: #if !defined(_WIN32_WCE) perror("BasicTaskScheduler::SingleStep(): select() fails"); #endif internalError(); } } // Call the handler function for one readable socket: HandlerIterator iter(*fHandlers); HandlerDescriptor* handler; // To ensure forward progress through the handlers, begin past the last // socket number that we handled: if (fLastHandledSocketNum >= 0) { while ((handler = iter.next()) != NULL) { if (handler->socketNum == fLastHandledSocketNum) break; } if (handler == NULL) { fLastHandledSocketNum = -1; iter.reset(); // start from the beginning instead } }
//查找可执行的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; } }
如果任然没有找到可执行的handler,从头开始找 if (handler == NULL && fLastHandledSocketNum >= 0) { // We didn't call a handler, but we didn't get to check all of them, // so try again from the beginning: iter.reset(); 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; } } if (handler == NULL) fLastHandledSocketNum = -1;//because we didn't call a handler }
响应事件 // Also handle any newly-triggered event (Note that we do this *after* calling a socket handler, // in case the triggered event handler modifies The set of readable sockets.) if (fTriggersAwaitingHandling != 0) { if (fTriggersAwaitingHandling == fLastUsedTriggerMask) { // Common-case optimization for a single event trigger: fTriggersAwaitingHandling = 0; if (fTriggeredEventHandlers[fLastUsedTriggerNum] != NULL) { (*fTriggeredEventHandlers[fLastUsedTriggerNum])(fTriggeredEventClientDatas[fLastUsedTriggerNum]); } } else { // Look for an event trigger that needs handling (making sure that we make forward progress through all possible triggers): unsigned i = fLastUsedTriggerNum; EventTriggerId mask = fLastUsedTriggerMask; do { i = (i+1)%MAX_NUM_EVENT_TRIGGERS; mask >>= 1; if (mask == 0) mask = 0x80000000; if ((fTriggersAwaitingHandling&mask) != 0) { fTriggersAwaitingHandling &=~ mask; if (fTriggeredEventHandlers[i] != NULL) { (*fTriggeredEventHandlers[i])(fTriggeredEventClientDatas[i]); } fLastUsedTriggerMask = mask; fLastUsedTriggerNum = i; break; } } while (i != fLastUsedTriggerNum); } } // Also handle any delayed event that may have come due. fDelayQueue.handleAlarm(); }