implement SIP
Stefan Schuermans

Stefan Schuermans commited on 2019-05-04 14:47:32
Showing 25 changed files, with 1025 additions and 1 deletions.

... ...
@@ -0,0 +1,70 @@
1
+<html>
2
+  <head>
3
+    <title>Blinker - SIP Phone Connector</title>
4
+  </head>
5
+  <body>
6
+    <h1>Blinker - SIP Phone Connector</h1>
7
+    <p>
8
+      The SIP phone connector module acts as SIP client and provides an
9
+      interface to the internal operator connections.
10
+      Each module instance is a separate SIP client and can handle a
11
+      single call at a time. The incoming calls are connected to an
12
+      internal operator connector. Received DTMF tones are delivered
13
+      to the operator connection. Sound play requests received over
14
+      the operator connection are processed based on <code>*.wav</code>
15
+      files in the <code>sounds</code> subdirectory, i.e., the sounds are
16
+      played on the SIP connection if the file is found.
17
+    </p>
18
+    <h2>Configuration</h2>
19
+    <p>
20
+      The configuration of the SIP phone connector module with name
21
+      <code>NAME</code> is located in the <code>sipphones/NAME</code>
22
+      subdirectory.
23
+    </p>
24
+    <h3>SIP Server Name</h3>
25
+    <p>
26
+      The file <code>server</code> contains the host name of the SIP
27
+      server.
28
+      <br>
29
+      The SIP phone connector module will register with this server and
30
+      accept incoming calls (or reject them if another call is already
31
+      in progress).
32
+    </p>
33
+    <h3>SIP User Name</h3>
34
+    <p>
35
+      The file <code>username</code> contains the user name for the SIP
36
+      connection. It is used for authentication and to form the SIP identity
37
+      by prepending it to the SIP server name (separated <code>@</code>).
38
+    </p>
39
+    <h3>SIP Password</h3>
40
+    <p>
41
+      The file <code>password</code> contains the password for the SIP
42
+      authentication. Please note that those files are not present in
43
+      the example configuration.
44
+    </p>
45
+    <h3>Target Operator Connection Slot</h3>
46
+    <p>
47
+      The file <code>target</code> contains the name of the operator
48
+      interface to contact via an operator connection if the extension
49
+      is called.
50
+    </p>
51
+    <h3>Sound Directory</h3>
52
+    <p>
53
+      The <code>*.wav</code> files used to process sound playback requests
54
+      received on the internal operator connection are read from the
55
+      <code>sounds</code> subdirectory. The file name extension
56
+      <code>.wav</code> is automatically appended to the name of the
57
+      requested sound. This means if a sound play request for
58
+      <code>SOUND</code> is received, the file <code>sounds/SOUND.wav</code>
59
+      if opened. If it exists, it is played in the SIP connection.
60
+    </p>
61
+    <h3>Log Files</h3>
62
+    <p>
63
+      Log information from the SIP library is written to the file
64
+      <code>sip.log</code>. When the file reaches 10000 lines, a new
65
+      log file is started (using the same file name). The previous
66
+      log file is kept under the name <code>sip.log.prev</code>.
67
+    </p>
68
+  </body>
69
+</html>
70
+
... ...
@@ -0,0 +1,2 @@
1
+/sip.log
2
+/sip.log.prev
... ...
@@ -0,0 +1,2 @@
1
+sip.log
2
+sip.log.prev
... ...
@@ -1,7 +1,7 @@
1 1
 CONFIG :=
2 2
 DEFINE :=
3 3
 INCLUDE := -Icommon
4
-CFLAGS := -Wall -Wextra -Werror -O2 $(EXTRA_CFLAGS)
4
+CFLAGS := -Wall -Wextra -Werror -std=c++11 -O2 $(EXTRA_CFLAGS)
5 5
 LDFLAGS := $(EXTRA_LDFLAGS)
6 6
 LIBS := -lBlinkenLib $(EXTRA_LIBS)
7 7
 LAST_LIBS :=
... ...
@@ -15,6 +15,7 @@ TARGET := Blinker.exe
15 15
 else
16 16
 CXX := g++
17 17
 PLATFORM := linux
18
+LIBS += -lpthread
18 19
 endif
19 20
 INCLUDE += -I$(PLATFORM)
20 21
 
... ...
@@ -0,0 +1,803 @@
1
+/* Blinker
2
+   Copyright 2011-2014 Stefan Schuermans <stefan@blinkenarea.org>
3
+   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
4
+   a blinkenarea.org project */
5
+
6
+#include <chrono>
7
+#include <condition_variable>
8
+#include <iostream>
9
+#include <mutex>
10
+#include <sstream>
11
+#include <stdarg.h>
12
+#include <stdio.h>
13
+#include <string>
14
+#include <string.h>
15
+#include <thread>
16
+
17
+#ifdef BLINKER_CFG_LINPHONE
18
+#include <linphone/linphonecore.h>
19
+#endif
20
+
21
+#include "Directory.h"
22
+#include "File.h"
23
+#include "Module.h"
24
+#include "NameFile.h"
25
+#include "OpConn.h"
26
+#include "OpConnIf.h"
27
+#include "SipPhone.h"
28
+#include "Time.h"
29
+#include "TimeCallee.h"
30
+
31
+namespace Blinker {
32
+
33
+#ifdef BLINKER_CFG_LINPHONE
34
+
35
+/**
36
+ * everything using the linphone library happens in a separate thread,
37
+ * because some call to linphone take long (up to two seconds)
38
+ */
39
+namespace linphone {
40
+
41
+static char const *loglevel2str(OrtpLogLevel lev)
42
+{
43
+  switch(lev) {
44
+  case ORTP_DEBUG: return "DEBUG";
45
+  case ORTP_MESSAGE: return "MESSAGE";
46
+  case ORTP_WARNING: return "WARNING";
47
+  case ORTP_ERROR: return "ERROR";
48
+  case ORTP_FATAL: return "FATAL";
49
+  case ORTP_TRACE: return "TRACE";
50
+  case ORTP_LOGLEV_END: return "LOGLEV_END";
51
+  default: return "?";
52
+  }
53
+}
54
+
55
+static char const *gstate2str(LinphoneGlobalState gstate)
56
+{
57
+  switch (gstate) {
58
+  case LinphoneGlobalOff: return "Off";
59
+  case LinphoneGlobalStartup: return "Startup";
60
+  case LinphoneGlobalOn: return "On";
61
+  case LinphoneGlobalShutdown: return "Shutdown";
62
+  default: return "?";
63
+  }
64
+}
65
+
66
+static char const *rstate2str(LinphoneRegistrationState rstate)
67
+{
68
+  switch (rstate) {
69
+  case LinphoneRegistrationNone: return "None";
70
+  case LinphoneRegistrationProgress: return "Progress";
71
+  case LinphoneRegistrationOk: return "Ok";
72
+  case LinphoneRegistrationCleared: return "Cleared";
73
+  case LinphoneRegistrationFailed: return "Failed";
74
+  default: return "?";
75
+  }
76
+}
77
+
78
+static char const *cstate2str(LinphoneCallState cstate)
79
+{
80
+  switch (cstate) {
81
+  case LinphoneCallIdle: return "Idle";
82
+  case LinphoneCallIncomingReceived: return "IncomingReceived";
83
+  case LinphoneCallOutgoingInit: return "OutgoingInit";
84
+  case LinphoneCallOutgoingProgress: return "OutgoingProgress";
85
+  case LinphoneCallOutgoingRinging: return "OutgoingRinging";
86
+  case LinphoneCallOutgoingEarlyMedia: return "OutgoingEarlyMedia";
87
+  case LinphoneCallConnected: return "Connected";
88
+  case LinphoneCallStreamsRunning: return "StreamsRunning";
89
+  case LinphoneCallPausing: return "Pausing";
90
+  case LinphoneCallPaused: return "Paused";
91
+  case LinphoneCallResuming: return "Resuming";
92
+  case LinphoneCallRefered: return "Refered";
93
+  case LinphoneCallError: return "Error";
94
+  case LinphoneCallEnd: return "End";
95
+  case LinphoneCallPausedByRemote: return "PausedByRemote";
96
+  case LinphoneCallUpdatedByRemote: return "UpdatedByRemote";
97
+  case LinphoneCallIncomingEarlyMedia: return "IncomingEarlyMedia";
98
+  case LinphoneCallUpdating: return "Updating";
99
+  case LinphoneCallReleased: return "Released";
100
+  default: return "?";
101
+  }
102
+}
103
+
104
+std::string glLogFileName; ///< name of global log file
105
+std::ofstream glLogStream; ///< stream to open global log file
106
+unsigned int glLogLineCnt; ///< number of lines already logged
107
+
108
+/**
109
+ * @brief log information from linphone library
110
+ * @param[in] lev log level
111
+ * @param[in] fmt format string
112
+ * @param[in] args arguments to insert into format string
113
+ */
114
+void log_handler(OrtpLogLevel lev, const char *fmt, va_list args)
115
+{
116
+  // (re-)open logfile on first line
117
+  if (glLogLineCnt == 0) {
118
+    // close old file and clean up (just to be on the safe side)
119
+    glLogStream.close();
120
+    glLogStream.clear();
121
+    // keep previous log file
122
+    rename(glLogFileName.c_str(), (glLogFileName + ".prev").c_str());
123
+    // open new file
124
+    glLogStream.open(glLogFileName.c_str(), std::ios::out);
125
+  }
126
+  // write log line
127
+  char buffer[4096];
128
+  vsnprintf(buffer, sizeof(buffer) - 1, fmt, args);
129
+  buffer[sizeof(buffer) - 1] = 0;
130
+  glLogStream << Time::now().toStr() << " "
131
+              << loglevel2str(lev) << " " << buffer << std::endl;
132
+  // count lines
133
+  ++glLogLineCnt;
134
+  // close file if maximum line count reached
135
+  if (glLogLineCnt >= 10000) {
136
+    // close old file and clean up
137
+    glLogStream.close();
138
+    glLogStream.clear();
139
+    // reset line count, so new file is opened on next line
140
+    glLogLineCnt = 0;
141
+  }
142
+}
143
+
144
+/// global init
145
+void init(std::string const &logFileName)
146
+{
147
+  glLogFileName = logFileName;
148
+  glLogLineCnt = 0;
149
+  linphone_core_set_log_handler(log_handler);
150
+}
151
+
152
+/// data of SIP client worker thread
153
+struct Data {
154
+  SipPhone::ConfigData const *configData; ///< config data for worker thread
155
+  SipPhone::SharedData *sharedData; ///< data shared with main Blinker thread
156
+  LinphoneCoreVTable callbacks; ///< callback table
157
+  LinphoneCore *lc; ///< linphone core object, if active
158
+  LinphoneCall *call; ///< current call, if available
159
+  std::string playback; ///< name of playback file
160
+  std::ofstream logStream; ///< stream to open log file
161
+  unsigned int logLineCnt; ///< number of lines already logged to file
162
+};
163
+
164
+/**
165
+ * @breif set playback file
166
+ * @param[in,out] data data of SIP client
167
+ * @param[in] name name of sound file (without extension)
168
+ */
169
+void set_playback(Data &data, std::string const &name)
170
+{
171
+  Directory dir(data.configData->soundDir);
172
+  data.playback = dir.getFile(name + ".wav").getPath();
173
+}
174
+
175
+void start_playback(Data &data)
176
+{
177
+  if (data.lc && ! data.playback.empty()) {
178
+    linphone_core_set_play_file(data.lc, data.playback.c_str());
179
+  }
180
+}
181
+
182
+/**
183
+ * @brief write log message
184
+ * @param[in,out] data data of SIP client
185
+ * @param[in] line lie to log
186
+ */
187
+void log(Data &data, std::string const &line)
188
+{
189
+  // (re-)open logfile on first line
190
+  if (data.logLineCnt == 0) {
191
+    // close old file and clean up (just to be on the safe side)
192
+    data.logStream.close();
193
+    data.logStream.clear();
194
+    // keep previous log file
195
+    rename(data.configData->logFileName.c_str(),
196
+           (data.configData->logFileName + ".prev").c_str());
197
+    // open new file
198
+    data.logStream.open(data.configData->logFileName.c_str(), std::ios::out);
199
+  }
200
+  // write log line
201
+  data.logStream << Time::now().toStr() << " " << line << std::endl;
202
+  // count lines
203
+  ++data.logLineCnt;
204
+  // close file if maximum line count reached
205
+  if (data.logLineCnt >= 10000) {
206
+    // close old file and clean up
207
+    data.logStream.close();
208
+    data.logStream.clear();
209
+    // reset line count, so new file is opened on next line
210
+    data.logLineCnt = 0;
211
+  }
212
+}
213
+
214
+/**
215
+ * @brief global SIP state changed
216
+ * @param[in] lc linphone core object
217
+ * @param[in] gstate new global SIP state
218
+ * @param[in] message informational message
219
+ */
220
+void global_state_changed(struct _LinphoneCore *lc,
221
+                          LinphoneGlobalState gstate,
222
+                          const char *message)
223
+{
224
+  Data *data = (Data*)linphone_core_get_user_data(lc);
225
+
226
+  std::stringstream line;
227
+  line << "global state changed to " << gstate2str(gstate) << ": " << message;
228
+  log(*data, line.str());
229
+}
230
+
231
+/**
232
+ * @brief registration state changed
233
+ * @param[in] lc linphone core object
234
+ * @param[in] cfg proxy configuration
235
+ * @param[in] rstate new registration state
236
+ * @param[in] message informational message
237
+ */
238
+void registration_state_changed(struct _LinphoneCore *lc,
239
+                                LinphoneProxyConfig *cfg,
240
+                                LinphoneRegistrationState rstate,
241
+                                const char *message)
242
+{
243
+  Data *data = (Data*)linphone_core_get_user_data(lc);
244
+  (void)cfg;
245
+
246
+  std::stringstream line;
247
+  line << "registration state changed to " << rstate2str(rstate) << ": "
248
+       << message;
249
+  log(*data, line.str());
250
+}
251
+
252
+/**
253
+ * @brief call state changed
254
+ * @param[in] lc linphone core object
255
+ * @param[in] call call object
256
+ * @param[in] cstate new call state
257
+ * @param[in] message informational message
258
+ */
259
+void call_state_changed(struct _LinphoneCore *lc, LinphoneCall *call,
260
+                        LinphoneCallState cstate, const char *message)
261
+{
262
+  Data *data = (Data*)linphone_core_get_user_data(lc);
263
+
264
+  std::stringstream line;
265
+  line << "call state changed to " << cstate2str(cstate) << ": "
266
+       << message;
267
+  log(*data, line.str());
268
+
269
+  // current call -> handle state changes
270
+  if (data->call == call) {
271
+    switch (cstate) {
272
+    case LinphoneCallStreamsRunning:
273
+      start_playback(*data);
274
+      break;
275
+    case LinphoneCallRefered:
276
+    case LinphoneCallError:
277
+    case LinphoneCallEnd:
278
+    case LinphoneCallReleased:
279
+      data->call = nullptr;
280
+      {
281
+        std::lock_guard<std::mutex> lock(data->sharedData->mtx);
282
+        data->sharedData->terminated = true;
283
+      }
284
+      break;
285
+    default:
286
+      break;
287
+    }
288
+  }
289
+  // other call, but current call active -> reject call
290
+  else if (data->call) {
291
+    switch (cstate) {
292
+    case LinphoneCallIncomingReceived:
293
+      log(*data, "rejecting call (busy)");
294
+      linphone_core_decline_call(lc, call, LinphoneReasonBusy);
295
+      break;
296
+    default:
297
+      break;
298
+    }
299
+  }
300
+  // no call active -> accept call
301
+  else {
302
+    switch (cstate) {
303
+    case LinphoneCallIncomingReceived:
304
+      data->call = call;
305
+      {
306
+        std::lock_guard<std::mutex> lock(data->sharedData->mtx);
307
+        data->sharedData->accepted = true;
308
+      }
309
+      data->playback.clear();
310
+      log(*data, "accepting call");
311
+      linphone_core_accept_call(lc, call);
312
+      break;
313
+    default:
314
+      break;
315
+    }
316
+  }
317
+}
318
+
319
+/**
320
+ * @brief DTMF received
321
+ * @param[in] lc linphone core object
322
+ * @param[in] call call object
323
+ * @param[in] dtmf DTMF as ASCII character
324
+ */
325
+void dtmf_received(struct _LinphoneCore* lc, LinphoneCall *call, int dtmf)
326
+{
327
+  Data *data = (Data*)linphone_core_get_user_data(lc);
328
+
329
+  // check if current call
330
+  bool current = data->call == call;
331
+
332
+  // check DTMF
333
+  char c;
334
+  if (dtmf >= '0' && dtmf <= '9') {
335
+    c = dtmf;
336
+  } else if (dtmf == '*') {
337
+    c = '*';
338
+  } else if (dtmf == '#') {
339
+    c = '#';
340
+  } else {
341
+    c = '?';
342
+  }
343
+
344
+  std::stringstream line;
345
+  line << "dtmf received " << (current ? "(current call)" : "(other call)")
346
+       << ": " << c;
347
+  log(*data, line.str());
348
+
349
+  // ignore DTMF from other calls
350
+  if (! current) {
351
+    return;
352
+  }
353
+
354
+  // report DTMF keys
355
+  {
356
+    std::lock_guard<std::mutex> lock(data->sharedData->mtx);
357
+    data->sharedData->dtmf += c;
358
+  }
359
+}
360
+
361
+/**
362
+ * @brief register if not active and login data available
363
+ * @param[in,out] data data of SIP client
364
+ * @param[in] server SIP server
365
+ * @param[in] username SIP username
366
+ * @param[in] password SIP password
367
+ */
368
+void do_register(Data &data, std::string const &server,
369
+                 std::string const &username, std::string &password)
370
+{
371
+  if (data.lc) {
372
+    return;
373
+  }
374
+  if (server.empty()) {
375
+    return;
376
+  }
377
+
378
+  // create linphone core object
379
+  data.lc = linphone_core_new(&data.callbacks, NULL, NULL, &data);
380
+  if (! data.lc) {
381
+    log(data, "failed to create linphone core");
382
+    return;
383
+  }
384
+  log(data, "linphone core created");
385
+
386
+  // set auth data
387
+  if (! username.empty() && ! password.empty()) {
388
+    LinphoneAuthInfo *auth_info =
389
+        linphone_auth_info_new(username.c_str(), username.c_str(),
390
+                               password.c_str(), "", "");
391
+    if (! auth_info) {
392
+      log(data, "failed to create auth info");
393
+      linphone_core_destroy(data.lc);
394
+      data.lc = nullptr;
395
+      return;
396
+    }
397
+    linphone_core_add_auth_info(data.lc, auth_info);
398
+    log(data, "auth info set");
399
+  } else {
400
+    log(data, "no auth data available");
401
+  }
402
+
403
+  // configure proxy
404
+  std::string sipserver = "sip:" + server;
405
+  std::string identity = "sip:" + username + "@" + server;
406
+  LinphoneProxyConfig *proxy = linphone_core_create_proxy_config(data.lc);
407
+  if (! proxy) {
408
+    log(data, "failed to create proxy config");
409
+    linphone_core_destroy(data.lc);
410
+    data.lc = nullptr;
411
+    return;
412
+  }
413
+  linphone_proxy_config_set_server_addr(proxy, sipserver.c_str());
414
+  linphone_proxy_config_set_identity(proxy, identity.c_str());
415
+  linphone_proxy_config_enable_register(proxy, TRUE);
416
+  linphone_core_add_proxy_config(data.lc, proxy);
417
+  log(data, "proxy config set");
418
+
419
+  // tell linphone not to rind and to use files instead of sound card
420
+  linphone_core_set_ring(data.lc, nullptr);
421
+  linphone_core_use_files(data.lc, TRUE);
422
+  linphone_core_set_play_file(data.lc, nullptr);
423
+}
424
+
425
+/**
426
+ * @brief deregister if active
427
+ * @param[in,out] data data of SIP client
428
+ */
429
+void do_deregister(Data &data)
430
+{
431
+  if (! data.lc) {
432
+    return;
433
+  }
434
+
435
+  log(data, "destroying...");
436
+  linphone_core_destroy(data.lc);
437
+  data.lc = nullptr;
438
+  data.call = nullptr;
439
+  log(data, "destroyed");
440
+}
441
+
442
+/**
443
+ * @brief play sound on call if active
444
+ * @param[in,out] data data of SIP client
445
+ * @param[in] name name of sound
446
+ */
447
+void do_play(Data &data, std::string const &name)
448
+{
449
+  if (! data.lc) {
450
+    return;
451
+  }
452
+  if (! data.call) {
453
+    return;
454
+  }
455
+
456
+  std::stringstream line;
457
+  line << "playing sound \"" << name << "\"";
458
+  log(data, line.str());
459
+  set_playback(data, name);
460
+  start_playback(data);
461
+}
462
+
463
+/**
464
+ * @brief hangup call if active
465
+ * @param[in,out] data data of SIP client
466
+ */
467
+void do_hangup(Data &data)
468
+{
469
+  if (! data.lc) {
470
+    return;
471
+  }
472
+  if (! data.call) {
473
+    return;
474
+  }
475
+
476
+  log(data, "terminating call...");
477
+  linphone_core_terminate_call(data.lc, data.call);
478
+  data.call = nullptr;
479
+  log(data, "call terminated");
480
+}
481
+
482
+/**
483
+ * @brief linphone worker thread
484
+ * @param[in] soundDir directory of sound files
485
+ * @param[in,out] data shared data for communication with main Blinker thread
486
+ */
487
+void worker(SipPhone::ConfigData const &configData,
488
+            SipPhone::SharedData &sharedData)
489
+{
490
+  // set up data of SIP client
491
+  Data data;
492
+  data.configData = &configData;
493
+  data.sharedData = &sharedData;
494
+  memset(&data.callbacks, 0, sizeof(data.callbacks));
495
+  data.callbacks.global_state_changed = global_state_changed;
496
+  data.callbacks.registration_state_changed = registration_state_changed;
497
+  data.callbacks.call_state_changed = call_state_changed;
498
+  data.callbacks.dtmf_received = dtmf_received;
499
+  data.lc = nullptr;
500
+  data.call = nullptr;
501
+  data.logLineCnt = 0;
502
+
503
+  // main loop of worker - with shared data locked
504
+  std::chrono::milliseconds timeout(10);
505
+  std::unique_lock<std::mutex> lock(sharedData.mtx);
506
+  while (sharedData.run) {
507
+
508
+    // hangup
509
+    if (sharedData.hangup) {
510
+      sharedData.hangup = false;
511
+      sharedData.reqPlay = false;
512
+      sharedData.dtmf.clear();
513
+      sharedData.terminated = true;
514
+      // execute hangup with unlocked shared data (parallelism!)
515
+      lock.unlock();
516
+      do_hangup(data);
517
+      lock.lock();
518
+      continue; // re-check everything after re-locking mutex
519
+    }
520
+
521
+    // re-register (including initial registration and derigstration)
522
+    if (sharedData.reregister) {
523
+      sharedData.reregister = false;
524
+      sharedData.hangup = false;
525
+      sharedData.reqPlay = false;
526
+      sharedData.dtmf.clear();
527
+      sharedData.terminated = true;
528
+      std::string server = sharedData.server;
529
+      std::string username = sharedData.username;
530
+      std::string password = sharedData.password;
531
+      // execute re-registration with unlocked shared data (parallelism!)
532
+      lock.unlock();
533
+      do_deregister(data);
534
+      do_register(data, server, username, password);
535
+      lock.lock();
536
+      continue; // re-check everything after re-locking mutex
537
+    }
538
+
539
+    // play sound
540
+    if (sharedData.reqPlay) {
541
+      sharedData.reqPlay = false;
542
+      std::string name = sharedData.reqPlayName;
543
+      // execute hangup with unlocked shared data (parallelism!)
544
+      lock.unlock();
545
+      do_play(data, name);
546
+      lock.lock();
547
+      continue; // re-check everything after re-locking mutex
548
+    }
549
+
550
+    // background processing by linphone if active
551
+    if (data.lc) {
552
+      lock.unlock();
553
+      linphone_core_iterate(data.lc);
554
+      lock.lock();
555
+    }
556
+
557
+    // nothing to do, wait for signal
558
+    sharedData.cond.wait_for(lock, timeout);
559
+
560
+  } // while (sharedData.run)
561
+}
562
+
563
+} // namespace linphone
564
+
565
+#endif // #ifdef BLINKER_CFG_LINPHONE
566
+
567
+/**
568
+ * @brief global initialization (call once before using this class)
569
+ * @param[in] globalLogDir directory for global SIP log files
570
+ */
571
+void SipPhone::init(const Directory &globalLogDir)
572
+{
573
+#ifdef BLINKER_CFG_LINPHONE
574
+  linphone::init(globalLogDir.getFile("sip.log").getPath());
575
+#endif
576
+}
577
+
578
+/**
579
+ * @brief constructor
580
+ * @param[in] name module name
581
+ * @param[in] mgrs managers
582
+ * @param[in] dirBase base directory
583
+ */
584
+SipPhone::SipPhone(const std::string &name, Mgrs &mgrs,
585
+                   const Directory &dirBase):
586
+  Module(name, mgrs, dirBase),
587
+  m_fileServer(dirBase.getFile("server")),
588
+  m_fileUsername(dirBase.getFile("username")),
589
+  m_filePassword(dirBase.getFile("password")),
590
+  m_fileTarget(dirBase.getFile("target")),
591
+  m_configData(),
592
+  m_worker(),
593
+  m_sharedData(),
594
+  m_curConn(nullptr)
595
+{
596
+  m_configData.soundDir = dirBase.getSubdir("sounds").getPath();
597
+  m_configData.logFileName = dirBase.getFile("sip.log").getPath();
598
+  m_sharedData.run = true;
599
+  m_sharedData.reregister = false;
600
+  m_sharedData.reqPlay = false;
601
+  m_sharedData.hangup = false;
602
+  m_sharedData.accepted = false;
603
+  m_sharedData.terminated = false;
604
+
605
+  m_mgrs.m_callMgr.requestTimeCall(this, Time::now());
606
+
607
+#ifdef BLINKER_CFG_LINPHONE
608
+  m_worker = std::thread(linphone::worker, std::ref(m_configData),
609
+                         std::ref(m_sharedData));
610
+#endif
611
+
612
+  sipRegister();
613
+}
614
+
615
+/// virtual destructor
616
+SipPhone::~SipPhone()
617
+{
618
+  if (m_curConn) {
619
+    m_curConn->close();
620
+    m_curConn = nullptr;
621
+  }
622
+
623
+  sipDeregister();
624
+
625
+  {
626
+    std::lock_guard<std::mutex> lock(m_sharedData.mtx);
627
+    m_sharedData.run = false;
628
+  }
629
+  m_sharedData.cond.notify_one();
630
+#ifdef BLINKER_CFG_LINPHONE
631
+  m_worker.join();
632
+#endif
633
+
634
+  m_mgrs.m_callMgr.cancelTimeCall(this);
635
+}
636
+
637
+/// check for update of configuration
638
+void SipPhone::updateConfig()
639
+{
640
+  // server, username or password file modified -> re-connect
641
+  if (m_fileServer.checkModified() || m_fileUsername.checkModified() ||
642
+                                      m_filePassword.checkModified()) {
643
+    // re-register
644
+    sipDeregister();
645
+    sipRegister();
646
+  }
647
+
648
+  // target file was modified -> re-get target operator interface to connect to
649
+  if (m_fileTarget.checkModified()) {
650
+    m_fileTarget.update();
651
+  }
652
+}
653
+
654
+/// callback when requested time reached
655
+void SipPhone::timeCall()
656
+{
657
+  Time now = Time::now();
658
+
659
+  // get information from SIP phone worker
660
+  bool accepted;
661
+  std::string dtmf;
662
+  bool terminated;
663
+  {
664
+    std::lock_guard<std::mutex> lock(m_sharedData.mtx);
665
+    accepted = m_sharedData.accepted;
666
+    m_sharedData.accepted = false;
667
+    dtmf = m_sharedData.dtmf;
668
+    m_sharedData.dtmf.clear();
669
+    terminated = m_sharedData.terminated;
670
+    m_sharedData.terminated = false;
671
+  }
672
+
673
+  // phone call has terminated or new one has been accepted
674
+  if (terminated || accepted) {
675
+    // close connection if any
676
+    if (m_curConn) {
677
+      // request hang up of phone
678
+      {
679
+        std::lock_guard<std::mutex> lock(m_sharedData.mtx);
680
+        m_sharedData.hangup = true;
681
+      }
682
+      // close and forget connection
683
+      m_curConn->close();
684
+      m_curConn = nullptr;
685
+    }
686
+  }
687
+
688
+  // phone call has been accepted
689
+  if (accepted) {
690
+    // try to open new connection to target operator interface
691
+    if (m_fileTarget.m_valid) {
692
+      m_curConn = m_mgrs.m_opMgr.connect(m_fileTarget.m_obj.m_str, this);
693
+    }
694
+  }
695
+
696
+  // DTMF received -> send to operator connection
697
+  if (! dtmf.empty()) {
698
+    if (m_curConn) {
699
+      for (char key: dtmf) {
700
+        m_curConn->sendKey(key);
701
+      }
702
+    }
703
+  }
704
+
705
+  // request next call
706
+  Time interval;
707
+  interval.fromMs(20);
708
+  m_mgrs.m_callMgr.requestTimeCall(this, now + interval);
709
+}
710
+
711
+/**
712
+ * @brief key command received on operator connection
713
+ * @param[in] pConn operator connection object
714
+ * @param[in] key key that was pressed
715
+ */
716
+void SipPhone::opConnRecvKey(OpConn *pConn, char key)
717
+{
718
+  // ignore keys received
719
+  (void)pConn;
720
+  (void)key;
721
+}
722
+
723
+/**
724
+ * @brief play command received on operator connection
725
+ * @param[in] pConn operator connection object
726
+ * @param[in] sound name of sound to play
727
+ */
728
+void SipPhone::opConnRecvPlay(OpConn *pConn, const std::string &sound)
729
+{
730
+  // only process current connection
731
+  if (pConn != m_curConn) {
732
+    return;
733
+  }
734
+
735
+  // request playing sound
736
+  {
737
+    std::lock_guard<std::mutex> lock(m_sharedData.mtx);
738
+    m_sharedData.reqPlay = true;
739
+    m_sharedData.reqPlayName = sound;
740
+  }
741
+}
742
+
743
+/**
744
+ * @brief operator connection is closed
745
+ * @param[in] pConn operator connection object
746
+ *
747
+ * The connection may not be used for sending any more in this callback.
748
+ */
749
+void SipPhone::opConnClose(OpConn *pConn)
750
+{
751
+  // only process current connection
752
+  if (pConn != m_curConn) {
753
+    return;
754
+  }
755
+
756
+  // request hang up of phone
757
+  {
758
+    std::lock_guard<std::mutex> lock(m_sharedData.mtx);
759
+    m_sharedData.hangup = true;
760
+  }
761
+
762
+  // forget connection
763
+  m_curConn = nullptr;
764
+}
765
+
766
+/// (re-)register with SIP server
767
+void SipPhone::sipRegister()
768
+{
769
+  // read settings
770
+  m_fileServer.update();
771
+  m_fileUsername.update();
772
+  m_filePassword.update();
773
+
774
+  // put server, username and password into shared data, trigger registration
775
+  {
776
+    std::lock_guard<std::mutex> lock(m_sharedData.mtx);
777
+    m_sharedData.server = m_fileServer.m_valid
778
+                          ? m_fileServer.m_obj.toStr() : std::string();
779
+    m_sharedData.username = m_fileUsername.m_valid
780
+                            ? m_fileUsername.m_obj.toStr() : std::string();
781
+    m_sharedData.password = m_filePassword.m_valid
782
+                            ? m_filePassword.m_obj.toStr() : std::string();
783
+    m_sharedData.reregister = true;
784
+  }
785
+  m_sharedData.cond.notify_one();
786
+}
787
+
788
+/// deregister with SIP server
789
+void SipPhone::sipDeregister()
790
+{
791
+  // remove login infor from shared data, trigger re-registration
792
+  {
793
+    std::lock_guard<std::mutex> lock(m_sharedData.mtx);
794
+    m_sharedData.server.clear();
795
+    m_sharedData.username.clear();
796
+    m_sharedData.password.clear();
797
+    m_sharedData.reregister = true;
798
+  }
799
+  m_sharedData.cond.notify_one();
800
+}
801
+
802
+} // namespace Blinker
803
+
... ...
@@ -0,0 +1,132 @@
1
+/* Blinker
2
+   Copyright 2011-2014 Stefan Schuermans <stefan@blinkenarea.org>
3
+   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
4
+   a blinkenarea.org project */
5
+
6
+#ifndef BLINKER_SIPPHONE_H
7
+#define BLINKER_SIPPHONE_H
8
+
9
+#include <condition_variable>
10
+#include <mutex>
11
+#include <string>
12
+#include <thread>
13
+
14
+#include "File.h"
15
+#include "Module.h"
16
+#include "NameFile.h"
17
+#include "OpConn.h"
18
+#include "OpConnIf.h"
19
+#include "Time.h"
20
+#include "TimeCallee.h"
21
+
22
+namespace Blinker {
23
+
24
+/// SIP phone connector
25
+class SipPhone: public Module, public OpConnIf, public TimeCallee
26
+{
27
+public:
28
+  /// configuration data for worker
29
+  struct ConfigData {
30
+    std::string soundDir; ///< directory of sound files
31
+    std::string logFileName; ///< base name of log file
32
+  };
33
+  /// data shared between linphone thread and main Blinker thread
34
+  struct SharedData {
35
+    std::mutex mtx; ///< protection for this structure
36
+    std::condition_variable cond; ///< worker threads waits on this condition
37
+    /// Blinker -> SIP
38
+    //@{
39
+    bool run; ///< whether to keep worker running (false = exit)
40
+    bool reregister; ///< flag to trigger (re-)registration
41
+    std::string server; ///< SIP server, empty means invalid
42
+    std::string username; ///< SIP username, empty = no auth
43
+    std::string password; ///< SIP password, empty = no auth
44
+    bool reqPlay; ///< flag to trigger playing sound
45
+    std::string reqPlayName; ///< name of sound to play
46
+    bool hangup; ///< flag to trigger hangup of phone call
47
+    //@}
48
+    /// SIP -> Blinker
49
+    //@{
50
+    bool accepted; ///< flag for incoming/accepted phone call
51
+    std::string dtmf; ///< DTMF keys received
52
+    bool terminated; ///< flag for phone call is terminated
53
+    //@}
54
+  };
55
+
56
+public:
57
+  /**
58
+   * @brief global initialization (call once before using this class)
59
+   * @param[in] globalLogDir directory for global SIP log files
60
+   */
61
+  static void init(const Directory &globalLogDir);
62
+
63
+public:
64
+  /**
65
+   * @brief constructor
66
+   * @param[in] name module name
67
+   * @param[in] mgrs managers
68
+   * @param[in] dirBase base directory
69
+   */
70
+  SipPhone(const std::string &name, Mgrs &mgrs, const Directory &dirBase);
71
+
72
+  /// virtual destructor
73
+  virtual ~SipPhone();
74
+
75
+private:
76
+  /// copy constructor disabled
77
+  SipPhone(const SipPhone &that);
78
+
79
+  /// assignment operator disabled
80
+  const SipPhone & operator=(const SipPhone &that);
81
+
82
+public:
83
+  /// check for update of configuration
84
+  virtual void updateConfig();
85
+
86
+  /// callback when requested time reached
87
+  virtual void timeCall();
88
+
89
+  /**
90
+   * @brief key command received on operator connection
91
+   * @param[in] pConn operator connection object
92
+   * @param[in] key key that was pressed
93
+   */
94
+  virtual void opConnRecvKey(OpConn *pConn, char key);
95
+
96
+  /**
97
+   * @brief play command received on operator connection
98
+   * @param[in] pConn operator connection object
99
+   * @param[in] sound name of sound to play
100
+   */
101
+  virtual void opConnRecvPlay(OpConn *pConn, const std::string &sound);
102
+
103
+  /**
104
+   * @brief operator connection is closed
105
+   * @param[in] pConn operator connection object
106
+   *
107
+   * The connection may not be used for sending any more in this callback.
108
+   */
109
+  virtual void opConnClose(OpConn *pConn);
110
+
111
+protected:
112
+  /// (re-)register with SIP server
113
+  void sipRegister();
114
+
115
+  /// deregister with SIP server
116
+  void sipDeregister();
117
+
118
+protected:
119
+  NameFile    m_fileServer;   ///< name of SIP server
120
+  NameFile    m_fileUsername; ///< SIP username
121
+  NameFile    m_filePassword; ///< SIP password
122
+  NameFile    m_fileTarget;   ///< target operator interface name to connect to
123
+  ConfigData  m_configData;   ///< configuration data for worker
124
+  std::thread m_worker;       ///< worker thread
125
+  SharedData  m_sharedData;   ///< data shared between worker and main
126
+  OpConn      *m_curConn;     ///< current connection
127
+}; // class SipPhone
128
+
129
+} // namespace Blinker
130
+
131
+#endif // #ifndef BLINKER_SIPPHONE_H
132
+
... ...
@@ -24,6 +24,7 @@
24 24
 #include "RateLimiter.h"
25 25
 #include "Resizer.h"
26 26
 #include "Scaler.h"
27
+#include "SipPhone.h"
27 28
 #include "SyncNameSplitter.h"
28 29
 #include "SyncPrinter.h"
29 30
 #include "Transformer.h"
... ...
@@ -42,6 +43,8 @@ void run(const std::string &dirConfig)
42 43
 {
43 44
   Directory cfg(dirConfig);
44 45
 
46
+  SipPhone::init(dirConfig);
47
+
45 48
   Mgrs mgrs;
46 49
 
47 50
 #define MODULEMGR(TYPE, CLASS) \
... ...
@@ -60,6 +63,7 @@ void run(const std::string &dirConfig)
60 63
   MODULEMGR(Resizer,          resizers);
61 64
   MODULEMGR(RateLimiter,      ratelimiters);
62 65
   MODULEMGR(Scaler,           scalers);
66
+  MODULEMGR(SipPhone,         sipphones);
63 67
   MODULEMGR(SyncNameSplitter, syncnamesplitters);
64 68
   MODULEMGR(SyncPrinter,      syncprinters);
65 69
   MODULEMGR(Transformer,      transformers);
... ...
@@ -0,0 +1,6 @@
1
+if echo '#include <linphone/linphonecore.h>' | g++ - -E >/dev/null 2>/dev/null
2
+then
3
+  echo CONFIG+=LPH
4
+  echo DEFINE+=-DBLINKER_CFG_LINPHONE
5
+  echo LIBS+=-llinphone
6
+fi
0 7