Live555源代码解读(5-4)

 live555 中有两个 streamstate,一个是类 StreamState ,一个是此处的结构 struct streamState。类 SteamState 就是 streamToken,而 struct streamState 中保存了 MediaSubsession (即track) 和类 StreamState 的对应。类 StreamState 代表一个真正流动起来的数据流。这个数据流是从源流到 Sink 。客户端与服务端的一个 rtp 会话中,有两个数据流,服务端是从 XXXFileSouce 流到 RTPSink,而客户端则是从 RTPSource 流到 XXXFileSink 。建立数据流的过程就是把 Source 与 Sink 连接起来。

     为何不把 StreamToken 保存在 MediaSubsession 中呢?看起来在 struct streamState 中是一个 MediaSubsession 对应一个 streamToken 呀? 因为MediaSubsession  代表一个 track 的静态数据,它是可以被其它 rtp 会话重用的。比如不同的用户可能会连接到同一个媒体的同一个 track 。所以 streamToken 与 MediaSubsession 独立存在,只是被 RTSPClientSession 给对应了起来。

streamToken的建立过程存在于函数subsession->getStreamParameters()中,让我们看一下下:

void OnDemandServerMediaSubsession::getStreamParameters(

unsigned clientSessionId,

netAddressBits clientAddress,

Port const& clientRTPPort,

Port const& clientRTCPPort,

int tcpSocketNum,

unsigned char rtpChannelId,

unsigned char rtcpChannelId,

netAddressBits& destinationAddress,

u_int8_t& /*destinationTTL*/,

Boolean& isMulticast,

Port& serverRTPPort,

Port& serverRTCPPort,

void*& streamToken)

{

if (destinationAddress == 0)

destinationAddress = clientAddress;



struct in_addr destinationAddr;

destinationAddr.s_addr = destinationAddress;

isMulticast = False;



//ServerMediaSubsession并没有保存所有基于自己的数据流,而是只记录了最后一次建立的数据流。

//利用这个变量和fReuseFirstSource可以实现多client连接到一个流的形式。

if (fLastStreamToken != NULL && fReuseFirstSource) {

//如果已经基于这个ServerMediaSubsession创建了一个连接,并且希望使用这个连接

//则直接返回这个连接。

// Special case: Rather than creating a new 'StreamState',

// we reuse the one that we've already created:

serverRTPPort = ((StreamState*) fLastStreamToken)->serverRTPPort();

serverRTCPPort = ((StreamState*) fLastStreamToken)->serverRTCPPort();

++((StreamState*) fLastStreamToken)->referenceCount();

streamToken = fLastStreamToken;

} else {

// Normal case: Create a new media source:

unsigned streamBitrate;

FramedSource* mediaSource = createNewStreamSource(clientSessionId,streamBitrate);



// Create 'groupsock' and 'sink' objects for the destination,

// using previously unused server port numbers:

RTPSink* rtpSink;

BasicUDPSink* udpSink;

Groupsock* rtpGroupsock;

Groupsock* rtcpGroupsock;

portNumBits serverPortNum;

if (clientRTCPPort.num() == 0) {

// We're streaming raw UDP (not RTP). Create a single groupsock:

NoReuse dummy; // ensures that we skip over ports that are already in use

for (serverPortNum = fInitialPortNum;; ++serverPortNum) {

struct in_addr dummyAddr;

dummyAddr.s_addr = 0;



serverRTPPort = serverPortNum;

rtpGroupsock = new Groupsock(envir(), dummyAddr, serverRTPPort, 255);

if (rtpGroupsock->socketNum() >= 0)

break; // success

}



rtcpGroupsock = NULL;

rtpSink = NULL;

udpSink = BasicUDPSink::createNew(envir(), rtpGroupsock);

} else {

// Normal case: We're streaming RTP (over UDP or TCP).  Create a pair of

// groupsocks (RTP and RTCP), with adjacent port numbers (RTP port number even):

NoReuse dummy; // ensures that we skip over ports that are already in use

for (portNumBits serverPortNum = fInitialPortNum;; serverPortNum += 2) {

struct in_addr dummyAddr;

dummyAddr.s_addr = 0;



serverRTPPort = serverPortNum;

rtpGroupsock = new Groupsock(envir(), dummyAddr, serverRTPPort, 255);

if (rtpGroupsock->socketNum() < 0) {

delete rtpGroupsock;

continue; // try again

}



serverRTCPPort = serverPortNum + 1;

rtcpGroupsock = new Groupsock(envir(), dummyAddr,serverRTCPPort, 255);

if (rtcpGroupsock->socketNum() < 0) {

delete rtpGroupsock;

delete rtcpGroupsock;

continue; // try again

}



break; // success

}



unsigned char rtpPayloadType = 96 + trackNumber() - 1; // if dynamic

rtpSink = createNewRTPSink(rtpGroupsock, rtpPayloadType,mediaSource);

udpSink = NULL;

}



// Turn off the destinations for each groupsock.  They'll get set later

// (unless TCP is used instead):

if (rtpGroupsock != NULL)

rtpGroupsock->removeAllDestinations();

if (rtcpGroupsock != NULL)

rtcpGroupsock->removeAllDestinations();



if (rtpGroupsock != NULL) {

// Try to use a big send buffer for RTP -  at least 0.1 second of

// specified bandwidth and at least 50 KB

unsigned rtpBufSize = streamBitrate * 25 / 2; // 1 kbps * 0.1 s = 12.5 bytes

if (rtpBufSize < 50 * 1024)

rtpBufSize = 50 * 1024;

increaseSendBufferTo(envir(), rtpGroupsock->socketNum(),rtpBufSize);

}



// Set up the state of the stream.  The stream will get started later:

streamToken = fLastStreamToken = new StreamState(*this, serverRTPPort,

serverRTCPPort, rtpSink, udpSink, streamBitrate, mediaSource,

rtpGroupsock, rtcpGroupsock);

}



// Record these destinations as being for this client session id:

Destinations* destinations;

if (tcpSocketNum < 0) { // UDP

destinations = new Destinations(destinationAddr, clientRTPPort, clientRTCPPort);

} else { // TCP

destinations = new Destinations(tcpSocketNum, rtpChannelId, rtcpChannelId);

}

//记录下所有clientSessionID对应的目的rtp/rtcp地址,是因为现在不能把目的rtp,rtcp地址加入到

//server端rtp的groupSocket中。试想在ReuseFirstSource时,这样会引起client端立即收到rtp数据。

//其次,也可以利用这个hash table找出client的rtp/rtcp端口等信息,好像其它地方还真没有可以保存的

//RTSPClientSession中的streamstates在ReuseFirstSource时也不能准确找出client端的端口等信息。

fDestinationsHashTable->Add((char const*) clientSessionId, destinations);

}

流程不复杂:如果需要重用上一次建立的流,就利用之(这样就可以实现一rtp server对应多个rtp client的形式);如果不需要,则创建合适的source,然后创建rtp sink,然后利用它们创建streamSoken。

启动一个流:当RTSPClientSession收到PLAY请求时,就开始传输RTP数据。下面看一下流启动的代码:

void RTSPServer::RTSPClientSession::handleCmd_PLAY(

ServerMediaSubsession* subsession,

char const* cseq,

char const* fullRequestStr)

{

char* rtspURL = fOurServer.rtspURL(fOurServerMediaSession,fClientInputSocket);

unsigned rtspURLSize = strlen(rtspURL);



// Parse the client's "Scale:" header, if any:

float scale;

Boolean sawScaleHeader = parseScaleHeader(fullRequestStr, scale);



// Try to set the stream's scale factor to this value:

if (subsession == NULL /*aggregate op*/) {

fOurServerMediaSession->testScaleFactor(scale);

} else {

subsession->testScaleFactor(scale);

}



char buf[100];

char* scaleHeader;

if (!sawScaleHeader) {

buf[0] = '\0'; // Because we didn't see a Scale: header, don't send one back

} else {

sprintf(buf, "Scale: %f\r\n", scale);

}

scaleHeader = strDup(buf);



//分析客户端对于播放范围的要求

// Parse the client's "Range:" header, if any:

double rangeStart = 0.0, rangeEnd = 0.0;

Boolean sawRangeHeader = parseRangeHeader(fullRequestStr, rangeStart,rangeEnd);



// Use this information, plus the stream's duration (if known), to create

// our own "Range:" header, for the response:

float duration = subsession == NULL /*aggregate op*/

? fOurServerMediaSession->duration() : subsession->duration();

if (duration < 0.0) {

// We're an aggregate PLAY, but the subsessions have different durations.

// Use the largest of these durations in our header

duration = -duration;

}



// Make sure that "rangeStart" and "rangeEnd" (from the client's "Range:" header) have sane values

// before we send back our own "Range:" header in our response:

if (rangeStart < 0.0)

rangeStart = 0.0;

else if (rangeStart > duration)

rangeStart = duration;

if (rangeEnd < 0.0)

rangeEnd = 0.0;

else if (rangeEnd > duration)

rangeEnd = duration;

if ((scale > 0.0 && rangeStart > rangeEnd && rangeEnd > 0.0)

|| (scale < 0.0 && rangeStart < rangeEnd)) {

// "rangeStart" and "rangeEnd" were the wrong way around; swap them:

double tmp = rangeStart;

rangeStart = rangeEnd;

rangeEnd = tmp;

}



char* rangeHeader;

if (!sawRangeHeader) {

buf[0] = '\0'; // Because we didn't see a Range: header, don't send one back

} else if (rangeEnd == 0.0 && scale >= 0.0) {

sprintf(buf, "Range: npt=%.3f-\r\n", rangeStart);

} else {

sprintf(buf, "Range: npt=%.3f-%.3f\r\n", rangeStart, rangeEnd);

}

rangeHeader = strDup(buf);



// Create a "RTP-Info:" line.  It will get filled in from each subsession's state:

char const* rtpInfoFmt = "%s" // "RTP-Info:", plus any preceding rtpInfo items

"%s"// comma separator, if needed

"url=%s/%s"

";seq=%d"

";rtptime=%u";

unsigned rtpInfoFmtSize = strlen(rtpInfoFmt);

char* rtpInfo = strDup("RTP-Info: ");

unsigned i, numRTPInfoItems = 0;



// Do any required seeking/scaling on each subsession, before starting streaming:

for (i = 0; i < fNumStreamStates; ++i) {

if (subsession == NULL /* means: aggregated operation */

|| subsession == fStreamStates[i].subsession) {

if (sawScaleHeader) {

fStreamStates[i].subsession->setStreamScale(fOurSessionId,

fStreamStates[i].streamToken, scale);

}

if (sawRangeHeader) {

double streamDuration = 0.0; // by default; means: stream until the end of the media

if (rangeEnd > 0.0 && (rangeEnd + 0.001) < duration) { // the 0.001 is because we limited the values to 3 decimal places

// We want the stream to end early.  Set the duration we want:

streamDuration = rangeEnd - rangeStart;

if (streamDuration < 0.0)

streamDuration = -streamDuration; // should happen only if scale < 0.0

}

u_int64_t numBytes;

fStreamStates[i].subsession->seekStream(fOurSessionId,

fStreamStates[i].streamToken, rangeStart,

streamDuration, numBytes);

}

}

}



// Now, start streaming:

for (i = 0; i < fNumStreamStates; ++i) {

if (subsession == NULL /* means: aggregated operation */

|| subsession == fStreamStates[i].subsession) {

unsigned short rtpSeqNum = 0;

unsigned rtpTimestamp = 0;

//启动流

fStreamStates[i].subsession->startStream(fOurSessionId,

fStreamStates[i].streamToken,

(TaskFunc*) noteClientLiveness, this, rtpSeqNum,

rtpTimestamp, handleAlternativeRequestByte, this);

const char *urlSuffix = fStreamStates[i].subsession->trackId();

char* prevRTPInfo = rtpInfo;

unsigned rtpInfoSize = rtpInfoFmtSize + strlen(prevRTPInfo) + 1

+ rtspURLSize + strlen(urlSuffix) + 5 /*max unsigned short len*/

+ 10 /*max unsigned (32-bit) len*/

+ 2 /*allows for trailing \r\n at final end of string*/;

rtpInfo = new char[rtpInfoSize];

sprintf(rtpInfo, rtpInfoFmt, prevRTPInfo,

numRTPInfoItems++ == 0 ? "" : ",", rtspURL, urlSuffix,

rtpSeqNum, rtpTimestamp);

delete[] prevRTPInfo;

}

}

if (numRTPInfoItems == 0) {

rtpInfo[0] = '\0';

} else {

unsigned rtpInfoLen = strlen(rtpInfo);

rtpInfo[rtpInfoLen] = '\r';

rtpInfo[rtpInfoLen + 1] = '\n';

rtpInfo[rtpInfoLen + 2] = '\0';

}



// Fill in the response:

snprintf((char*) fResponseBuffer, sizeof fResponseBuffer,

"RTSP/1.0 200 OK\r\n"

"CSeq: %s\r\n"

"%s"

"%s"

"%s"

"Session: %08X\r\n"

"%s\r\n", cseq, dateHeader(), scaleHeader, rangeHeader,

fOurSessionId, rtpInfo);

delete[] rtpInfo;

delete[] rangeHeader;

delete[] scaleHeader;

delete[] rtspURL;

}


有个问题,如果这个streamToken使用的是已存在的(还记得ReuseFirstSource吗),为它再次调用startStream()时,究竟会做什么呢?你猜啊!呵呵我猜吧:应该是把这个客户端的地址和rtp/rtcp端口传给rtp server的groupSocket,rtp server自然就开始向这个客户端发送数据了。是不是这样尼?看代码吧:

void StreamState::startPlaying(

Destinations* dests,

TaskFunc* rtcpRRHandler,

void* rtcpRRHandlerClientData,

ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,

void* serverRequestAlternativeByteHandlerClientData) 

{

//目标ip address rtp && rtcp port

if (dests == NULL)

return;



//创建RTCPInstance对象,以计算和收发RTCP包

if (fRTCPInstance == NULL && fRTPSink != NULL) {

// Create (and start) a 'RTCP instance' for this RTP sink:

fRTCPInstance = RTCPInstance::createNew(fRTPSink->envir(), fRTCPgs,

fTotalBW, (unsigned char*) fMaster.fCNAME, fRTPSink,

NULL /* we're a server */);

// Note: This starts RTCP running automatically

}



if (dests->isTCP) {

//如果RTP over TCP

// Change RTP and RTCP to use the TCP socket instead of UDP:

if (fRTPSink != NULL) {

fRTPSink->addStreamSocket(dests->tcpSocketNum, dests->rtpChannelId);

fRTPSink->setServerRequestAlternativeByteHandler(

dests->tcpSocketNum, serverRequestAlternativeByteHandler,

serverRequestAlternativeByteHandlerClientData);

}

if (fRTCPInstance != NULL) {

fRTCPInstance->addStreamSocket(dests->tcpSocketNum,

dests->rtcpChannelId);

fRTCPInstance->setSpecificRRHandler(dests->tcpSocketNum,

dests->rtcpChannelId, rtcpRRHandler,

rtcpRRHandlerClientData);

}

} else {

//向RTP和RTCP的groupsocket增加这个目标

// Tell the RTP and RTCP 'groupsocks' about this destination

// (in case they don't already have it):

if (fRTPgs != NULL)

fRTPgs->addDestination(dests->addr, dests->rtpPort);

if (fRTCPgs != NULL)

fRTCPgs->addDestination(dests->addr, dests->rtcpPort);

if (fRTCPInstance != NULL) {

fRTCPInstance->setSpecificRRHandler(dests->addr.s_addr,

dests->rtcpPort, rtcpRRHandler, rtcpRRHandlerClientData);

}

}



if (!fAreCurrentlyPlaying && fMediaSource != NULL) {

//如果还没有启动传输,现在启动之。

if (fRTPSink != NULL) {

fRTPSink->startPlaying(*fMediaSource, afterPlayingStreamState,

this);

fAreCurrentlyPlaying = True;

} else if (fUDPSink != NULL) {

fUDPSink->startPlaying(*fMediaSource, afterPlayingStreamState,this);

fAreCurrentlyPlaying = True;

}

}

}


转载于:https://my.oschina.net/seanx/blog/621684

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Live555是一款开源的多媒体流服务器,支持实时流媒体的传输。下面是部分Live555源代码的分析: 1. MediaSession类 MediaSession类是Live555中最核心的类之一,它表示了一个媒体会话,包括了媒体流的传输协议、媒体编码格式、传输方式等信息。MediaSession类中的成员变量包括: - fServerMediaSession:代表了一个媒体服务器会话,负责提供媒体流的传输。 - fSubsessions:代表了一个或多个媒体流的传输会话,可以是RTP/RTCP、HTTP/RTSP等协议。 - fSdpLines:代表了SDP协议中的信息,可以是媒体流的编码格式、传输方式等信息。 MediaSession类中的核心方法包括: - createNew:用于创建一个新的媒体会话。 - addSubsession:用于添加一个媒体流的传输会话。 - generateSdpDescription:用于生成SDP协议描述信息。 - startStream:用于开始媒体流的传输。 - pauseStream:用于暂停媒体流的传输。 - seekStream:用于跳转媒体流的传输。 2. MediaSubsession类 MediaSubsession类表示了一个媒体流的传输会话,包括了媒体流的传输协议、媒体编码格式、传输方式等信息。MediaSubsession类中的成员变量包括: - fParentSession:代表了父级MediaSession类的实例。 - fRTPSink:代表了RTP数据的发送器。 - fRTCPInstance:代表了RTCP数据的发送器。 - fTrackNumber:代表了媒体流的轨道编号。 - fCodecName:代表了媒体流的编码格式。 - fMediumName:代表了媒体流的传输方式。 MediaSubsession类中的核心方法包括: - initiate:用于初始化媒体流的传输。 - startStream:用于开始媒体流的传输。 - pauseStream:用于暂停媒体流的传输。 - seekStream:用于跳转媒体流的传输。 3. RTSPServer类 RTSPServer类是Live555中实现RTSP协议的服务器类。RTSPServer类中的成员变量包括: - fServerMediaSession:代表了一个媒体服务器会话,负责提供媒体流的传输。 - fHTTPServerPort:代表了HTTP服务器的端口号。 - fRTSPServerPort:代表了RTSP服务器的端口号。 RTSPServer类中的核心方法包括: - createNew:用于创建一个新的RTSP服务器。 - start:用于启动RTSP服务器。 - stop:用于停止RTSP服务器。 - incomingConnectionHandler:用于处理RTSP客户端的连接请求。 - handleCmd_DESCRIBE:用于处理DESCRIBE命令。 - handleCmd_SETUP:用于处理SETUP命令。 - handleCmd_PLAY:用于处理PLAY命令。 - handleCmd_PAUSE:用于处理PAUSE命令。 - handleCmd_TEARDOWN:用于处理TEARDOWN命令。 以上是Live555源代码的部分分析,希望对你有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值