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 +5 -2
- package/binding.gyp +7 -0
- package/index.js +1 -0
- package/package.json +2 -2
- package/prebuilds/linux-x64/sip-lab.node +0 -0
- package/samples/text_to_speech.js +176 -0
- package/src/addon.cpp +37 -0
- package/src/pjmedia/include/pjmedia/flite_port.h +26 -0
- package/src/pjmedia/src/pjmedia/flite_port.c +194 -0
- package/src/sip.cpp +189 -35
- package/src/sip.hpp +2 -0
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
|
|
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.
|
|
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.
|
|
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
|