class Tracker: protocol = PeerProtocol() def run(self): _, port = config.proxy.tracker.split(':') port = int(port) self.trans = reactor.listenUDP(port, self.protocol) self.refresh_loop = LoopingCall(self.protocol.refresh_peers).start(60) def stop(self): self.refresh_loop.cancel() self.trans.stopListening()
class Room: """A room is a conference. Everyone in the room hears everyone else (well, kinda) """ # Theory of operation. Rather than rely on the individual sources # timer loops (which would be, well, horrid), we trigger off our # own timer. # This means we don't have to worry about the end systems not # contributing during a window. _open = False def __init__(self, name, MaxSpeakers=4): self._name = name self._members = Set() self._audioOut = {} self._audioOutDefault = '' self._maxSpeakers = MaxSpeakers self.start() def start(self): self._audioCalcLoop = LoopingCall(self.mixAudio) self._audioCalcLoop.start(0.020) self._open = True def getName(self): return self._name def __repr__(self): if self._open: o = '' else: o = ' (closed)' return "<ConferenceRoom %s%s with %d members>"%(self._name, o, len(self._members)) def shutdown(self): if hasattr(self._audioCalcLoop, 'cancel'): self._audioCalcLoop.cancel() else: self._audioCalcLoop.stop() # XXX close down any running sources! self._members = Set() del self._audioOut self._open = False removeRoom(self._name) def addMember(self, confsource): self._members.add(confsource) if CONFDEBUG: print "added", confsource, "to room", self if not self._open: self.start() def removeMember(self, confsource): if len(self._members) and confsource in self._members: self._members.remove(confsource) if CONFDEBUG: print "removed", confsource, "from", self else: raise ConferenceMemberNotFoundError(confsource) if not len(self._members): if CONFDEBUG: print "No members left, shutting down" self.shutdown() def isMember(self, confsource): return confsource in self._members def isOpen(self): return self._open def memberCount(self): return len(self._members) def readAudio(self, confsource): if self._open: return self._audioOut.get(confsource, self._audioOutDefault) else: raise ConferenceClosedError() def mixAudio(self): # XXX see the comment above about storing a decaying number for the # volume. For instance, each time round the loop, take the calculated # volume, and the stored volume, and do something like: # newStoredVolume = (oldStoredVolume * 0.33) + (thisPacketVolume * 0.66) import audioop self._audioOut = {} if not self._open: log.msg('mixing closed room %r'%(self,), system='doug') return audioIn = {} for m in self._members: bytes = m.getAudioForRoom() if bytes: audioIn[m] = bytes if CONFDEBUG: print "room %r has %d members"%(self, len(self._members)) print "got %d samples this time"%len(audioIn) print "samples: %r"%(audioIn.items(),) # short-circuit this case if len(self._members) < 2: if CONFDEBUG: print "less than 2 members, no sound" self._audioOutDefault = '' return # Samples is (confsource, audio) samples = audioIn.items() # power is three-tuples of (rms,audio,confsource) power = [ (audioop.rms(x[1],2),x[1], x[0]) for x in samples ] power.sort(); power.reverse() if CONFDEBUG: for rms,audio,confsource in power: print confsource, rms # Speakers is a list of the _maxSpeakers loudest speakers speakers = Set([x[2] for x in power[:self._maxSpeakers]]) # First we calculate the 'default' audio. Used for everyone who's # not a speaker in the room. samples = [ x[1] for x in power[:self._maxSpeakers] ] scaledsamples = [ audioop.mul(x, 2, 1.0/len(samples)) for x in samples ] if scaledsamples: # ooo. a use of reduce. first time for everything... try: combined = reduce(lambda x,y: audioop.add(x, y, 2), scaledsamples) except audioop.error, exc: # XXX tofix! print "combine got error %s"%(exc,) print "lengths", [len(x) for x in scaledsamples] combined = '' else:
class Room: """A room is a conference. Everyone in the room hears everyone else (well, kinda) """ # Theory of operation. Rather than rely on the individual sources # timer loops (which would be, well, horrid), we trigger off our # own timer. # This means we don't have to worry about the end systems not # contributing during a window. _open = False def __init__(self, name, MaxSpeakers=4): self._name = name self._members = Set() self._audioOut = {} self._audioOutDefault = '' self._maxSpeakers = MaxSpeakers self.start() def start(self): self._audioCalcLoop = LoopingCall(self.mixAudio) self._audioCalcLoop.start(0.020) self._open = True def getName(self): return self._name def __repr__(self): if self._open: o = '' else: o = ' (closed)' return "<ConferenceRoom %s%s with %d members>"%(self._name, o, len(self._members)) def shutdown(self): if hasattr(self._audioCalcLoop, 'cancel'): self._audioCalcLoop.cancel() else: self._audioCalcLoop.stop() # XXX close down any running sources! self._members = Set() del self._audioOut self._open = False removeRoom(self._name) def addMember(self, confsource): self._members.add(confsource) if CONFDEBUG: print("added", confsource, "to room", self) if not self._open: self.start() def removeMember(self, confsource): if len(self._members) and confsource in self._members: self._members.remove(confsource) if CONFDEBUG: print("removed", confsource, "from", self) else: raise ConferenceMemberNotFoundError(confsource) if not len(self._members): if CONFDEBUG: print("No members left, shutting down") self.shutdown() def isMember(self, confsource): return confsource in self._members def isOpen(self): return self._open def memberCount(self): return len(self._members) def readAudio(self, confsource): if self._open: return self._audioOut.get(confsource, self._audioOutDefault) else: raise ConferenceClosedError() def mixAudio(self): # XXX see the comment above about storing a decaying number for the # volume. For instance, each time round the loop, take the calculated # volume, and the stored volume, and do something like: # newStoredVolume = (oldStoredVolume * 0.33) + (thisPacketVolume * 0.66) import audioop self._audioOut = {} if not self._open: log.msg('mixing closed room %r'%(self,), system='doug') return audioIn = {} for m in self._members: bytes = m.getAudioForRoom() if bytes: audioIn[m] = bytes if CONFDEBUG: print("room %r has %d members"%(self, len(self._members))) print("got %d samples this time"%len(audioIn)) print("samples: %r"%(audioIn.items(),)) # short-circuit this case if len(self._members) < 2: if CONFDEBUG: print("less than 2 members, no sound") self._audioOutDefault = '' return # Samples is (confsource, audio) samples = audioIn.items() # power is three-tuples of (rms,audio,confsource) power = [ (audioop.rms(x[1],2),x[1], x[0]) for x in samples ] power.sort(); power.reverse() if CONFDEBUG: for rms,audio,confsource in power: print(confsource, rms) # Speakers is a list of the _maxSpeakers loudest speakers speakers = Set([x[2] for x in power[:self._maxSpeakers]]) # First we calculate the 'default' audio. Used for everyone who's # not a speaker in the room. samples = [ x[1] for x in power[:self._maxSpeakers] ] scaledsamples = [ audioop.mul(x, 2, 1.0/len(samples)) for x in samples ] if scaledsamples: # ooo. a use of reduce. first time for everything... try: combined = reduce(lambda x,y: audioop.add(x, y, 2), scaledsamples) except audioop.error as exc: # XXX tofix! print("combine got error %s"%(exc,)) print("lengths", [len(x) for x in scaledsamples]) combined = '' else: combined = '' self._audioOutDefault = combined # Now calculate output for each speaker. allsamples = {} for p,sample,speaker in power: allsamples[speaker] = p, sample for s in speakers: # For each speaker, take the set of (other speakers), grab # the top N speakers, and combine them. Add to the _audioOut # dictionary all = allsamples.copy() del all[s] power = all.values() power.sort() ; power.reverse() samples = [ x[1] for x in power[:self._maxSpeakers] ] if samples: scaled = [ audioop.mul(x, 2, 1.0/len(samples)) for x in samples] try: out = reduce(lambda x,y: audioop.add(x, y, 2), scaled) except audioop.error as exc: # XXX tofix! print("combine got error %s"%(exc,)) print("lengths", [len(x) for x in scaled]) out = '' else: out = '' if CONFDEBUG: print("calc for", s, "is", audioop.rms(out, 2)) self._audioOut[s] = out