sip-lab 1.20.0 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,13 +7,16 @@ It uses pjproject for SIP and media processing.
7
7
 
8
8
  It permits to:
9
9
  - make audio calls using UDP, TCP and TLS transports
10
- - send and receive DTMF inband/RFC2833/INFO.
10
+ - send/receive DTMF inband/RFC2833/INFO.
11
11
  - play/record wav file on a call
12
12
  - send/receive fax (T.30 only)
13
13
  - send/receive MRCPv2 messages (TCP only, no TLS)
14
+ - send/receive audio using SRTP
15
+ - generate speech from text into a call (using flite)
14
16
 
15
17
  TODO:
16
18
  - add support for video playing/recording from/to file
19
+ - add support for speech recognition using pocketsphinx
17
20
  - add support for T.38 fax
18
21
  - add support for WebSocket
19
22
  - add support for WebRTC
@@ -26,7 +29,7 @@ It is distributed with prebuild binaries for node.js 15.0.0 and above (but built
26
29
 
27
30
  To install it, first install build dependencies:
28
31
  ```
29
- apt install build-essential automake autoconf libtool libspeex-dev libopus-dev libsdl2-dev libavdevice-dev libswscale-dev libv4l-dev libopencore-amrnb-dev libopencore-amrwb-dev libvo-amrwbenc-dev libvo-amrwbenc-dev libboost-dev libtiff-dev libpcap-dev libssl-dev uuid-dev cmake
32
+ apt install build-essential automake autoconf libtool libspeex-dev libopus-dev libsdl2-dev libavdevice-dev libswscale-dev libv4l-dev libopencore-amrnb-dev libopencore-amrwb-dev libvo-amrwbenc-dev libvo-amrwbenc-dev libboost-dev libtiff-dev libpcap-dev libssl-dev uuid-dev flite-dev cmake
30
33
  ```
31
34
 
32
35
  Then install sip-lab (local build of the addon might be triggered here if this is not Debian 11):
package/binding.gyp CHANGED
@@ -80,6 +80,12 @@
80
80
  '-lswscale',
81
81
  '-lavutil',
82
82
  '-lspeex',
83
+ '-lflite',
84
+ '-lflite_cmu_us_awb',
85
+ '-lflite_cmu_us_kal',
86
+ '-lflite_cmu_us_rms',
87
+ '-lflite_cmu_us_slt',
88
+ '-lflite_cmu_us_kal16',
83
89
  '-l srtp-x86_64-unknown-linux-gnu',
84
90
  ],
85
91
  },
@@ -109,6 +115,7 @@
109
115
  'src/addon.cpp',
110
116
  'src/pjmedia/src/pjmedia/dtmfdet.c',
111
117
  'src/pjmedia/src/pjmedia/fax_port.c',
118
+ 'src/pjmedia/src/pjmedia/flite_port.c',
112
119
  ],
113
120
  },
114
121
  ],
package/index.js CHANGED
@@ -66,6 +66,7 @@ addon.call = {
66
66
  stop_play_wav: (c_id, params) => { return addon.call_stop_play_wav(c_id, JSON.stringify(params ? params : {})) },
67
67
  start_fax: (c_id, params) => { return addon.call_start_fax(c_id, JSON.stringify(params)) },
68
68
  stop_fax: (c_id, params) => { return addon.call_stop_fax(c_id, JSON.stringify(params ? params : {})) },
69
+ start_speech_synth: (c_id, params) => { return addon.call_start_speech_synth(c_id, JSON.stringify(params)) },
69
70
  get_stream_stat: (c_id, params) => { return addon.call_get_stream_stat(c_id, JSON.stringify(params ? params : {})) },
70
71
  //refer: (c_id, params) => { return addon.call_refer(c_id, JSON.stringify(params)) },
71
72
  get_info: addon.call_get_info,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sip-lab",
3
- "version": "1.20.0",
3
+ "version": "1.22.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "engines": {
@@ -21,7 +21,7 @@
21
21
  "gypfile": true,
22
22
  "homepage": "https://github.com/MayamaTakeshi/sip-lab",
23
23
  "dependencies": {
24
- "@mayama/zeq": "^4.16.0",
24
+ "@mayama/zeq": "^4.17.0",
25
25
  "mrcp": "^1.4.0",
26
26
  "mrcp-matching": "^1.0.0",
27
27
  "node-addon-api": "^5.0.0",
Binary file
@@ -0,0 +1,176 @@
1
+ var sip = require ('../index.js')
2
+ var Zeq = require('@mayama/zeq')
3
+ var z = new Zeq()
4
+ var m = require('data-matching')
5
+ var sip_msg = require('sip-matching')
6
+ var sdp = require('sdp-matching')
7
+
8
+ async function test() {
9
+ //sip.set_log_level(6)
10
+ sip.dtmf_aggregation_on(500)
11
+
12
+ z.trap_events(sip.event_source, 'event', (evt) => {
13
+ var e = evt.args[0]
14
+ return e
15
+ })
16
+
17
+ console.log(sip.start((data) => { console.log(data)} ))
18
+
19
+ t1 = sip.transport.create({address: "127.0.0.1", type: 'tcp'})
20
+ t2 = sip.transport.create({address: "127.0.0.1", type: 'tcp'})
21
+
22
+ console.log("t1", t1)
23
+ console.log("t2", t2)
24
+
25
+ oc = sip.call.create(t1.id, {
26
+ from_uri: '"abc"<sip:alice@test.com>',
27
+ to_uri: `sip:bob@${t2.address}:${t2.port}`,
28
+ headers: {
29
+ 'X-MyHeader1': 'abc',
30
+ 'X-MyHeader2': 'def',
31
+ },
32
+ })
33
+
34
+ await z.wait([
35
+ {
36
+ event: "incoming_call",
37
+ call_id: m.collect("call_id"),
38
+ msg: sip_msg({
39
+ $rm: 'INVITE',
40
+ $fU: 'alice',
41
+ $fd: 'test.com',
42
+ $tU: 'bob',
43
+ '$hdr(X-MyHeader1)': 'abc',
44
+ 'hdr_x_myheader2': 'def',
45
+ }),
46
+ },
47
+ {
48
+ event: 'response',
49
+ call_id: oc.id,
50
+ method: 'INVITE',
51
+ msg: sip_msg({
52
+ $rs: '100',
53
+ $rr: 'Trying',
54
+ '$(hdrcnt(via))': 1,
55
+ 'hdr_call_id': m.collect('sip_call_id'),
56
+ $fU: 'alice',
57
+ $fd: 'test.com',
58
+ $tU: 'bob',
59
+ '$hdr(l)': '0',
60
+ }),
61
+ },
62
+ ], 1000)
63
+
64
+ ic = {
65
+ id: z.store.call_id,
66
+ sip_call_id: z.store.sip_call_id,
67
+ }
68
+
69
+ sip.call.respond(ic.id, {
70
+ code: 200,
71
+ reason:'OK',
72
+ headers: {
73
+ 'X-MyHeader3': 'ghi',
74
+ 'X-MyHeader4': 'jkl',
75
+ },
76
+ })
77
+
78
+ await z.wait([
79
+ {
80
+ event: 'media_update',
81
+ call_id: oc.id,
82
+ status: 'ok',
83
+ },
84
+ {
85
+ event: 'media_update',
86
+ call_id: ic.id,
87
+ status: 'ok',
88
+ },
89
+ {
90
+ event: 'response',
91
+ call_id: oc.id,
92
+ method: 'INVITE',
93
+ msg: sip_msg({
94
+ $rs: '200',
95
+ $rr: 'OK',
96
+ '$(hdrcnt(v))': 1,
97
+ $fU: 'alice',
98
+ $fd: 'test.com',
99
+ $tU: 'bob',
100
+ '$hdr(content-type)': 'application/sdp',
101
+ $rb: '!{_}a=sendrecv',
102
+ '$hdr(X-MyHeader3)': 'ghi',
103
+ '$hdr(X-MyHeader4)': 'jkl',
104
+ }),
105
+ },
106
+ ], 1000)
107
+
108
+ await z.sleep(500)
109
+
110
+ sip.call.start_record_wav(oc.id, {file: './oc.wav'})
111
+ sip.call.start_record_wav(ic.id, {file: './ic.wav'})
112
+
113
+ sip.call.send_dtmf(oc.id, {digits: '1234', mode: 1})
114
+ sip.call.send_dtmf(ic.id, {digits: '1234', mode: 1})
115
+
116
+ await z.wait([
117
+ {
118
+ event: 'dtmf',
119
+ call_id: ic.id,
120
+ digits: '1234',
121
+ mode: 1,
122
+ media_id: 0
123
+ },
124
+ {
125
+ event: 'dtmf',
126
+ call_id: oc.id,
127
+ digits: '1234',
128
+ mode: 1,
129
+ media_id: 0
130
+ },
131
+ ], 3000)
132
+
133
+ sip.call.start_speech_synth(oc.id, {voice: 'slt', text: 'Hello World.'})
134
+ sip.call.start_speech_synth(ic.id, {voice: 'kal', text: 'How are you?'})
135
+
136
+ await z.sleep(1500)
137
+
138
+ sip.call.stop_record_wav(oc.id)
139
+ sip.call.stop_record_wav(ic.id)
140
+
141
+ sip.call.terminate(oc.id)
142
+
143
+ await z.wait([
144
+ {
145
+ event: 'call_ended',
146
+ call_id: oc.id,
147
+ },
148
+ {
149
+ event: 'call_ended',
150
+ call_id: ic.id,
151
+ },
152
+ {
153
+ event: 'response',
154
+ call_id: oc.id,
155
+ method: 'BYE',
156
+ msg: sip_msg({
157
+ $rs: '200',
158
+ $rr: 'OK',
159
+ }),
160
+ },
161
+ ], 1000)
162
+
163
+ await z.sleep(1000)
164
+
165
+ console.log("Success")
166
+
167
+ sip.stop()
168
+ }
169
+
170
+
171
+ test()
172
+ .catch(e => {
173
+ console.error(e)
174
+ process.exit(1)
175
+ })
176
+
package/src/addon.cpp CHANGED
@@ -569,6 +569,40 @@ Napi::Value call_start_fax(const Napi::CallbackInfo &info) {
569
569
  return env.Null();
570
570
  }
571
571
 
572
+ Napi::Value call_start_speech_synth(const Napi::CallbackInfo &info) {
573
+ Napi::Env env = info.Env();
574
+
575
+ if (info.Length() != 2) {
576
+ Napi::Error::New(env,
577
+ "Wrong number of arguments. Expected: call_id, params.")
578
+ .ThrowAsJavaScriptException();
579
+ return env.Null();
580
+ }
581
+
582
+ if (!info[0].IsNumber()) {
583
+ Napi::TypeError::New(env, "call_id must be number.")
584
+ .ThrowAsJavaScriptException();
585
+ return env.Null();
586
+ }
587
+ int call_id = info[0].As<Napi::Number>().Int32Value();
588
+
589
+ if (!info[1].IsString()) {
590
+ Napi::TypeError::New(env, "params must be a JSON string.")
591
+ .ThrowAsJavaScriptException();
592
+ return env.Null();
593
+ }
594
+ const string json = info[1].As<Napi::String>().Utf8Value();
595
+
596
+ int res = pjw_call_start_speech_synth(call_id, json.c_str());
597
+
598
+ if (res != 0) {
599
+ Napi::Error::New(env, pjw_get_error()).ThrowAsJavaScriptException();
600
+ return env.Null();
601
+ }
602
+
603
+ return env.Null();
604
+ }
605
+
572
606
  Napi::Value call_stop_record_wav(const Napi::CallbackInfo &info) {
573
607
  Napi::Env env = info.Env();
574
608
 
@@ -1265,6 +1299,9 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
1265
1299
  exports.Set("call_start_play_wav",
1266
1300
  Napi::Function::New(env, call_start_play_wav));
1267
1301
  exports.Set("call_start_fax", Napi::Function::New(env, call_start_fax));
1302
+
1303
+ exports.Set("call_start_speech_synth", Napi::Function::New(env, call_start_speech_synth));
1304
+
1268
1305
  exports.Set("call_stop_record_wav",
1269
1306
  Napi::Function::New(env, call_stop_record_wav));
1270
1307
  exports.Set("call_stop_play_wav",
@@ -0,0 +1,26 @@
1
+ #ifndef __FLITE_PORT_H__
2
+ #define __FLITE_PORT_H__
3
+
4
+ #include <pjmedia/port.h>
5
+
6
+ PJ_BEGIN_DECL
7
+
8
+ PJ_DEF(pj_status_t) pjmedia_flite_port_create( pj_pool_t *pool,
9
+ unsigned clock_rate,
10
+ unsigned channel_count,
11
+ unsigned samples_per_frame,
12
+ unsigned bits_per_sample,
13
+ void (*cb)(pjmedia_port*,
14
+ void *user_data,
15
+ int result),
16
+ void *user_data,
17
+ const char *voice,
18
+ pjmedia_port **p_port);
19
+
20
+ PJ_DEF(pj_status_t) pjmedia_flite_port_speak( pjmedia_port *port,
21
+ const char *text,
22
+ unsigned options);
23
+
24
+ PJ_END_DECL
25
+
26
+ #endif /* __FLITE_PORT_H__ */
@@ -0,0 +1,194 @@
1
+ /* adapted from https://github.com/signalwire/freeswitch/blob/master/src/mod/asr_tts/mod_flite/mod_flite.c */
2
+
3
+ #include "flite_port.h"
4
+ #include <pjmedia/errno.h>
5
+ #include <pjmedia/port.h>
6
+ #include <pj/assert.h>
7
+ #include <pj/lock.h>
8
+ #include <pj/pool.h>
9
+ #include <pj/string.h>
10
+
11
+ #include "siplab_constants.h"
12
+
13
+ #include <flite/flite.h>
14
+
15
+ #define SIGNATURE PJMEDIA_SIGNATURE('f', 'l', 'i', 't')
16
+ #define THIS_FILE "flite_port.c"
17
+
18
+ #if 0
19
+ # define TRACE_(expr) PJ_LOG(4,expr)
20
+ #else
21
+ # define TRACE_(expr)
22
+ #endif
23
+
24
+ static pj_status_t flite_get_frame(pjmedia_port *port,
25
+ pjmedia_frame *frame);
26
+ static pj_status_t flite_on_destroy(pjmedia_port *port);
27
+
28
+
29
+ cst_voice *register_cmu_us_awb(void);
30
+ void unregister_cmu_us_awb(cst_voice * v);
31
+
32
+ cst_voice *register_cmu_us_kal(void);
33
+ void unregister_cmu_us_kal(cst_voice * v);
34
+
35
+ cst_voice *register_cmu_us_rms(void);
36
+ void unregister_cmu_us_rms(cst_voice * v);
37
+
38
+ cst_voice *register_cmu_us_slt(void);
39
+ void unregister_cmu_us_slt(cst_voice * v);
40
+
41
+ cst_voice *register_cmu_us_kal16(void);
42
+ void unregister_cmu_us_kal16(cst_voice * v);
43
+
44
+ static int initialized = 0;
45
+
46
+ static struct {
47
+ cst_voice *awb;
48
+ cst_voice *kal;
49
+ cst_voice *rms;
50
+ cst_voice *slt;
51
+ cst_voice *kal16;
52
+ } globals;
53
+
54
+ struct flite_t {
55
+ struct pjmedia_port base;
56
+ void (*flite_cb)(pjmedia_port*, void*, int);
57
+ void *flite_cb_user_data;
58
+ cst_voice *v;
59
+ int written_samples;
60
+ cst_wave *w;
61
+ char *buffer;
62
+ };
63
+
64
+ #define free_wave(w) if (w) {delete_wave(w) ; w = NULL; }
65
+ #define FLITE_BLOCK_SIZE 1024 * 32
66
+
67
+ PJ_DEF(pj_status_t) pjmedia_flite_port_create( pj_pool_t *pool,
68
+ unsigned clock_rate,
69
+ unsigned channel_count,
70
+ unsigned samples_per_frame,
71
+ unsigned bits_per_sample,
72
+ void (*cb)(pjmedia_port*,
73
+ void *user_data,
74
+ int result),
75
+ void *user_data,
76
+ const char *voice,
77
+ pjmedia_port **p_port)
78
+ {
79
+ struct flite_t *flite;
80
+ const pj_str_t name = pj_str("flite_data");
81
+
82
+ if(!initialized) {
83
+ flite_init();
84
+ globals.awb = register_cmu_us_awb();
85
+ globals.kal = register_cmu_us_kal();
86
+ globals.rms = register_cmu_us_rms();
87
+ globals.slt = register_cmu_us_slt();
88
+ globals.kal16 = register_cmu_us_kal16();
89
+ initialized = 1;
90
+ }
91
+
92
+ PJ_ASSERT_RETURN(pool && clock_rate && channel_count &&
93
+ samples_per_frame && bits_per_sample == 16 &&
94
+ p_port != NULL, PJ_EINVAL);
95
+
96
+ PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL);
97
+
98
+ flite = PJ_POOL_ZALLOC_T(pool, struct flite_t);
99
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
100
+
101
+ pjmedia_port_info_init(&flite->base.info, &name, SIGNATURE, clock_rate,
102
+ channel_count, bits_per_sample, samples_per_frame);
103
+
104
+ flite->base.get_frame = &flite_get_frame;
105
+ flite->base.on_destroy = &flite_on_destroy;
106
+
107
+ if (!strcasecmp(voice, "awb")) {
108
+ flite->v = globals.awb;
109
+ } else if (!strcasecmp(voice, "kal")) {
110
+ /* "kal" is 8kHz and the native rate is set to 16kHz
111
+ * so kal talks a little bit too fast ...
112
+ * for now: "symlink" kal to kal16
113
+ */ flite->v = globals.kal16;
114
+ } else if (!strcasecmp(voice, "rms")) {
115
+ flite->v = globals.rms;
116
+ } else if (!strcasecmp(voice, "slt")) {
117
+ flite->v = globals.slt;
118
+ } else if (!strcasecmp(voice, "kal16")) {
119
+ flite->v = globals.kal16;
120
+ } else {
121
+ TRACE_((THIS_FILE, "Invalid voice"));
122
+ return 0;
123
+ }
124
+
125
+ flite->flite_cb = cb;
126
+ flite->flite_cb_user_data = user_data;
127
+
128
+ TRACE_((THIS_FILE, "flite_device created: %u/%u/%u/%u", clock_rate,
129
+ channel_count, samples_per_frame, bits_per_sample));
130
+
131
+ *p_port = &flite->base;
132
+ return PJ_SUCCESS;
133
+ }
134
+
135
+ PJ_DEF(pj_status_t) pjmedia_flite_port_speak( pjmedia_port *port,
136
+ const char *text,
137
+ unsigned options) {
138
+ struct flite_t *flite = (struct flite_t*)port;
139
+ if(flite->w) {
140
+ free_wave(flite->w);
141
+ }
142
+
143
+ flite->w = flite_text_to_wave(text, flite->v);
144
+ if (flite->w->sample_rate != PJMEDIA_PIA_SRATE(&port->info)) {
145
+ cst_wave_resample(flite->w, PJMEDIA_PIA_SRATE(&port->info));
146
+ }
147
+ flite->written_samples = 0;
148
+
149
+ return PJ_SUCCESS;
150
+ }
151
+
152
+ // called when pjmedia needs data to be sent out
153
+ static pj_status_t flite_get_frame(pjmedia_port *port,
154
+ pjmedia_frame *frame) {
155
+
156
+ PJ_ASSERT_RETURN(port && frame, PJ_EINVAL);
157
+
158
+ struct flite_t *flite = (struct flite_t*)port;
159
+
160
+ if(!flite->w) {
161
+ printf("flite no data\n");
162
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
163
+ return PJ_SUCCESS;
164
+ }
165
+
166
+ printf("written_samples=%i num_samples=%i\n", flite->written_samples, flite->w->num_samples);
167
+ if (flite->written_samples + PJMEDIA_PIA_SPF(&port->info) > flite->w->num_samples) {
168
+ printf("flite no more data\n");
169
+ free_wave(flite->w);
170
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
171
+ return PJ_SUCCESS;
172
+ }
173
+
174
+ memcpy(frame->buf, flite->w->samples + flite->written_samples, PJMEDIA_PIA_SPF(&port->info)*2);
175
+ flite->written_samples += PJMEDIA_PIA_SPF(&port->info);
176
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
177
+ printf("flite data written samples=%i\n", PJMEDIA_PIA_SPF(&port->info));
178
+
179
+ return PJ_SUCCESS;
180
+ }
181
+
182
+ /*
183
+ * Destroy port.
184
+ */
185
+ static pj_status_t flite_on_destroy(pjmedia_port *port)
186
+ {
187
+ printf("flite_on_destroy\n");
188
+
189
+ struct flite_t *flite = (struct flite_t*)port;
190
+ free_wave(flite->w);
191
+
192
+ return PJ_SUCCESS;
193
+ }
194
+
package/src/sip.cpp CHANGED
@@ -22,6 +22,7 @@
22
22
 
23
23
  #include "dtmfdet.h"
24
24
  #include "fax_port.h"
25
+ #include "flite_port.h"
25
26
 
26
27
  #include <ctime>
27
28
 
@@ -329,6 +330,7 @@ struct AudioEndpoint {
329
330
  ConfBridgePort tonegen_cbp;
330
331
  ConfBridgePort dtmfdet_cbp;
331
332
  ConfBridgePort fax_cbp;
333
+ ConfBridgePort flite_cbp;
332
334
  };
333
335
 
334
336
  struct VideoEndpoint {
@@ -620,6 +622,7 @@ bool prepare_wav_player(Call *c, AudioEndpoint *ae, const char *file);
620
622
  bool prepare_wav_writer(Call *c, AudioEndpoint *ae, const char *file);
621
623
  bool prepare_fax(Call *c, AudioEndpoint *ae, bool is_sender, const char *file,
622
624
  unsigned flags);
625
+ bool prepare_flite(Call *c, AudioEndpoint *ae, const char *voice);
623
626
 
624
627
  void prepare_error_event(ostringstream *oss, char *scope, char *details);
625
628
  // void prepare_pjsipcall_error_event(ostringstream *oss, char *scope, char
@@ -638,6 +641,7 @@ typedef pj_status_t (*audio_endpoint_stop_op_t)(Call *call, AudioEndpoint *ae);
638
641
  pj_status_t audio_endpoint_stop_play_wav(Call *call, AudioEndpoint *ae);
639
642
  pj_status_t audio_endpoint_stop_record_wav(Call *call, AudioEndpoint *ae);
640
643
  pj_status_t audio_endpoint_stop_fax(Call *call, AudioEndpoint *ae);
644
+ pj_status_t audio_endpoint_stop_flite(Call *call, AudioEndpoint *ae);
641
645
 
642
646
  static pjsip_module mod_tester = {
643
647
  NULL,
@@ -3176,6 +3180,37 @@ out:
3176
3180
  return 0;
3177
3181
  }
3178
3182
 
3183
+ pj_status_t audio_endpoint_remove_port(Call *call, ConfBridgePort *cbp) {
3184
+ printf("audio_endpoint_remove_port\n");
3185
+ pj_status_t status;
3186
+
3187
+ if(cbp->port) {
3188
+ /*
3189
+ no need to call pjmedia_conf_disconnect_port because pjmedia_conf_remove_port calls:
3190
+ pjmedia_conf_disconnect_port_from_sources(conf, port);
3191
+ pjmedia_conf_disconnect_port_from_sinks(conf, port);
3192
+ */
3193
+
3194
+ status = pjmedia_conf_remove_port(call->conf, cbp->slot);
3195
+ if (status != PJ_SUCCESS) {
3196
+ set_error("pjmedia_conf_remove_port failed");
3197
+ return false;
3198
+ }
3199
+ cbp->slot = 0;
3200
+
3201
+ status = pjmedia_port_destroy(cbp->port);
3202
+ if (status != PJ_SUCCESS) {
3203
+ set_error("pjmedia_port_destroy failed");
3204
+ return false;
3205
+ }
3206
+ cbp->port = NULL;
3207
+ }
3208
+
3209
+ printf("success\n");
3210
+ return PJ_SUCCESS;
3211
+ }
3212
+
3213
+
3179
3214
  int pjw_call_reinvite(long call_id, const char *json) {
3180
3215
  addon_log(L_DBG, "pjw_call_reinvite call_id=%d\n", call_id);
3181
3216
 
@@ -3609,6 +3644,125 @@ out:
3609
3644
  return 0;
3610
3645
  }
3611
3646
 
3647
+ pj_status_t audio_endpoint_start_speech_synth(Call *call, AudioEndpoint *ae, const char * voice, const char *text) {
3648
+ pj_status_t status;
3649
+
3650
+ if(!ae->stream_cbp.port) {
3651
+ set_error("stream port is not ready yet");
3652
+ return -1;
3653
+ }
3654
+
3655
+ // First stop and destroy existing flite port.
3656
+ status = audio_endpoint_stop_flite(call, ae);
3657
+ if(status != PJ_SUCCESS) {
3658
+ return -1;
3659
+ }
3660
+
3661
+ if (!prepare_flite(call, ae, voice)) {
3662
+ return -1;
3663
+ }
3664
+
3665
+ pjmedia_flite_port_speak(ae->flite_cbp.port, text, 0);
3666
+
3667
+ return PJ_SUCCESS;
3668
+ }
3669
+
3670
+ int pjw_call_start_speech_synth(long call_id, const char *json) {
3671
+ PJW_LOCK();
3672
+ clear_error();
3673
+
3674
+ long val;
3675
+ Call *call;
3676
+
3677
+ MediaEndpoint *me;
3678
+ AudioEndpoint *ae;
3679
+ int ae_count;
3680
+
3681
+ unsigned media_id = 0;
3682
+
3683
+ char *voice;
3684
+
3685
+ char *text;
3686
+
3687
+ char buffer[MAX_JSON_INPUT];
3688
+
3689
+ Document document;
3690
+
3691
+ const char *valid_params[] = {"voice", "text", "media_id", ""};
3692
+
3693
+ if (!g_call_ids.get(call_id, val)) {
3694
+ set_error("Invalid call_id");
3695
+ goto out;
3696
+ }
3697
+ call = (Call *)val;
3698
+
3699
+ ae_count = count_media_by_type(call, ENDPOINT_TYPE_AUDIO);
3700
+
3701
+ if (ae_count == 0) {
3702
+ set_error("No audio endpoint");
3703
+ goto out;
3704
+ }
3705
+
3706
+ if (!parse_json(document, json, buffer, MAX_JSON_INPUT)) {
3707
+ goto out;
3708
+ }
3709
+
3710
+ if (!validate_params(document, valid_params)) {
3711
+ goto out;
3712
+ }
3713
+
3714
+ if (json_get_string_param(document, "voice", false, &voice) <= 0) {
3715
+ goto out;
3716
+ }
3717
+
3718
+ if (!voice[0]) {
3719
+ set_error("voice cannot be blank string");
3720
+ goto out;
3721
+ }
3722
+
3723
+ if (json_get_string_param(document, "text", false, &text) <= 0) {
3724
+ goto out;
3725
+ }
3726
+
3727
+ if (!text[0]) {
3728
+ set_error("text cannot be blank string");
3729
+ goto out;
3730
+ }
3731
+
3732
+ if (ae_count > 1) {
3733
+ if (json_get_uint_param(document, "media_id", false, &media_id) <= 0) {
3734
+ goto out;
3735
+ }
3736
+ }
3737
+
3738
+ if ((int)media_id >= call->media_count) {
3739
+ set_error("invalid media_id");
3740
+ goto out;
3741
+ }
3742
+
3743
+ me = (MediaEndpoint *)call->media[media_id];
3744
+ if (ENDPOINT_TYPE_AUDIO != me->type) {
3745
+ set_error("media_endpoint is not audio endpoint");
3746
+ goto out;
3747
+ }
3748
+
3749
+ ae = (AudioEndpoint *)me->endpoint.audio;
3750
+
3751
+ audio_endpoint_start_speech_synth(call, ae, voice, text);
3752
+
3753
+ out:
3754
+ PJW_UNLOCK();
3755
+ if (pjw_errorstring[0]) {
3756
+ return -1;
3757
+ }
3758
+
3759
+ return 0;
3760
+ }
3761
+
3762
+ pj_status_t audio_endpoint_stop_flite(Call *call, AudioEndpoint *ae) {
3763
+ return audio_endpoint_remove_port(call, &ae->flite_cbp);
3764
+ }
3765
+
3612
3766
  pj_status_t call_stop_audio_endpoints_op(Call *call,
3613
3767
  audio_endpoint_stop_op_t op) {
3614
3768
  addon_log(L_DBG, "call_stop_audio_endpoints_op media_count=%d\n",
@@ -3630,37 +3784,6 @@ pj_status_t call_stop_audio_endpoints_op(Call *call,
3630
3784
  return PJ_SUCCESS;
3631
3785
  }
3632
3786
 
3633
- pj_status_t audio_endpoint_remove_port(Call *call, ConfBridgePort *cbp) {
3634
- printf("audio_endpoint_remove_port\n");
3635
- pj_status_t status;
3636
-
3637
- if(cbp->port) {
3638
- /*
3639
- no need to call pjmedia_conf_disconnect_port because pjmedia_conf_remove_port calls:
3640
- pjmedia_conf_disconnect_port_from_sources(conf, port);
3641
- pjmedia_conf_disconnect_port_from_sinks(conf, port);
3642
- */
3643
-
3644
- status = pjmedia_conf_remove_port(call->conf, cbp->slot);
3645
- if (status != PJ_SUCCESS) {
3646
- set_error("pjmedia_conf_remove_port failed");
3647
- return false;
3648
- }
3649
- cbp->slot = 0;
3650
-
3651
- status = pjmedia_port_destroy(cbp->port);
3652
- if (status != PJ_SUCCESS) {
3653
- set_error("pjmedia_port_destroy failed");
3654
- return false;
3655
- }
3656
- cbp->port = NULL;
3657
- }
3658
-
3659
- printf("success\n");
3660
- return PJ_SUCCESS;
3661
- }
3662
-
3663
-
3664
3787
  pj_status_t audio_endpoint_stop_play_wav(Call *call, AudioEndpoint *ae) {
3665
3788
  return audio_endpoint_remove_port(call, &ae->wav_player_cbp);
3666
3789
  }
@@ -6394,7 +6517,6 @@ bool prepare_tonegen(Call *c, AudioEndpoint *ae) {
6394
6517
  return true;
6395
6518
  }
6396
6519
 
6397
- printf("call.id=%i p1\n", c->id);
6398
6520
  status = pjmedia_tonegen_create(
6399
6521
  c->inv->pool, PJMEDIA_PIA_SRATE(&ae->stream_cbp.port->info),
6400
6522
  PJMEDIA_PIA_CCNT(&ae->stream_cbp.port->info),
@@ -6405,21 +6527,18 @@ bool prepare_tonegen(Call *c, AudioEndpoint *ae) {
6405
6527
  return false;
6406
6528
  }
6407
6529
 
6408
- printf("call.id=%i p2\n", c->id);
6409
6530
  status = pjmedia_conf_add_port(c->conf, c->inv->pool, ae->tonegen_cbp.port, NULL, &ae->tonegen_cbp.slot);
6410
6531
  if (status != PJ_SUCCESS) {
6411
6532
  set_error("pjmedia_conf_add_port failed");
6412
6533
  return false;
6413
6534
  }
6414
6535
 
6415
- printf("call.id=%i p3\n", c->id);
6416
6536
  status = pjmedia_conf_connect_port(c->conf, ae->tonegen_cbp.slot, ae->stream_cbp.slot, 0);
6417
6537
  if (status != PJ_SUCCESS) {
6418
6538
  set_error("pjmedia_conf_connect_port failed");
6419
6539
  return false;
6420
6540
  }
6421
6541
 
6422
- printf("call.id=%i p4 (success)\n", c->id);
6423
6542
  return true;
6424
6543
  }
6425
6544
 
@@ -6553,6 +6672,41 @@ bool prepare_fax(Call *c, AudioEndpoint *ae, bool is_sender, const char *file,
6553
6672
  return true;
6554
6673
  }
6555
6674
 
6675
+ bool prepare_flite(Call *c, AudioEndpoint *ae, const char *voice) {
6676
+ printf("prepare_flite call.id=%i\n", c->id);
6677
+ pj_status_t status;
6678
+
6679
+ if(ae->flite_cbp.port) {
6680
+ printf("already prepared\n");
6681
+ return true;
6682
+ }
6683
+
6684
+ status = pjmedia_flite_port_create(
6685
+ c->inv->pool, PJMEDIA_PIA_SRATE(&ae->stream_cbp.port->info),
6686
+ PJMEDIA_PIA_CCNT(&ae->stream_cbp.port->info),
6687
+ PJMEDIA_PIA_SPF(&ae->stream_cbp.port->info),
6688
+ PJMEDIA_PIA_BITS(&ae->stream_cbp.port->info), NULL, 0, voice, &ae->flite_cbp.port);
6689
+ if (status != PJ_SUCCESS) {
6690
+ set_error("pjmedia_flite_port_create failed");
6691
+ return false;
6692
+ }
6693
+
6694
+ status = pjmedia_conf_add_port(c->conf, c->inv->pool, ae->flite_cbp.port, NULL, &ae->flite_cbp.slot);
6695
+ if (status != PJ_SUCCESS) {
6696
+ set_error("pjmedia_conf_add_port failed");
6697
+ return false;
6698
+ }
6699
+
6700
+ status = pjmedia_conf_connect_port(c->conf, ae->flite_cbp.slot, ae->stream_cbp.slot, 0);
6701
+ if (status != PJ_SUCCESS) {
6702
+ set_error("pjmedia_conf_connect_port failed");
6703
+ return false;
6704
+ }
6705
+
6706
+ return true;
6707
+ }
6708
+
6709
+
6556
6710
  void on_rx_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code,
6557
6711
  pj_str_t **p_st_text, pjsip_hdr *res_hdr,
6558
6712
  pjsip_msg_body **p_body) {
package/src/sip.hpp CHANGED
@@ -57,6 +57,8 @@ int pjw_call_start_fax(long call_id, const char *json);
57
57
 
58
58
  int pjw_call_stop_fax(long call_id, const char *json);
59
59
 
60
+ int pjw_call_start_speech_synth(long call_id, const char *json);
61
+
60
62
  int pjw_call_get_stream_stat(long call_id, const char *json, char *out_stats);
61
63
 
62
64
  // int pjw_call_refer(long call_id, const char *json, long