当我们使用live555作为流媒体服务器时,某个通道对应的所有客户端断开后, 不能正常回调关闭。
某一通道同时支持视频和音频输出, 即video和audio两个track
VLC和EasyPlayer播放库来中的RTSPClient则都会请求(所以不存在问题);
而某些客户端则只请求了一个track, 比如video;
此时再关闭,会有两种情况:
在VLC或自有播放库来断开连接时,都会正常回调关闭;
在仅请求了video的客户端断开连接时, 一定不会回调关闭;
分析问题:
在RTSPClient断开时,一定会调用RTSPServer::stopTCPStreamingOnSocket, 当流为RTP Over TCP时, 则会调用deleteStreamByTrack;
在deleteStreamByTrack函数中, 是一个for循环,检测fStreamStates的subsession, 因为sdp中存在video和audio, fNumStreamStates的值为2, 而客户端只请求了video,所以在以下的代码中,判断不成立:
Boolean noSubsessionsRemain = True;
for (unsigned i = 0; i < fNumStreamStates; ++i) {
if (fStreamStates[i].subsession != NULL) {
noSubsessionsRemain = False; //因为存在video和audio, 所以此处条件会成立
break;
}
}
因为noSubsessionsRemain为False, 所以下面的代码不会被执行;
if (noSubsessionsRemain) delete this;
顺便说一下, delete this是删除RTSPClientSession, 在ClientSession的析构函数中,会减少引用计数,如果计数为0, 则删除fOurServerMediaSession;
解决问题:
在RTSPClientConnection中增加一个计数变量, 用于记录客户端请求的track个数, 然后在deleteStreamByTrack中递减,如果为0, 则使上面的noSubsessionsRemain为True;
修改如下:
//在RTSPClientConnection的声明中,增加以下两个变量
int clientRequestTrackNum; //for rtp over tcp
char fClientSessionIdStr[16]; //for rtp over udp
void RTSPServer::RTSPClientSession
::handleCmd_SETUP(UsageEnvironment *pEnv, RTSPServer::RTSPClientConnection* ourClientConnection,
char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) {
......
if (streamingMode == RTP_TCP) {
// Note that we'll be streaming over the RTSP TCP connection:
fStreamStates[trackNum].tcpSocketNum = ourClientConnection->fClientOutputSocket;
fOurRTSPServer.noteTCPStreamingOnSocket(fStreamStates[trackNum].tcpSocketNum, this, trackNum);
//此处增加计数
ourClientConnection->clientRequestTrackNum ++;
}
......
}
修改deleteStreamByTrack的声明和实现:
void deleteStreamByTrack(UsageEnvironment *pEnv, unsigned trackNum, Boolean lockFlag, int *clientTrackNum);
void RTSPServer::RTSPClientSession::deleteStreamByTrack(UsageEnvironment *pEnv, unsigned trackNum, Boolean lockFlag, int *clientTrackNum) {
lockClientFlag = lockFlag;
if (trackNum >= fNumStreamStates) return; // sanity check; shouldn't happen
if (fStreamStates[trackNum].subsession != NULL) {
fStreamStates[trackNum].subsession->deleteStream(fOurSessionId, fStreamStates[trackNum].streamToken);
fStreamStates[trackNum].subsession = NULL;
}
// Optimization: If all subsessions have now been deleted, then we can delete ourself now:
Boolean noSubsessionsRemain = True;
for (unsigned i = 0; i < fNumStreamStates; ++i) {
if (fStreamStates[i].subsession != NULL) {
noSubsessionsRemain = False;
break;
}
}
if (NULL!=clientTrackNum)
{
if (*clientTrackNum > 0) *clientTrackNum -= 1;
if (*clientTrackNum == 0) noSubsessionsRemain = True;
}
if (noSubsessionsRemain) delete this;
}