/* * conference.cxx * * Conferencing functions for a simple MCU * * Copyright (c) 2000 Equivalence Pty. Ltd. * Copyright (c) 2004 Post Increment * * The contents of this file are subject to the Mozilla Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is Portable Windows Library. * * The Initial Developer of the Original Code is Equivalence Pty. Ltd. * * Portions of this code were written by Post Increment (http://www.postincrement.com) * with the assistance of funding from Citron Networks (http://www.citron.com.tw) * * Portions are Copyright (C) 1993 Free Software Foundation, Inc. * All Rights Reserved. * * Contributor(s): Derek J Smithies (derek@indranet.co.nz) * ------------------------------ * * $Log: conference.cxx,v $ * Revision 2.5 2004/09/22 22:39:26 csoutheren * Fixed race condition, thanks to Neil McCurdy * * Revision 2.4 2004/05/26 06:54:31 csoutheren * Changed to be a PHTTPServiceProcess * Added ability to play WAV files on member entry and exit * Added additional documentation on all classes * Preparation for re-introducing video * * Revision 2.3 2004/03/31 03:36:38 csoutheren * Fixed problem with user indication messages * Fixed problems with room listener and unlisten * * Revision 2.2 2004/03/30 11:27:23 csoutheren * Reverted to old mixing algorithm * * Revision 2.1 2004/03/23 11:40:06 csoutheren * Fixed problem where deleting map element in-place causes crash at end of call * Fixed problem where referencing map by iterator rather than ID * Fixed code formatting problems * * Revision 2.0 2004/03/08 02:06:24 csoutheren * Totally rewritten to use new connection locking mecahnism * Added ability to monitor conferences * Added initial support for H.323 MCU messages * Thanks to Citron Networks for supporting this work * */ #include #include "conference.h" // size of a PCM data packet, in samples #define PCM_PACKET_LEN 240 // size of a PCM data buffer, in bytes #define PCM_BUFFER_LEN (PCM_PACKET_LEN * 2) // number of PCM buffers to keep #define PCM_BUFFER_COUNT 2 #define PCM_BUFFER_SIZE (PCM_BUFFER_LEN * PCM_BUFFER_COUNT) //////////////////////////////////////////////////////////////////////////////////// ConferenceManager::ConferenceManager() { } Conference * ConferenceManager::MakeConference(const PString & roomToCreate, const PString & name) { OpalGloballyUniqueID conferenceID; { PWaitAndSignal m(conferenceListMutex); ConferenceList::const_iterator r; for (r = conferenceList.begin(); r != conferenceList.end(); ++r) { if (roomToCreate == r->second->GetNumber()) { conferenceID = r->second->GetID(); break; } } } return MakeConference(conferenceID, roomToCreate, name); } Conference * ConferenceManager::MakeConference(const OpalGloballyUniqueID & conferenceID, const PString & roomToCreate, const PString & name) { Conference * conference = NULL; BOOL newConference = FALSE; { PWaitAndSignal m(conferenceListMutex); ConferenceList::const_iterator r = conferenceList.find(conferenceID); if (r != conferenceList.end()) conference = r->second; else { conference = CreateConference(conferenceID, roomToCreate, name); conferenceList.insert(std::pair(conferenceID, conference)); newConference = TRUE; } } if (newConference) OnCreateConference(conference); return conference; } Conference * ConferenceManager::CreateConference(const OpalGloballyUniqueID & _guid, const PString & _number, const PString & _name) { return new Conference(*this, _guid, _number, _name); } BOOL ConferenceManager::HasConference(const OpalGloballyUniqueID & conferenceID) { PWaitAndSignal m(conferenceListMutex); ConferenceList::const_iterator r = conferenceList.find(conferenceID); return r != conferenceList.end(); } BOOL ConferenceManager::HasConference(const PString & number) { PWaitAndSignal m(conferenceListMutex); ConferenceList::const_iterator r; for (r = conferenceList.begin(); r != conferenceList.end(); ++r) { if (r->second->GetNumber() == number) return TRUE; } return FALSE; } void ConferenceManager::RemoveConference(const OpalGloballyUniqueID & confId) { PWaitAndSignal m(conferenceListMutex); ConferenceList::iterator r = conferenceList.find(confId); if (r != conferenceList.end()) { Conference * conf = r->second; OnDestroyConference(conf); conferenceList.erase(confId); delete conf; } } void ConferenceManager::RemoveMember(const OpalGloballyUniqueID & confId, ConferenceMember * toRemove) { Conference * conf = toRemove->GetConference(); BOOL removeConf = conf->RemoveMember(toRemove); delete toRemove; if (removeConf) RemoveConference(conf->GetID()); } //////////////////////////////////////////////////////////////////////////////////// Conference::Conference( ConferenceManager & _manager, const OpalGloballyUniqueID & _guid, const PString & _number, const PString & _name) : manager(_manager), guid(_guid), number(_number), name(_name) { //memberList.DisallowDeleteObjects(); PTRACE(3, "Conference\tNew conference started: ID=" << guid << ", number = " << number); #ifndef NO_MCU_VIDEO // if this is the first connection to a room, create the video buffer. if (create_new_room) { //cout << "CREATING VIDEO BUFFER FOR "<GetName() << " to conference " << guid); cout << memberToAdd->GetName() << " joining conference " << number << "(" << guid << ")" << endl; // add the member to the conference memberToAdd->AddToConference(this); { // lock the member list PWaitAndSignal m(memberListMutex); // add this member to the conference member list memberList.insert(std::pair(memberToAdd->GetID(), memberToAdd)); // make sure each member has a connection created for the new member // make sure the new member has a connection created for reach existing member std::map::const_iterator r; for (r = memberList.begin(); r != memberList.end(); r++) { ConferenceMember * conn = r->second; if (conn != memberToAdd) { conn->AddConnection(memberToAdd); memberToAdd->AddConnection(conn); } } } // call the callback function OnMemberJoining(memberToAdd); } BOOL Conference::RemoveMember(ConferenceMember * memberToRemove) { PTRACE(3, "Conference\tRemoving call " << memberToRemove->GetName() << " from conference " << guid << " with size " << (PINDEX)memberList.size()); cout << memberToRemove->GetName() << " leaving conference " << number << "(" << guid << ")" << endl; BOOL result; { PWaitAndSignal m(memberListMutex); // remove this member from the connection lists for all other members std::map::iterator r; for (r = memberList.begin(); r != memberList.end(); r++) { ConferenceMember * conn = r->second; if (conn != memberToRemove) r->second->RemoveConnection(memberToRemove->GetID()); } // remove this connection from the member list memberList.erase(memberToRemove->GetID()); // return TRUE if conference is empty result = memberList.size() == 0; } // call the callback function if (!result) OnMemberLeaving(memberToRemove); return result; } #ifndef NO_MCU_VIDEO PINDEX Conference::FindTokensVideoPosn(const PString & thisToken, const PString & roomID) { PStringList & videoPosnList = videoPosnDict[roomID]; PINDEX keyIndex; for (keyIndex = 0; keyIndex= videoPosnList.GetSize()) { PString tokenToWipe = spokenList[keyIndex-videoPosnList.GetSize()]; keyIndex = FindTokensVideoPosn(tokenToWipe,roomID); } if(keyIndex != P_MAX_INDEX) videoPosnList[keyIndex] = thisToken; } processAudio: #endif PINDEX i; for (i = 0; i < memberList.GetSize(); i++) memberList.GetDataAt(i).WriteAudio(source, buffer, amount); return TRUE; } #endif void Conference::OnMemberJoining(ConferenceMember * member) { manager.OnMemberJoining(this, member); } void Conference::OnMemberLeaving(ConferenceMember * member) { manager.OnMemberLeaving(this, member); } /////////////////////////////////////////////////////////////////////////// ConferenceMember::ConferenceMember(void * _id) : id(_id) { conference = NULL; } BOOL ConferenceMember::AddToConference(Conference * _conference) { if (conference != NULL) return FALSE; conference = _conference; return TRUE; } void ConferenceMember::RemoveFromConference() { if (conference != NULL) { if (conference->RemoveMember(this)) conference->GetManager().RemoveConference(conference->GetID()); } } void ConferenceMember::AddConnection(ConferenceMember * memberToAdd) { void * newID = memberToAdd->GetID(); PTRACE(3, "Conference\tAdding " << newID << " to connection " << id); if (lock.Wait(TRUE)) { ConferenceConnection * conn = memberToAdd->CreateConnection(); memberList.insert(std::pair(newID, memberToAdd)); connectionList.insert(std::pair(newID, conn)); lock.Signal(TRUE); } } void ConferenceMember::RemoveConnection(void * idToDelete) { PTRACE(3, "Conference\tRemoving member " << idToDelete << " from connection " << id); if (lock.Wait(TRUE)) { memberList.erase(idToDelete); connectionList.erase(idToDelete); lock.Signal(TRUE); } } void ConferenceMember::SendUserInputIndication(const PString & str) { PTRACE(3, "Conference\tConnection " << id << " sending user indication " << str); if (lock.Wait()) { MemberList::iterator r; for (r = memberList.begin(); r != memberList.end(); ++r) r->second->OnReceivedUserInputIndication(str); lock.Signal(); } } void ConferenceMember::WriteAudio(const void * buffer, PINDEX amount) { if (lock.Wait()) { MemberList::iterator r; for (r = memberList.begin(); r != memberList.end(); ++r) r->second->OnExternalAudio(id, (BYTE *)buffer, amount); lock.Signal(); } } void ConferenceMember::ReadAudio(void * buffer, PINDEX amount) { // First, set the buffer to empty. memset(buffer, 0, amount); if (lock.Wait()) { // get number of channels to mix ConnectionList::iterator r; for (r = connectionList.begin(); r != connectionList.end(); ++r) r->second->ReadAndMixAudio((BYTE *)buffer, amount, (PINDEX)connectionList.size()); lock.Signal(); } } void ConferenceMember::OnExternalAudio(void * source, const void * buffer, PINDEX amount) { if (lock.Wait()) { ConnectionList::iterator r = connectionList.find(source); if (r != connectionList.end()) r->second->Write((BYTE *)buffer, amount); lock.Signal(); } } #ifndef NO_MCU_VIDEO virtual BOOL ConferenceMember::OnOutgoingVideo(void * buffer, PINDEX & amount) { } virtual BOOL ConferenceMember::OnIncomingVideo(const void * buffer, PINDEX amount) { } #endif /////////////////////////////////////////////////////////////////////////// ConferenceConnection::ConferenceConnection(void * _id) : id(_id), bufferSize(PCM_BUFFER_SIZE) { buffer = new BYTE[bufferSize]; bufferStart = bufferLen = 0; } ConferenceConnection::~ConferenceConnection() { delete[] buffer; } void ConferenceConnection::Write(const BYTE * data, PINDEX amount) { if (amount == 0) return; PWaitAndSignal mutex(audioBufferMutex); // if there is not enough room for the new data, make room PINDEX newLen = bufferLen + amount; if (newLen > bufferSize) { PINDEX toRemove = newLen - bufferSize; bufferStart = (bufferStart + toRemove) % bufferSize; bufferLen -= toRemove; } // copy data to the end of the new data, up to the end of the buffer PINDEX copyStart = (bufferStart + bufferLen) % bufferSize; if ((copyStart + amount) > bufferSize) { PINDEX toCopy = bufferSize - copyStart; memcpy(buffer + copyStart, data, toCopy); copyStart = 0; data += toCopy; amount -= toCopy; bufferLen += toCopy; } // copy the rest of the data if (amount > 0) { memcpy(buffer + copyStart, data, amount); bufferLen += amount; } } void ConferenceConnection::ReadAudio(BYTE * data, PINDEX amount) { if (amount == 0) return; PWaitAndSignal mutex(audioBufferMutex); if (bufferLen == 0) { memset(data, 0, amount); // nothing in the buffer. return silence return; } // fill output data block with silence if audiobuffer is // almost empty. if (amount > bufferLen) memset(data + bufferLen, 0, amount - bufferLen); // only copy up to the amount of data remaining PINDEX copyLeft = PMIN(amount, bufferLen); // if buffer is wrapping, get first part if ((bufferStart + copyLeft) > bufferSize) { PINDEX toCopy = bufferSize - bufferStart; memcpy(data, buffer + bufferStart, toCopy); data += toCopy; bufferLen -= toCopy; copyLeft -= toCopy; bufferStart = 0; } // get the remainder of the buffer if (copyLeft > 0) { memcpy(data, buffer + bufferStart, copyLeft); bufferLen -= copyLeft; bufferStart = (bufferStart + copyLeft) % bufferSize; } } void ConferenceConnection::ReadAndMixAudio(BYTE * data, PINDEX amount, PINDEX channels) { if (amount == 0) { PTRACE(3, "Mixer\tNo data to read"); return; } PWaitAndSignal mutex(audioBufferMutex); if (bufferLen == 0) { // nothing in the buffer to mix. return; } // only mix up to the amount of data remaining PINDEX copyLeft = PMIN(amount, bufferLen); // if buffer is wrapping, get first part if ((bufferStart + copyLeft) > bufferSize) { PINDEX toCopy = bufferSize - bufferStart; Mix(data, buffer + bufferStart, toCopy, channels); data += toCopy; bufferLen -= toCopy; copyLeft -= toCopy; bufferStart = 0; } // get the remainder of the buffer if (copyLeft > 0) { Mix(data, buffer + bufferStart, copyLeft, channels); bufferLen -= copyLeft; bufferStart = (bufferStart + copyLeft) % bufferSize; } } void ConferenceConnection::Mix(BYTE * dst, const BYTE * src, PINDEX count, PINDEX /*channels*/) { #if 0 memcpy(dst, src, count); #else PINDEX i; for (i = 0; i < count; i += 2) { int srcVal = *(short *)src; int dstVal = *(short *)dst; int newVal = dstVal; #if 0 //The loudest person gains the channel. #define mix_abs(x) ((x) >= 0 ? (x) : -(x)) if (mix_abs(newVal) > mix_abs(srcVal)) dstVal = newVal; else dstVal = srcVal; #else //Just add up all the channels. if ((newVal + srcVal) > 0x7fff) dstVal = 0x7fff; else dstVal += srcVal; #endif *(short *)dst = (short)dstVal; dst += 2; src += 2; } #endif } /////////////////////////////////////////////////////////////// MCULock::MCULock() { closing = FALSE; count = 0; } BOOL MCULock::Wait(BOOL hard) { mutex.Wait(); if (hard) return TRUE; BOOL ret = TRUE; if (!closing) count++; else ret = FALSE; mutex.Signal(); return ret; } void MCULock::Signal(BOOL hard) { if (hard) { mutex.Signal(); return; } mutex.Wait(); if (count > 0) count--; if (closing) closeSync.Signal(); mutex.Signal(); } void MCULock::WaitForClose() { mutex.Wait(); closing = TRUE; BOOL wait = count > 0; mutex.Signal(); while (wait) { closeSync.Wait(); mutex.Wait(); wait = count > 0; mutex.Signal(); } }