Пример #1
0
Boolean Groupsock::handleRead(unsigned char* buffer, unsigned bufferMaxSize,
			      unsigned& bytesRead,
			      struct sockaddr_in& fromAddress) {
  // Read data from the socket, and relay it across any attached tunnels
  //##### later make this code more general - independent of tunnels

  bytesRead = 0;

  int maxBytesToRead = bufferMaxSize - TunnelEncapsulationTrailerMaxSize;
  int numBytes = readSocket(env(), socketNum(),
			    buffer, maxBytesToRead, fromAddress);
  if (numBytes < 0) {
    if (DebugLevel >= 0) { // this is a fatal error
      env().setResultMsg("Groupsock read failed: ",
			 env().getResultMsg());
    }
    return False;
  }

  // If we're a SSM group, make sure the source address matches:
  if (isSSM()
      && fromAddress.sin_addr.s_addr != sourceFilterAddress().s_addr) {
    return True;
  }

  // We'll handle this data.
  // Also write it (with the encapsulation trailer) to each member,
  // unless the packet was originally sent by us to begin with.
  bytesRead = numBytes;

  int numMembers = 0;
  if (!wasLoopedBackFromUs(env(), fromAddress)) {
    statsIncoming.countPacket(numBytes);
    statsGroupIncoming.countPacket(numBytes);
    numMembers =
      outputToAllMembersExcept(NULL, ttl(),
			       buffer, bytesRead,
			       fromAddress.sin_addr.s_addr);
    if (numMembers > 0) {
      statsRelayedIncoming.countPacket(numBytes);
      statsGroupRelayedIncoming.countPacket(numBytes);
    }
  }
  if (DebugLevel >= 3) {
    env() << *this << ": read " << bytesRead << " bytes from ";
    env() << our_inet_ntoa(fromAddress.sin_addr);
    if (numMembers > 0) {
      env() << "; relayed to " << numMembers << " members";
    }
    env() << "\n";
  }

  return True;
}
Пример #2
0
Groupsock::~Groupsock() {
  if (isSSM()) {
    if (!socketLeaveGroupSSM(env(), socketNum(), groupAddress().s_addr,
			     sourceFilterAddress().s_addr)) {
      socketLeaveGroup(env(), socketNum(), groupAddress().s_addr);
    }
  } else {
    socketLeaveGroup(env(), socketNum(), groupAddress().s_addr);
  }

  delete fDests;

  if (DebugLevel >= 2) env() << *this << ": deleting\n";
}
Пример #3
0
void MediaSubsession::setDestinations(netAddressBits defaultDestAddress) {
  // Get the destination address from the connection endpoint name
  // (This will be 0 if it's not known, in which case we use the default)
  netAddressBits destAddress = connectionEndpointAddress();
  if (destAddress == 0) destAddress = defaultDestAddress;
  struct in_addr destAddr; destAddr.s_addr = destAddress;

  // The destination TTL remains unchanged:
  int destTTL = ~0; // means: don't change

  if (fRTPSocket != NULL) {
    Port destPort(serverPortNum);
    fRTPSocket->changeDestinationParameters(destAddr, destPort, destTTL);
  }
  if (fRTCPSocket != NULL && !isSSM()) {
    // Note: For SSM sessions, the dest address for RTCP was already set.
    Port destPort(serverPortNum+1);
    fRTCPSocket->changeDestinationParameters(destAddr, destPort, destTTL);
  }
}
Пример #4
0
int Groupsock::outputToAllMembersExcept(DirectedNetInterface* exceptInterface,
					u_int8_t ttlToFwd,
					unsigned char* data, unsigned size,
					netAddressBits sourceAddr) {
  // Don't forward TTL-0 packets
  if (ttlToFwd == 0) return 0;

  DirectedNetInterfaceSet::Iterator iter(members());
  unsigned numMembers = 0;
  DirectedNetInterface* interf;
  while ((interf = iter.next()) != NULL) {
    // Check whether we've asked to exclude this interface:
    if (interf == exceptInterface)
      continue;

    // Check that the packet's source address makes it OK to
    // be relayed across this interface:
    UsageEnvironment& saveEnv = env();
    // because the following call may delete "this"
    if (!interf->SourceAddrOKForRelaying(saveEnv, sourceAddr)) {
      if (strcmp(saveEnv.getResultMsg(), "") != 0) {
				// Treat this as a fatal error
	return -1;
      } else {
	continue;
      }
    }

    if (numMembers == 0) {
      // We know that we're going to forward to at least one
      // member, so fill in the tunnel encapsulation trailer.
      // (Note: Allow for it not being 4-byte-aligned.)
      TunnelEncapsulationTrailer* trailerInPacket
	= (TunnelEncapsulationTrailer*)&data[size];
      TunnelEncapsulationTrailer* trailer;

      Boolean misaligned = ((unsigned long)trailerInPacket & 3) != 0;
      unsigned trailerOffset;
      u_int8_t tunnelCmd;
      if (isSSM()) {
	// add an 'auxilliary address' before the trailer
	trailerOffset = TunnelEncapsulationTrailerAuxSize;
	tunnelCmd = TunnelDataAuxCmd;
      } else {
	trailerOffset = 0;
	tunnelCmd = TunnelDataCmd;
      }
      unsigned trailerSize = TunnelEncapsulationTrailerSize + trailerOffset;
      unsigned tmpTr[TunnelEncapsulationTrailerMaxSize];
      if (misaligned) {
	trailer = (TunnelEncapsulationTrailer*)&tmpTr;
      } else {
	trailer = trailerInPacket;
      }
      trailer += trailerOffset;

      if (fDests != NULL) {
	trailer->address() = fDests->fGroupEId.groupAddress().s_addr;
	trailer->port() = fDests->fPort; // structure copy, outputs in network order
      }
      trailer->ttl() = ttlToFwd;
      trailer->command() = tunnelCmd;

      if (isSSM()) {
	trailer->auxAddress() = sourceFilterAddress().s_addr;
      }

      if (misaligned) {
	memmove(trailerInPacket, trailer-trailerOffset, trailerSize);
      }

      size += trailerSize;
    }

    interf->write(data, size);
    ++numMembers;
  }

  return numMembers;
}
Пример #5
0
Boolean MediaSubsession::initiate(int useSpecialRTPoffset) {
  if (fReadSource != NULL) return True; // has already been initiated

  do {
    if (fCodecName == NULL) {
      env().setResultMsg("Codec is unspecified");
      break;
    }

    // Create RTP and RTCP 'Groupsocks' on which to receive incoming data.
    // (Groupsocks will work even for unicast addresses)
    struct in_addr tempAddr;
    tempAddr.s_addr = connectionEndpointAddress();
        // This could get changed later, as a result of a RTSP "SETUP"

    if (fClientPortNum != 0) {
      // The sockets' port numbers were specified for us.  Use these:
      fClientPortNum = fClientPortNum&~1; // even
      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;
      }
      
      // 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);
      }
      if (fRTCPSocket == NULL) {
	char tmpBuf[100];
	sprintf(tmpBuf, "Failed to create RTCP socket (port %d)", rtcpPortNum);
	env().setResultMsg(tmpBuf);
	break;
      }
    } else {
      // Port numbers were not specified in advance, so we use ephemeral port numbers.
      // Create sockets until we get a port-number pair (even: RTP; even+1: RTCP).
      // We need to make sure that we don't keep trying to use the same bad port numbers over and over again.
      // so we store bad sockets in a table, and delete them all when we're done.
      HashTable* socketHashTable = HashTable::create(ONE_WORD_HASH_KEYS);
      if (socketHashTable == NULL) break;
      Boolean success = False;

      while (1) {
	// Create a new socket:
	if (isSSM()) {
	  fRTPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr, 0);
	} else {
	  fRTPSocket = new Groupsock(env(), tempAddr, 0, 255);
	}
	if (fRTPSocket == NULL) {
	  env().setResultMsg("MediaSession::initiate(): unable to create RTP and RTCP sockets");
	  break;
	}

	// Get the client port number, and check whether it's even (for RTP):
	Port clientPort(0);
	if (!getSourcePort(env(), fRTPSocket->socketNum(), clientPort)) {
	  break;
	}
	fClientPortNum = ntohs(clientPort.num()); 
	if ((fClientPortNum&1) != 0) { // it's odd
	  // Record this socket in our table, and keep trying:
	  unsigned key = (unsigned)fClientPortNum;
	  socketHashTable->Add((char const*)key, fRTPSocket);
	  continue;
	}

	// Make sure we can use the next (i.e., odd) port number, for RTCP:
	portNumBits rtcpPortNum = fClientPortNum|1;
	if (isSSM()) {
	  fRTCPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr, rtcpPortNum);
	} else {
	  fRTCPSocket = new Groupsock(env(), tempAddr, rtcpPortNum, 255);
	}
	if (fRTCPSocket != NULL) {
	  // Success! Use these two sockets (and delete any others that we've created):
	  Groupsock* oldGS;
	  while ((oldGS = (Groupsock*)socketHashTable->RemoveNext()) != NULL) {
	    delete oldGS;
	  }
	  delete socketHashTable;
	  success = True;
	  break;
	} else {
	  // We couldn't create the RTCP socket (perhaps that port number's already in use elsewhere?).
	  // Record the first socket in our table, and keep trying:
	  unsigned key = (unsigned)fClientPortNum;
	  socketHashTable->Add((char const*)key, fRTPSocket);
	  continue;
	}
      }
      if (!success) break; // a fatal error occurred trying to create the RTP and RTCP sockets; we can't continue
    }

    // ASSERT: fRTPSocket != NULL && fRTCPSocket != NULL
    if (isSSM()) {
      // Special case for RTCP SSM: Send RTCP packets back to the source via unicast:
      fRTCPSocket->changeDestinationParameters(fSourceFilterAddr,0,~0);
    }

	//////////////////////////////////////////////////////////////////////////
	// 裁剪掉不需要的Source.

    // Check "fProtocolName"
    if (strcmp(fProtocolName, "UDP") == 0) {

#ifndef CUT_MIN_SIZE
      // A UDP-packetized stream (*not* a RTP stream)
      fReadSource = BasicUDPSource::createNew(env(), fRTPSocket);
      fRTPSource = NULL; // Note!

      if (strcmp(fCodecName, "MP2T") == 0) { // MPEG-2 Transport Stream
	fReadSource = MPEG2TransportStreamFramer::createNew(env(), fReadSource);
	    // this sets "durationInMicroseconds" correctly, based on the PCR values
      }
#endif

    } else {
      // Check "fCodecName" against the set of codecs that we support,
      // and create our RTP source accordingly
      // (Later make this code more efficient, as this set grows #####)
      // (Also, add more fmts that can be implemented by SimpleRTPSource#####)
      Boolean createSimpleRTPSource = False;
      Boolean doNormalMBitRule = False; // used if "createSimpleRTPSource"

	  if (strcmp(fCodecName, "H264") == 0) {
		  fReadSource = fRTPSource
			  = H264VideoRTPSource::createNew(env(), fRTPSocket,
			  fRTPPayloadFormat,
			  fRTPTimestampFrequency);
	  }  
	  else if (strcmp(fCodecName, "MP4V-ES") == 0) 
	  { // MPEG-4 Elem Str vid
		  fReadSource = fRTPSource
			  = MPEG4ESVideoRTPSource::createNew(env(), fRTPSocket,
			  fRTPPayloadFormat,
			  fRTPTimestampFrequency);
	  }
#ifndef CUT_MIN_SIZE	  
	  else if (strcmp(fCodecName, "QCELP") == 0) { // QCELP audio
	fReadSource =
	  QCELPAudioRTPSource::createNew(env(), fRTPSocket, fRTPSource,
					 fRTPPayloadFormat,
					 fRTPTimestampFrequency);
	// Note that fReadSource will differ from fRTPSource in this case
      } else if (strcmp(fCodecName, "AMR") == 0) { // AMR audio (narrowband)
	fReadSource =
	  AMRAudioRTPSource::createNew(env(), fRTPSocket, fRTPSource,
				       fRTPPayloadFormat, 0 /*isWideband*/,
				       fNumChannels, fOctetalign, fInterleaving,
				       fRobustsorting, fCRC);
	// Note that fReadSource will differ from fRTPSource in this case
      } else if (strcmp(fCodecName, "AMR-WB") == 0) { // AMR audio (wideband)
	fReadSource =
	  AMRAudioRTPSource::createNew(env(), fRTPSocket, fRTPSource,
				       fRTPPayloadFormat, 1 /*isWideband*/,
				       fNumChannels, fOctetalign, fInterleaving,
				       fRobustsorting, fCRC);
	// Note that fReadSource will differ from fRTPSource in this case
      } else if (strcmp(fCodecName, "MPA") == 0) { // MPEG-1 or 2 audio
	fReadSource = fRTPSource
	  = MPEG1or2AudioRTPSource::createNew(env(), fRTPSocket,
					      fRTPPayloadFormat,
					      fRTPTimestampFrequency);
      } else if (strcmp(fCodecName, "MPA-ROBUST") == 0) { // robust MP3 audio
	fRTPSource
	  = MP3ADURTPSource::createNew(env(), fRTPSocket, fRTPPayloadFormat,
				       fRTPTimestampFrequency);
	if (fRTPSource == NULL) break;

	// Add a filter that deinterleaves the ADUs after depacketizing them:
	MP3ADUdeinterleaver* deinterleaver
	  = MP3ADUdeinterleaver::createNew(env(), fRTPSource);
	if (deinterleaver == NULL) break;

	// Add another filter that converts these ADUs to MP3 frames:
	fReadSource = MP3FromADUSource::createNew(env(), deinterleaver);
      } else if (strcmp(fCodecName, "X-MP3-DRAFT-00") == 0) {
	// a non-standard variant of "MPA-ROBUST" used by RealNetworks
	// (one 'ADU'ized MP3 frame per packet; no headers)
	fRTPSource
	  = SimpleRTPSource::createNew(env(), fRTPSocket, fRTPPayloadFormat,
				       fRTPTimestampFrequency,
				       "audio/MPA-ROBUST" /*hack*/);
	if (fRTPSource == NULL) break;

	// Add a filter that converts these ADUs to MP3 frames:
	fReadSource = MP3FromADUSource::createNew(env(), fRTPSource,
						  False /*no ADU header*/);
      } else if (strcmp(fCodecName, "MP4A-LATM") == 0) { // MPEG-4 LATM audio
	fReadSource = fRTPSource
	  = MPEG4LATMAudioRTPSource::createNew(env(), fRTPSocket,
					       fRTPPayloadFormat,
					       fRTPTimestampFrequency);
      } else if (strcmp(fCodecName, "AC3") == 0) { // AC3 audio
	fReadSource = fRTPSource
	  = AC3AudioRTPSource::createNew(env(), fRTPSocket,
					 fRTPPayloadFormat,
					 fRTPTimestampFrequency);
      } else if (strcmp(fCodecName, "MPEG4-GENERIC") == 0) {
	fReadSource = fRTPSource
	  = MPEG4GenericRTPSource::createNew(env(), fRTPSocket,
					     fRTPPayloadFormat,
					     fRTPTimestampFrequency,
					     fMediumName, fMode,
					     fSizelength, fIndexlength,
					     fIndexdeltalength);
      } else if (strcmp(fCodecName, "MPV") == 0) { // MPEG-1 or 2 video
	fReadSource = fRTPSource
	  = MPEG1or2VideoRTPSource::createNew(env(), fRTPSocket,
					      fRTPPayloadFormat,
					      fRTPTimestampFrequency);
      } else 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);
	    // this sets "durationInMicroseconds" correctly, based on the PCR values
      } else if (strcmp(fCodecName, "H261") == 0) { // H.261
	fReadSource = fRTPSource
	  = H261VideoRTPSource::createNew(env(), fRTPSocket,
					  fRTPPayloadFormat,
					  fRTPTimestampFrequency);
      } else if (strcmp(fCodecName, "H263-1998") == 0 ||
		 strcmp(fCodecName, "H263-2000") == 0) { // H.263+
	fReadSource = fRTPSource
	  = H263plusVideoRTPSource::createNew(env(), fRTPSocket,
					      fRTPPayloadFormat,
					      fRTPTimestampFrequency);
      } else if (strcmp(fCodecName, "JPEG") == 0) { // motion JPEG
	fReadSource = fRTPSource
	  = JPEGVideoRTPSource::createNew(env(), fRTPSocket,
					  fRTPPayloadFormat,
					  fRTPTimestampFrequency,
					  videoWidth(),
					  videoHeight());
      } else if (strcmp(fCodecName, "X-QT") == 0
		 || strcmp(fCodecName, "X-QUICKTIME") == 0) {
	// Generic QuickTime streams, as defined in
	// <http://developer.apple.com/quicktime/icefloe/dispatch026.html>
	char* mimeType
	  = new char[strlen(mediumName()) + strlen(codecName()) + 2] ;
	sprintf(mimeType, "%s/%s", mediumName(), codecName());
	fReadSource = fRTPSource
	  = QuickTimeGenericRTPSource::createNew(env(), fRTPSocket,
						 fRTPPayloadFormat,
						 fRTPTimestampFrequency,
						 mimeType);
	delete[] mimeType;
#ifdef SUPPORT_REAL_RTSP
      } else if (strcmp(fCodecName, "X-PN-REALAUDIO") == 0 ||
		 strcmp(fCodecName, "X-PN-MULTIRATE-REALAUDIO-LIVE") == 0 ||
		 strcmp(fCodecName, "X-PN-REALVIDEO") == 0 ||
		 strcmp(fCodecName, "X-PN-MULTIRATE-REALVIDEO-LIVE") == 0) {
	// A RealNetworks 'RDT' stream (*not* a RTP stream)
	fReadSource = RealRDTSource::createNew(env());
	fRTPSource = NULL; // Note!
	parentSession().isRealNetworksRDT = True;
#endif
      } 
#endif	// CUT_MIN_SIZE

	  else if (  strcmp(fCodecName, "PCMU") == 0 // PCM u-law audio
		   || strcmp(fCodecName, "GSM") == 0 // GSM audio
		   || strcmp(fCodecName, "PCMA") == 0 // PCM a-law audio
		   || strcmp(fCodecName, "L16") == 0 // 16-bit linear audio
		   || strcmp(fCodecName, "MP1S") == 0 // MPEG-1 System Stream
		   || strcmp(fCodecName, "MP2P") == 0 // MPEG-2 Program Stream
		   || strcmp(fCodecName, "L8") == 0 // 8-bit linear audio
		   || strcmp(fCodecName, "G726-16") == 0 // G.726, 16 kbps
		   || strcmp(fCodecName, "G726-24") == 0 // G.726, 24 kbps
		   || strcmp(fCodecName, "G726-32") == 0 // G.726, 32 kbps
		   || strcmp(fCodecName, "G726-40") == 0 // G.726, 40 kbps
		   || strcmp(fCodecName, "SPEEX") == 0 // SPEEX audio
		   ) {
	createSimpleRTPSource = True;
	useSpecialRTPoffset = 0;
      } else if (useSpecialRTPoffset >= 0) {
	// We don't know this RTP payload format, but try to receive
	// it using a 'SimpleRTPSource' with the specified header offset:
	createSimpleRTPSource = True;
      } else {
	env().setResultMsg("RTP payload format unknown or not supported");
	break;
      }

      if (createSimpleRTPSource) {
	char* mimeType
	  = new char[strlen(mediumName()) + strlen(codecName()) + 2] ;
	sprintf(mimeType, "%s/%s", mediumName(), codecName());
	fReadSource = fRTPSource
	  = SimpleRTPSource::createNew(env(), fRTPSocket, fRTPPayloadFormat,
				       fRTPTimestampFrequency, mimeType,
				       (unsigned)useSpecialRTPoffset,
				       doNormalMBitRule);
	delete[] mimeType;
      }
    }

    if (fReadSource == NULL) {
      env().setResultMsg("Failed to create read source");
      break;
    }

    // Finally, create our RTCP instance. (It starts running automatically)
    if (fRTPSource != NULL) {
      unsigned totSessionBandwidth = 500; // HACK - later get from SDP#####
      fRTCPInstance = RTCPInstance::createNew(env(), fRTCPSocket,
					      totSessionBandwidth,
					      (unsigned char const*)
					      fParent.CNAME(),
					      NULL /* we're a client */,
					      fRTPSource);
      if (fRTCPInstance == NULL) {
	env().setResultMsg("Failed to create RTCP instance");
	break;
      }
    }

    return True;
  } while (0);

  delete fRTPSocket; fRTPSocket = NULL;
  delete fRTCPSocket; fRTCPSocket = NULL;
  Medium::close(fRTCPInstance); fRTCPInstance = NULL;
  Medium::close(fReadSource); fReadSource = fRTPSource = NULL;
  fClientPortNum = 0;
  return False;
}
Пример #6
0
Boolean MediaSubsession::initiate(int useSpecialRTPoffset) {
  if (fReadSource != NULL) return True; // has already been initiated

  do {
    if (fCodecName == NULL) {
      env().setResultMsg("Codec is unspecified");
      break;
    }

    // Create RTP and RTCP 'Groupsocks' on which to receive incoming data.
    // (Groupsocks will work even for unicast addresses)
    struct in_addr tempAddr;
    tempAddr.s_addr = connectionEndpointAddress();
        // This could get changed later, as a result of a RTSP "SETUP"

    if (fClientPortNum != 0) {
      // The sockets' port numbers were specified for us.  Use these:
      Boolean const protocolIsRTP = strcmp(fProtocolName, "RTP") == 0;
      if (protocolIsRTP) {
	fClientPortNum = fClientPortNum&~1; // use an even-numbered port for RTP, and the next (odd-numbered) port for RTCP
      }
      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) {
	// 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);
	}
      }
    } else {
      // Port numbers were not specified in advance, so we use ephemeral port numbers.
      // Create sockets until we get a port-number pair (even: RTP; even+1: RTCP).
      // We need to make sure that we don't keep trying to use the same bad port numbers over and over again.
      // so we store bad sockets in a table, and delete them all when we're done.
      HashTable* socketHashTable = HashTable::create(ONE_WORD_HASH_KEYS);
      if (socketHashTable == NULL) break;
      Boolean success = False;
      NoReuse dummy(env()); // ensures that our new ephemeral port number won't be one that's already in use

      while (1) {
	// Create a new socket:
	if (isSSM()) {
	  fRTPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr, 0);
	} else {
	  fRTPSocket = new Groupsock(env(), tempAddr, 0, 255);
	}
	if (fRTPSocket == NULL) {
	  env().setResultMsg("MediaSession::initiate(): unable to create RTP and RTCP sockets");
	  break;
	}

	// Get the client port number, and check whether it's even (for RTP):
	Port clientPort(0);
	if (!getSourcePort(env(), fRTPSocket->socketNum(), clientPort)) {
	  break;
	}
	fClientPortNum = ntohs(clientPort.num()); 
	if ((fClientPortNum&1) != 0) { // it's odd
	  // Record this socket in our table, and keep trying:
	  unsigned key = (unsigned)fClientPortNum;
	  Groupsock* existing = (Groupsock*)socketHashTable->Add((char const*)key, fRTPSocket);
	  delete existing; // in case it wasn't NULL
	  continue;
	}

	// Make sure we can use the next (i.e., odd) port number, for RTCP:
	portNumBits rtcpPortNum = fClientPortNum|1;
	if (isSSM()) {
	  fRTCPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr, rtcpPortNum);
	} else {
	  fRTCPSocket = new Groupsock(env(), tempAddr, rtcpPortNum, 255);
	}
	if (fRTCPSocket != NULL && fRTCPSocket->socketNum() >= 0) {
	  // Success! Use these two sockets.
	  success = True;
	  break;
	} else {
	  // We couldn't create the RTCP socket (perhaps that port number's already in use elsewhere?).
	  delete fRTCPSocket;

	  // Record the first socket in our table, and keep trying:
	  unsigned key = (unsigned)fClientPortNum;
	  Groupsock* existing = (Groupsock*)socketHashTable->Add((char const*)key, fRTPSocket);
	  delete existing; // in case it wasn't NULL
	  continue;
	}
      }

      // Clean up the socket hash table (and contents):
      Groupsock* oldGS;
      while ((oldGS = (Groupsock*)socketHashTable->RemoveNext()) != NULL) {
	delete oldGS;
      }
      delete socketHashTable;

      if (!success) break; // a fatal error occurred trying to create the RTP and RTCP sockets; we can't continue
    }

    // Try to use a big receive buffer for RTP - at least 0.1 second of
    // specified bandwidth and at least 50 KB
    unsigned rtpBufSize = fBandwidth * 25 / 2; // 1 kbps * 0.1 s = 12.5 bytes
    if (rtpBufSize < 50 * 1024)
      rtpBufSize = 50 * 1024;
    increaseReceiveBufferTo(env(), fRTPSocket->socketNum(), rtpBufSize);

    if (isSSM() && fRTCPSocket != NULL) {
      // Special case for RTCP SSM: Send RTCP packets back to the source via unicast:
      fRTCPSocket->changeDestinationParameters(fSourceFilterAddr,0,~0);
    }

    // Create "fRTPSource" and "fReadSource":
    if (!createSourceObjects(useSpecialRTPoffset)) break;

    if (fReadSource == NULL) {
      env().setResultMsg("Failed to create read source");
      break;
    }

    // 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);
      if (fRTCPInstance == NULL) {
	env().setResultMsg("Failed to create RTCP instance");
	break;
      }
    }

    return True;
  } while (0);

  delete fRTPSocket; fRTPSocket = NULL;
  delete fRTCPSocket; fRTCPSocket = NULL;
  Medium::close(fRTCPInstance); fRTCPInstance = NULL;
  Medium::close(fReadSource); fReadSource = fRTPSource = NULL;
  fClientPortNum = 0;
  return False;
}