/* * 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 #include #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() << "
" << 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 = "" "Current Proxies: \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("?", 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") << "\n" << PHTML::Body() << app.GetPageGraphic() << PHTML::Paragraph() << "
" //<< PHTML::Form("POST") << PHTML::TableStart("border=1") << PHTML::TableRow() << PHTML::TableHeader() << " Room Name " << PHTML::TableHeader() << " Room Members " << PHTML::TableHeader() << "" << PHTML::TableRow() << PHTML::TableData() << "" << PHTML::TableData() << "" << "" << 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::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 << "" "" ""; 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 << "" ""; } } } members << "
" " Name " "" " Duration " "" " Codec " "" " Packets/Bytes tx " "" " Packets/Bytes rx " "
" << conn.GetRemotePartyName() << "" << (now -callStart) << " mins" << "" << conn.GetAudioTransmitCodecName() << '/' << conn.GetAudioReceiveCodecName() << "" << session->GetPacketsSent() << '/' << session->GetOctetsSent() << "" << session->GetPacketsReceived() << '/' << session->GetOctetsReceived() << "
"; 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 ///////////////////////////////////////////////////////////////