From 64c28c023aeb558af1f4e82676af2a9bad4432c9 Mon Sep 17 00:00:00 2001 From: zhongjin Date: Sat, 22 Oct 2022 00:27:02 +0800 Subject: [PATCH] =?UTF-8?q?fgcom.cxx=E5=8E=9F=E5=A7=8B=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Network/fgcom.cxx.orgin | 748 ++++++++++++++++++++++++++++++++++++ 1 file changed, 748 insertions(+) create mode 100644 src/Network/fgcom.cxx.orgin diff --git a/src/Network/fgcom.cxx.orgin b/src/Network/fgcom.cxx.orgin new file mode 100644 index 0000000..273ad62 --- /dev/null +++ b/src/Network/fgcom.cxx.orgin @@ -0,0 +1,748 @@ +// fgcom.cxx -- FGCom: Voice communication +// +// Written by Clement de l'Hamaide, started May 2013. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# include + +#include "fgcom.hxx" + +// standard library includes +#include + +// simgear includes +#include +#include +#include +#include +#include + +// flightgear includes +#include
+#include
+#include +#include +#include +#include
+ +#include + + +#define NUM_CALLS 4 +#define MAX_GND_RANGE 10.0 +#define MAX_TWR_RANGE 50.0 +#define MAX_RANGE 100.0 +#define MIN_RANGE 20.0 +#define MIN_GNDTWR_RANGE 0.0 +#define DEFAULT_SERVER "fgcom.jingweiht.com" +#define IAX_DELAY 300 // delay between calls in milliseconds +#define TEST_FREQ 910.00 +#define NULL_ICAO "ZZZZ" + +const int special_freq[] = { // Define some freq who need to be used with NULL_ICAO + 910000, + 911000, + 700000, + 123450, + 122750, + 121500, + 123500 }; + +static FGCom* static_instance = NULL; + + + +static int iaxc_callback( iaxc_event e ) +{ + switch( e.type ) + { + case IAXC_EVENT_TEXT: + static_instance->iaxTextEvent(e.ev.text); + break; + default: + return 0; + } + return 1; +} + + + +void FGCom::iaxTextEvent(struct iaxc_ev_text text) +{ + if( (text.type == IAXC_TEXT_TYPE_STATUS || + text.type == IAXC_TEXT_TYPE_IAX) && + _showMessages_node->getBoolValue() ) + { + _text_node->setStringValue(text.message); + } + + auto level = SG_INFO; + if ((text.type == IAXC_TEXT_TYPE_ERROR) || (text.type == IAXC_TEXT_TYPE_FATALERROR)) { + level = SG_ALERT; + } + + SG_LOG(SG_SOUND, level, std::string{"FCCom IAX:"} + text.message); +} + + + +FGCom::FGCom() +{ + _maxRange = MAX_RANGE; + _minRange = MIN_RANGE; +} + + + +FGCom::~FGCom() +{ +} + + + +void FGCom::bind() +{ + SGPropertyNode *node = fgGetNode("/sim/fgcom", 0, true); + _test_node = node->getChild( "test", 0, true ); + _server_node = node->getChild( "server", 0, true ); + _enabled_node = node->getChild( "enabled", 0, true ); + _micBoost_node = node->getChild( "mic-boost", 0, true ); + _micLevel_node = node->getChild( "mic-level", 0, true ); + _silenceThd_node = node->getChild( "silence-threshold", 0, true ); + _speakerLevel_node = node->getChild( "speaker-level", 0, true ); + _selectedInput_node = node->getChild( "device-input", 0, true ); + _selectedOutput_node = node->getChild( "device-output", 0, true ); + _showMessages_node = node->getChild( "show-messages", 0, true ); + + SGPropertyNode *reg_node = node->getChild("register", 0, true); + _register_node = reg_node->getChild( "enabled", 0, true ); + _username_node = reg_node->getChild( "username", 0, true ); + _password_node = reg_node->getChild( "password", 0, true ); + + _ptt_node = fgGetNode("/controls/radios/comm-ptt", true); + _selected_comm_node = fgGetNode("/controls/radios/comm-radio-selected", true); + _callsign_node = fgGetNode("/sim/multiplay/callsign", true); + _text_node = fgGetNode("/sim/messages/atc", true ); + _version_node = fgGetNode("/sim/version/flightgear", true ); + + // Set default values if not provided + if ( !_enabled_node->hasValue() ) + _enabled_node->setBoolValue(true); + + if ( !_test_node->hasValue() ) + _test_node->setBoolValue(false); + + if ( !_micBoost_node->hasValue() ) + _micBoost_node->setIntValue(1); + + if ( !_server_node->hasValue() ) + _server_node->setStringValue(DEFAULT_SERVER); + + if ( !_speakerLevel_node->hasValue() ) + _speakerLevel_node->setFloatValue(1.0); + + if ( !_micLevel_node->hasValue() ) + _micLevel_node->setFloatValue(1.0); + + if ( !_silenceThd_node->hasValue() ) + _silenceThd_node->setFloatValue(-35.0); + + if ( !_register_node->hasValue() ) + _register_node->setBoolValue(false); + + if ( !_username_node->hasValue() ) + _username_node->setStringValue("guest"); + + if ( !_password_node->hasValue() ) + _password_node->setStringValue("guest"); + + if ( !_showMessages_node->hasValue() ) + _showMessages_node->setBoolValue(false); + + _mpTransmitFrequencyNode = fgGetNode("sim/multiplay/comm-transmit-frequency-hz", 0, true); + _mpTransmitPowerNode = fgGetNode("sim/multiplay/comm-transmit-power-norm", 0, true); + + _selectedOutput_node->addChangeListener(this); + _selectedInput_node->addChangeListener(this); + _speakerLevel_node->addChangeListener(this); + _silenceThd_node->addChangeListener(this); + _micBoost_node->addChangeListener(this); + _micLevel_node->addChangeListener(this); + _enabled_node->addChangeListener(this); + _ptt_node->addChangeListener(this); + _selected_comm_node->addChangeListener(this); + _test_node->addChangeListener(this); + // if you listen to more properties, be sure to remove + // the listener in unbind() as well +} + + + +void FGCom::unbind() +{ + _selectedOutput_node->removeChangeListener(this); + _selectedInput_node->removeChangeListener(this); + _speakerLevel_node->removeChangeListener(this); + _silenceThd_node->removeChangeListener(this); + _micBoost_node->removeChangeListener(this); + _micLevel_node->removeChangeListener(this); + _enabled_node->removeChangeListener(this); + _ptt_node->removeChangeListener(this); + _selected_comm_node->removeChangeListener(this); + _test_node->removeChangeListener(this); +} + + + +void FGCom::init() +{ + _enabled = _enabled_node->getBoolValue(); + _server = _server_node->getStringValue(); + _register = _register_node->getBoolValue(); + _username = _username_node->getStringValue(); + _password = _password_node->getStringValue(); + + _currentCommFrequency = 0.0; + + _maxRange = MAX_RANGE; + _minRange = MIN_RANGE; +} + + + +void FGCom::postinit() +{ + if( !_enabled ) { + return; + } + + //WARNING: this _must_ be executed after sound system is totally initialized ! + if( iaxc_initialize(NUM_CALLS) ) { + SG_LOG(SG_IO, SG_ALERT, "FGCom: cannot initialize iaxclient"); + _enabled = false; + return; + } + + assert( static_instance == NULL ); + static_instance = this; + iaxc_set_event_callback( iaxc_callback ); + + // FIXME: To be implemented in IAX audio driver + //iaxc_mic_boost_set( _micBoost_node->getIntValue() ); + std::string app = "FGFS-"; + app += _version_node->getStringValue(); + + iaxc_set_callerid( _callsign_node->getStringValue().c_str(), app.c_str() ); + iaxc_set_formats (IAXC_FORMAT_SPEEX, IAXC_FORMAT_ULAW|IAXC_FORMAT_SPEEX); + iaxc_set_speex_settings(1, 5, 0, 1, 0, 3); + iaxc_set_filters(IAXC_FILTER_AGC | IAXC_FILTER_DENOISE); + iaxc_set_silence_threshold(_silenceThd_node->getFloatValue()); + iaxc_start_processing_thread (); + + // Now IAXClient is initialized + _initialized = true; + + if ( _register ) { + _regId = iaxc_register( const_cast(_username.c_str()), + const_cast(_password.c_str()), + const_cast(_server.c_str()) ); + if( _regId == -1 ) { + SG_LOG(SG_IO, SG_ALERT, "FGCom: cannot register iaxclient"); + return; + } + } + + /* + Here we will create the list of available audio devices + Each audio device has a name, an ID, and a list of capabilities + If an audio device can output sound, available-output=true + If an audio device can input sound, available-input=true + + /sim/fgcom/selected-input (int) + /sim/fgcom/selected-output (int) + + /sim/fgcom/device[n]/id (int) + /sim/fgcom/device[n]/name (string) + /sim/fgcom/device[n]/available-input (bool) + /sim/fgcom/device[n]/available-output (bool) + */ + + //FIXME: OpenAL driver use an hard-coded device + // so all following is unused finally until someone + // implement "multi-device" support in IAX audio driver + SGPropertyNode *node = fgGetNode("/sim/fgcom", 0, true); + + struct iaxc_audio_device *devs; + int nDevs, input, output, ring; + + iaxc_audio_devices_get(&devs,&nDevs, &input, &output, &ring); + + for(int i=0; igetChild("device", i, true); + + // devID + _deviceID_node[i] = in_node->getChild("id", 0, true); + _deviceID_node[i]->setIntValue(devs[i].devID); + + // name + _deviceName_node[i] = in_node->getChild("name", 0, true); + _deviceName_node[i]->setStringValue(devs[i].name); + + // input capability + _deviceInput_node[i] = in_node->getChild("available-input", 0, true); + if( devs[i].capabilities & IAXC_AD_INPUT ) + _deviceInput_node[i]->setBoolValue(true); + else + _deviceInput_node[i]->setBoolValue(false); + + // output capability + _deviceOutput_node[i] = in_node->getChild("available-output", 0, true); + if( devs[i].capabilities & IAXC_AD_OUTPUT ) + _deviceOutput_node[i]->setBoolValue(true); + else + _deviceOutput_node[i]->setBoolValue(false); + + // use default device at start + if( devs[i].capabilities & IAXC_AD_INPUT_DEFAULT ) + _selectedInput_node->setIntValue(devs[i].devID); + if( devs[i].capabilities & IAXC_AD_OUTPUT_DEFAULT ) + _selectedOutput_node->setIntValue(devs[i].devID); + } + + // Mute the mic and set speaker at start + iaxc_input_level_set( 0.0 ); + iaxc_output_level_set(getCurrentCommVolume()); + + iaxc_millisleep(50); + + // Do the first call at start + setupCommFrequency(); + connectToCommFrequency(); +} + +double FGCom::getCurrentCommVolume() const { + double rv = 1.0; + + if (_speakerLevel_node) + rv = _speakerLevel_node->getFloatValue(); + + if (_commVolumeNode) + rv = rv * _commVolumeNode->getFloatValue(); + + return rv; +} +double FGCom::getCurrentFrequencyKhz() const { + return 10 * static_cast(_currentCommFrequency * 100 + 0.25); +} + +void FGCom::setupCommFrequency(int channel) { + if (channel < 1) { + if (_selected_comm_node != nullptr) { + channel = _selected_comm_node->getIntValue(); + } + } + // disconnect if channel set to 0 + if (channel < 1) { + if (_currentCallIdent != -1) { + iaxc_dump_call_number(_currentCallIdent); + SG_LOG(SG_IO, SG_INFO, "FGCom: disconnect as channel 0 " << _currentCallIdent); + _currentCallIdent = -1; + } + _currentCommFrequency = 0.0; + return; + } + + if (channel > 0) + { + channel--; // adjust back to zero based index. + SGPropertyNode *commRadioNode = fgGetNode("/instrumentation/")->getChild("comm", channel, false); + if (commRadioNode) { + SGPropertyNode *frequencyNode = commRadioNode->getChild("frequencies"); + if (_commVolumeNode) + _commVolumeNode->removeChangeListener(this); + _commVolumeNode = commRadioNode->getChild("volume"); + if (frequencyNode) { + frequencyNode = frequencyNode->getChild("selected-mhz"); + if (frequencyNode) { + if (_commFrequencyNode != frequencyNode) { + if (_commFrequencyNode) + _commFrequencyNode->removeChangeListener(this); + _commFrequencyNode = frequencyNode; + _commFrequencyNode->addChangeListener(this); + _commVolumeNode->addChangeListener(this); + } + _currentCommFrequency = frequencyNode->getDoubleValue(); + return; + } + } + } + SG_LOG(SG_IO, SG_INFO, "FGCom: setupCommFrequency node listener failed: channel " << channel); + } + + if (_commFrequencyNode) + _commFrequencyNode->removeChangeListener(this); + SG_LOG(SG_IO, SG_INFO, "FGCom: setupCommFrequency invalid channel " << channel); + + _currentCommFrequency = 0.0; +} + +void FGCom::connectToCommFrequency() { + // ensure that the current comm is still in range + if ((_currentCallFrequency > 0.0) && !isInRange(_currentCallFrequency)) { + SG_LOG(SG_IO, SG_WARN, "FGCom: call out of range of: " << _currentCallFrequency); + _currentCallFrequency = 0.0; + } + + // don't connected (and disconnect if already connected) when tuned freq is 0 + if (_currentCommFrequency < 1.0) { + if (_currentCallIdent != -1) { + iaxc_dump_call_number(_currentCallIdent); + SG_LOG(SG_IO, SG_INFO, "FGCom: disconnect as freq 0: current call " << _currentCallIdent); + _currentCallIdent = -1; + } + return; + } + + if (_currentCallFrequency != _currentCommFrequency || _currentCallIdent == -1) { + if (_currentCallIdent != -1) { + iaxc_dump_call_number(_currentCallIdent); + SG_LOG(SG_IO, SG_INFO, "FGCom: dump_call_number " << _currentCallIdent); + _currentCallIdent = -1; + } + + if (_currentCallIdent == -1) + { + std::string num = computePhoneNumber(_currentCommFrequency, getAirportCode(_currentCommFrequency)); + _processingTimer.stamp(); + if (!isInRange(_currentCommFrequency)) { + if (_currentCallIdent != -1) { + SG_LOG(SG_IO, SG_INFO, "FGCom: disconnect call as not in range " << _currentCallIdent); + if (_currentCallIdent != -1) { + iaxc_dump_call_number(_currentCallIdent); + _currentCallIdent = -1; + } + } + return; + } + if (!num.empty()) { + _currentCallIdent = iaxc_call(num.c_str()); + if (_currentCallIdent == -1) + SG_LOG(SG_IO, SG_DEBUG, "FGCom: cannot call " << num.c_str()); + else { + SG_LOG(SG_IO, SG_DEBUG, "FGCom: call established " << num.c_str() << " Freq: " << _currentCommFrequency); + _currentCallFrequency = _currentCommFrequency; + } + } + else + SG_LOG(SG_IO, SG_WARN, "FGCom: frequency " << _currentCommFrequency << " does not map to valid IAX address"); + } + } +} + +void FGCom::updateCall() +{ + if (_processingTimer.elapsedMSec() > IAX_DELAY) { + _processingTimer.stamp(); + connectToCommFrequency(); + } +} + + + +void FGCom::update(double dt) +{ + if (!_enabled || !_initialized) { + return; + } + + updateCall(); +} + + + +void FGCom::shutdown() +{ + if( !_enabled ) { + return; + } + + _initialized = false; + _enabled = false; + + iaxc_set_event_callback(NULL); + iaxc_unregister(_regId); + iaxc_stop_processing_thread(); + iaxc_shutdown(); + + // added to help debugging lingering IAX thread after fgOSCloseWindow + flightgear::addSentryBreadcrumb("Did shutdown FGCom", "info"); + + assert( static_instance == this ); + static_instance = NULL; +} + +void FGCom::valueChanged(SGPropertyNode *prop) +{ + if (prop == _enabled_node) { + bool isEnabled = prop->getBoolValue(); + if (_enabled == isEnabled) { + return; + } + + if( isEnabled ) { + init(); + postinit(); + } else { + shutdown(); + } + return; + } + + // avoid crash when properties change before FGCom::postinit + // https://sourceforge.net/p/flightgear/codetickets/2574/ + if (!_initialized) { + return; + } + + if (prop == _commVolumeNode && _enabled) { + if (_ptt_node->getIntValue()) { + SG_LOG(SG_IO, SG_INFO, "FGCom: ignoring change comm volume as PTT pressed"); + } + else + { + iaxc_input_level_set(0.0); + iaxc_output_level_set(getCurrentCommVolume()); + SG_LOG(SG_IO, SG_INFO, "FGCom: change comm volume=" << _commVolumeNode->getFloatValue()); + } + } + + if (prop == _selected_comm_node && _enabled) { + setupCommFrequency(); + SG_LOG(SG_IO, SG_INFO, "FGCom: change comm frequency (selected node): set to " << _currentCommFrequency); + } + + if (prop == _commFrequencyNode && _enabled) { + setupCommFrequency(); + SG_LOG(SG_IO, SG_INFO, "FGCom: change comm frequency (property updated): set to " << _currentCommFrequency); + } + + if (prop == _ptt_node && _enabled) { + if (_ptt_node->getIntValue()) { + // ensure that we are on the right channel by calling setupCommFrequency + setupCommFrequency(); + iaxc_output_level_set(0.0); + iaxc_input_level_set(_micLevel_node->getFloatValue()); //0.0 = min , 1.0 = max + _mpTransmitFrequencyNode->setValue(_currentCallFrequency * 1000000); + _mpTransmitPowerNode->setValue(1.0); + SG_LOG(SG_IO, SG_INFO, "FGCom: PTT active: " << _currentCallFrequency); + } + else { + iaxc_output_level_set(getCurrentCommVolume()); + iaxc_input_level_set(0.0); + SG_LOG(SG_IO, SG_INFO, "FGCom: PTT release: " << _currentCallFrequency << " vol=" << getCurrentCommVolume()); + _mpTransmitFrequencyNode->setValue(0); + _mpTransmitPowerNode->setValue(0); + } + } + + if (prop == _test_node) { + testMode( prop->getBoolValue() ); + return; + } + + if (prop == _silenceThd_node && _initialized) { + float silenceThd = prop->getFloatValue(); + SG_CLAMP_RANGE( silenceThd, -60, 0 ); + iaxc_set_silence_threshold( silenceThd ); + return; + } + + //FIXME: not implemented in IAX audio driver (audio_openal.c) + if (prop == _micBoost_node && _initialized) { + int micBoost = prop->getIntValue(); + SG_CLAMP_RANGE( micBoost, 0, 1 ); + iaxc_mic_boost_set( micBoost ) ; // 0 = enabled , 1 = disabled + return; + } + + //FIXME: not implemented in IAX audio driver (audio_openal.c) + if ((prop == _selectedInput_node || prop == _selectedOutput_node) && _initialized) { + int selectedInput = _selectedInput_node->getIntValue(); + int selectedOutput = _selectedOutput_node->getIntValue(); + iaxc_audio_devices_set(selectedInput, selectedOutput, 0); + return; + } + + if (_listener_active) + return; + + _listener_active++; + + if (prop == _speakerLevel_node && _enabled) { + float speakerLevel = prop->getFloatValue(); + SG_CLAMP_RANGE( speakerLevel, 0.0, 1.0 ); + _speakerLevel_node->setFloatValue(speakerLevel); + iaxc_output_level_set(speakerLevel); + } + + if (prop == _micLevel_node && _enabled) { + float micLevel = prop->getFloatValue(); + SG_CLAMP_RANGE( micLevel, 0.0, 1.0 ); + _micLevel_node->setFloatValue(micLevel); + //iaxc_input_level_set(micLevel); + } + + _listener_active--; +} + + + +void FGCom::testMode(bool testMode) +{ + if(testMode && _initialized) { + _enabled = false; + iaxc_dump_all_calls(); + iaxc_input_level_set( 1.0 ); + iaxc_output_level_set( _speakerLevel_node->getFloatValue() ); + std::string num = computePhoneNumber(TEST_FREQ, NULL_ICAO); + if( num.size() > 0 ) { + iaxc_millisleep(IAX_DELAY); + _currentCallIdent = iaxc_call(num.c_str()); + } + if( _currentCallIdent == -1 ) + SG_LOG( SG_IO, SG_DEBUG, "FGCom: cannot call " << num.c_str() ); + } else { + if( _initialized ) { + iaxc_dump_all_calls(); + iaxc_millisleep(IAX_DELAY); + iaxc_input_level_set( 0.0 ); + iaxc_output_level_set(getCurrentCommVolume()); + _currentCallIdent = -1; + _enabled = true; + } + } +} + + + +/* + \param freq The requested frequency e.g 120.825 + \return The ICAO code as string e.g LFMV +*/ + +std::string FGCom::getAirportCode(const double& freq) +{ + SGGeod aircraftPos = globals->get_aircraft_position(); + + for(size_t i=0; itype() == FGPositioned::FREQ_TOWER ) { + _maxRange = MAX_TWR_RANGE; + _minRange = MIN_GNDTWR_RANGE; + } else if( apt->type() == FGPositioned::FREQ_GROUND ) { + _maxRange = MAX_GND_RANGE; + _minRange = MIN_GNDTWR_RANGE; + } else { + _maxRange = MAX_RANGE; + _minRange = MIN_RANGE; + } + + _aptPos = apt->geod(); + return apt->airport()->ident(); +} + + + +/* + \param freq The requested frequency e.g 120.825 + \param iaco The associated ICAO code e.g LFMV + \return The phone number as string i.e username:password@fgcom.flightgear.org/0176707786120825 +*/ + +std::string FGCom::computePhoneNumber(const double& freq, const std::string& icao) const +{ + if( icao.empty() ) + return std::string(); + + char phoneNumber[256]; + char exten[32]; + char tmp[5]; + + /*Convert ICAO to ASCII */ + sprintf( tmp, "%4s", icao.c_str() ); + + /*Built the phone number */ + sprintf( exten, + "%02d%02d%02d%02d%02d%06d", + 01, + tmp[0], + tmp[1], + tmp[2], + tmp[3], + (int) (freq * 1000 + 0.5) ); + exten[16] = '\0'; + + snprintf( phoneNumber, + sizeof (phoneNumber), + "%s:%s@%s/%s", + _username.c_str(), + _password.c_str(), + _server.c_str(), + exten); + + return phoneNumber; +} + + + +/* + \return A boolean value, 1=in range, 0=out of range +*/ + +bool FGCom::isInRange(const double &freq) const +{ + for(size_t i=0; iget_aircraft_position(); + double distNm = SGGeodesy::distanceNm(_aptPos, acftPos); + double delta_elevation_ft = fabs(acftPos.getElevationFt() - _aptPos.getElevationFt()); + double rangeNm = 1.23 * sqrt(delta_elevation_ft); + + if (rangeNm > _maxRange) rangeNm = _maxRange; + if (rangeNm < _minRange) rangeNm = _minRange; + if( distNm > rangeNm ) return 0; + return 1; +} + + +// Register the subsystem. +SGSubsystemMgr::Registrant registrantFGCom;