/*
* main.cxx
*
* A simple H.323 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)
* Craig Southeren (craig@postincrement.com)
* $Log: main.cxx,v $
* Revision 2.13 2005/12/21 00:27:08 csoutheren
* Disable sending room member lists as this code is never implemented in clients
*
* Revision 2.12 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.11 2004/05/10 13:07:19 rjongbloed
* Changed G.726 and MS-ADPCM to plug in codecs.
*
* Revision 2.10 2004/05/04 12:21:16 rjongbloed
* Converted LPC-10 codec to plug in.
*
* Revision 2.9 2004/05/03 13:25:47 rjongbloed
* Converted everything to be codec plug in freindly
* Removed GSM and G.729 as now plug ins are "the way"!
*
* Revision 2.8 2004/05/02 01:35:01 csoutheren
* Move --disable-menu to outside video #ifdef
*
* Revision 2.7 2004/04/06 11:27:50 rjongbloed
* Changes to support native C++ Run Time Type Information
* Changes for codec plug ins
*
* Revision 2.6 2004/04/02 01:10:52 csoutheren
* Fixed unitialised variable
*
* Revision 2.5 2004/03/31 03:40:25 csoutheren
* Added various codecs back in
*
* Revision 2.4 2004/03/31 03:36:38 csoutheren
* Fixed problem with user indication messages
* Fixed problems with room listener and unlisten
*
* Revision 2.3 2004/03/30 04:02:54 csoutheren
* Fixed problem with not using incoming alias for default room name
*
* Revision 2.2 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.1 2004/03/11 20:49:44 csoutheren
* Removed warnings
*
* 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 <ptlib/pipechan.h>
#include "version.h"
#include "main.h"
PCREATE_PROCESS(OpenMCU);
const WORD DefaultHTTPPort = 1420;
static const char LogLevelKey[] = "Log Level";
static const char UserNameKey[] = "Username";
static const char PasswordKey[] = "Password";
static const char HttpPortKey[] = "HTTP Port";
static const char LocalUserNameKey[] = "Local User Name";
static const char GatekeeperPasswordKey[] = "Gatekeeper Password";
static const char GatekeeperModeKey[] = "Gatekeeper Mode";
static const char GatekeeperKey[] = "Gatekeeper";
static const char DefaultRoomKey[] = "Default room";
static const char DisableCodecsKey[] = "Disable codecs";
static const char CallLogFilenameKey[] = "Call log filename";
static const char InterfaceKey[] = "Interface";
static const char ConnectingWAVFileKey[] = "Connecting WAV File";
static const char EnteringWAVFileKey[] = "Entering WAV File";
static const char LeavingWAVFileKey[] = "Leaving WAV File";
#if P_SSL
static const char HTTPCertificateFileKey[] = "HTTP Certificate";
#endif
static const char DefaultCallLogFilename[] = "mcu_log.txt";
static const char DefaultRoom[] = "room101";
static const char DefaultConnectingWAVFile[] = "connecting.wav";
static const char DefaultEnteringWAVFile[] = "entering.wav";
static const char DefaultLeavingWAVFile[] = "leaving.wav";
static const char * GKModeLabels[] = {
"No gatekeeper",
"Find gatekeeper",
"Use gatekeeper",
};
enum {
Gatekeeper_None,
Gatekeeper_Find,
Gatekeeper_Explicit
};
#define GKMODE_LABEL_COUNT (sizeof(GKModeLabels)/sizeof(char *))
#define new PNEW
///////////////////////////////////////////////////////////////
class MainStatusPage : public PServiceHTTPString
{
PCLASSINFO(MainStatusPage, PServiceHTTPString);
public:
MainStatusPage(OpenMCU & app, PHTTPAuthority & auth);
virtual BOOL Post(
PHTTPRequest & request,
const PStringToString &,
PHTML & msg
);
private:
OpenMCU & app;
};
///////////////////////////////////////////////////////////////
OpenMCU::OpenMCU()
: OpenMCUProcessAncestor(ProductInfo)
{
endpoint = NULL;
}
void OpenMCU::Main()
{
Suspend();
}
BOOL OpenMCU::OnStart()
{
// change to the default directory to the one containing the executable
PDirectory exeDir = GetFile().GetDirectory();
#if defined(_WIN32) && defined(_DEBUG)
// Special check to aid in using DevStudio for debugging.
if (exeDir.Find("\\Debug\\") != P_MAX_INDEX)
exeDir = exeDir.GetParent();
#endif
exeDir.Change();
httpNameSpace.AddResource(new PHTTPDirectory("data", "data"));
httpNameSpace.AddResource(new PServiceHTTPDirectory("html", "html"));
endpoint = new MyH323EndPoint();
return PHTTPServiceProcess::OnStart();
}
void OpenMCU::OnStop()
{
delete endpoint;
endpoint = NULL;
PHTTPServiceProcess::OnStop();
}
void OpenMCU::OnControl()
{
// This function get called when the Control menu item is selected in the
// tray icon mode of the service.
PStringStream url;
url << "http://";
PString host = PIPSocket::GetHostName();
PIPSocket::Address addr;
if (PIPSocket::GetHostAddress(host, addr))
url << host;
else
url << "localhost";
url << ':' << DefaultHTTPPort;
PURL::OpenBrowser(url);
}
BOOL OpenMCU::Initialise(const char * initMsg)
{
PConfig cfg("Parameters");
// Set log level as early as possible
SetLogLevel((PSystemLog::Level)cfg.GetInteger(LogLevelKey, GetLogLevel()));
#if PTRACING
if (GetLogLevel() >= PSystemLog::Warning)
PTrace::SetLevel(GetLogLevel()-PSystemLog::Warning);
else
PTrace::SetLevel(0);
PTrace::ClearOptions(PTrace::Timestamp);
PTrace::SetOptions(PTrace::DateAndTime);
#endif
// Get the HTTP basic authentication info
PString adminUserName = cfg.GetString(UserNameKey);
PString adminPassword = PHTTPPasswordField::Decrypt(cfg.GetString(PasswordKey));
PHTTPSimpleAuth authority(GetName(), adminUserName, adminPassword);
// Create the parameters URL page, and start adding fields to it
PConfigPage * rsrc = new PConfigPage(*this, "Parameters", "Parameters", authority);
// HTTP authentication username/password
rsrc->Add(new PHTTPStringField(UserNameKey, 25, adminUserName));
rsrc->Add(new PHTTPPasswordField(PasswordKey, 25, adminPassword));
// Log level for messages
rsrc->Add(new PHTTPIntegerField(LogLevelKey,
PSystemLog::Fatal, PSystemLog::NumLogLevels-1,
GetLogLevel(),
"1=Fatal only, 2=Errors, 3=Warnings, 4=Info, 5=Debug"));
#if P_SSL
// SSL certificate file.
PString certificateFile = cfg.GetString(HTTPCertificateFileKey, "server.pem");
rsrc->Add(new PHTTPStringField(HTTPCertificateFileKey, 25, certificateFile));
if (!SetServerCertificate(certificateFile, TRUE)) {
PSYSTEMLOG(Fatal, "BMAC\tCould not load certificate \"" << certificateFile << '"');
return FALSE;
}
#endif
// HTTP Port number to use.
WORD httpPort = (WORD)cfg.GetInteger(HttpPortKey, DefaultHTTPPort);
rsrc->Add(new PHTTPIntegerField(HttpPortKey, 1, 32767, httpPort));
endpoint->Initialise(cfg, rsrc);
// get default "room" (conference) name
defaultRoomName = cfg.GetString(DefaultRoomKey, DefaultRoom);
rsrc->Add(new PHTTPStringField(DefaultRoomKey, 25, defaultRoomName));
// get WAV file played to a user when they enter a conference
connectingWAVFile = cfg.GetString(ConnectingWAVFileKey, DefaultConnectingWAVFile);
rsrc->Add(new PHTTPStringField(ConnectingWAVFileKey, 50, connectingWAVFile));
// get WAV file played to a conference when a new user enters
enteringWAVFile = cfg.GetString(EnteringWAVFileKey, DefaultEnteringWAVFile);
rsrc->Add(new PHTTPStringField(EnteringWAVFileKey, 50, enteringWAVFile));
// get WAV file played to a conference when a new user enters
leavingWAVFile = cfg.GetString(LeavingWAVFileKey, DefaultLeavingWAVFile);
rsrc->Add(new PHTTPStringField(LeavingWAVFileKey, 50, leavingWAVFile));
// default log file name
logFilename = cfg.GetString(CallLogFilenameKey, DefaultCallLogFilename);
rsrc->Add(new PHTTPStringField(CallLogFilenameKey, 50, logFilename));
// see if audio loopback is required
//audioLoopbackRoom = args.GetOptionString("audio-loopback");
// get singleStream flag
//endpoint.singleStream = args.HasOption("single-stream");
#ifndef NO_MCU_VIDEO
// If videoLarge was specified
endpoint.videoLarge = args.HasOption("videolarge");
if (args.HasOption('v')) {
//Add capability to allow the reception of video.
// this is the only way I know of at present to transmit
// CIF size video - pez
// Do not allow CIF video if size is medium
if (endpoint.videoLarge)
endpoint.SetCapability(0, 1, new H323_H261Capability(0, 4, FALSE, FALSE, 6217)); // CIF
endpoint.SetCapability(0, 1, new H323_H261Capability(2, 0, FALSE, FALSE, 6217)); // QCIF
}
#endif
// Finished the resource to add, generate HTML for it and add to name space
PServiceHTML html("System Parameters");
rsrc->BuildHTML(html);
httpNameSpace.AddResource(rsrc, PHTTPSpace::Overwrite);
// Create the status page
httpNameSpace.AddResource(new MainStatusPage(*this, authority), PHTTPSpace::Overwrite);
// Add log file links
if (!systemLogFileName && systemLogFileName != "-") {
httpNameSpace.AddResource(new PHTTPFile("logfile.txt", systemLogFileName, authority));
httpNameSpace.AddResource(new PHTTPTailFile("tail_logfile", systemLogFileName, authority));
}
// create the home page
static const char welcomeHtml[] = "welcome.html";
if (PFile::Exists(welcomeHtml))
httpNameSpace.AddResource(new PServiceHTTPFile(welcomeHtml, TRUE), PHTTPSpace::Overwrite);
else {
PHTML html;
html << PHTML::Title("Welcome to OpenMCU")
<< PHTML::Body()
<< GetPageGraphic()
<< PHTML::Paragraph() << "<center>"
<< PHTML::HotLink("Parameters") << "Parameters" << PHTML::HotLink()
<< PHTML::Paragraph()
<< PHTML::HotLink("Status") << "Status" << PHTML::HotLink()
<< PHTML::Paragraph();
if (!systemLogFileName && systemLogFileName != "-")
html << PHTML::HotLink("logfile.txt") << "Full Log File" << PHTML::HotLink()
<< PHTML::BreakLine()
<< PHTML::HotLink("tail_logfile") << "Tail Log File" << PHTML::HotLink()
<< PHTML::Paragraph();
html << PHTML::HRule()
<< GetCopyrightText()
<< PHTML::Body();
httpNameSpace.AddResource(new PServiceHTTPString("welcome.html", html), PHTTPSpace::Overwrite);
}
// create monitoring page
PString monitorText = "<!--#equival monitorinfo-->"
"Current Proxies: <!--#equival ProxyCount-->\n";
httpNameSpace.AddResource(new PServiceHTTPString("monitor.txt", monitorText, "text/plain", authority), PHTTPSpace::Overwrite);
// set up the HTTP port for listening & start the first HTTP thread
if (ListenForHTTP(httpPort))
PSYSTEMLOG(Info, "Opened master socket for HTTP: " << httpListeningSocket->GetPort());
else {
PSYSTEMLOG(Fatal, "Cannot run without HTTP port: " << httpListeningSocket->GetErrorText());
return FALSE;
}
PSYSTEMLOG(Info, "Service " << GetName() << ' ' << initMsg);
return TRUE;
}
void OpenMCU::OnConfigChanged()
{
}
PString OpenMCU::GetNewRoomNumber()
{
static PAtomicInteger number = 100;
return PString(PString::Unsigned, ++number);
}
void OpenMCU::LogMessage(const PString & str)
{
static PMutex logMutex;
static PTextFile logFile;
PTime now;
PString msg = now.AsString("dd/MM/yyyy") & str;
logMutex.Wait();
if (!logFile.IsOpen()) {
logFile.Open(logFilename, PFile::ReadWrite);
logFile.SetPosition(0, PFile::End);
}
logFile.WriteLine(msg);
logFile.Close();
logMutex.Signal();
}
//////////////////////////////////////////////////////////////////////////////////////////////
void MyConferenceManager::OnMemberJoining(Conference * conf, ConferenceMember * member)
{
if (!PIsDescendant(member, ConferenceFileMember))
conf->AddMember(new ConferenceFileMember(OpenMCU::Current().GetEnteringWAVFile(), PFile::ReadOnly));
}
void MyConferenceManager::OnMemberLeaving(Conference * conf, ConferenceMember * member)
{
if (!PIsDescendant(member, ConferenceFileMember))
conf->AddMember(new ConferenceFileMember(OpenMCU::Current().GetLeavingWAVFile(), PFile::ReadOnly));
}
//////////////////////////////////////////////////////////////////////////////////////////////
void SpliceMacro(PString & text, const PString & token, const PString & value)
{
PRegularExpression RegEx("<?!--#status[ \t\r\n]+" + token + "[ \t\r\n]*-->?",
PRegularExpression::Extended|PRegularExpression::IgnoreCase);
PINDEX pos, len;
while (text.FindRegEx(RegEx, pos, len))
text.Splice(value, pos, len);
}
PCREATE_SERVICE_MACRO_BLOCK(RoomStatus,P_EMPTY,P_EMPTY,block)
{
return OpenMCU::Current().GetEndpoint().GetRoomStatus(block);
}
MainStatusPage::MainStatusPage(OpenMCU & _app, PHTTPAuthority & auth)
: PServiceHTTPString("Status", "", "text/html; charset=UTF-8", auth),
app(_app)
{
PHTML html;
html << PHTML::Title("OpenH323 MCU Status")
<< "<meta http-equiv=\"Refresh\" content=\"30\">\n"
<< PHTML::Body()
<< app.GetPageGraphic()
<< PHTML::Paragraph() << "<center>"
//<< PHTML::Form("POST")
<< PHTML::TableStart("border=1")
<< PHTML::TableRow()
<< PHTML::TableHeader()
<< " Room Name "
<< PHTML::TableHeader()
<< " Room Members "
<< PHTML::TableHeader()
<< "<!--#macrostart RoomStatus-->"
<< PHTML::TableRow()
<< PHTML::TableData()
<< "<!--#status RoomName-->"
<< PHTML::TableData()
<< "<!--#status RoomMembers-->"
<< "<!--#macroend RoomStatus-->"
<< PHTML::TableEnd()
<< PHTML::Paragraph()
//<< PHTML::Form()
<< PHTML::HRule()
<< app.GetCopyrightText()
<< PHTML::Body();
string = html;
}
BOOL MainStatusPage::Post(PHTTPRequest & request,
const PStringToString & data,
PHTML & msg)
{
/*
PTRACE(2, "VGGK\tClear call POST received " << data);
msg << PHTML::Title() << "Accepted Control Command" << PHTML::Body()
<< PHTML::Heading(1) << "Accepted Control Command" << PHTML::Heading(1);
PWaitAndSignal m(app.endpointMutex);
std::vector<MyGatekeeperH323EndPoint *>::iterator r;
for (r = app.endpointList.begin(); r != app.endpointList.end(); ++r) {
if ((*r)->OnPostControl(data, msg))
msg << PHTML::Heading(2) << "No calls or endpoints!" << PHTML::Heading(2);
PServiceHTML::ProcessMacros(request, msg, "html/status.html",
PServiceHTML::LoadFromFile|PServiceHTML::NoSignatureForFile);
}
msg << PHTML::Paragraph()
<< PHTML::HotLink(request.url.AsString()) << "Reload page" << PHTML::HotLink()
<< " "
<< PHTML::HotLink("/") << "Home page" << PHTML::HotLink();
*/
return TRUE;
}
///////////////////////////////////////////////////////////////
MyH323EndPoint::MyH323EndPoint()
{
#if 0
terminalType = e_MCUWithAudioMP;
#ifndef NO_MCU_VIDEO
terminalType = e_MCUWithAVMP;
#endif
conferenceListener = NULL;
#endif
}
void MyH323EndPoint::Initialise(PConfig & cfg, PConfigPage * rsrc)
{
// Local alias name for H.323 endpoint
SetLocalUserName(cfg.GetString(LocalUserNameKey, "OpenH323 MCU v" + OpenMCU::Current().GetVersion()));
// Gatekeeper mode
PStringArray labels(GKMODE_LABEL_COUNT, GKModeLabels);
PINDEX idx = labels.GetStringsIndex(cfg.GetString(GatekeeperModeKey, labels[0]));
PINDEX gkMode = (idx == P_MAX_INDEX) ? 0 : idx;
rsrc->Add(new PHTTPRadioField(GatekeeperModeKey, labels, gkMode));
// Gatekeeper password
PString gkName = cfg.GetString(GatekeeperKey);
// Gatekeeper password
PString gkPassword = PHTTPPasswordField::Decrypt(cfg.GetString(GatekeeperPasswordKey));
SetGatekeeperPassword(gkPassword);
// start the H.323 listeners
PString defaultInterface = "*:1720";
H323TransportAddressArray interfaces;
PINDEX arraySize = cfg.GetInteger(PString(InterfaceKey) + " Array Size");
if (arraySize == 0)
StartListener(defaultInterface);
else {
for (int i = 0; i < arraySize; i++)
interfaces.Append(new H323TransportAddress(cfg.GetString(psprintf("%s %u", InterfaceKey, i+1), "")));
StartListeners(interfaces);
}
rsrc->Add(new PHTTPFieldArray(new PHTTPStringField(cfg.GetDefaultSection() + "\\" + InterfaceKey, InterfaceKey, 20, defaultInterface), FALSE));
if (listeners.IsEmpty()) {
PSYSTEMLOG(Fatal, "Main\tCould not open H.323 Listener");
}
/*
if (args.GetOptionString('q').IsEmpty()) {
endpoint.behind_masq = FALSE;
} else {
endpoint.masqAddressPtr = new PIPSocket::Address(args.GetOptionString('q'));
endpoint.behind_masq = TRUE;
cout << "Masquerading as address " << *(endpoint.masqAddressPtr) << endl;
}
*/
AddAllCapabilities(0, 0, "*");
// disable codecs as required
PString disableCodecs = cfg.GetString(DisableCodecsKey);
rsrc->Add(new PHTTPStringField(DisableCodecsKey, 50, disableCodecs));
if (!disableCodecs.IsEmpty()) {
PStringArray toRemove = disableCodecs.Tokenise(' ', FALSE);
capabilities.Remove(toRemove);
}
#ifndef NO_MCU_VIDEO
EnableVideoReception(args.HasOption('v'));
int videoTxQual = 10;
if (args.HasOption("videotxquality"))
videoTxQual = args.GetOptionString("videotxquality").AsInteger();
endpoint.videoTxQuality = PMAX(1, PMIN(31, videoTxQual));
int videoF = 2;
if (args.HasOption("videofill"))
videoF = args.GetOptionString("videofill").AsInteger();
endpoint.videoFill = PMAX(1, PMIN(99, videoF));
int videoFPS = 10;
if (args.HasOption("videotxfps"))
videoFPS = args.GetOptionString("videotxfps").AsInteger();
endpoint.videoFramesPS = PMAX(1,PMIN(30,videoFPS));
int videoBitRate = 0; //disable setting videoBitRate.
if (args.HasOption("videobitrate")) {
videoBitRate = args.GetOptionString("videobitrate").AsInteger();
videoBitRate = 1024 * PMAX(16, PMIN(2048, videoBitRate));
}
endpoint.videoBitRate = videoBitRate;
if (args.HasOption('v')) {
#if H323_AVCODEC
PTRACE(3, "Video bitrate for ffmpeg h263 is " << videoBitRate);
if (endpoint.videoLarge)
endpoint.SetCapability(0, 1, new H323_FFH263Capability(0, 0, 1, 0, 0, videoBitRate, videoFPS));
endpoint.SetCapability(0, 1, new H323_FFH263Capability(0, 1, 0, 0, 0, videoBitRate, videoFPS));
#endif
#if H323_VICH263
PTRACE(3, "Video bitrate for vic h263 is " << videoBitRate);
if (endpoint.videoLarge)
endpoint.SetCapability(0, 1, new H323_H263Capability(0, 0, 1, 0, 0, videoBitRate, videoFPS));
endpoint.SetCapability(0, 1, new H323_H263Capability(0, 1, 0, 0, 0, videoBitRate, videoFPS));
#endif
}
#endif /*End of test on NO_MCU_VIDEO */
AddAllUserInputCapabilities(0, 2);
switch (gkMode) {
default:
case Gatekeeper_None:
break;
case Gatekeeper_Find:
if (!DiscoverGatekeeper(new H323TransportUDP(*this)))
PSYSTEMLOG(Error, "No gatekeeper found");
break;
case Gatekeeper_Explicit:
if (!SetGatekeeper(gkName, new H323TransportUDP(*this)))
PSYSTEMLOG(Error, "Error registering with gatekeeper at \"" << gkName << '"');
}
PTRACE(2, "MCU\tCodecs (in preference order):\n" << setprecision(2) << GetCapabilities());;
}
H323Connection * MyH323EndPoint::CreateConnection(unsigned callReference)
{
return new OpenMCUH323Connection(*this, callReference);
}
void MyH323EndPoint::TranslateTCPAddress(PIPSocket::Address &localAddr, const PIPSocket::Address &remoteAddr)
{
// if (this->behind_masq &&
// (remoteAddr.Byte1() != 192)) {
// localAddr = *(this->masqAddressPtr);
// }
return;
}
PString MyH323EndPoint::GetRoomStatus(const PString & block)
{
PString substitution;
PWaitAndSignal m(conferenceManager.GetConferenceListMutex());
ConferenceList & conferenceList = conferenceManager.GetConferenceList();
ConferenceList::iterator r;
for (r = conferenceList.begin(); r != conferenceList.end(); ++r) {
// make a copy of the repeating html chunk
PString insert = block;
PStringStream members;
members << "<table border=1>"
"<tr>"
"<th>"
" Name "
"</th><th>"
" Duration "
"</th><th>"
" Codec "
"</th><th>"
" Packets/Bytes tx "
"</th><th>"
" Packets/Bytes rx "
"</th></tr>";
Conference & conference = *(r->second);
size_t memberListSize = 0;
{
PWaitAndSignal m(conference.GetMutex());
Conference::MemberList & memberList = conference.GetMemberList();
memberListSize = memberList.size();
Conference::MemberList::const_iterator s;
for (s = memberList.begin(); s != memberList.end(); ++s) {
if (PIsDescendant(s->second, H323Connection_ConferenceMember)) {
OpenMCUH323Connection & conn = *((H323Connection_ConferenceMember *)(s->second))->GetConnection();
PTime now;
PTime callStart = conn.GetConnectionStartTime();
RTP_Session * session = conn.GetSession(RTP_Session::DefaultAudioSessionID);
members << "<tr>"
"<td>"
<< conn.GetRemotePartyName()
<< "</td><td>"
<< (now -callStart) << " mins"
<< "</td><td>"
<< conn.GetAudioTransmitCodecName() << '/' << conn.GetAudioReceiveCodecName()
<< "</td><td>"
<< session->GetPacketsSent() << '/' << session->GetOctetsSent()
<< "</td><td>"
<< session->GetPacketsReceived() << '/' << session->GetOctetsReceived()
<< "</td></tr>";
}
}
}
members << "</table>";
SpliceMacro(insert, "RoomName", conference.GetNumber());
SpliceMacro(insert, "RoomMemberCount", PString(PString::Unsigned, (long)memberListSize));
SpliceMacro(insert, "RoomMembers", members);
substitution += insert;
}
return substitution;
}
Conference * MyH323EndPoint::ConferenceRequest(H323Connection & connection,
const H323SignalPDU & setupPDU,
H323Connection::AnswerCallResponse & response)
{
const H225_Setup_UUIE & setup = setupPDU.m_h323_uu_pdu.m_h323_message_body;
// get the conference ID from the incoming call
OpalGloballyUniqueID conferenceID = setup.m_conferenceID;
// we accept creates and joins. Invites come later :)
if (setup.m_conferenceGoal.GetTag() != H225_Setup_UUIE_conferenceGoal::e_create &&
setup.m_conferenceGoal.GetTag() != H225_Setup_UUIE_conferenceGoal::e_join) {
PTRACE(3, "MCU\tUnknown conference goal " << setup.m_conferenceGoal.GetTag());
response = H323Connection::AnswerCallDenied;
return NULL;
}
// see if the destination address or called party matches the name of a room
PString userSpecifiedRoom;
BOOL userSpecifiedRoomFound = FALSE;
if (setup.m_destinationAddress.GetSize() > 0) {
PINDEX i;
for (i = 0; !userSpecifiedRoomFound && (i < setup.m_destinationAddress.GetSize()); i++) {
PString roomNumber = H323GetAliasAddressString(setup.m_destinationAddress[i++]);
if (userSpecifiedRoom.IsEmpty())
userSpecifiedRoom = roomNumber;
userSpecifiedRoomFound = conferenceManager.HasConference(roomNumber);
}
PString calledPartyNumber;
if (!userSpecifiedRoomFound &&
setupPDU.GetQ931().GetCalledPartyNumber(calledPartyNumber) &&
!calledPartyNumber.IsEmpty()) {
if (userSpecifiedRoom.IsEmpty())
userSpecifiedRoom = calledPartyNumber;
userSpecifiedRoomFound = conferenceManager.HasConference(calledPartyNumber);
}
}
// join an existing conference if one matches the ID
if (conferenceManager.HasConference(conferenceID)) {
// there is a small chance that the conference has disappeared since the HasConference above
// so always use a new valid room number when creating the conference
Conference * conference = conferenceManager.MakeConference(conferenceID, OpenMCU::Current().GetNewRoomNumber(), "");
PTRACE(3, "MCU\tJoining conference by ID " << conference->GetID());
response = H323Connection::AnswerCallNow;
return conference;
}
// if we are doing a join, then we joining by ID failed
if (setup.m_conferenceGoal.GetTag() == H225_Setup_UUIE_conferenceGoal::e_join) {
PTRACE(3, "MCU\tJoin request for conference with unknown ID " << conferenceID);
response = H323Connection::AnswerCallDeniedByInvalidCID;
return NULL;
}
// if a room exists with the called party name, use that room even though the conference IDs don't match
if (userSpecifiedRoomFound) {
Conference * conference = conferenceManager.MakeConference(userSpecifiedRoom, "");
PTRACE(3, "MCU\tJoining conference " << conference->GetID() << " specified by number " << userSpecifiedRoom);
response = H323Connection::AnswerCallNow;
return conference;
}
// if no room exists with the called party name, then create a new room by that name and with the specified conference ID
if (!userSpecifiedRoom.IsEmpty()) {
Conference * conference = conferenceManager.MakeConference(conferenceID, userSpecifiedRoom, "");
PTRACE(3, "MCU\tCreating new conference " << conference->GetID() << " with number " << userSpecifiedRoom);
response = H323Connection::AnswerCallNow;
return conference;
}
// determine if the endpoint supports conference lists
// ver 2 clients are supposed to handle conference lists, but we know NM does not
// and we know OpenH323 apps prior to 1.14 cannot handle lists either
BOOL canDoConferenceList = connection.GetSignallingVersion() >= 2;
PString appInfo = connection.GetRemoteApplication();
if (appInfo.Find("NetMeeting") != P_MAX_INDEX)
canDoConferenceList = FALSE;
else if (appInfo.Find("OpenH323") != P_MAX_INDEX) {
PINDEX pos = appInfo.Find("OpenH323 ");
if (pos != P_MAX_INDEX) {
PStringArray version = appInfo.Mid(pos+9).Tokenise('.');
if (!(version.GetSize() == 3 &&
version[0].AsUnsigned() >= 1 &&
version[1].AsUnsigned() >= 14 &&
version[2].AsUnsigned() >= 0))
canDoConferenceList = FALSE;
}
}
#if 0
// if no conference, and no default room, then send the terminal a conference list if it can understand it
if (canDoConferenceList) {
H323SignalPDU facilityPDU;
H225_Facility_UUIE & pdu = *facilityPDU.BuildFacility(connection, FALSE);
pdu.RemoveOptionalField(H225_Facility_UUIE::e_callIdentifier);
pdu.RemoveOptionalField(H225_Facility_UUIE::e_multipleCalls);
pdu.RemoveOptionalField(H225_Facility_UUIE::e_maintainConnection);
pdu.m_reason = H225_FacilityReason::e_conferenceListChoice;
pdu.IncludeOptionalField(H225_Facility_UUIE::e_conferences);
H225_ArrayOf_ConferenceList & conferenceList = pdu.m_conferences;
conferenceList.SetSize(0);
{
PWaitAndSignal m(conferenceManager.GetConferenceListMutex());
ConferenceList & roomList = conferenceManager.GetConferenceList();
ConferenceList::const_iterator r;
PINDEX i = 0;
for (r = roomList.begin(); r != roomList.end(); ++r) {
Conference & conf = *(r->second);
if (conf.IsVisible()) {
conferenceList.SetSize(conferenceList.GetSize()+1);
H225_ConferenceList & conference = conferenceList[i++];
conference.IncludeOptionalField(H225_ConferenceList::e_conferenceID);
conference.m_conferenceID = conf.GetID();
conference.IncludeOptionalField(H225_ConferenceList::e_conferenceAlias);
H323SetAliasAddress(conf.GetName(), conference.m_conferenceAlias, -1);
}
}
}
PTRACE(3, "MCU\tSending conference list");
// if the PDU write fails, then close the call
if (!connection.WriteSignalPDU(facilityPDU))
response = H323Connection::AnswerCallDenied;
else
response = H323Connection::AnswerCallDeferred;
return NULL;
}
#endif
// if there is a room to create, then join this call to that conference
PString roomToCreate = OpenMCU::Current().GetDefaultRoomName();
if (!roomToCreate.IsEmpty()) {
Conference * conference = conferenceManager.MakeConference(roomToCreate, "");
PTRACE(3, "MCU\tCreating/joining call to default room " << roomToCreate);
response = H323Connection::AnswerCallNow;
return conference;
}
PTRACE(3, "MCU\tRefusing call because no room specified, and no default room");
response = H323Connection::AnswerCallDenied;
return NULL;
}
///////////////////////////////////////////////////////////////
OpenMCUH323Connection::OpenMCUH323Connection(MyH323EndPoint & _ep, unsigned callReference)
: H323Connection(_ep, callReference), ep(_ep), connected(FALSE)
{
incomingAudio = NULL;
outgoingAudio = NULL;
conference = NULL;
conferenceMember = NULL;
sendingAllowed = TRUE;
audioReceiveCodecName = audioTransmitCodecName = "none";
#ifndef NO_MCU_VIDEO
incomingVideo = NULL;
outgoingVideo = NULL;
videoReceiveCodecName = videoTransmitCodecName = "none";
#endif
connected = FALSE;
cout << "Opening connection" << endl;
}
OpenMCUH323Connection::~OpenMCUH323Connection()
{
cout << "Closing connection" << endl;
}
void OpenMCUH323Connection::CleanUpOnCallEnd()
{
if (conference != NULL && conferenceMember != NULL) {
LogCall();
conferenceMember->WaitForClose();
ConferenceMember * cmToDelete = conferenceMember;
conferenceMember = NULL;
if (conference->RemoveMember(cmToDelete))
ep.GetConferenceManager().RemoveConference(conference->GetID());
delete cmToDelete;
}
H323Connection::CleanUpOnCallEnd();
}
H323Connection::AnswerCallResponse OpenMCUH323Connection::OnAnswerCall(const PString & /*caller*/,
const H323SignalPDU & setupPDU,
H323SignalPDU & /*connectPDU*/)
{
H323Connection::AnswerCallResponse response;
conference = ep.ConferenceRequest(*this, setupPDU, response);
if (conference == NULL)
return response;
connected = TRUE;
conferenceMember = new H323Connection_ConferenceMember(this);
conference->AddMember(conferenceMember);
return AnswerCallNow;
}
BOOL OpenMCUH323Connection::OnSendSignalSetup( H323SignalPDU & callProceedingPDU)
{
// We are making a connection to a remote EP so add this connection to
// the list of rooms and members.
// We will add them to the default room as we have no method of
// specifying which room our connection should join.
connected = TRUE;
conference = NULL;
PTRACE(3, "Conference\tOutgoing connection");
return H323Connection::OnSendSignalSetup( callProceedingPDU );
}
BOOL OpenMCUH323Connection::OpenAudioChannel(BOOL isEncoding, unsigned /* bufferSize */, H323AudioCodec & codec)
{
PString codecName= codec.GetMediaFormat();
codec.SetSilenceDetectionMode( H323AudioCodec::NoSilenceDetection );
// if (!codec.IsDescendant(H323_GSM0610Codec::Class()) &&
// - need to add MS-GSM here along with any other codecs
// !codec.IsDescendant(H323_muLawCodec::Class())) {
// cerr << "Unknown codec \"" << codecName << endl;
// return FALSE;
// }
if ((incomingAudio == NULL) && sendingAllowed) {
incomingAudio = new IncomingAudio(ep, *this);
}
if (outgoingAudio == NULL) {
outgoingAudio = new OutgoingAudio(ep, *this);
PFilePath fn = OpenMCU::Current().GetConnectingWAVFile();
PTRACE(4, "MCU\tOpening outgoing audio with file " << fn);
playFile.Open(fn, PFile::ReadOnly);
}
if (isEncoding) {
audioTransmitCodecName = codecName;
codec.AttachChannel(outgoingAudio, TRUE);
} else if (sendingAllowed) {
audioReceiveCodecName = codecName;
codec.AttachChannel(incomingAudio, TRUE);
}
return TRUE;
}
#ifndef NO_MCU_VIDEO
BOOL OpenMCUH323Connection::OpenVideoChannel(BOOL isEncoding,
H323VideoCodec & codec)
{
PStringStream codecName;
codecName << codec;
PWaitAndSignal mutex(videoMutex);
if (isEncoding) {
if (outgoingVideo == NULL) {
outgoingVideo = new OutgoingVideo(ep, *this, ep.videoFramesPS, ep.videoLarge);
codec.AttachChannel(outgoingVideo,TRUE);
// outgoingVideo->SetFrameSize(352>>1,288>>1);
}
/*At last. Modularity. The video codec is told the parameters of video compresion/decompression.
The only thing the video codec knows about the ouside world is how to acquire/render data, which
is via the video channel, provided by the OutgoingVideo class.
The codec does provide a second interface, through which ethernet packets enter (or leave) */
videoTransmitCodecName = codecName;
codec.SetTxQualityLevel(ep.videoTxQuality);
codec.SetBackgroundFill(ep.videoFill);
if (ep.videoBitRate != 0)
codec.SetMaxBitRate(ep.videoBitRate);
} else {
if ((incomingVideo == NULL) && sendingAllowed)
incomingVideo = new IncomingVideo(ep, *this);
codec.AttachChannel(incomingVideo,TRUE);
videoReceiveCodecName = codecName;
}
return TRUE;
}
#endif
void OpenMCUH323Connection::OnUserInputString(const PString & str)
{
if (conferenceMember != NULL)
conferenceMember->SendUserInputIndication(str);
}
BOOL OpenMCUH323Connection::OnIncomingAudio(const void * buffer, PINDEX amount)
{
if (conferenceMember != NULL)
conferenceMember->WriteAudio(buffer, amount);
return TRUE;
}
BOOL OpenMCUH323Connection::OnOutgoingAudio(void * buffer, PINDEX amount)
{
if (playFile.IsOpen()) {
if (!playFile.Read(buffer, amount)) {
PTRACE(4, "MCU\tFinished playing file");
playFile.Close();
} else {
int len = playFile.GetLastReadCount();
if (len < amount) {
memset(((BYTE *)buffer)+len, 0, amount-len);
}
return TRUE;
}
}
if (conferenceMember != NULL)
conferenceMember->ReadAudio(buffer, amount);
return TRUE;
}
#ifndef NO_MCU_VIDEO
BOOL OpenMCUH323Connection::OnOutgoingVideo(void * buffer, PINDEX & amount)
{
peerList.OnOutgoingVideo(buffer, amount);
}
BOOL OpenMCUH323Connection::OnIncomingVideo(const void * buffer, PINDEX amount)
{
return conference->OnIncomingVideo(buffer, amount);
}
#endif
///////////////////////////////////////////////////////////////
void OpenMCUH323Connection::LogCall(const BOOL accepted)
{
H323TransportAddress address = GetControlChannel().GetRemoteAddress();
PIPSocket::Address ip;
WORD port;
PStringStream stringStream, timeStream;
address.GetIpAndPort(ip, port);
timeStream << GetConnectionStartTime().AsString("hh:mm:ss");
stringStream << ' ' << "caller-ip:" << ip << ':' << port << ' '
<< GetRemotePartyName()
<< " room:" << ((conference != NULL) ? conference->GetNumber() : PString());
if (accepted) {
PStringStream connectionDuration;
connectionDuration << setprecision(0) << setw(5) << (PTime() - GetConnectionStartTime());
OpenMCU::Current().LogMessage(timeStream + stringStream + " connection duration:" + connectionDuration);
}
else
OpenMCU::Current().LogMessage(timeStream + " Call denied:" + stringStream);
}
///////////////////////////////////////////////////////////////
OutgoingAudio::OutgoingAudio(H323EndPoint & _ep, OpenMCUH323Connection & _conn)
: ep(_ep), conn(_conn)
{
os_handle = 0;
}
void OutgoingAudio::CreateSilence(void * buffer, PINDEX amount)
{
memset(buffer, 0, amount);
lastReadCount = amount;
}
BOOL OutgoingAudio::Read(void * buffer, PINDEX amount)
{
PWaitAndSignal mutexR(audioChanMutex);
if (!IsOpen())
return FALSE;
if (!delay.Delay(amount / 16)) {
// do the read call here, by calling conn.OnOutgoingAudio():
BOOL doSilence = !conn.OnOutgoingAudio(buffer, amount);
if (doSilence)
CreateSilence(buffer, amount);
}
lastReadCount = amount;
return TRUE;
}
BOOL OutgoingAudio::Close()
{
if (!IsOpen())
return FALSE;
PWaitAndSignal mutexC(audioChanMutex);
os_handle = -1;
return TRUE;
}
///////////////////////////////////////////////////////////////////////////
IncomingAudio::IncomingAudio(H323EndPoint & _ep, OpenMCUH323Connection & _conn)
: ep(_ep), conn(_conn)
{
os_handle = 0;
}
BOOL IncomingAudio::Write(const void * buffer, PINDEX amount)
{
PWaitAndSignal mutexW(audioChanMutex);
if (!IsOpen())
return FALSE;
if (!delay.Delay(amount / 16))
conn.OnIncomingAudio(buffer, amount);
return TRUE;
}
BOOL IncomingAudio::Close()
{
if (!IsOpen())
return FALSE;
PWaitAndSignal mutexA(audioChanMutex);
os_handle = -1;
return TRUE;
}
///////////////////////////////////////////////////////////////////////////
// End of File ///////////////////////////////////////////////////////////////
syntax highlighted by Code2HTML, v. 0.9.1