/* * main.cxx * * A simple H.323 MCU * * Copyright (c) 2000 Equivalence Pty. Ltd. * * 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 are Copyright (C) 1993 Free Software Foundation, Inc. * All Rights Reserved. * * Contributor(s): Derek J Smithies (derek@indranet.co.nz) * ------------------------------ * * $Log: main.cxx,v $ * Revision 1.59 2002/12/16 08:57:12 robertj * Fixed problem with spoken list crashing with video, thanks Bob Lindell * * Revision 1.58 2002/11/21 07:58:32 robertj * Fixed stupid mistake in removing make call function. * * Revision 1.57 2002/11/21 07:54:52 robertj * Removed redundent and incorrect function for making outgoing call. * * Revision 1.56 2002/11/13 10:23:57 rogerh * Enable Speex codec by default, now it is part of OpenH323. * * Revision 1.55 2002/11/10 08:12:42 robertj * Moved constants for "well known" ports to better place (OPAL change). * * Revision 1.54 2002/10/31 00:55:42 robertj * Enhanced jitter buffer system so operates dynamically between minimum and * maximum values. Altered API to assure app writers note the change! * * Revision 1.53 2002/10/16 10:01:28 rogerh * Add Speex 8k and 15k codecs. * * Revision 1.52 2002/06/14 08:05:08 dereks * Added tweak to support operation behind a NAT that forwards ports * This will use TranslateTCPAddress to change the address reported * to connections that occur from outside the 192. subnet. Thanks Sahai. * * * Revision 1.51 2002/04/18 10:54:34 rogerh * Fix bug in audio mixing reportde by Bob Lindell * AudioBuffer::Read() had been optimised to do reading and mixing and * contained memset()'s which were incorrect. Clean up the code with * a proper Read() method and an optimised ReadAndMix() method. * * Revision 1.50 2002/04/06 00:54:43 rogerh * Clean up the videoBufferSize code * * Revision 1.49 2002/04/06 00:15:14 rogerh * Fix bugs in the Clear() method. * * Revision 1.48 2002/02/17 08:46:20 rogerh * Add simple wildcard of "*" for --audio-loopback to enable loopback * in all rooms * * Revision 1.47 2002/02/17 08:30:17 rogerh * The new openh323 library checks for getLastReadCount * * Revision 1.46 2002/02/07 12:19:15 rogerh * add --listenport option. Requested by Damien * * Revision 1.45 2002/01/16 10:19:45 rogerh * Tidy code and screen alignment * * Revision 1.44 2001/12/02 08:28:39 rogerh * Update help for audio-loopback * * Revision 1.43 2001/12/02 08:17:57 rogerh * Change --audio-loopback to take a room name. Only connections to the * specified room will have audio loopback applied. The remainder of the rooms * in OpenMCU will work as normal. * Submitted by Malcolm Caldwell * I hard coded room "loopback" as a loopback room regardless of * the --audio-loopback parameter. * * Revision 1.42 2001/11/27 12:33:56 rogerh * Fix bug in statictics which caused a core dump * * Revision 1.41 2001/11/22 13:06:38 rogerh * Add --audio-loopback. Users of OpenMCU can hear their own voice echoed back * which is very handy for testing purposes. * * Revision 1.40 2001/08/27 03:47:39 robertj * Changed if statement with constant expression to be #ifdef. * * Revision 1.39 2001/08/24 13:48:01 rogerh * Delete the listener thread if StartListener() fails. * * Revision 1.38 2001/08/23 07:54:09 rogerh * Add code which allows a user to hear their own audio echo back from the MCU * This needs to be enabled at compile time with the HEAR_OWN_AUDIO define. * * Revision 1.37 2001/07/23 03:55:13 rogerh * Seperate out codec names for audio and video * * Revision 1.36 2001/07/23 03:28:03 rogerh * Display the codec name in the statistics page * * Revision 1.35 2001/07/17 11:49:38 rogerh * Change title, Mcu -> MCU * * Revision 1.34 2001/07/17 10:57:45 rogerh * Add LPC-10 codec support, from Santiago Garcia Mantinan * * Revision 1.33 2001/07/12 06:18:27 rogerh * Add G711 A-Law codec * * Revision 1.32 2001/06/28 06:08:17 rogerh * Fix compilation warning * * Revision 1.31 2001/06/13 06:01:35 rogerh * Add some comments * * Revision 1.30 2001/06/13 05:47:19 rogerh * Fix the Make Call menu option which was broken when multiple rooms were * added. Calls made from OpenMCU are added to the default room name. * Add the Microsoft GSM codec to the codecs list. * * Revision 1.29 2001/05/31 17:01:52 rogerh * Fixes from Dan Johansson to make video work. * Go back to using 'closed' for the Video Classes. This is needed as * the the video classes come from PVideoChannel which does not use os_handle * in its IsOpen() method. Instead, we must define our own IsOpen() method. * Also, back out the size of the image change. * Finally, add a new feature. For the first 4 connections, video from an * endpoint is displayed immediatly rather than waiting until that ep sends * some audio. (handy for endpoints with video only and with no talking) * * Revision 1.28 2001/05/31 14:29:29 rogerh * Add --disable-menu to OpenMCU * * Revision 1.27 2001/05/17 22:07:51 dereks * fixed calculation of the number of bytes in the image. * * Revision 1.26 2001/05/08 13:43:11 rogerh * Connections without a room name will now join the default room (room101) * Handy for NetMeeting users who cannot specify the room to OpenMCU. * Add --defaultroom to change the default room name. Add --no-defaultroom to * prevent use of the default room and to reject calls without a room name. * * Revision 1.25 2001/05/08 11:30:39 rogerh * clean up display * * Revision 1.24 2001/05/08 10:47:07 rogerh * Handle the removal of members correctly. * * Revision 1.23 2001/05/04 08:07:49 rogerh * Improve stats when there are no connections and fix nested mutex bug * * Revision 1.22 2001/03/20 23:34:33 robertj * Added creating of room member lists on first use of room. * Used the new PTrace::Initialise function for starting trace code. * * Revision 1.21 2001/03/18 07:40:45 robertj * Fixed MSVC compatibility. * * Revision 1.20 2001/03/18 06:50:20 robertj * More changes for multiple conferences from Patrick Koorevaar. * * Revision 1.19 2001/03/06 04:32:50 robertj * Fixed another error in previous update, variable declared twice. * * Revision 1.18 2001/03/05 22:36:22 robertj * Changes for logging and multiple conferences from Patrick Koorevaar. * * Revision 1.17 2001/02/19 05:04:18 robertj * Added more fixes on shutdown, thanks Paul E. Zaremba. * * Revision 1.16 2001/02/09 06:09:42 robertj * Added fix for crashing on exit problem, thanks Dhomin. * * Revision 1.15 2001/02/08 07:06:37 robertj * Added 'm' command to make call, thanks Paul Zaremba. * Added ability to send CIF size images, thanks again Paul Zaremba. * * Revision 1.14 2001/01/04 01:35:43 dereks * Add user interface thread to openmcu. * tidy up the exiting process, but it is still in need of work. * * Revision 1.13 2001/01/03 03:59:26 dereks * Adjusted algorithm for selecting which corners contain which video stream. * Add gsmframes and g711frames option. Add documentation describing data flows. * * Revision 1.12 2000/12/22 08:28:23 dereks * Optimise video handling, and reduce load on mcu computer * Include noise detection routine, to determine which images are displayed when > 4 connections. * * Revision 1.11 2000/12/19 22:41:44 dereks * Add video conferencing - limited to 5 connected nodes. * Use the video channel facility now in openh323 and pwlib modules * Add simple interface to handle commands entered at the keyboard. * * Revision 1.10 2000/11/08 04:27:33 robertj * Fixed MSVC warnings. * * Revision 1.9 2000/11/03 06:38:07 craigs * Added conditional for windows until we decide what to do * * Revision 1.8 2000/11/02 03:33:41 craigs * Changed to provide some sort of software timeing loop * * Revision 1.7 2000/06/20 02:38:32 robertj * Changed H323TransportAddress to default to IP. * * Revision 1.6 2000/05/25 13:26:18 robertj * Fixed incorrect "save" parameter specification. * * Revision 1.5 2000/05/25 12:06:20 robertj * Added PConfigArgs class so can save program arguments to config files. * * Revision 1.4 2000/05/11 11:47:11 robertj * Fixed alpha linux GNU compiler problems. * * Revision 1.3 2000/05/11 09:54:02 robertj * Win32 compilation * * Revision 1.2 2000/05/10 08:11:57 craigs * Fixed copyrights and names * * Revision 1.1 2000/05/10 05:54:06 craigs * Initial version * */ #include #include #include #include "version.h" #include "mscodecs.h" #include "lpc10codec.h" #include "speexcodec.h" #ifndef NO_VIDEO #include "h261codec.h" #include "videoio.h" #endif #include "main.h" //#include "acmencr.h" PCREATE_PROCESS(OpenMcu); const WORD DefaultHTTPPort = 5719; static const char LogLevelKey[] = "PTrace Log Level"; static const char UserNameKey[] = "Admin User Name"; static const char PasswordKey[] = "Admin Password"; static const char HttpPortKey[] = "HTTP Listener Port"; static const char H323PortKey[] = "H.323 Listener Port"; static const char ListenInterfaceKey[] = "H.323 Listener Interface"; static const char LocalUserNameKey[] = "Local User Name"; static const char GatekeeperWantedKey[] = "Register with Gatekeeper?"; static const char GatekeeperNameKey[] = "Gatekeeper Name"; static const char GatekeeperNeededKey[] = "Gatekeeper Required"; static const char TranslateAddressKey[] = "NAT Translation Address"; static const char JitterKey[] = "Jitter ([min-]max)"; static const char G711FramesKey[] = "G.711 Frames"; static const char GSMFramesKey[] = "GSM Frames"; #ifndef NO_VIDEO static const char VideoEnableKey[] = "Enable H.261 Video"; static const char VideoLargeKey[] = "Enable Large (352x288) Video"; static const char VideoTxQualityKey[] = "Video Quality (1-31)"; static const char VideoFillKey[] = "Video Background Fill Rate (1-99)"; static const char VideoFramesSecKey[] = "Video Frames/sec (1-10"; #endif static const char DefaultRoomNameKey[] = "Default Room Name"; static const char NoDefaultRoomKey[] = "Disable Default Room"; static const char AudioLoopbackRoomsKey[] = "Users hear own voice in which rooms?"; static const char EndpointUsernameKey[] = "Endpoint Username"; static const char XMLRPCServer[] = "Config XMLRPC Server"; static const char XMLRPCSetupURL[] = "XMLRPC Setup URL"; static const char XMLRPCTeardownURL[] = "XMLRPC Teardown URL"; #define new PNEW // 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) PXMLRPC_STRUCT_BEGIN(ActionStruct) PXMLRPC_STRING (ActionStruct, PString, action0); PXMLRPC_STRING (ActionStruct, PString, action1); PXMLRPC_STRING (ActionStruct, PString, action2); PXMLRPC_STRING (ActionStruct, PString, action3); PXMLRPC_STRING (ActionStruct, PString, action4); PXMLRPC_STRING (ActionStruct, PString, action5); PXMLRPC_STRING (ActionStruct, PString, action6); PXMLRPC_STRING (ActionStruct, PString, action7); PXMLRPC_STRING (ActionStruct, PString, action8); PXMLRPC_STRING (ActionStruct, PString, action9); PXMLRPC_STRING (ActionStruct, PString, actionstar); PXMLRPC_STRING (ActionStruct, PString, actionhash); PXMLRPC_STRUCT_END() PXMLRPC_STRUCT_BEGIN(ResponseStruct) PXMLRPC_STRING (ResponseStruct, PString, answerCall); PXMLRPC_STRING (ResponseStruct, PString, privs); PXMLRPC_STRING (ResponseStruct, PString, roomID); PXMLRPC_STRUCT (ResponseStruct, ActionStruct, actions); PXMLRPC_STRUCT_END() /////////////////////////////////////////////////////////////// #ifdef LOGGING static PString DEFAULT_CALL_LOG = "c:\\mcu_log.txt"; static PMutex logMutex; static PTextFile logFile; static PFilePath logFilename = DEFAULT_CALL_LOG; static void LogMessage(const PString & str) { 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(); } static void LogCall(MyH323Connection& connection, const BOOL accepted = TRUE) { H323TransportAddress address = connection.GetControlChannel().GetRemoteAddress(); PIPSocket::Address ip; WORD port; PStringStream stringStream, timeStream; address.GetIpAndPort(ip, port); timeStream << connection.GetConnectionStartTime().AsString("hh:mm:ss"); stringStream << ' ' << "caller-ip:" << ip << ':' << port << ' ' << connection.GetRemotePartyName() << " room:" << connection.GetRoomID(); if (accepted) { PStringStream connectionDuration; connectionDuration << setprecision(0) << setw(5) << (PTime() - connection.GetConnectionStartTime()); LogMessage(timeStream + stringStream + " connection duration:" + connectionDuration); } else LogMessage(timeStream + " Call denied:" + stringStream); } #endif static PString QuoteHTML(PString original) { static PRegularExpression AmpRe("&"); static PRegularExpression LTRe("<"); static PRegularExpression GTRe(">"); PINDEX pos=0, sz=0; while (original.FindRegEx(AmpRe, pos, sz)) original.Splice("&", pos, sz); pos = 0; sz = 0; while (original.FindRegEx(GTRe, pos, sz)) original.Splice("<", pos, sz); pos = 0; sz = 0; while (original.FindRegEx(LTRe, pos, sz)) original.Splice(">", pos, sz); return original; } /////////////////////////////////////////////////////////////// PHTTPServiceProcess::Info mcuInfo = {"OpenMCU", "Anthony Baxter and OpenH323 Project", MAJOR_VERSION, MINOR_VERSION, PProcess::BUILD_TYPE, BUILD_NUMBER, __TIME__ __DATE__, {{ 0 }}, { NULL }, 0, {{ 0 }}, // Only relevent for commercial apps NULL, //HOME_PAGE NULL, //EMAIL "OpenMCU", NULL, // GIF HTML, use calculated from below "openmcu.gif", //GIF_NAME, 200, //GIF_WIDTH, 60 //GIF_HEIGHT }; OpenMcu::OpenMcu() : PHTTPServiceProcess(mcuInfo) { endpoint = NULL; } OpenMcu::~OpenMcu() { } void OpenMcu::OnControl() { } BOOL OpenMcu::OnStart() { cout << "OpenMcu OnStart called!" << endl; PTrace::SetStream(&cerr); PTrace::Initialise(4); endpoint = new MyH323EndPoint(); return PHTTPServiceProcess::OnStart(); } void OpenMcu::OnStop() { cout << "OpenMcu OnStop called!" << endl; delete endpoint ; PHTTPServiceProcess::OnStop(); } void OpenMcu::OnConfigChanged() { } void OpenMcu::Main() { cout << "openmcu::main" << endl; Suspend(); } BOOL OpenMcu::Initialise(const char *msg) { cout << GetName() << " " << msg << endl << " Version " << GetVersion(TRUE) << " by " << GetManufacturer() << " on " << GetOSClass() << ' ' << GetOSName() << " (" << GetOSVersion() << '-' << GetOSHardware() << ")\n"; //PArgList & args = GetArguments(); PConfig cfg("Parameters"); PTrace::SetLevel(cfg.GetInteger(LogLevelKey, PTrace::GetLevel())); // HTTP setup PString adminUserName = cfg.GetString(UserNameKey); PString adminPassword = PHTTPPasswordField::Decrypt(cfg.GetString(PasswordKey)); PHTTPSimpleAuth authority(GetName(), adminUserName, adminPassword); 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,1,5,PTrace::GetLevel())); cout << "endpoint init" << endl; PTRACE(0, "Ptrace output"); cout << "PTrace currently at " << PTrace::GetLevel() << endl; // more setting of options &c. if (!endpoint->Initialise(cfg, rsrc)) { cerr << "Endpoint Initialisation failed!" << endl; return FALSE; } PServiceHTML html("System Parameters"); rsrc->BuildHTML(html); html << "Submitting may have bad effects on connected users!" << endl; httpNameSpace.AddResource(rsrc, PHTTPSpace::Overwrite); // Add the status page httpNameSpace.AddResource(new MainStatusPage(*this, authority), PHTTPSpace::Overwrite); PHTML front; front << PHTML::Title("Welcome to OpenMCU") << PHTML::Body() << PHTML::Paragraph() << "
" << "Welcome" << PHTML::Paragraph() << "OpenMCU started at " << PTime() << "
" //<< PHTML::Paragraph() //<< PHTML::HotLink("Parameters") << "Parameters" << PHTML::HotLink() //<< PHTML::Paragraph() << PHTML::HotLink("Status") << "Status" << PHTML::HotLink() << PHTML::HRule() << PHTML::Body(); httpNameSpace.AddResource(new PServiceHTTPString("welcome.html", front), PHTTPSpace::Overwrite); if (ListenForHTTP(DefaultHTTPPort)) { cout << "Listening for HTTP on " << DefaultHTTPPort << endl; } else { cerr << "Couldn't listen for HTTP on " << DefaultHTTPPort << endl; } cout << "endpoint listen" << endl; endpoint->ListenForIncomingCalls(); endpoint->AwaitTermination(); cout << "GoodBye. " << endl; PThread::Current()->Sleep(2000); cout << "Finished 2 sec sleep" < 240) { cerr << "error: G.711 frame size must be in range 10 to 240" << endl; g711Frames = 30; } rsrc->Add(new PHTTPIntegerField(G711FramesKey, 10, 240, g711Frames)); int gsmFrames = cfg.GetInteger(GSMFramesKey, 4); if (gsmFrames < 1 || gsmFrames > 7) { cerr << "error: GSM frame size must be in range 1 to 7" << endl; gsmFrames = 4; } rsrc->Add(new PHTTPIntegerField(GSMFramesKey, 1, 7, gsmFrames)); H323Capability * gsm = new H323_GSM0610Capability; H323Capability * msgsm = new MicrosoftGSMAudioCapability; H323Capability * ulaw64 = new H323_G711Capability(H323_G711Capability::muLaw, H323_G711Capability::At64k); H323Capability * alaw64 = new H323_G711Capability(H323_G711Capability::ALaw, H323_G711Capability::At64k); H323Capability * lpc10 = new H323_LPC10Capability(*this); H323Capability * speex3 = new SpeexNarrow3AudioCapability(); // 8k H323Capability * speex5 = new SpeexNarrow5AudioCapability(); // 15k H323Capability * userinput = new H323_UserInputCapability( H323_UserInputCapability::BasicString); SetCapability(0, 0, userinput); SetCapability(0, 0, speex3); SetCapability(0, 0, speex5); SetCapability(0, 0, gsm); gsm->SetTxFramesInPacket(gsmFrames); SetCapability(0, 0, msgsm); msgsm->SetTxFramesInPacket(gsmFrames); SetCapability(0, 0, ulaw64); ulaw64->SetTxFramesInPacket(g711Frames); SetCapability(0, 0, alaw64); alaw64->SetTxFramesInPacket(g711Frames); SetCapability(0, 0, lpc10); /* TODO if (args.HasOption('j')) { unsigned minJitter; unsigned maxJitter; PStringArray delays = args.GetOptionString('j').Tokenise(",-"); if (delays.GetSize() == 1) { minJitter = delays[0].AsUnsigned(); maxJitter = delays[1].AsUnsigned(); } else { maxJitter = delays[0].AsUnsigned(); minJitter = PMIN(GetMinAudioJitterDelay(), maxJitter); } if (minJitter >= 20 && minJitter <= maxJitter && maxJitter <= 1000) SetAudioJitterDelay(minJitter, maxJitter); else { cerr << "Jitter should be between 20 and 1000 milliseconds." << endl; return; } } */ // start the H.323 listener H323ListenerTCP * listener; WORD listenPort = cfg.GetInteger(H323PortKey, H323EndPoint::DefaultTcpPort); rsrc->Add(new PHTTPIntegerField(H323PortKey, 1, 32767, listenPort)); PString listenAddress = cfg.GetString(ListenInterfaceKey); if (listenAddress.IsEmpty()) { PIPSocket::Address interfaceAddress(INADDR_ANY); listener = new H323ListenerTCP(*this, interfaceAddress, listenPort); } else { PIPSocket::Address interfaceAddress(listenAddress); listener = new H323ListenerTCP(*this, interfaceAddress, listenPort); } if (!StartListener(listener)) { // TODO LOG cout << "Could not open H.323 listener port on " << listener->GetListenerPort() << endl; delete listener; return FALSE; } rsrc->Add(new PHTTPStringField(ListenInterfaceKey, 40, listenAddress)); cout << "Listening on port " << listener->GetListenerPort() << endl; // TODO behind_masq = FALSE; //if (args.GetOptionString('q').IsEmpty()) { // behind_masq = FALSE; //} else { // masqAddressPtr = new PIPSocket::Address(args.GetOptionString('q')); // behind_masq = TRUE; // cout << "Masquerading as address " << *(masqAddressPtr) << endl; //} #if 0 // TODO #ifndef NO_VIDEO // If videoLarge was specified if (args.HasOption("videolarge")) { videoLarge = TRUE; SetVideoSize(352,288); } else { videoLarge = FALSE; } 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 ( videoLarge ) SetCapability(0, 1, new H323_H261Capability(0, 4, FALSE, FALSE, 6217)); // CIF SetCapability(0, 1, new H323_H261Capability(2, 0, FALSE, FALSE, 6217)); // QCIF } EnableVideoReception(args.HasOption('v')); int videoTxQual = 10; if (args.HasOption("videotxquality")) videoTxQual = args.GetOptionString("videotxquality").AsInteger(); videoTxQuality = PMAX(1, PMIN(31, videoTxQual)); int videoF = 2; if (args.HasOption("videofill")) videoF = args.GetOptionString("videofill").AsInteger(); videoFill = PMAX(1, PMIN(99, videoF)); int videoFPS = 10; if (args.HasOption("videotxfps")) videoFPS = args.GetOptionString("videotxfps").AsInteger(); videoFramesPS = PMAX(1,PMIN(30,videoFPS)); #endif #endif PString gkName = cfg.GetString(GatekeeperNameKey); rsrc->Add(new PHTTPStringField(GatekeeperNameKey, 40, gkName)); BOOL gkWanted = cfg.GetBoolean(GatekeeperWantedKey, TRUE); rsrc->Add(new PHTTPBooleanField(GatekeeperWantedKey, gkWanted)); BOOL gkNeeded = cfg.GetBoolean(GatekeeperNeededKey, FALSE); rsrc->Add(new PHTTPBooleanField(GatekeeperNeededKey, gkNeeded)); // Now actually try and do gatekeeper stuff if (!gkName.IsEmpty()) { if (SetGatekeeper(gkName, new H323TransportUDP(*this))) { cout << "Gatekeeper set: " << *GetGatekeeper() << endl; } else { cout << "Error registering with gatekeeper at \"" << gkName << '"' << endl; return FALSE; } } else if (gkWanted) { cout << "Searching for gatekeeper..." << flush; if (DiscoverGatekeeper(new H323TransportUDP(*this))) { cout << "\nGatekeeper found: " << *GetGatekeeper() << endl; } } else { cout << "\nNo gatekeeper found." << endl; if (cfg.GetBoolean(GatekeeperNeededKey, FALSE)) { return FALSE; } } PString roomName = cfg.GetString(DefaultRoomNameKey, "room101"); rsrc->Add(new PHTTPStringField(DefaultRoomNameKey, 40, roomName)); xmlrpcServer = cfg.GetString(XMLRPCServer, ""); rsrc->Add(new PHTTPStringField(XMLRPCServer, 50, xmlrpcServer)); xmlrpcSetupURL = cfg.GetString(XMLRPCSetupURL, ""); rsrc->Add(new PHTTPStringField(XMLRPCSetupURL, 40, xmlrpcSetupURL)); xmlrpcTeardownURL = cfg.GetString(XMLRPCTeardownURL, ""); rsrc->Add(new PHTTPStringField(XMLRPCTeardownURL, 40, xmlrpcTeardownURL)); BOOL defRoomOk = cfg.GetBoolean(NoDefaultRoomKey, FALSE); if (!defRoomOk) roomName = ""; SetDefaultRoomName(roomName); rsrc->Add(new PHTTPBooleanField(NoDefaultRoomKey, defRoomOk)); hasMenu = FALSE; /* if (args.HasOption("disable-menu")) { hasMenu = FALSE; } else { hasMenu = TRUE; } */ audioLoopbackRoom = cfg.GetString(AudioLoopbackRoomsKey, ""); rsrc->Add(new PHTTPStringField(AudioLoopbackRoomsKey, 25, audioLoopbackRoom, "* for all rooms, blank for none")); cout << "Codecs (in preference order):\n" << setprecision(2) << GetCapabilities() << endl << endl; return TRUE; } /////////////////////////////////////////////////////////////// // Call the base class (H323EndPoint) constructor MyH323EndPoint::MyH323EndPoint() // : H323EndPoint::H323EndPoint() { terminalType = e_MCUWithAudioMP; #ifndef NO_VIDEO terminalType = e_MCUWithAVMP; PINDEX i; for(i=0;ibehind_masq && (remoteAddr.Byte1() != 192)) { localAddr = *(this->masqAddressPtr); } return; } void MyH323EndPoint::ListenForIncomingCalls() { cout << "Waiting for incoming calls for \"" << GetLocalUserName() << '"' << endl; } void MyH323EndPoint::AwaitTermination() { PThread * userInterfaceThread = NULL; if (hasMenu) userInterfaceThread = new UserInterfaceThread(*this); // poll the handset every 100ms looking for state changes while (!exitFlag.Wait(100)) { // lock the user interface state whilst we change it } if (userInterfaceThread != NULL) { userInterfaceThread->Terminate(); userInterfaceThread->WaitForTermination(); delete userInterfaceThread; cout << "User Interface thread has terminated successfully."<> ch; switch (tolower(ch)) { case '?' : cout << help << endl; break; case 'm' : console >> user; if (!MakeCall(user, str)) cerr << "Error making call to \"" << user << '"' << endl; break; case 's' : break; #ifndef NO_VIDEO case 'v' : for(i=0;i> str; PTRACE(0,"Log message=" << str); break; case 'q' : case 'x' : cout << "Exiting." << endl; memberMutex.Wait(); //Stops flow of data into/out of audio buffers. ClearAllCalls(H323Connection::EndedByLocalUser, FALSE); /*for (i = 0; i < memberList.GetSize(); i++) { PString token = memberList[i]; MyH323Connection * conn = (MyH323Connection *)FindConnectionWithLock(token); if (conn != NULL) conn->ClearCall(); }*/ cout << "All calls cleared"<GetConnectionStartTime(); statout << "Connected to : " << conn->GetRemotePartyName() << " for " << setw(5) << setprecision(0) << (now - callStart) << " mins " <GetCallerID() << endl; statout << "Muted : " << conn->GetMutedStatus(); statout << " Last DTMF key : " << conn->GetLastInput() << endl; RTP_Session * session = conn->GetSession( RTP_Session::DefaultAudioSessionID); if (session == NULL) { statout << " : " << "Sending audio with " << conn->GetAudioTransmitCodecName() << endl; statout << " : " << "Receiving audio with " << conn->GetAudioReceiveCodecName() << endl; } else { statout << " : " << session->GetPacketsSent() << '/' << session->GetOctetsSent() << " audio packets/bytes sent (" << conn->GetAudioTransmitCodecName() << ")" << endl; statout << " : " << session->GetPacketsReceived() << '/' << session->GetOctetsReceived() << " audio packets/bytes received (" << conn->GetAudioReceiveCodecName() << ")" << endl; } #ifndef NO_VIDEO statout << " : " << "Sending video with " << conn->GetVideoTransmitCodecName() << endl; statout << " : " << "Receiving video with " << conn->GetVideoReceiveCodecName() << endl; #endif conn->Unlock(); } } } } memberMutex.Signal(); return PString(statout); } void MyH323EndPoint::SetDefaultRoomName(PString roomName) { defaultRoomName = roomName; } PString MyH323EndPoint::GetDefaultRoomName(void) { return defaultRoomName; } void MyH323EndPoint::AddMember(MyH323Connection * newMember) { PWaitAndSignal mutex(memberMutex); PString newToken = newMember->GetCallToken(); PString newRoomID = newMember->GetRoomID(); // ROOM: creates a new room. if (!memberListDict.Contains(newRoomID)) memberListDict.SetAt(newRoomID, new PStringList); if (!roomDict.Contains(newRoomID)) { OpenMCURoom *nr = new OpenMCURoom(newRoomID); nr->SetMuted(FALSE); roomDict.SetAt(newRoomID, nr); } PStringList & memberList = memberListDict[newRoomID]; // ROOM: adds a user to a room. memberList.AppendString(newToken); OpenMCURoom *nr = roomDict.GetAt(newRoomID); newMember->SetRoom(nr); #ifndef NO_VIDEO if (!spokenListDict.Contains(newRoomID)) spokenListDict.SetAt(newRoomID, new PStringList); // 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. AddVideoPosnToken(newToken); #endif // add the new member to every other member, // and add every other member to the new member. PINDEX i; for (i = 0; i < memberList.GetSize(); i++) { PString token = memberList[i]; if (token != newToken) { cout << "Adding member " << newToken << " to list of " << token << endl; MyH323Connection * conn = (MyH323Connection *)FindConnectionWithLock(token); if (conn != NULL) { conn->AddMember(newToken); newMember->AddMember(token); conn->Unlock(); } } else { if ((newRoomID == audioLoopbackRoom) || (newRoomID == "loopback") || (audioLoopbackRoom == "*") // basic wildcard ) { cout << "Member " << newToken << " will hear their own voice" << endl; newMember->AddMember(newToken); } else { cout << "Member " << newToken << " will not hear their own voice" << endl; } } } } void MyH323EndPoint::RemoveMember(MyH323Connection * oldConn) { PWaitAndSignal mutex(memberMutex); PString oldToken = oldConn->GetCallToken(); PString oldRoomID = oldConn->GetRoomID(); // ROOM: get the list of members of this room PStringList & memberList = memberListDict[oldRoomID]; PINDEX i; #ifndef NO_VIDEO PStringList & spokenList = spokenListDict[oldRoomID]; PINDEX keyIndex = spokenList.GetStringsIndex(oldToken); if (keyIndex != P_MAX_INDEX) spokenList.RemoveAt(keyIndex); // ROOM: remove entry if no members left if (spokenList.GetSize() == 0) spokenListDict.RemoveAt(oldRoomID); //Clear the corner of the video window that the connection just //vacated. i = FindTokensVideoPosn(oldToken); if (i != P_MAX_INDEX) { videoBuffer.Clear(i); videoPosn[i] = ""; } #endif // remove this member from the audio buffer lists for (i = 0; i < memberList.GetSize(); i++) { PString token = memberList[i]; if (token != oldToken) { MyH323Connection * conn = (MyH323Connection *)FindConnectionWithLock(token); if (conn != NULL) { conn->RemoveMember(oldToken); oldConn->RemoveMember(token); conn->Unlock(); } } } // ROOM: remove this connection from the room member list memberList.RemoveAt(memberList.GetStringsIndex(oldToken)); // ROOM: if there are no more members in this room, delete the room if (memberList.GetSize() == 0) { OpenMCURoom *room = roomDict.GetAt(oldRoomID); if (!room->GetStickyFlag()) { cout << "Room " << oldRoomID << " is now empty" << endl; memberListDict.RemoveAt(oldRoomID); delete room; } else { cout << "Room " << oldRoomID << " is now empty, but sticky." << endl; } } } #ifndef NO_VIDEO PINDEX MyH323EndPoint::FindTokensVideoPosn(const PString & thisToken) { PINDEX keyIndex; for (keyIndex = 0; keyIndex= PARRAYSIZE(videoPosn)) { PString tokenToWipe = spokenList[keyIndex-PARRAYSIZE(videoPosn)]; keyIndex = FindTokensVideoPosn(tokenToWipe); } if(keyIndex != P_MAX_INDEX) videoPosn[keyIndex] = thisToken; } processAudio: #endif // ROOM: get list of members in a room to send audio to them. PStringList & memberList = memberListDict[roomID]; PINDEX i; for (i = 0; i < memberList.GetSize(); i++) { PString token = memberList[i]; if (token != thisToken) { MyH323Connection * conn = (MyH323Connection *)FindConnectionWithLock(token); if (conn != NULL) { conn->WriteAudio(thisToken, buffer, amount); conn->Unlock(); } } else { // Do not lock as we are looking for our own connection. // This should only happen in audio loopback mode as we do not // normally want to add our own voice to the audio we hear from the MCU. MyH323Connection * conn = (MyH323Connection *)FindConnectionWithoutLocks(thisToken); if (conn != NULL) { conn->WriteAudio(thisToken, buffer, amount); } } } return TRUE; } BOOL MyH323EndPoint::ReadAudio(const PString & thisToken, void * buffer, PINDEX amount, PString roomID) { PWaitAndSignal mutex(memberMutex); // ROOM: get list of members to figure out who's generated audio PStringList & memberList = memberListDict[roomID]; PINDEX i; for (i = 0; i < memberList.GetSize(); i++) { PString token = memberList[i]; if (token == thisToken) { MyH323Connection * conn = (MyH323Connection *)FindConnectionWithLock(token); if (conn != NULL) { conn->ReadAudio(thisToken, buffer, amount); conn->Unlock(); } } } return TRUE; } #ifndef NO_VIDEO BOOL MyH323EndPoint::DetectNoise(const void * buffer, PINDEX amount) { short *start = (short *)buffer; short *end = start + (amount/2); int sum; sum=0; while (start != end) if(*start<0) sum -= *start++; else sum += *start++; return (sum/amount) > 50; } BOOL MyH323EndPoint::WriteVideo(const PString & thisToken, const void * buffer, PINDEX amount, PString roomID) { PWaitAndSignal mutex(memberMutex); // The last four elements of spokenList indicate the last // four connections from which audio was received. PINDEX keyIndex = FindTokensVideoPosn(thisToken); if (keyIndex != P_MAX_INDEX) videoBuffer.Write((BYTE *)buffer, amount, keyIndex); return TRUE; } BOOL MyH323EndPoint::ReadVideo(const PString & /*thisToken*/, void * buffer, PINDEX amount) { PWaitAndSignal mutex(memberMutex); videoBuffer.Read((BYTE *)buffer,amount); return TRUE; } #endif /////////////////////////////////////////////////////////////// MyH323Connection::MyH323Connection(MyH323EndPoint & _ep, unsigned callReference) : H323Connection(_ep, callReference), ep(_ep), connected(FALSE) { incomingAudio = NULL; outgoingAudio = NULL; audioReceiveCodecName = audioTransmitCodecName = "none"; #ifndef NO_VIDEO incomingVideo = NULL; outgoingVideo = NULL; videoReceiveCodecName = videoTransmitCodecName = "none"; #endif aborted = FALSE; cout << "Opening connection" << endl; room = NULL; } MyH323Connection::~MyH323Connection() { if (!ep.GetXMLRPCServer().IsEmpty()) { PXMLRPC rpc(ep.GetXMLRPCServer()); PXMLRPCBlock request(ep.GetXMLRPCTeardownURL()); PXMLRPCBlock response; request.AddParam(QuoteHTML(GetCallToken())); // call token rpc.MakeRequest(request, response); // don't care about response } cout << "Closing connection" << endl; #ifdef LOGGING LogCall(*this); #endif delete incomingAudio; delete outgoingAudio; #ifndef NO_VIDEO delete incomingVideo; delete outgoingVideo; #endif } void MyH323Connection::CleanUpOnCallEnd() { if (incomingAudio) incomingAudio->Close(); if (outgoingAudio) outgoingAudio->Close(); #ifndef NO_VIDEO if (incomingVideo) incomingVideo->Close(); if (outgoingVideo) outgoingVideo->Close(); #endif if (!aborted && connected) ep.RemoveMember(this); H323Connection::CleanUpOnCallEnd(); } H323Connection::AnswerCallResponse MyH323Connection::OnAnswerCall(const PString & caller, const H323SignalPDU & setupPDU, H323SignalPDU & /*connectPDU*/) { if (!setupPDU.GetQ931().GetCalledPartyNumber(roomID)) { const H225_Setup_UUIE & setup = setupPDU.m_h323_uu_pdu.m_h323_message_body; PINDEX i; for (i = 0; i < setup.m_destinationAddress.GetSize(); i++) { roomID = H323GetAliasAddressString(setup.m_destinationAddress[i]); if (!roomID) break; } // If a room was not specified in the connection then use the default // room name. PString defaultRoomName = ep.GetDefaultRoomName(); if ((roomID.IsEmpty()) && (defaultRoomName.IsEmpty()) ) { cout << "Incoming H.323 call from " << caller << " has not selected a room." << endl; cout << "No default room specified." << endl; #ifdef LOGGING LogCall(*this, FALSE); #endif aborted = TRUE; return AnswerCallDenied; } if ((roomID.IsEmpty()) && (!defaultRoomName.IsEmpty()) ) { cout << "Incoming H.323 call from " << caller << " has not selected a room." << endl; cout << "Using room " << defaultRoomName << " as the default." << endl; roomID = defaultRoomName; } } if (!setupPDU.GetQ931().GetCallingPartyNumber(callerID)) { cout << "Couldn't get CallingPartyNumber" << endl; callerID = ""; } else { cout << "Got CallingPartyNumber " << callerID << endl; } PString product = "Unknown"; const H225_Setup_UUIE & setup = setupPDU.m_h323_uu_pdu.m_h323_message_body; const H225_EndpointType & epInfo = setup.m_sourceInfo; if (epInfo.HasOptionalField(H225_EndpointType::e_vendor)) { const H225_VendorIdentifier & vendorInfo = epInfo.m_vendor; if (vendorInfo.HasOptionalField(H225_VendorIdentifier::e_productId)) product = vendorInfo.m_productId.AsString(); if (vendorInfo.HasOptionalField(H225_VendorIdentifier::e_versionId)) product = product + "/" + vendorInfo.m_versionId.AsString(); } if (!ep.GetXMLRPCServer().IsEmpty()) { PXMLRPC rpc(ep.GetXMLRPCServer()); PXMLRPCBlock request(ep.GetXMLRPCSetupURL()); PXMLRPCBlock response; PString srcnum, dstnum; if (!setupPDU.GetSourceE164(srcnum)) srcnum = "Unknown"; if (!setupPDU.GetDestinationE164(dstnum)) dstnum = "Unknown"; request.AddParam(QuoteHTML(GetCallToken())); // call token request.AddParam(QuoteHTML(caller)); // caller name request.AddParam(QuoteHTML(setupPDU.GetQ931().GetDisplayName())); request.AddParam(QuoteHTML(GetControlChannel().GetRemoteAddress())); // request.AddParam(QuoteHTML(srcnum)); request.AddParam(QuoteHTML(dstnum)); request.AddParam(QuoteHTML(roomID)); if (!rpc.MakeRequest(request, response)) { cerr << "RPC Conf Setup FAILED: " << rpc.GetFaultCode() << ", " << rpc.GetFaultText() << endl; aborted = TRUE; return AnswerCallDenied; } cout << "Got XMLRPC response" << endl; // Now parse the response. We should get two structs, one with // info on what to do, and an optional second with actions PString type, val; PXMLRPCVariableBase *var; if (response.GetParamCount() == 1) { // For now - need to support Array if (response.GetParam(0, type, val)) { if (type == "struct") { ResponseStruct dict; response.GetParam(0, dict); var = dict.GetVariable("answerCall"); if (!var) { cerr << "no answerCall in response" << endl; aborted = TRUE; return AnswerCallDenied; } if (var->ToString() != "OK") { cerr << "got answer!=OK -> " << var->ToString() << endl; aborted = TRUE; return AnswerCallDenied; } var = dict.GetVariable("roomID"); if (var && !var->ToString().IsEmpty()) { cout << "roomID: " << roomID << " -> " << var->ToString() << endl; roomID = var->ToString(); } roomOwner = FALSE; var = dict.GetVariable("privs"); if (var && !var->ToString().IsEmpty()) { cout << "privs: " << var->ToString() << endl; if ( var->ToString() == "owner" ) { cout << "setting owner flag to true" << endl; roomOwner = TRUE; } } if (dict.actions.GetNumVariables()) cout << "Got actions dict with " << dict.actions.GetNumVariables() << endl; cout << "dict: "<< dict.actions << endl; int i; PString actionval; for (i=0;i<10;i++) { actionval = dict.actions.GetVariable("action"+PString(i))->ToString(); if (!actionval.IsEmpty()) actions.SetAt(PString(i),(const char *)actionval); } actionval = dict.actions.GetVariable("actionstar")->ToString(); if (!actionval.IsEmpty()) actions.SetAt("*",(const char *)actionval); actionval = dict.actions.GetVariable("actionhash")->ToString(); if (!actionval.IsEmpty()) actions.SetAt("#",(const char *)actionval); } else { cerr << "response: got " << type << " instead of struct" << endl; aborted = TRUE; return AnswerCallDenied; } } else { cerr << "error: getting struct 0 " << response.GetFaultText() << endl; aborted = TRUE; return AnswerCallDenied; } #if 0 if (response.GetParam(1, type, val)) { if (type == "struct") { PStringToString actions; response.GetParam(0, actions); } else { cerr << "actions: got " << type << " instead of struct" << endl; aborted = TRUE; return AnswerCallDenied; } } else { cerr << "error: getting actions" << response.GetFaultText() << endl; aborted = TRUE; return AnswerCallDenied; } cout << endl; #endif } else { // will need to be 2, eventually. cerr << "got " << response.GetParamCount() << " params, not 1" << endl; aborted = TRUE; return AnswerCallDenied; } } cout << "Accepting call from " << caller << " using " << product << " with room id " << roomID << endl; connected = TRUE; userMuted = FALSE; // by default, start un-muted roomMuted = FALSE; // by default, start un-muted ep.AddMember(this); return AnswerCallNow; } BOOL MyH323Connection::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; roomID = ep.GetDefaultRoomName(); cout << "Adding connection to room " << roomID << endl; ep.AddMember(this); return H323Connection::OnSendSignalSetup( callProceedingPDU ); } BOOL MyH323Connection::OpenAudioChannel(BOOL isEncoding, unsigned /* bufferSize */, H323AudioCodec & codec) { PStringStream codecName; codecName << codec; 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; // } PWaitAndSignal mutex(audioMutex); if (incomingAudio == NULL) { incomingAudio = new IncomingAudio(ep, *this); } if (outgoingAudio == NULL) { outgoingAudio = new OutgoingAudio(ep, *this); } if (isEncoding) { audioTransmitCodecName = codecName; codec.AttachChannel(outgoingAudio, FALSE); } else { audioReceiveCodecName = codecName; codec.AttachChannel(incomingAudio, FALSE); } return TRUE; } #ifndef NO_VIDEO BOOL MyH323Connection::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,FALSE); // 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) */ codec.SetTxQualityLevel(ep.videoTxQuality); codec.SetBackgroundFill(ep.videoFill); videoTransmitCodecName = codecName; } else { if (incomingVideo == NULL) incomingVideo = new IncomingVideo(ep, *this); codec.AttachChannel(incomingVideo,FALSE); videoReceiveCodecName = codecName; } return TRUE; } #endif BOOL MyH323Connection::OnStartLogicalChannel(H323Channel & channel) { if (!H323Connection::OnStartLogicalChannel(channel)) return FALSE; cout << "Started logical channel: "; switch (channel.GetDirection()) { case H323Channel::IsTransmitter : cout << "sending "; break; case H323Channel::IsReceiver : cout << "receiving "; break; default : break; } cout << channel.GetCapability() << endl; return TRUE; } BOOL MyH323Connection::OnOutgoingAudio(void * buffer, PINDEX amount) { return ep.ReadAudio(GetCallToken(), buffer, amount, roomID); } BOOL MyH323Connection::OnIncomingAudio(const void * buffer, PINDEX amount) { if (GetMuted()) return TRUE; return ep.WriteAudio(GetCallToken(), buffer, amount, roomID); } void MyH323Connection::OnUserInputTone(char tone, unsigned duration, unsigned logicalChannel, unsigned rtpTimestamp) { //cout << "Got user tone " << tone << endl; endpoint.OnUserInputTone(*this, tone,duration,logicalChannel, rtpTimestamp); } void MyH323Connection::OnUserInputString(const PString & value) { cout << "Got user input " << value << " from " << callerID << endl; lastInput = value; PString act; if (actions.Contains(lastInput)) { act = actions[lastInput]; if (act == "toggleMute") { // user mute. userMuted = !userMuted; cout << "got (un)mute request from " << callerID ; cout << ", now " << GetMutedStatus() << endl; } else if (lastInput == "roomMute") { // owner mutes room if (roomOwner) { cout << "got room mute request from " << callerID << endl; room->SetMuted(TRUE); } } else if (lastInput == "roomUnmute") { // owner unmutes room if (roomOwner) { cout << "got room unmute request from " << callerID << endl; room->SetMuted(FALSE); } } } } void MyH323Connection::SetRoom(OpenMCURoom *newRoom) { // Set the current room. room = newRoom; } #ifndef NO_VIDEO BOOL MyH323Connection::OnOutgoingVideo(void * buffer, PINDEX & amount) { return ep.ReadVideo(GetCallToken(), buffer, amount); } BOOL MyH323Connection::OnIncomingVideo(const void * buffer, PINDEX amount) { return ep.WriteVideo(GetCallToken(), buffer, amount, roomID); } #endif void MyH323Connection::AddMember(const PString & token) { audioMutex.Wait(); cout << "Adding audio buffer for " << token << " to connection " << GetCallToken() << endl; // Create a new audio buffer to hold audio between the connection // called 'token' and this connection. audioBuffers.SetAt(token, new AudioBuffer); audioMutex.Signal(); } void MyH323Connection::RemoveMember(const PString & token) { PWaitAndSignal mutex(audioMutex); cout << "Removing audio buffer for " << token << " from connection " << GetCallToken() << endl; audioBuffers.RemoveAt(token); } BOOL MyH323Connection::WriteAudio(const PString & token, const void * buffer, PINDEX amount) { PWaitAndSignal mutex(audioMutex); AudioBuffer * audioBuffer = audioBuffers.GetAt(token); if (audioBuffer != NULL) audioBuffer->Write((BYTE *)buffer, amount); return TRUE; } BOOL MyH323Connection::ReadAudio(const PString & /*token*/, void * buffer, PINDEX amount) { PWaitAndSignal mutex(audioMutex); // First, set the buffer to empty. memset(buffer, 0, amount); // get number of channels to mix PINDEX numChannels = audioBuffers.GetSize(); if (numChannels== 0) return TRUE; // scan through the audio buffers and mix the signals PINDEX i; for (i = 0; i < numChannels; i++) { PString key = audioBuffers.GetKeyAt(i); audioBuffers[key].ReadAndMix((BYTE *)buffer, amount, numChannels); } return TRUE; } /////////////////////////////////////////////////////////////// OutgoingAudio::OutgoingAudio(MyH323EndPoint & _ep, MyH323Connection & _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(MyH323EndPoint & _ep, MyH323Connection & _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; } /////////////////////////////////////////////////////////////////////////// AudioBuffer::AudioBuffer() : bufferSize(PCM_BUFFER_SIZE) { buffer = new BYTE[bufferSize]; bufferStart = bufferLen = 0; } AudioBuffer::~AudioBuffer() { delete[] buffer; } void AudioBuffer::Read(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 AudioBuffer::ReadAndMix(BYTE * data, PINDEX amount, PINDEX channels) { if (amount == 0) 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 AudioBuffer::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 1 //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 } void AudioBuffer::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; } } /////////////////////////////////////////////////////////////////////////// #ifndef NO_VIDEO IncomingVideo::IncomingVideo(MyH323EndPoint & _ep, MyH323Connection & _conn) : ep(_ep), conn(_conn), width(0), height(0), frameSize(0) { closed = FALSE; } IncomingVideo::~IncomingVideo() { IncomingVideo::Close(); PVideoChannel::Close(); } BOOL IncomingVideo::Write(const void * buffer, PINDEX amount) { amount = (frameSize*3) >> 1; // frameSize==width*height PWaitAndSignal mutex( videoChanMutex ); if (closed){ return FALSE; } conn.OnIncomingVideo(buffer, amount); return TRUE; } void IncomingVideo::SetRenderFrameSize(int _width, int _height) { PTRACE(3,"IncomingVideo Set size"); width = _width; height = _height; frameSize = width * height; } BOOL IncomingVideo::Close() { PWaitAndSignal mutex(videoChanMutex); closed = TRUE; return TRUE; } /////////////////////////////////////////////////////////////////////////// OutgoingVideo::OutgoingVideo(MyH323EndPoint & _ep, MyH323Connection & _conn, int framesPerSec, BOOL _videoLarge) : ep(_ep), conn(_conn), videoLarge(_videoLarge) { closed = FALSE; if ( ( framesPerSec>0 ) && ( framesPerSec < 31 ) ) { msBetweenFrames= 1000/framesPerSec; } else { cerr << "Invalid video transmit frame rate. Frame rate should be between 1 and 30 frames per second"<> 1); if (!delay.Delay(msBetweenFrames)) conn.OnOutgoingVideo(buffer, amount); return TRUE; } BOOL OutgoingVideo::Close() { // PWaitAndSignal mutex(videoChanMutex); closed = TRUE; return TRUE; } /////////////////////////////////////////////////////////////////////////// VideoBuffer::VideoBuffer() : xSize(176), ySize(144) { buffer = NULL; SetSize( xSize, ySize ); } VideoBuffer::~VideoBuffer() { delete[] buffer; } //Writes data into the specified posn of the buffer. //0 == top left, 1 == top right //2 == bot left, 3 == bot right void VideoBuffer::Write(BYTE * data, PINDEX amount, PINDEX posn) { // It appears that a full frame is always written. // We can determine the size of the frame by how much // is written. if (amount == 0) return; PWaitAndSignal mutex(videoBufferMutex); BYTE *yFirst, *ySecond, *u, *v, *srcYFirst, *srcYSecond, *srcU, *srcV; int srcFrameSize = (amount<<1)/3; int srcXSize = (srcFrameSize == (176 * 144) ? 176 : 352 ); int srcYSize = (srcFrameSize == (176 * 144) ? 144 : 288 ); yFirst = buffer; u= buffer + bufferFrameSize; v= buffer + bufferFrameSize + (bufferFrameSize >> 2); srcYFirst = data; srcYSecond = srcYFirst + (xSize == srcXSize ? srcXSize << 1 : srcXSize); srcU = data + srcFrameSize; srcV = data + srcFrameSize + (srcFrameSize >> 2); switch (posn) { case 0: break; case 1: yFirst +=(xSize >> 1); u +=(xSize >> 2); v +=(xSize >> 2); break; case 2: yFirst += (bufferFrameSize >> 1); u += (bufferFrameSize >> 3); v += (bufferFrameSize >> 3); break; case 3: yFirst += (bufferFrameSize >> 1) + (xSize >> 1); u += (bufferFrameSize >> 3) + (xSize >> 2); v += (bufferFrameSize >> 3) + (xSize >> 2); break; default: return; } ySecond = yFirst + xSize; // added down here so that any changes to // yFirst are taken into account - pez // Special case, can fit 'in' images perfectly inside 'out' images if ( xSize == 352 && srcXSize == 176 ) { for(int i=0; i < 144; i+=2) { memcpy(yFirst, srcYFirst, 176); memcpy(ySecond, srcYSecond, 176); memcpy(u, srcU, 88 ); memcpy(v, srcV, 88 ); srcYFirst += 352; srcYSecond += 352; yFirst += 704; ySecond += 704; srcU += 88; srcV += 88; u += 176; v += 176; } } else { // This code handles the other 2 cases in a generic fashion int src_step = 3 * srcXSize; int step = xSize + (xSize >> 1); int srcuv_step = (srcXSize >> 1); int uv_step = (xSize >> 2); for(int i=0; i> 1); u += (xSize >> 2); v += (xSize >> 2); break; case 2: yFirst += (bufferFrameSize >> 1); u += (bufferFrameSize >> 3); v += (bufferFrameSize >> 3); break; case 3: yFirst += (bufferFrameSize >>1) + (xSize >> 1); u += (bufferFrameSize >> 3) + (xSize >> 2); v += (bufferFrameSize >> 3) + (xSize >> 2); break; default: return; } ySecond = yFirst + xSize; for(int y=0; y < (ySize>>1); y+=2) { memset(yFirst, 0x80, xSize >> 1); // Mid Grey memset(ySecond, 0x80, xSize >> 1); // Mid Grey memset(u, 0x80, xSize >> 2); memset(v, 0x80, xSize >> 2); yFirst += xSize * 2; ySecond += xSize * 2; u += (xSize >> 1); v += (xSize >> 1); } return; } void VideoBuffer::SetSize(int x, int y) { PWaitAndSignal mutex(videoBufferMutex); if ( buffer != NULL ) delete[] buffer; xSize = x; ySize = y; bufferFrameSize = xSize * ySize; videoBufferSize = bufferFrameSize + (bufferFrameSize >> 2 ) + (bufferFrameSize >> 2); // Y + U + V; buffer = new BYTE[ videoBufferSize ]; memset( buffer, 0x80, videoBufferSize); // Set Y, U and V to 0x80 - Mid Grey. } void VideoBuffer::Read(BYTE * data, PINDEX amount) { if (amount == 0) return; PWaitAndSignal mutex(videoBufferMutex); memcpy(data,buffer,amount); } #endif //NO_VIDEO /////////////////////////////////////////////////////////////////////////// #define MIN_HEADROOM 30 #define MAX_HEADROOM 60 AudioDelay::AudioDelay() { firstTime = TRUE; error = 0; } void AudioDelay::Restart() { firstTime = TRUE; } BOOL AudioDelay::Delay(int frameTime) { if (firstTime) { firstTime = FALSE; previousTime = PTime(); return TRUE; } error += frameTime; PTime now; PTimeInterval delay = now - previousTime; error -= (int)delay.GetMilliSeconds(); previousTime = now; if (error > 0) #ifdef P_LINUX usleep(error * 1000); #else PThread::Current()->Sleep(error); #endif return error <= -frameTime; //if (headRoom > MAX_HEADROOM) // PThread::Current()->Sleep(headRoom - MIN_HEADROOM); } MainStatusPage::MainStatusPage(OpenMcu & _app, PHTTPAuthority & auth) : PServiceHTTPString("Status", "", "text/html; charset=UTF-8", auth), app(_app) { PHTML html; html << PHTML::Title("OpenH323 MCU Status") << PHTML::Body() << PHTML::Paragraph() << "
" << PHTML::TableStart("border=1") << PHTML::TableRow() << PHTML::TableHeader() << "Room Number" << PHTML::TableHeader() << "Number of Calls" << "" << PHTML::TableRow() << PHTML::TableData("NOWRAP") << "" << PHTML::TableData("NOWRAP") << "" << PHTML::TableData("NOWRAP") << "" << PHTML::TableEnd() << "
" << "Old-style statistics dump" << PHTML::Paragraph() << "
"
           << ""
           << ""
           << "
" << PHTML::Paragraph() << PHTML::Body() ; string = html; } BOOL MainStatusPage::Post(PHTTPRequest & request, const PStringToString & data, PHTML & msg) { PTRACE(2, "Main\tClear call POST received " << data); msg << "Posted" ; return TRUE; } PCREATE_SERVICE_MACRO_BLOCK(RoomStatus,P_EMPTY,P_EMPTY,block) { return OpenMcu::Current().GetEndPoint().OnLoadRoomStatus(block); } PCREATE_SERVICE_MACRO_BLOCK(SimpleStatus,P_EMPTY,P_EMPTY,block) { return OpenMcu::Current().GetEndPoint().OnLoadSimpleStatus(block); } static 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); } } PString MyH323EndPoint::OnLoadSimpleStatus(const PString & htmlBlock) { return SimpleStatus(); } PString MyH323EndPoint::OnLoadRoomStatus(const PString & htmlBlock) { PINDEX i; PString substitution; // ROOM: get number of active users. bodgy. PINDEX roomcount = memberListDict.GetSize(); for (i=0; i< roomcount; i++) { PString insert = htmlBlock; // copy macro section PStringList & roomMembers = memberListDict.GetDataAt(i); SpliceMacro(insert, "RoomNumber", memberListDict.GetKeyAt(i)); SpliceMacro(insert, "ActiveUsers", roomMembers.GetSize()); substitution += insert; } return substitution; } OpenMCURoom::OpenMCURoom(PString name) { creationTime = PTime(); roomName = name; sticky = FALSE; } OpenMCURoom::OpenMCURoom(PString name, BOOL stickyflag) { creationTime = PTime(); roomName = name; sticky = sticky; // does the room hang around? } PStringList OpenMCURoom::GetMembers() { } void OpenMCURoom::AddMember(PString newMember) { } void OpenMCURoom::RemoveMember(PString oldMember) { } // End of File ///////////////////////////////////////////////////////////////