]> git.mxchange.org Git - flightgear.git/blob - src/Network/fgcom.cxx
FGCom[-sa]: add IAX denoiser and auto gain + set silence threshold
[flightgear.git] / src / Network / fgcom.cxx
1 // fgcom.cxx -- FGCom: Voice communication
2 //
3 // Written by Clement de l'Hamaide, started May 2013.
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18
19 #ifdef HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22
23 #include "fgcom.hxx"
24
25 // standard library includes
26 #include <cstdio>
27
28 // simgear includes
29 #include <simgear/compiler.h>
30 #include <simgear/sg_inlines.h>
31 #include <simgear/debug/logstream.hxx>
32 #include <simgear/math/sg_geodesy.hxx>
33 #include <simgear/timing/timestamp.hxx>
34
35 // flightgear includes
36 #include <Main/fg_props.hxx>
37 #include <Main/globals.hxx>
38 #include <ATC/CommStation.hxx>
39 #include <Airports/airport.hxx>
40 #include <Navaids/navlist.hxx>
41
42 #include <3rdparty/iaxclient/lib/iaxclient.h>
43
44
45 #define NUM_CALLS 4
46 #define MAX_GND_RANGE 10.0
47 #define MAX_TWR_RANGE 50.0
48 #define MAX_RANGE 100.0
49 #define MIN_RANGE  20.0
50 #define MIN_GNDTWR_RANGE 0.0
51 #define DEFAULT_SERVER "fgcom.flightgear.org"
52 #define IAX_DELAY 300  // delay between calls in milliseconds
53 #define TEST_FREQ 910.00
54 #define NULL_ICAO "ZZZZ"
55
56 const int special_freq[] = { // Define some freq who need to be used with NULL_ICAO
57         910000,
58         911000,
59         700000,
60         123450,
61         122750,
62         121500,
63         123500,
64         121000,
65         723340 };
66
67 static FGCom* static_instance = NULL;
68
69
70
71 static int iaxc_callback( iaxc_event e )
72 {
73   switch( e.type )
74   {
75     case IAXC_EVENT_TEXT:
76       static_instance->iaxTextEvent(e.ev.text);
77       break;
78     default:
79       return 0;
80   }
81   return 1;
82 }
83
84
85
86 void FGCom::iaxTextEvent(struct iaxc_ev_text text)
87 {
88   if( (text.type == IAXC_TEXT_TYPE_STATUS ||
89        text.type == IAXC_TEXT_TYPE_IAX) &&
90        _showMessages_node->getBoolValue() )
91   {
92     _text_node->setStringValue(text.message);
93   }
94 }
95
96
97
98 FGCom::FGCom() :
99     _register(true),
100     _enabled(false),
101     _initialized(false),
102     _listener_active(0)
103 {
104 }
105
106
107
108 FGCom::~FGCom()
109 {
110 }
111
112
113
114 void FGCom::bind()
115 {
116   SGPropertyNode *node     = fgGetNode("/sim/fgcom", 0, true);
117   _test_node               = node->getChild( "test", 0, true );
118   _server_node             = node->getChild( "server", 0, true );
119   _enabled_node            = node->getChild( "enabled", 0, true );
120   _micBoost_node           = node->getChild( "mic-boost", 0, true );
121   _micLevel_node           = node->getChild( "mic-level", 0, true );
122   _speakerLevel_node       = node->getChild( "speaker-level", 0, true );
123   _selectedInput_node      = node->getChild( "device-input", 0, true );
124   _selectedOutput_node     = node->getChild( "device-output", 0, true );
125   _showMessages_node       = node->getChild( "show-messages", 0, true );
126
127   SGPropertyNode *reg_node = node->getChild("register", 0, true);
128   _register_node           = reg_node->getChild( "enabled", 0, true );
129   _username_node           = reg_node->getChild( "username", 0, true );
130   _password_node           = reg_node->getChild( "password", 0, true );
131
132   _comm0_node              = fgGetNode("/instrumentation/comm[0]/frequencies/selected-mhz", true);
133   _comm1_node              = fgGetNode("/instrumentation/comm[1]/frequencies/selected-mhz", true);
134   _ptt0_node               = fgGetNode("/instrumentation/comm[0]/ptt", true); //FIXME: what about /instrumentation/comm[1]/ptt ?
135   _callsign_node           = fgGetNode("/sim/multiplay/callsign", true);
136   _text_node               = fgGetNode("/sim/messages/atc", true );
137   _version_node            = fgGetNode("/sim/version/flightgear", true );
138
139   // Set default values if not provided
140   if ( !_enabled_node->hasValue() )
141       _enabled_node->setBoolValue(true);
142
143   if ( !_test_node->hasValue() )
144       _test_node->setBoolValue(false);
145
146   if ( !_micBoost_node->hasValue() )
147       _micBoost_node->setIntValue(1);
148
149   if ( !_server_node->hasValue() )
150       _server_node->setStringValue(DEFAULT_SERVER);
151
152   if ( !_speakerLevel_node->hasValue() )
153       _speakerLevel_node->setFloatValue(1.0);
154
155   if ( !_micLevel_node->hasValue() )
156       _micLevel_node->setFloatValue(1.0);
157
158   if ( !_register_node->hasValue() )
159       _register_node->setBoolValue(false);
160
161   if ( !_username_node->hasValue() )
162       _username_node->setStringValue("guest");
163
164   if ( !_password_node->hasValue() )
165       _password_node->setStringValue("guest");
166
167   if ( !_showMessages_node->hasValue() )
168       _showMessages_node->setBoolValue(false);
169
170   _selectedOutput_node->addChangeListener(this);
171   _selectedInput_node->addChangeListener(this);
172   _speakerLevel_node->addChangeListener(this);
173   _micBoost_node->addChangeListener(this);
174   _micLevel_node->addChangeListener(this);
175   _enabled_node->addChangeListener(this);
176   _comm0_node->addChangeListener(this);
177   _comm1_node->addChangeListener(this);
178   _ptt0_node->addChangeListener(this);
179   _test_node->addChangeListener(this);
180 }
181
182
183
184 void FGCom::unbind()
185 {
186 }
187
188
189
190 void FGCom::init()
191 {
192   _enabled          = _enabled_node->getBoolValue();
193   _server           = _server_node->getStringValue();
194   _register         = _register_node->getBoolValue();
195   _username         = _username_node->getStringValue();
196   _password         = _password_node->getStringValue();
197   _selectedComm     = 0;
198
199   _currentComm0     = _comm0_node->getDoubleValue();
200   _currentComm1     = _comm1_node->getDoubleValue();
201
202   _comm0Changed     = false;
203   _comm1Changed     = false;
204
205   _maxRange         = MAX_RANGE;
206   _minRange         = MIN_RANGE;
207 }
208
209
210
211 void FGCom::postinit()
212 {
213     if( !_enabled ) {
214         return;
215     }
216     
217     //WARNING: this _must_ be executed after sound system is totally initialized !
218     if( iaxc_initialize(NUM_CALLS) ) {
219         SG_LOG(SG_IO, SG_ALERT, "FGCom: cannot initialize iaxclient");
220         _enabled = false;
221         return;
222     }
223
224     assert( static_instance == NULL );
225     static_instance = this;
226     iaxc_set_event_callback( iaxc_callback );
227     
228     // FIXME: To be implemented in IAX audio driver
229     //iaxc_mic_boost_set( _micBoost_node->getIntValue() );
230     std::string app = "FGFS-";
231     app += _version_node->getStringValue();
232
233     iaxc_set_callerid( _callsign_node->getStringValue(), app.c_str() );
234     iaxc_set_formats (IAXC_FORMAT_SPEEX, IAXC_FORMAT_SPEEX);
235     iaxc_set_speex_settings(1, 5, 0, 1, 0, 3);
236     iaxc_set_filters(IAXC_FILTER_AGC | IAXC_FILTER_DENOISE);
237     iaxc_set_silence_threshold(-20.0);
238     iaxc_start_processing_thread ();
239
240     // Now IAXClient is initialized
241     _initialized = true;
242     
243     if ( _register ) {
244       _regId = iaxc_register( const_cast<char*>(_username.c_str()),
245                               const_cast<char*>(_password.c_str()),
246                               const_cast<char*>(_server.c_str()) );
247         if( _regId == -1 ) {
248             SG_LOG(SG_IO, SG_ALERT, "FGCom: cannot register iaxclient");
249             return;
250         }
251     }
252
253     /*
254       Here we will create the list of available audio devices
255       Each audio device has a name, an ID, and a list of capabilities
256       If an audio device can output sound, available-output=true 
257       If an audio device can input sound, available-input=true 
258
259       /sim/fgcom/selected-input (int)
260       /sim/fgcom/selected-output (int)
261
262       /sim/fgcom/device[n]/id (int)
263       /sim/fgcom/device[n]/name (string)
264       /sim/fgcom/device[n]/available-input (bool)
265       /sim/fgcom/device[n]/available-output (bool)
266     */
267
268     //FIXME: OpenAL driver use an hard-coded device
269     //       so all following is unused finally until someone
270     //       implement "multi-device" support in IAX audio driver
271     SGPropertyNode *node     = fgGetNode("/sim/fgcom", 0, true);
272
273     struct iaxc_audio_device *devs;
274     int nDevs, input, output, ring;
275
276     iaxc_audio_devices_get(&devs,&nDevs, &input, &output, &ring);
277
278     for(int i=0; i<nDevs; i++ ) {
279       SGPropertyNode *in_node = node->getChild("device", i, true);
280
281       // devID
282       _deviceID_node[i] = in_node->getChild("id", 0, true);
283       _deviceID_node[i]->setIntValue(devs[i].devID);
284
285       // name
286       _deviceName_node[i] = in_node->getChild("name", 0, true);
287       _deviceName_node[i]->setStringValue(devs[i].name);
288
289       // input capability
290       _deviceInput_node[i] = in_node->getChild("available-input", 0, true);
291       if( devs[i].capabilities & IAXC_AD_INPUT )
292         _deviceInput_node[i]->setBoolValue(true);
293       else 
294         _deviceInput_node[i]->setBoolValue(false);
295
296       // output capability
297       _deviceOutput_node[i] = in_node->getChild("available-output", 0, true);
298       if( devs[i].capabilities & IAXC_AD_OUTPUT )
299         _deviceOutput_node[i]->setBoolValue(true);
300       else 
301         _deviceOutput_node[i]->setBoolValue(false);
302
303       // use default device at start
304       if( devs[i].capabilities & IAXC_AD_INPUT_DEFAULT )
305         _selectedInput_node->setIntValue(devs[i].devID);
306       if( devs[i].capabilities & IAXC_AD_OUTPUT_DEFAULT )
307         _selectedOutput_node->setIntValue(devs[i].devID);
308     }
309
310     // Mute the mic and set speaker at start
311     iaxc_input_level_set( 0.0 );
312     iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
313
314     iaxc_millisleep(50);
315
316     // Do the first call at start
317     const double freq = _comm0_node->getDoubleValue();
318     _currentFreqKhz = 10 * static_cast<int>(freq * 100 + 0.25);
319     std::string num = computePhoneNumber(freq, getAirportCode(freq));
320     if( !num.empty() ) {
321       _callComm0 = iaxc_call(num.c_str());
322     }
323     if( _callComm0 == -1 )
324       SG_LOG( SG_IO, SG_DEBUG, "FGCom: cannot call " << num.c_str() );
325 }
326
327
328
329 void FGCom::updateCall(bool& changed, int& callNo, double freqMHz)
330 {
331
332     _currentFreqKhz = 10 * static_cast<int>(freqMHz * 100 + 0.25);
333
334     if (!changed) {
335         if( !isInRange(freqMHz) ) {
336             iaxc_dump_call_number(callNo);
337             callNo = -1;
338             return;
339         } else {
340             if(callNo != -1)
341                 return;
342         }
343     }
344
345     changed = false; // FIXME, out-params are confusing
346
347     if( callNo != -1 ) {
348         iaxc_dump_call_number( callNo );
349         callNo = -1;
350     }
351
352     if(_p.elapsedMSec() > IAX_DELAY) {
353         std::string num = computePhoneNumber(freqMHz, getAirportCode(freqMHz));
354         if( !isInRange(freqMHz) )
355             return;
356         if( !num.empty() ) {
357             callNo = iaxc_call(num.c_str());
358
359             if( callNo == -1 )
360                 SG_LOG( SG_IO, SG_DEBUG, "FGCom: cannot call " << num.c_str() );
361         }
362     } else {
363         changed = true;
364     }
365 }
366
367
368
369 void FGCom::update(double dt)
370 {
371     if ( !_enabled || !_initialized ) {
372         return;
373     }
374
375     // For now we manage FGCom for only one freq because IAXClient
376     // is not able to handle multiple calls at same time.
377     if( _selectedComm == 0) {
378         updateCall(_comm0Changed, _callComm0, _comm0_node->getDoubleValue());
379     } else {
380         updateCall(_comm1Changed, _callComm0, _comm1_node->getDoubleValue());
381     }
382 }
383
384
385
386 void FGCom::shutdown()
387 {
388     if( !_enabled ) {
389         return;
390     }
391
392   _initialized = false;
393   _enabled = false;
394
395   iaxc_set_event_callback(NULL);
396   iaxc_unregister(_regId);
397   iaxc_stop_processing_thread();
398   iaxc_shutdown();
399
400   assert( static_instance == this );
401   static_instance = NULL;
402 }
403
404
405
406 void FGCom::valueChanged(SGPropertyNode *prop)
407 {
408   if (prop == _enabled_node) {
409       bool isEnabled = prop->getBoolValue();
410       if (_enabled == isEnabled) {
411           return;
412       }
413       
414     if( isEnabled ) {
415       init();
416       postinit();
417     } else {
418       shutdown();
419     }
420     return;
421   }
422
423   if (prop == _ptt0_node && _enabled) {
424     if( _ptt0_node->getIntValue() == 2 ) {
425       if( _selectedComm == 0 ) {
426           SG_LOG( SG_IO, SG_INFO, "FGCom: change comm source to comm[1]" );
427           _comm1Changed = true;
428           _selectedComm = 1;
429       } else {
430           SG_LOG( SG_IO, SG_INFO, "FGCom: change comm source to comm[0]" );
431           _comm0Changed = true;
432           _selectedComm = 0;
433       }
434       return;
435     }
436     if( _ptt0_node->getBoolValue() ) {
437       iaxc_output_level_set( 0.0 );
438       iaxc_input_level_set( _micLevel_node->getFloatValue() ); //0.0 = min , 1.0 = max
439     } else {
440       iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
441       iaxc_input_level_set( 0.0 );
442     }
443   }
444
445   if (prop == _test_node) {
446     testMode( prop->getBoolValue() );
447     return;
448   }
449
450   //FIXME: not implemented in IAX audio driver (audio_openal.c)
451   if (prop == _micBoost_node && _initialized) {
452     int micBoost = prop->getIntValue();
453     SG_CLAMP_RANGE<int>( micBoost, 0, 1 );
454     iaxc_mic_boost_set( micBoost ) ; // 0 = enabled , 1 = disabled
455     return;
456   }
457
458   //FIXME: not implemented in IAX audio driver (audio_openal.c)
459   if ((prop == _selectedInput_node || prop == _selectedOutput_node) && _initialized) {
460     int selectedInput = _selectedInput_node->getIntValue();
461     int selectedOutput = _selectedOutput_node->getIntValue();
462     iaxc_audio_devices_set(selectedInput, selectedOutput, 0);
463     return;
464   }
465
466   if (_listener_active)
467     return;
468
469   _listener_active++;
470
471   if (prop == _speakerLevel_node && _enabled) {
472     float speakerLevel = prop->getFloatValue();
473     SG_CLAMP_RANGE<float>( speakerLevel, 0.0, 1.0 );
474     _speakerLevel_node->setFloatValue(speakerLevel);
475     //iaxc_output_level_set(speakerLevel);
476   }
477
478   if (prop == _micLevel_node && _enabled) {
479     float micLevel = prop->getFloatValue();
480     SG_CLAMP_RANGE<float>( micLevel, 0.0, 1.0 );
481     _micLevel_node->setFloatValue(micLevel);
482     //iaxc_input_level_set(micLevel);
483   }
484
485   if (prop == _comm0_node) {
486     if( _currentComm0 != prop->getDoubleValue() ) {
487       _currentComm0 = prop->getDoubleValue();
488       _p.stamp();
489       _comm0Changed = true;
490     }
491   }
492
493   if (prop == _comm1_node) {
494     if( _currentComm1 != prop->getDoubleValue() ) {
495       _currentComm1 = prop->getDoubleValue();
496       _p.stamp();
497       _comm1Changed = true;
498     }
499   }
500
501   _listener_active--;
502 }
503
504
505
506 void FGCom::testMode(bool testMode)
507 {
508   if(testMode && _initialized) {
509     _enabled = false;
510     iaxc_dump_all_calls();
511     iaxc_input_level_set( 1.0 );
512     iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
513     std::string num = computePhoneNumber(TEST_FREQ, NULL_ICAO);
514     if( num.size() > 0 ) {
515       iaxc_millisleep(IAX_DELAY);
516       _callComm0 = iaxc_call(num.c_str());
517     }
518     if( _callComm0 == -1 )
519       SG_LOG( SG_IO, SG_DEBUG, "FGCom: cannot call " << num.c_str() );
520   } else {
521     if( _initialized ) {
522       iaxc_dump_all_calls();
523       iaxc_millisleep(IAX_DELAY);
524       iaxc_input_level_set( 0.0 );
525       iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
526       _callComm0 = -1;
527       _enabled = true;
528     }
529   }
530 }
531
532
533
534 /*
535   \param freq The requested frequency e.g 120.825
536   \return The ICAO code as string e.g LFMV
537 */
538
539 std::string FGCom::getAirportCode(const double& freq)
540 {
541   SGGeod aircraftPos = globals->get_aircraft_position();
542
543   for(size_t i=0; i<sizeof(special_freq)/sizeof(special_freq[0]); i++) { // Check if it's a special freq
544     if(special_freq[i] == _currentFreqKhz) {
545       return NULL_ICAO;
546     }
547   }
548
549   flightgear::CommStation* apt = flightgear::CommStation::findByFreq(_currentFreqKhz, aircraftPos);
550   if( !apt ) {
551     return std::string();
552   }
553
554   if( apt->type() == FGPositioned::FREQ_TOWER ) {
555     _maxRange = MAX_TWR_RANGE;
556     _minRange = MIN_GNDTWR_RANGE;
557   } else if( apt->type() == FGPositioned::FREQ_GROUND ) {
558     _maxRange = MAX_GND_RANGE;
559     _minRange = MIN_GNDTWR_RANGE;
560   } else {
561     _maxRange = MAX_RANGE;
562     _minRange = MIN_RANGE;
563   }
564
565   _aptPos = apt->geod();
566   return apt->airport()->ident();
567 }
568
569
570
571 /*
572   \param freq The requested frequency e.g 120.825
573   \param iaco The associated ICAO code e.g LFMV
574   \return The phone number as string i.e username:password@fgcom.flightgear.org/0176707786120825
575 */
576
577 std::string FGCom::computePhoneNumber(const double& freq, const std::string& icao) const
578 {
579   if( icao.empty() )
580     return std::string(); 
581
582   char phoneNumber[256];
583   char exten[32];
584   char tmp[5];
585
586   /*Convert ICAO to ASCII */
587   sprintf( tmp, "%4s", icao.c_str() );
588
589   /*Built the phone number */
590   sprintf( exten,
591            "%02d%02d%02d%02d%02d%06d",
592            01,
593            tmp[0],
594            tmp[1],
595            tmp[2],
596            tmp[3],
597            (int) (freq * 1000 + 0.5) );
598   exten[16] = '\0';
599
600   snprintf( phoneNumber,
601             sizeof (phoneNumber),
602             "%s:%s@%s/%s",
603             _username.c_str(),
604             _password.c_str(),
605             _server.c_str(),
606             exten);
607
608   return phoneNumber;
609 }
610
611
612
613 /*
614   \return A boolean value, 1=in range, 0=out of range
615 */
616
617 bool FGCom::isInRange(const double &freq) const
618 {
619     for(size_t i=0; i<sizeof(special_freq)/sizeof(special_freq[0]); i++) { // Check if it's a special freq
620       if( (special_freq[i]) == _currentFreqKhz ) {
621         return 1;
622       }
623     }
624
625     SGGeod acftPos = globals->get_aircraft_position();
626     double distNm = SGGeodesy::distanceNm(_aptPos, acftPos);
627     double delta_elevation_ft = fabs(acftPos.getElevationFt() - _aptPos.getElevationFt());
628     double rangeNm = 1.23 * sqrt(delta_elevation_ft);
629
630     if (rangeNm > _maxRange) rangeNm = _maxRange;
631     if (rangeNm < _minRange) rangeNm = _minRange;
632     if( distNm > rangeNm )   return 0;
633     return 1;
634 }
635
636