/*
* 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 <ptlib.h>
#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<OpalGloballyUniqueID, Conference *>(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 "<<newRoomID<<endl;
videoBufferDict.SetAt(newRoomID, new VideoBuffer);
VideoBuffer & videoBuffer = videoBufferDict[newRoomID];
if (videoLarge)
videoBuffer.SetSize(352,288);
// create the spoken list.
spokenListDict.SetAt(newRoomID, new PStringList);
if (singleStream) {
PStringList & spokenList = spokenListDict[newRoomID];
spokenList.AppendString(newToken);
}
// create the position list.
videoPosnDict.SetAt(newRoomID, new PStringList);
PStringList & videoPosnList = videoPosnDict[newRoomID];
if (singleStream) {
videoPosnList.AppendString("");
} else {
PINDEX i;
for(i=0;i<4;i++) // hard coded to support 4 images
videoPosnList.AppendString("");
}
}
// Normally we display the video for the "active" users, ie
// people who are currently speaking (based on noise detection)
//
// If there are currently less than 4 members then we can display
// the video right from the start, before they begin talking.
// This is also handy for connections with video, but no audio.
//
// Problem is finding out whether or not an ep sends video.
// That is for a coming version.
if (singleStream) {
//only first connection must send video
if (create_new_room) {
AddVideoPosnToken(newToken,newRoomID);
}
} else {
AddVideoPosnToken(newToken,newRoomID);
}
#endif
}
Conference::~Conference()
{
}
void Conference::AddMember(ConferenceMember * memberToAdd)
{
PTRACE(3, "Conference\tAdding member " << memberToAdd->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<void *, ConferenceMember *>(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<void *, ConferenceMember *>::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<void *, ConferenceMember *>::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(); keyIndex++)
if (thisToken == videoPosnList[keyIndex] )
return keyIndex;
return P_MAX_INDEX;
}
BOOL Conference::AddVideoPosnToken(const PString & thisToken,
const PString & roomID)
{
PStringList & videoPosnList = videoPosnDict[roomID];
PINDEX keyIndex;
for (keyIndex = 0; keyIndex<videoPosnList.GetSize(); keyIndex++)
if (videoPosnList[keyIndex] == "" ) {
videoPosnList[keyIndex] = thisToken;
return TRUE;
}
return FALSE;
}
#endif
#if 0
BOOL Conference::WriteAudio(OpenMCUH323Connection * source, const void * buffer, PINDEX amount)
{
//The spokenList construct is required to determine who were the last
//to speak, which is needed for determining which four videoimages are displayed.
//spokenList contains a sorted list of when the last packet was received from
//which connection. Thus, when an audio packet is received from a connection,
//the name of the connection is moved to the end of the list.
// If someone new comes along, everyone moves down.
#ifndef NO_MCU_VIDEO
if ( DetectNoise(buffer,amount) && !singleStream ) {
PStringList & spokenList = spokenListDict[roomID];
PINDEX keyIndex = spokenList.GetStringsIndex(thisToken);
if (keyIndex != P_MAX_INDEX)
spokenList.RemoveAt(keyIndex);
spokenList.AppendString(thisToken);
if (FindTokensVideoPosn(thisToken,roomID) != P_MAX_INDEX)
goto processAudio;
if (!singleStream && AddVideoPosnToken(thisToken,roomID))
goto processAudio;
keyIndex = spokenList.GetSize() - 1;
PStringList & videoPosnList = videoPosnDict[roomID];
if (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<void *, ConferenceMember *>(newID, memberToAdd));
connectionList.insert(std::pair<void *, ConferenceConnection *>(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();
}
}
syntax highlighted by Code2HTML, v. 0.9.1