流程概述
1)创建任务对象(TaskScheduler);
2)创建环境对象(UsageEnvironment);
3)处理用户输入的参数(RTSP地址记录)
4)创建RTSPClient实例(RTSPClient);
5)发出第一个RTSP请求(DESCRIBE);
6)进入事件循环(doEventLoop);
源码分析
建立任务对象
TaskScheduler则提供了任务调度功能。主要负责调度任务,执行任务(任务就是一个函数)。TaskScheduler由于在全局中只有一个(单例),保存在UsageEnvironment中。
TaskScheduler* scheduler = BasicTaskScheduler::createNew();
创建环境对象
BasicUsageEnvironment和UsageEnvironment中的类都是用于整个系统的基础功能类.比如UsageEnvironment代表了整个系统运行的环境,它提供了错误记录和错误报告的功能,无论哪一个类要输出错误,就需要保存UsageEnvironment的指针。
UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler); // scheduler作为参数传入
日志记录示例:
UsageEnvironment& env = rtspClient->envir();
if (resultCode != 0) {
env << *rtspClient << "Failed to get a SDP description: " << resultString << "\n";
delete[] resultString;
break;
}
处理用户输入的参数
主要是记录用户信息。
void usage(UsageEnvironment& env, char const* progName) {
env << "Usage: " << progName << " <rtsp-url-1> ... <rtsp-url-N>\n";
env << "\t(where each <rtsp-url-i> is a \"rtsp://\" URL)\n";
}
创建RTSPClient实例
注意:RTSPClient实例在全局中只有一个。
RTSPClient* rtspClient = ourRTSPClient::createNew(env, rtspURL, RTSP_CLIENT_VERBOSITY_LEVEL, progName);
if (rtspClient == NULL) {
env << "Failed to create a RTSP client for URL \"" << rtspURL << "\": " << env.getResultMsg() << "\n";
return;
}
RTSPClient的构造中主要做了一件工作:如果已经与RTSPServer建立连接,则设置事件回调,处理事件响应。
if (socketNumToServer >= 0) {
fInputSocketNum = fOutputSocketNum = socketNumToServer;
env.taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION,
(TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);
}
发出第一个RTSP请求
自此开启rtsp的信令交互流程。
rtspClient->sendDescribeCommand(continueAfterDESCRIBE);
发送请求是由虚函数sendRequest来完成的,sendRequest的参数RequestRecord类包装了不同的请求命令。例如:
// responseHandler是一个回调函数, 发送请求成功后将结果返回给上层调用
unsigned RTSPClient::sendDescribeCommand(responseHandler* responseHandler, Authenticator* authenticator) {
if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
return sendRequest(new RequestRecord(++fCSeq, "DESCRIBE", responseHandler));
}
sendRequest主要完成的工作是组装并发送命令:
unsigned RTSPClient::sendRequest(RequestRecord* request) {
...
// write来发送组装后的命令
if (write(cmd, strlen(cmd)) < 0) {
char const* errFmt = "%s write() 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;
}
...
}
// 通过socket发送数据
int RTSPClient::write(const char* data, unsigned count) {
if (fTLS.isNeeded) {
return fTLS.write(data, count);
} else {
return send(fOutputSocketNum, data, count, 0);
}
}
来看一下发送并响应成功后的处理,这里以continueAfterDESCRIBE为例分析:
void continueAfterDESCRIBE(RTSPClient* rtspClient, int resultCode, char* resultString) {
do {
...
// 根据返回的sdp信息创建一个媒体会话
scs.session = MediaSession::createNew(env, sdpDescription);
// 根据媒体会话创建媒体子会话
scs.iter = new MediaSubsessionIterator(*scs.session);
setupNextSubsession(rtspClient);
return;
} while (0);
// 出错关闭流
shutdownStream(rtspClient);
}
说明:MediaSession是流媒体会话,MediaSession和MediaSubsession共同用于执行流媒体传输和状态维护。
创建完会话后,执行setupNextSubsession。setupNextSubsession主要做了一个判断,子会话有没有initiate成功,如果没有成功,发送SETUP命令;如果建立成功了,则发送PLAY命令请求取流。
void setupNextSubsession(RTSPClient* rtspClient) {
UsageEnvironment& env = rtspClient->envir(); // alias
StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
scs.subsession = scs.iter->next();
if (scs.subsession != NULL) {
if (!scs.subsession->initiate()) {
// 通过发送SETUP命令建立子会话
rtspClient->sendSetupCommand(*scs.subsession, continueAfterSETUP, False, REQUEST_STREAMING_OVER_TCP);
}
return;
}
// 如果已经建立了子会话 则发送PLAY命令
if (scs.session->absStartTime() != NULL) {
rtspClient->sendPlayCommand(*scs.session, continueAfterPLAY, scs.session->absStartTime(), scs.session->absEndTime());
} else {
scs.duration = scs.session->playEndTime() - scs.session->playStartTime();
rtspClient->sendPlayCommand(*scs.session, continueAfterPLAY);
}
}
initiate到底做了什么事情呢?来看下它的实现:
Boolean MediaSubsession::initiate(int useSpecialRTPoffset) {
if (fReadSource != NULL) return True; // has already been initiated
do {
...
if (isSSM()) {
fRTPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr, fClientPortNum);
} else {
fRTPSocket = new Groupsock(env(), tempAddr, fClientPortNum, 255);
}
if (fRTPSocket == NULL) {
env().setResultMsg("Failed to create RTP socket");
break;
}
if (protocolIsRTP) {
if (fMultiplexRTCPWithRTP) {
// Use the RTP 'groupsock' object for RTCP as well:
fRTCPSocket = fRTPSocket;
} else {
// Set our RTCP port to be the RTP port + 1:
portNumBits const rtcpPortNum = fClientPortNum|1;
if (isSSM()) {
fRTCPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr, rtcpPortNum);
} else {
fRTCPSocket = new Groupsock(env(), tempAddr, rtcpPortNum, 255);
}
}
}
}
...
// Create "fRTPSource" and "fReadSource":
if (!createSourceObjects(useSpecialRTPoffset)) break;
if (fReadSource == NULL) {
env().setResultMsg("Failed to create read source");
break;
}
SRTPCryptographicContext* ourCrypto = NULL;
if (useSRTP) {
// For SRTP, we need key management. If MIKEY (key management) state wasn't given
// to us in the SDP description, then create it now:
ourCrypto = getCrypto();
if (ourCrypto == NULL) { // then fMIKEYState is also NULL; create both
fMIKEYState = new MIKEYState();
ourCrypto = fCrypto = new SRTPCryptographicContext(*fMIKEYState);
}
if (fRTPSource != NULL) fRTPSource->setCrypto(ourCrypto);
}
// Finally, create our RTCP instance. (It starts running automatically)
if (fRTPSource != NULL && fRTCPSocket != NULL) {
// If bandwidth is specified, use it and add 5% for RTCP overhead.
// Otherwise make a guess at 500 kbps.
unsigned totSessionBandwidth
= fBandwidth ? fBandwidth + fBandwidth / 20 : 500;
fRTCPInstance = RTCPInstance::createNew(env(), fRTCPSocket,
totSessionBandwidth,
(unsigned char const*)
fParent.CNAME(),
NULL /* we're a client */,
fRTPSource,
False /* we're not a data transmitter */,
ourCrypto);
if (fRTCPInstance == NULL) {
env().setResultMsg("Failed to create RTCP instance");
break;
}
}
return True;
} while (0);
deInitiate();
fClientPortNum = 0;
return False;
}
代码有点长,分析下来主要做了两件事:
1)创建RTP和RTCP连接(RTPSocket和RTCPSocket);
2)创建RTP资源和RTCP资源(RTPSource和RTCPSource);
这里也可以看出,RTP和RTCP的连接是在DESCRIBE之后完成的。而Source可以理解为生产者,对应的Sink可以理解为消费者。
接着我们来看下SETUP响应成功之后做了什么。
void continueAfterSETUP(RTSPClient* rtspClient, int resultCode, char* resultString) {
do {
scs.subsession->sink = DummySink::createNew(env, *scs.subsession, rtspClient->url());
break;
}
scs.subsession->miscPtr = rtspClient;
scs.subsession->sink->startPlaying(*(scs.subsession->readSource()),
subsessionAfterPlaying, scs.subsession);
if (scs.subsession->rtcpInstance() != NULL) {
scs.subsession->rtcpInstance()->setByeWithReasonHandler(subsessionByeHandler, scs.subsession);
}
} while (0);
delete[] resultString;
// Set up the next subsession, if any:
setupNextSubsession(rtspClient);
}
这里主要做了两个工作:
1)创建Sink(即上层消费者,可以理解为接收流的人,DummySink为客户端实现);
2)开始请求流;
流数据接收处理类
上面有介绍到在开始取流之前创建了DummySink类用来接收流数据,来看一下DummySink是怎么实现的。
DummySink* DummySink::createNew(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId) {
return new DummySink(env, subsession, streamId);
}
DummySink::DummySink(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId)
: MediaSink(env),
fSubsession(subsession) {
fStreamId = strDup(streamId);
fReceiveBuffer = new u_int8_t[DUMMY_SINK_RECEIVE_BUFFER_SIZE];
}
DummySink::~DummySink() {
delete[] fReceiveBuffer;
delete[] fStreamId;
}
// 获取流数据
void DummySink::afterGettingFrame(void* clientData, unsigned frameSize, unsigned numTruncatedBytes,
struct timeval presentationTime, unsigned durationInMicroseconds) {
DummySink* sink = (DummySink*)clientData;
sink->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime, durationInMicroseconds);
}
#define DEBUG_PRINT_EACH_RECEIVED_FRAME 1
void DummySink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,
struct timeval presentationTime, unsigned /*durationInMicroseconds*/) {
continuePlaying();
}
Boolean DummySink::continuePlaying() {
if (fSource == NULL) return False;
// fSource是维护在MediaSink中的FramedSource类的对象
fSource->getNextFrame(fReceiveBuffer, DUMMY_SINK_RECEIVE_BUFFER_SIZE,
afterGettingFrame, this,
onSourceClosure, this);
return True;
}
DummySink继承于MediaSink类,通过afterGettingFrame函数来接收一次数据回调。 MediaSink保存了FramedSource* fSource; 的指针,可以通过其来获取下一帧数据。
进入事件循环
env->taskScheduler().doEventLoop(&eventLoopWatchVariable);