live555 源码阅读

live555 源码阅读

liveMedia: 主要的媒体处理逻辑。

UsageEnvironment: 处理事件循环和任务调度。

groupsock: 网络层,处理组播和单播的socket通信。

BasicUsageEnvironment: 提供基本的环境支持,是UsageEnvironment的一个简单实现。

https://blog.csdn.net/C1033177205/article/details/104331224

事件循环主要处理三类事件:Socket 事件、触发器事件和定时任务。

SDP

1
2
3
4
5
6
7
8
SDP(Session Description Protocol,会议描述协议)是一个用于描述多媒体通信会话的协议。SDP主要用于协商和定义参与会话的各方在通信时需要的媒体格式和网络参数。它常用于多媒体通信和流媒体应用中,如视频会议、VoIP(网络电话)、实时流媒体播放等。

SDP的主要功能包括:

会话描述:定义会话的基本属性,如会话的名称、时间、地点等。
媒体信息:描述会话中传输的多媒体流的信息,包括媒体类型(音频、视频等)、编码格式、传输地址和端口等。
会话参数协商:在会话的参与方之间协商媒体参数,以确保所有参与方能够兼容和正确处理媒体流。
SDP通常与其他协议一起使用,如SIP(会话发起协议)和RTSP(实时流协议),以建立和管理多媒体会话。SDP本身不传输媒体数据,而是用于描述和协商会话参数,媒体数据的实际传输通常通过RTP(实时传输协议)等协议进行。

TaskScheduler和BasicTaskScheduler
任务调度的模块,TaskScheduler定义了接口,BasicTaskScheduler继承BasicTaskScheduler0,BasicTaskScheduler0继承TaskScheduler。

testRTSPClient

  1. 创建scheduler 及env;
  2. 打开播放地址;
  3. 让程序进入消息循环跑起来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main(int argc, char** argv) {
// Begin by setting up our usage environment:
TaskScheduler* scheduler = BasicTaskScheduler::createNew();
UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);

// We need at least one "rtsp://" URL argument:
if (argc < 2) {
usage(*env, argv[0]);
return 1;
}
openURL(*env, argv[0], argv[1]);
if(argc >= 3 && strstr(argv[2],"tcp")){
REQUEST_STREAMING_OVER_TCP = true;
}


env->taskScheduler().doEventLoop(&eventLoopWatchVariable);
return 0;

DESCRIBE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int RTSPClient::openConnection() {
do {
//1.解析是否需要账号密码,有些RTSP连接是携带账号密码的,这个在视频监控领域比较常见:
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;
}

//2.建立TCP Socket,连接服务器:
fInputSocketNum = fOutputSocketNum = setupStreamSocket(envir(), Port(0), destAddress.getFamily());
if (fInputSocketNum < 0) break;
ignoreSigPipeOnSocket(fInputSocketNum); // so that servers on the same host that get killed don't also kill us
// Connect to the remote endpoint:
fServerAddress = destAddress;
int connectResult = connectToServer(fInputSocketNum, destPortNum);
if (connectResult < 0) break;
else if (connectResult > 0) {
// 3.连接成功,在taskScheduler轮训IO,socket读到数据的回调函数为incomingDataHandler
envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION,
(TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);

发出DESCRIBE

建立TCP连接int connectResult = openConnection();

RTSP命令封装发出。
socket回调函数incomingDataHandler读取RTSP命令响应。

handleResponseBytes处理RTSP命令的响应,解析各种RTSP响应头字段,如”RTP-Info:””Range:”等等,对于SETUP/PLAY等命令,会有一些不同的处理。然后根据responseCode,看是否正常,常见的错误像403,401。302则需要重定向,200则正常。

回到DESCRIBE命令 回调函数continueAfterDESCRIBE,根据SDP信息创建MediaSession,并setupNextSubsession

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Create a media session object from this SDP description:
scs.session = MediaSession::createNew(env, sdpDescription);
delete[] sdpDescription; // because we don't need it anymore
if (scs.session == NULL) {
break;
} else if (!scs.session->hasSubsessions()) {
env << *rtspClient << "This session has no media subsessions (i.e., no \"m=\" lines)\n";
break;
}

scs.iter = new MediaSubsessionIterator(*scs.session);
setupNextSubsession(rtspClient);
return;
} while (0);

MediaSession createNew函数 主要是initializeWithSDP函数,其中会解析出封装协议及编码方式,后面需要根据此来创建Source。每一个”m=”字段会创建一个MediaSubsession,最后所有MediaSubsession会存放到链表MediaSubsessionIterator里。

setup MediaSubsession

setupNextSubsession(RTSPClient* rtspClient) {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  UsageEnvironment& env = rtspClient->envir(); // alias
StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias

scs.subsession = scs.iter->next();
if (scs.subsession != NULL) {
if (!scs.subsession->initiate()) {
//init失败,setup下个链接
setupNextSubsession(rtspClient); // give up on this subsession; go to the next one
} else {
//init成功,发送SETUP命令,
rtspClient->sendSetupCommand(*scs.subsession, continueAfterSETUP, False, REQUEST_STREAMING_OVER_TCP);
}
return;
}
//全部链接都建立好了,发送PLAY
scs.duration = scs.session->playEndTime() - scs.session->playStartTime();
rtspClient->sendPlayCommand(*scs.session, continueAfterPLAY);
}

PLAY

发送PLAY命令后,等待服务器响应并解析。还是在handleResponseBytes中对PLAY响应有专门的处理handlePLAYResponse,主要是解析各种响应参数
Scale、Speed、Range、RTP-Info,RTP-Info中可以携带RTP包的信息如URL,序号,时间戳,如:

1
2
3
4
5
6
7
unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* responseHandler,
double start, double end, float scale,
Authenticator* authenticator) {
if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
sendDummyUDPPackets(session); // hack to improve NAT traversal
return sendRequest(new RequestRecord(++fCSeq, "PLAY", responseHandler, &session, NULL, 0, start, end, scale));
}

基本流程

初始化和连接

RTSP 客户端首先需要初始化和建立连接。这个过程包括解析 SDP(Session Description Protocol)信息,建立 RTP(Real-Time Transport Protocol)套接字等。

1
2
3
4
5
6
TaskScheduler* scheduler = BasicTaskScheduler::createNew();
env = BasicUsageEnvironment::createNew(*scheduler);
RTSPServer* rtspServer = RTSPServer::createNew(*env, port);
if (rtspServer == nullptr) {
*env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
}
1
2
UsageEnvironment& env = rtspClient->envir();
StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs;

env:获取 RTSP 客户端的环境对象。

scs:获取 RTSP 客户端的状态对象。

SDP 解析

接收到 SDP 信息后,客户端会根据 SDP 信息中的内容来初始化相关的流媒体会话。例如,如果 SDP 中包含 IP 地址,客户端会使用这个地址来建立 RTP 套接字。

GroupSock

GroupSock是Live555对网络接口的封装,支持UDP/TCP,同时也支持单播/组播。

1
udpGroupsock = new Groupsock(*env, udpIP, udpPort, ttl);

通过IP及端口,就可以创建一个GroupSock,liveMedia中的模块就可以通过该socket进行网络数据的读写。

创建Media

1
2
3
4
5
6
7
8
9
if (strcmp(fCodecName, "MP2T") == 0) { // MPEG-2 Transport Stream
fRTPSource = SimpleRTPSource::createNew(env(), fRTPSocket, fRTPPayloadFormat,
fRTPTimestampFrequency, "video/MP2T",
0, False);
fReadSource = MPEG2TransportStreamFramer::createNew(env(), fRTPSource);
}

SimpleRTPSource:根据编码格式创建 RTP 源。
MPEG2TransportStreamFramer:创建帧处理器,处理 RTP 包中的 MPEG-2 传输流。

发送SETUP

1
rtspClient->sendSetupCommand(*scs.subsession, continueAfterSETUP, False, REQUEST_STREAMING_OVER_TCP);

处理服务器响应

1
2
3
4
5
6
7
8
9
10
11
if (streamUsingTCP) { // 使用 TCP 传输
transportTypeStr = "/TCP;unicast";
portTypeStr = ";interleaved";
rtpNumber = fTCPStreamIdCount++;
rtcpNumber = fTCPStreamIdCount++;
} else { // 使用 UDP 传输
transportTypeStr = "/UDP;unicast";
portTypeStr = ";client_port";
rtpNumber = subsession.clientPort().port();
rtcpNumber = subsession.rtcpIsMuxed() ? rtpNumber : rtpNumber + 1;
}

parseTransportParams:解析服务器响应中的传输参数,设置子会话的传输地址和端口。

处理播放和事件循环

播放过程中,客户端会通过事件循环处理 RTP 数据包

1
2
3
4
5
6
void BasicTaskScheduler0::doEventLoop(char volatile* watchVariable) {
while (1) {
if (watchVariable != NULL && *watchVariable != 0) break;
SingleStep();
}
}

doEventLoop:事件循环,通过不断调用 SingleStep 方法来处理不同类型的事件

RTSP 服务器初始化

在Live555中,RTSP服务器由RTSPServer类实现

1
2
3
4
5
6
TaskScheduler* scheduler = BasicTaskScheduler::createNew();
env = BasicUsageEnvironment::createNew(*scheduler);
RTSPServer* rtspServer = RTSPServer::createNew(*env, port);
if (rtspServer == nullptr) {
*env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
}

媒体会话

1
2
ServerMediaSession* sms = ServerMediaSession::createNew(*env, sessionName, sessionDescription, streamDescription);
rtspServer->addServerMediaSession(sms);

RTSP请求的处理由RTSPServer类和RTSPServer::RTSPClientConnection类完成。RTSPServer监听客户端连接,当有新的RTSP请求到达时,会创建一个RTSPServer::RTSPClientConnection对象来处理该请求

RTSP请求的处理由RTSPServer类和RTSPServer::RTSPClientConnection类完成。RTSPServer监听客户端连接,当有新的RTSP请求到达时,会创建一个RTSPServer::RTSPClientConnection对象来处理该请求

1
2
3
4
5
6
7
8
9
void RTSPServer::RTSPClientConnection::handleCmd_OPTIONS() {
snprintf((char*)fResponseBuffer, sizeof(fResponseBuffer),
"RTSP/1.0 200 OK\r\n"
"CSeq: %s\r\n"
"Public: %s\r\n\r\n",
fCurrentCSeq,
"OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER");
send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char const*)fResponseBuffer), 0);
}

媒体数据的实际传输通过RTP(实时传输协议)进行。每个媒体子会话都会创建一个RTPSink对象来发送RTP包。

1
2
RTPSink* sink = RTPSink::createNew(*env, rtpGroupsock, 96);
sink->startPlaying(source, NULL, NULL);

client 发出 teardown请求是 关闭会话并释放资源