sip-lab 1.9.0 → 1.11.2
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 +26 -12
- package/binding.gyp +1 -0
- package/index.js +2 -0
- package/package.json +1 -1
- package/samples/artifacts/marielle_presente.tiff +0 -0
- package/samples/artifacts/sample.tiff +0 -0
- package/samples/artifacts/this-is-never-ok.tiff +0 -0
- package/samples/g729.js +0 -2
- package/samples/late_negotiation.js +0 -4
- package/samples/reinvite_and_dtmf.js +0 -2
- package/samples/send_and_receive_fax.js +145 -0
- package/samples/sip_cancel.js +0 -2
- package/src/addon.cpp +67 -0
- package/src/event_templates.cpp +4 -0
- package/src/event_templates.hpp +2 -0
- package/src/pjmedia/Makefile +3 -1
- package/src/pjmedia/include/chainlink/chainlink_fax.h +25 -0
- package/src/pjmedia/src/chainlink/chainlink_fax.c +274 -0
- package/src/sip.cpp +139 -1
- package/src/sip.hpp +2 -0
package/README.md
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
A nodejs module that helps to write functional tests for SIP systems (including media operations).
|
|
6
6
|
It uses pjproject for SIP and media processing.
|
|
7
7
|
|
|
8
|
+
It permits to:
|
|
9
|
+
- send and receive DTMF inband/RFC2833/INFO.
|
|
10
|
+
- play/record wav file on a call
|
|
11
|
+
- send/receive fax (T.30 only)
|
|
12
|
+
|
|
13
|
+
|
|
8
14
|
### Installation
|
|
9
15
|
|
|
10
16
|
This will require for you to have some libraries installed. So do:
|
|
@@ -24,26 +30,34 @@ Then install sip-lab by doing:
|
|
|
24
30
|
npm install sip-lab
|
|
25
31
|
```
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
To test from within this repo just build and install by doing:
|
|
28
34
|
```
|
|
29
|
-
npm install -g
|
|
35
|
+
npm install -g node-gyp
|
|
36
|
+
npm install --unsafe-perm
|
|
30
37
|
```
|
|
31
|
-
|
|
32
|
-
But if you do so, you will need to set NODE_PATH for node to find it by doing:
|
|
38
|
+
And run some sample script from subfolder samples:
|
|
33
39
|
```
|
|
34
|
-
|
|
40
|
+
node samples/simple.js
|
|
35
41
|
```
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
The module is known to work properly in Ubuntu 18.04.4, Ubuntu 20.04.4, Debian 8 and Debian 10 (and it is expected to work in Debian 9).
|
|
44
|
+
It was originally developed with node v.10 and tested with v.12 and it is expected to work with latest versions of node.
|
|
45
|
+
(it is known to not work with node v.8)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
Since running
|
|
38
49
|
```
|
|
39
|
-
|
|
40
|
-
|
|
50
|
+
npm install sip-lab
|
|
51
|
+
```
|
|
52
|
+
takes some time to fetch and build pjproject and the node addon for it, you could install sip-lab globally:
|
|
41
53
|
|
|
42
|
-
|
|
54
|
+
```
|
|
55
|
+
npm install -g sip-lab
|
|
43
56
|
```
|
|
44
57
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
(
|
|
58
|
+
But if you do so, you will need to set NODE_PATH for node to find it by doing:
|
|
59
|
+
```
|
|
60
|
+
export NODE_PATH=$(npm root --quiet -g)
|
|
61
|
+
```
|
|
48
62
|
|
|
49
63
|
|
package/binding.gyp
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
'src/pjmedia/src/chainlink/chainlink_wav_player.c',
|
|
15
15
|
'src/pjmedia/src/chainlink/chainlink_wav_writer.c',
|
|
16
16
|
'src/pjmedia/src/chainlink/chainlink_wire_port.c',
|
|
17
|
+
'src/pjmedia/src/chainlink/chainlink_fax.c',
|
|
17
18
|
],
|
|
18
19
|
'include_dirs': [
|
|
19
20
|
"pjproject/pjsip/include",
|
package/index.js
CHANGED
|
@@ -59,6 +59,8 @@ addon.call = {
|
|
|
59
59
|
start_playing: addon.call_start_play_wav,
|
|
60
60
|
stop_recording: addon.call_stop_record_wav,
|
|
61
61
|
stop_playing: addon.call_stop_play_wav,
|
|
62
|
+
start_fax: addon.call_start_fax,
|
|
63
|
+
stop_fax: addon.call_stop_fax,
|
|
62
64
|
get_stream_stat: addon.call_get_stream_stat,
|
|
63
65
|
refer: addon.call_refer,
|
|
64
66
|
get_info: addon.call_get_info,
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/samples/g729.js
CHANGED
|
@@ -208,8 +208,6 @@ async function test() {
|
|
|
208
208
|
},
|
|
209
209
|
], 500)
|
|
210
210
|
|
|
211
|
-
await z.sleep(100)
|
|
212
|
-
|
|
213
211
|
sip.call.reinvite(oc.id, false, flags)
|
|
214
212
|
|
|
215
213
|
await z.wait([
|
|
@@ -275,8 +273,6 @@ async function test() {
|
|
|
275
273
|
},
|
|
276
274
|
], 1000)
|
|
277
275
|
|
|
278
|
-
await z.sleep(1000)
|
|
279
|
-
|
|
280
276
|
console.log("Success")
|
|
281
277
|
|
|
282
278
|
sip.stop()
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
var sip = require ('../index.js')
|
|
2
|
+
var Zester = require('zester')
|
|
3
|
+
var z = new Zester()
|
|
4
|
+
var m = require('data-matching')
|
|
5
|
+
var sip_msg = require('sip-matching')
|
|
6
|
+
|
|
7
|
+
async function test() {
|
|
8
|
+
//sip.set_log_level(6)
|
|
9
|
+
sip.dtmf_aggregation_on(500)
|
|
10
|
+
|
|
11
|
+
z.trap_events(sip.event_source, 'event', (evt) => {
|
|
12
|
+
var e = evt.args[0]
|
|
13
|
+
return e
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
console.log(sip.start((data) => { console.log(data)} ))
|
|
17
|
+
|
|
18
|
+
t1 = sip.transport.create("127.0.0.1", 5090, 1)
|
|
19
|
+
t2 = sip.transport.create("127.0.0.1", 5092, 1)
|
|
20
|
+
|
|
21
|
+
console.log("t1", t1)
|
|
22
|
+
console.log("t2", t2)
|
|
23
|
+
|
|
24
|
+
flags = 0
|
|
25
|
+
|
|
26
|
+
oc = sip.call.create(t1.id, flags, 'sip:a@t', 'sip:b@127.0.0.1:5092')
|
|
27
|
+
|
|
28
|
+
await z.wait([
|
|
29
|
+
{
|
|
30
|
+
event: "incoming_call",
|
|
31
|
+
call_id: m.collect("call_id"),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
event: 'response',
|
|
35
|
+
call_id: oc.id,
|
|
36
|
+
method: 'INVITE',
|
|
37
|
+
msg: sip_msg({
|
|
38
|
+
$rs: '100',
|
|
39
|
+
$rr: 'Trying',
|
|
40
|
+
'$(hdrcnt(via))': 1,
|
|
41
|
+
'$hdr(call-id)': m.collect('sip_call_id'),
|
|
42
|
+
$fU: 'a',
|
|
43
|
+
$fd: 't',
|
|
44
|
+
$tU: 'b',
|
|
45
|
+
'$hdr(l)': '0',
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
], 1000)
|
|
49
|
+
|
|
50
|
+
ic = {
|
|
51
|
+
id: z.store.call_id,
|
|
52
|
+
sip_call_id: z.store.sip_call_id,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
sip.call.respond(ic.id, 200, 'OK')
|
|
56
|
+
|
|
57
|
+
await z.wait([
|
|
58
|
+
{
|
|
59
|
+
event: 'media_status',
|
|
60
|
+
call_id: oc.id,
|
|
61
|
+
status: 'setup_ok',
|
|
62
|
+
local_mode: 'sendrecv',
|
|
63
|
+
remote_mode: 'sendrecv',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
event: 'media_status',
|
|
67
|
+
call_id: ic.id,
|
|
68
|
+
status: 'setup_ok',
|
|
69
|
+
local_mode: 'sendrecv',
|
|
70
|
+
remote_mode: 'sendrecv',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
event: 'response',
|
|
74
|
+
call_id: oc.id,
|
|
75
|
+
method: 'INVITE',
|
|
76
|
+
msg: sip_msg({
|
|
77
|
+
$rs: '200',
|
|
78
|
+
$rr: 'OK',
|
|
79
|
+
'$(hdrcnt(VIA))': 1,
|
|
80
|
+
$fU: 'a',
|
|
81
|
+
$fd: 't',
|
|
82
|
+
$tU: 'b',
|
|
83
|
+
'$hdr(content-type)': 'application/sdp',
|
|
84
|
+
$rb: '!{_}a=sendrecv',
|
|
85
|
+
}),
|
|
86
|
+
},
|
|
87
|
+
], 1000)
|
|
88
|
+
|
|
89
|
+
await z.sleep(1000)
|
|
90
|
+
|
|
91
|
+
var is_sender = true
|
|
92
|
+
|
|
93
|
+
var in_file = 'samples/artifacts/this-is-never-ok.tiff'
|
|
94
|
+
var out_file = "received.tiff"
|
|
95
|
+
|
|
96
|
+
sip.call.start_fax(oc.id, is_sender, in_file)
|
|
97
|
+
sip.call.start_fax(ic.id, !is_sender, out_file)
|
|
98
|
+
|
|
99
|
+
await z.wait([
|
|
100
|
+
{
|
|
101
|
+
event: 'fax_result',
|
|
102
|
+
call_id: oc.id,
|
|
103
|
+
result: 0,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
event: 'fax_result',
|
|
107
|
+
call_id: ic.id,
|
|
108
|
+
result: 0,
|
|
109
|
+
},
|
|
110
|
+
], 180 * 1000)
|
|
111
|
+
|
|
112
|
+
sip.call.terminate(oc.id)
|
|
113
|
+
|
|
114
|
+
await z.wait([
|
|
115
|
+
{
|
|
116
|
+
event: 'call_ended',
|
|
117
|
+
call_id: oc.id,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
event: 'call_ended',
|
|
121
|
+
call_id: ic.id,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
event: 'response',
|
|
125
|
+
call_id: oc.id,
|
|
126
|
+
method: 'BYE',
|
|
127
|
+
msg: sip_msg({
|
|
128
|
+
$rs: '200',
|
|
129
|
+
$rr: 'OK',
|
|
130
|
+
}),
|
|
131
|
+
},
|
|
132
|
+
], 1000)
|
|
133
|
+
|
|
134
|
+
console.log(`Success. Fax was transmitted as ${in_file} and received as ${out_file}`)
|
|
135
|
+
|
|
136
|
+
sip.stop()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
test()
|
|
141
|
+
.catch(e => {
|
|
142
|
+
console.error(e)
|
|
143
|
+
process.exit(1)
|
|
144
|
+
})
|
|
145
|
+
|
package/samples/sip_cancel.js
CHANGED
package/src/addon.cpp
CHANGED
|
@@ -669,6 +669,47 @@ Napi::Value call_start_play_wav(const Napi::CallbackInfo& info) {
|
|
|
669
669
|
return env.Null();
|
|
670
670
|
}
|
|
671
671
|
|
|
672
|
+
Napi::Value call_start_fax(const Napi::CallbackInfo& info) {
|
|
673
|
+
Napi::Env env = info.Env();
|
|
674
|
+
|
|
675
|
+
if (info.Length() != 3) {
|
|
676
|
+
Napi::Error::New(env, "Wrong number of arguments. Expected: call_id, is_sender, file]").ThrowAsJavaScriptException();
|
|
677
|
+
return env.Null();
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (!info[0].IsNumber()) {
|
|
681
|
+
Napi::TypeError::New(env, "call_id must be number.").ThrowAsJavaScriptException();
|
|
682
|
+
return env.Null();
|
|
683
|
+
}
|
|
684
|
+
int call_id = info[0].As<Napi::Number>().Int32Value();
|
|
685
|
+
|
|
686
|
+
if (!info[1].IsBoolean()) {
|
|
687
|
+
Napi::TypeError::New(env, "is_sender must be boolean.").ThrowAsJavaScriptException();
|
|
688
|
+
return env.Null();
|
|
689
|
+
}
|
|
690
|
+
bool is_sender = info[1].As<Napi::Boolean>().Value();
|
|
691
|
+
|
|
692
|
+
if (!info[2].IsString()) {
|
|
693
|
+
Napi::TypeError::New(env, "file must be string.").ThrowAsJavaScriptException();
|
|
694
|
+
return env.Null();
|
|
695
|
+
}
|
|
696
|
+
string file = info[2].As<Napi::String>().Utf8Value();
|
|
697
|
+
|
|
698
|
+
if (file.length() == 0) {
|
|
699
|
+
Napi::Error::New(env, "input_file is invalid (blank string)").ThrowAsJavaScriptException();
|
|
700
|
+
return env.Null();
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
int res = pjw_call_start_fax(call_id, is_sender, file.c_str());
|
|
704
|
+
|
|
705
|
+
if(res != 0) {
|
|
706
|
+
Napi::Error::New(env, pjw_get_error()).ThrowAsJavaScriptException();
|
|
707
|
+
return env.Null();
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return env.Null();
|
|
711
|
+
}
|
|
712
|
+
|
|
672
713
|
Napi::Value call_stop_record_wav(const Napi::CallbackInfo& info) {
|
|
673
714
|
Napi::Env env = info.Env();
|
|
674
715
|
|
|
@@ -717,6 +758,30 @@ Napi::Value call_stop_play_wav(const Napi::CallbackInfo& info) {
|
|
|
717
758
|
return env.Null();
|
|
718
759
|
}
|
|
719
760
|
|
|
761
|
+
Napi::Value call_stop_fax(const Napi::CallbackInfo& info) {
|
|
762
|
+
Napi::Env env = info.Env();
|
|
763
|
+
|
|
764
|
+
if (info.Length() != 1) {
|
|
765
|
+
Napi::Error::New(env, "Wrong number of arguments. Expected: call_id").ThrowAsJavaScriptException();
|
|
766
|
+
return env.Null();
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (!info[0].IsNumber()) {
|
|
770
|
+
Napi::TypeError::New(env, "call_id must be number.").ThrowAsJavaScriptException();
|
|
771
|
+
return env.Null();
|
|
772
|
+
}
|
|
773
|
+
int call_id = info[0].As<Napi::Number>().Int32Value();
|
|
774
|
+
|
|
775
|
+
int res = pjw_call_stop_fax(call_id);
|
|
776
|
+
|
|
777
|
+
if(res != 0) {
|
|
778
|
+
Napi::Error::New(env, pjw_get_error()).ThrowAsJavaScriptException();
|
|
779
|
+
return env.Null();
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return env.Null();
|
|
783
|
+
}
|
|
784
|
+
|
|
720
785
|
Napi::Value call_get_stream_stat(const Napi::CallbackInfo& info) {
|
|
721
786
|
Napi::Env env = info.Env();
|
|
722
787
|
|
|
@@ -1381,8 +1446,10 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
|
|
|
1381
1446
|
exports.Set("call_send_request", Napi::Function::New(env, call_send_request));
|
|
1382
1447
|
exports.Set("call_start_record_wav", Napi::Function::New(env, call_start_record_wav));
|
|
1383
1448
|
exports.Set("call_start_play_wav", Napi::Function::New(env, call_start_play_wav));
|
|
1449
|
+
exports.Set("call_start_fax", Napi::Function::New(env, call_start_fax));
|
|
1384
1450
|
exports.Set("call_stop_record_wav", Napi::Function::New(env, call_stop_record_wav));
|
|
1385
1451
|
exports.Set("call_stop_play_wav", Napi::Function::New(env, call_stop_play_wav));
|
|
1452
|
+
exports.Set("call_stop_fax", Napi::Function::New(env, call_stop_fax));
|
|
1386
1453
|
exports.Set("call_get_stream_stat", Napi::Function::New(env, call_get_stream_stat));
|
|
1387
1454
|
exports.Set("call_refer", Napi::Function::New(env, call_refer));
|
|
1388
1455
|
exports.Set("call_get_info", Napi::Function::New(env, call_get_info));
|
package/src/event_templates.cpp
CHANGED
|
@@ -53,3 +53,7 @@ int make_evt_registration_status(char *dest, int size, long account_id, int code
|
|
|
53
53
|
return snprintf(dest, size, "{\"event\": \"registration_status\", \"account_id\": %i, \"code\": %i, \"reason\": \"%s\", \"expires\": %i}", account_id, code, reason, expires);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
int make_evt_fax_result(char *dest, int size, long call_id, int result) {
|
|
57
|
+
return snprintf(dest, size, "{\"event\": \"fax_result\", \"call_id\": %i, \"result\": %i}", call_id, result);
|
|
58
|
+
}
|
|
59
|
+
|
package/src/event_templates.hpp
CHANGED
|
@@ -22,6 +22,8 @@ int make_evt_internal_error(char *dest, int size, char *msg);
|
|
|
22
22
|
|
|
23
23
|
int make_evt_registration_status(char *dest, int size, long account_id, int code, char *reason, int expires);
|
|
24
24
|
|
|
25
|
+
int make_evt_fax_result(char *dest, int size, long call_id, int result);
|
|
26
|
+
|
|
25
27
|
#endif
|
|
26
28
|
|
|
27
29
|
|
package/src/pjmedia/Makefile
CHANGED
|
@@ -18,7 +18,7 @@ all: libchainlink.a
|
|
|
18
18
|
|
|
19
19
|
#libchainlink.a: chainlink_wire_port.o chainlink_dtmfdet.o chainlink_tonegen.o chainlink_wav_player.o
|
|
20
20
|
#libchainlink.a: chainlink_wire_port.o chainlink_dtmfdet.o chainlink_tonegen.o
|
|
21
|
-
libchainlink.a: chainlink_wire_port.o chainlink_dtmfdet.o chainlink_wav_player.o chainlink_wav_writer.o chainlink_tonegen.o
|
|
21
|
+
libchainlink.a: chainlink_wire_port.o chainlink_dtmfdet.o chainlink_wav_player.o chainlink_wav_writer.o chainlink_tonegen.o chainlink_fax.o
|
|
22
22
|
ar rcs $@ $^
|
|
23
23
|
|
|
24
24
|
chainlink_wire_port.o:
|
|
@@ -31,6 +31,8 @@ chainlink_wav_writer.o:
|
|
|
31
31
|
|
|
32
32
|
chainlink_tonegen.o:
|
|
33
33
|
|
|
34
|
+
chainlink_fax.o:
|
|
35
|
+
|
|
34
36
|
clean:
|
|
35
37
|
rm -f *.o
|
|
36
38
|
rm -f *.a
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#ifndef __CHAINLINK_FAX_H__
|
|
2
|
+
#define __CHAINLINK_FAX_H__
|
|
3
|
+
|
|
4
|
+
#include <pjmedia/port.h>
|
|
5
|
+
|
|
6
|
+
PJ_BEGIN_DECL
|
|
7
|
+
|
|
8
|
+
PJ_DECL(pj_status_t) chainlink_fax_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
|
+
int is_caller,
|
|
18
|
+
int is_sender,
|
|
19
|
+
const char *file,
|
|
20
|
+
pjmedia_port **p_port);
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
PJ_END_DECL
|
|
24
|
+
|
|
25
|
+
#endif /* __CHAINLINK_FAX_H__ */
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#include "chainlink.h"
|
|
2
|
+
#include "chainlink_fax.h"
|
|
3
|
+
|
|
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
|
+
#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES
|
|
12
|
+
#include <spandsp.h>
|
|
13
|
+
|
|
14
|
+
#define SIGNATURE PJMEDIA_SIGNATURE('L', 'f', 'a', 'x')
|
|
15
|
+
#define THIS_FILE "chainlink_fax.c"
|
|
16
|
+
|
|
17
|
+
#if 0
|
|
18
|
+
# define TRACE_(expr) PJ_LOG(4,expr)
|
|
19
|
+
#else
|
|
20
|
+
# define TRACE_(expr)
|
|
21
|
+
#endif
|
|
22
|
+
|
|
23
|
+
#define FAX_DATA_CHUNK 320
|
|
24
|
+
#define T38_DATA_CHUNK 160
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
enum
|
|
28
|
+
{
|
|
29
|
+
/*! No compression */
|
|
30
|
+
T30_SUPPORT_NO_COMPRESSION = 0x01,
|
|
31
|
+
/*! T.1 1D compression */
|
|
32
|
+
T30_SUPPORT_T4_1D_COMPRESSION = 0x02,
|
|
33
|
+
/*! T.4 2D compression */
|
|
34
|
+
T30_SUPPORT_T4_2D_COMPRESSION = 0x04,
|
|
35
|
+
/*! T.6 2D compression */
|
|
36
|
+
T30_SUPPORT_T6_COMPRESSION = 0x08,
|
|
37
|
+
/*! T.85 monochrome JBIG compression */
|
|
38
|
+
T30_SUPPORT_T85_COMPRESSION = 0x10,
|
|
39
|
+
/*! T.43 colour JBIG compression */
|
|
40
|
+
T30_SUPPORT_T43_COMPRESSION = 0x20,
|
|
41
|
+
/*! T.45 run length colour compression */
|
|
42
|
+
T30_SUPPORT_T45_COMPRESSION = 0x40,
|
|
43
|
+
/*! T.81 + T.30 Annex E colour JPEG compression */
|
|
44
|
+
T30_SUPPORT_T81_COMPRESSION = 0x80,
|
|
45
|
+
/*! T.81 + T.30 Annex K colour sYCC-JPEG compression */
|
|
46
|
+
T30_SUPPORT_SYCC_T81_COMPRESSION = 0x100,
|
|
47
|
+
/*! T.88 monochrome JBIG2 compression */
|
|
48
|
+
T30_SUPPORT_T88_COMPRESSION = 0x200
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
static pj_status_t fax_get_frame(pjmedia_port *this_port,
|
|
53
|
+
pjmedia_frame *frame);
|
|
54
|
+
static pj_status_t fax_put_frame(pjmedia_port *this_port,
|
|
55
|
+
pjmedia_frame *frame);
|
|
56
|
+
static pj_status_t fax_on_destroy(pjmedia_port *this_port);
|
|
57
|
+
|
|
58
|
+
struct fax_device
|
|
59
|
+
{
|
|
60
|
+
struct chainlink link;
|
|
61
|
+
fax_state_t fax;
|
|
62
|
+
void (*fax_cb)(pjmedia_port*, void*, int);
|
|
63
|
+
void *fax_cb_user_data;
|
|
64
|
+
int is_caller;
|
|
65
|
+
int is_sender;
|
|
66
|
+
|
|
67
|
+
pj_lock_t *lock;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
static int phase_b_handler(t30_state_t* s, void* user_data, int result)
|
|
71
|
+
{
|
|
72
|
+
printf("fax phase_b_handler user_data=%p result=%i\n", user_data, result);
|
|
73
|
+
return T30_ERR_OK;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static int phase_d_handler(t30_state_t* s, void* user_data, int result)
|
|
77
|
+
{
|
|
78
|
+
printf("fax phase_b_handler user_data=%p result=%i\n", user_data, result);
|
|
79
|
+
return T30_ERR_OK;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static void phase_e_handler(t30_state_t* s, void* user_data, int result)
|
|
83
|
+
{
|
|
84
|
+
printf("fax phase_e_handler user_data=%p result=%i\n", user_data, result);
|
|
85
|
+
|
|
86
|
+
if (!user_data) {
|
|
87
|
+
printf("not user_data\n");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
struct fax_device *fd = (struct fax_device*)user_data;
|
|
92
|
+
if(!fd->fax_cb) {
|
|
93
|
+
printf("not fax_cb\n");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
printf("sending result event\n");
|
|
98
|
+
fd->fax_cb((pjmedia_port*)fd, fd->fax_cb_user_data, result);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static int document_handler(t30_state_t* s, void* user_data, int result)
|
|
102
|
+
{
|
|
103
|
+
printf("fax document_handler user_data=%p result=%i\n", user_data, result);
|
|
104
|
+
|
|
105
|
+
if (!user_data) return 0;
|
|
106
|
+
|
|
107
|
+
struct fax_device *fd = (struct fax_device*)user_data;
|
|
108
|
+
if(!fd->fax_cb) return 0;
|
|
109
|
+
|
|
110
|
+
//fd->fax_cb((pjmedia_port*)fd, fd->fax_cb_user_data, result);
|
|
111
|
+
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
PJ_DEF(pj_status_t) chainlink_fax_port_create( pj_pool_t *pool,
|
|
116
|
+
unsigned clock_rate,
|
|
117
|
+
unsigned channel_count,
|
|
118
|
+
unsigned samples_per_frame,
|
|
119
|
+
unsigned bits_per_sample,
|
|
120
|
+
void (*cb)(pjmedia_port*,
|
|
121
|
+
void *user_data,
|
|
122
|
+
int result),
|
|
123
|
+
void *user_data,
|
|
124
|
+
int is_caller,
|
|
125
|
+
int is_sender,
|
|
126
|
+
const char *file,
|
|
127
|
+
pjmedia_port **p_port)
|
|
128
|
+
{
|
|
129
|
+
struct fax_device *fd;
|
|
130
|
+
const pj_str_t name = pj_str("fax_device");
|
|
131
|
+
|
|
132
|
+
PJ_ASSERT_RETURN(pool && clock_rate && channel_count &&
|
|
133
|
+
samples_per_frame && bits_per_sample == 16 &&
|
|
134
|
+
p_port != NULL, PJ_EINVAL);
|
|
135
|
+
|
|
136
|
+
PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL);
|
|
137
|
+
|
|
138
|
+
fd = PJ_POOL_ZALLOC_T(pool, struct fax_device);
|
|
139
|
+
PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
|
|
140
|
+
|
|
141
|
+
pjmedia_port_info_init(&fd->link.port.info, &name, SIGNATURE, clock_rate,
|
|
142
|
+
channel_count, bits_per_sample, samples_per_frame);
|
|
143
|
+
|
|
144
|
+
fd->link.port.get_frame = &fax_get_frame;
|
|
145
|
+
fd->link.port.put_frame = &fax_put_frame;
|
|
146
|
+
fd->link.port.on_destroy = &fax_on_destroy;
|
|
147
|
+
|
|
148
|
+
fax_init(&fd->fax, is_caller);
|
|
149
|
+
//fax_set_transmit_on_idle(&fd->fax,1);
|
|
150
|
+
|
|
151
|
+
t30_state_t *t30 = fax_get_t30_state(&fd->fax);
|
|
152
|
+
|
|
153
|
+
span_log_set_level(fax_get_logging_state(&fd->fax), SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
|
|
154
|
+
span_log_set_level(t30_get_logging_state(t30), SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
|
|
155
|
+
|
|
156
|
+
char ident[] = "chainlink_fax";
|
|
157
|
+
|
|
158
|
+
t30_set_tx_ident(t30, ident);
|
|
159
|
+
t30_set_phase_b_handler(t30, &phase_b_handler, (void*)fd);
|
|
160
|
+
t30_set_phase_d_handler(t30, &phase_d_handler, (void*)fd);
|
|
161
|
+
t30_set_phase_e_handler(t30, &phase_e_handler, (void*)fd);
|
|
162
|
+
//printf("setting document_handler with user_data=%p\n", (void*)fd);
|
|
163
|
+
t30_set_document_handler(t30, &document_handler, (void*)fd);
|
|
164
|
+
|
|
165
|
+
fd->is_caller = is_caller;
|
|
166
|
+
fd->is_sender = is_sender;
|
|
167
|
+
|
|
168
|
+
pj_status_t status = pj_lock_create_simple_mutex(pool, "fax", &fd->lock);
|
|
169
|
+
|
|
170
|
+
if (status != PJ_SUCCESS) {
|
|
171
|
+
printf("failed to create lock\n");
|
|
172
|
+
return status;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (is_sender)
|
|
176
|
+
t30_set_tx_file(t30,file,-1,-1);
|
|
177
|
+
else
|
|
178
|
+
t30_set_rx_file(t30,file,-1);
|
|
179
|
+
|
|
180
|
+
t30_set_ecm_capability(t30,1);
|
|
181
|
+
t30_set_supported_compressions(t30,T30_SUPPORT_T4_1D_COMPRESSION |
|
|
182
|
+
T30_SUPPORT_T4_2D_COMPRESSION | T30_SUPPORT_T6_COMPRESSION);
|
|
183
|
+
|
|
184
|
+
fax_set_transmit_on_idle(&fd->fax, 1);
|
|
185
|
+
|
|
186
|
+
fd->fax_cb = cb;
|
|
187
|
+
fd->fax_cb_user_data = user_data;
|
|
188
|
+
|
|
189
|
+
TRACE_((THIS_FILE, "fax_device created: %u/%u/%u/%u", clock_rate,
|
|
190
|
+
channel_count, samples_per_frame, bits_per_sample));
|
|
191
|
+
|
|
192
|
+
*p_port = &fd->link.port;
|
|
193
|
+
return PJ_SUCCESS;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// called when pjmedia needs data to be sent out
|
|
197
|
+
static pj_status_t fax_get_frame(pjmedia_port *this_port,
|
|
198
|
+
pjmedia_frame *frame) {
|
|
199
|
+
|
|
200
|
+
//printf("ENTER fax_get_frame frame_size=%i\n", frame->size);
|
|
201
|
+
|
|
202
|
+
PJ_ASSERT_RETURN(this_port && frame, PJ_EINVAL);
|
|
203
|
+
char *p = (char*)frame->buf;
|
|
204
|
+
|
|
205
|
+
struct fax_device *fd = (struct fax_device*)this_port;
|
|
206
|
+
pj_lock_acquire(fd->lock);
|
|
207
|
+
|
|
208
|
+
int tx = 0;
|
|
209
|
+
|
|
210
|
+
if ((tx = fax_tx(&fd->fax, (int16_t *)frame->buf, frame->size/2)) < 0) {
|
|
211
|
+
printf("fax_tx reported an error\n");
|
|
212
|
+
pj_lock_release(fd->lock);
|
|
213
|
+
printf("EXIT fax_get_frame\n");
|
|
214
|
+
return PJ_FALSE;
|
|
215
|
+
}
|
|
216
|
+
pj_lock_release(fd->lock);
|
|
217
|
+
|
|
218
|
+
frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
|
|
219
|
+
frame->timestamp.u64 = 0;
|
|
220
|
+
|
|
221
|
+
//printf("EXIT fax_get_frame\n");
|
|
222
|
+
return PJ_SUCCESS;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// called when pjmedia has received data
|
|
226
|
+
static pj_status_t fax_put_frame(pjmedia_port *this_port,
|
|
227
|
+
pjmedia_frame *frame)
|
|
228
|
+
{
|
|
229
|
+
if(frame->type != PJMEDIA_FRAME_TYPE_AUDIO) return PJ_SUCCESS;
|
|
230
|
+
//printf("ENTER fax_put_frame frame->buf=%x frame->size=%i\n", frame->buf, frame->size);
|
|
231
|
+
|
|
232
|
+
struct fax_device *fd = (struct fax_device*) this_port;
|
|
233
|
+
pj_lock_acquire(fd->lock);
|
|
234
|
+
|
|
235
|
+
unsigned int pos = 0;
|
|
236
|
+
|
|
237
|
+
while (pos < frame->size)
|
|
238
|
+
{
|
|
239
|
+
// feed the decoder with small chunks of data (16 bytes/ms)
|
|
240
|
+
int len = frame->size - pos;
|
|
241
|
+
if (len > FAX_DATA_CHUNK) len = FAX_DATA_CHUNK;
|
|
242
|
+
|
|
243
|
+
/* Pass the new incoming audio frame to the fax_rx function */
|
|
244
|
+
if (fax_rx(&fd->fax, (int16_t *)(frame->buf)+pos, len/2)) {
|
|
245
|
+
printf("fax_rx reported an error\n");
|
|
246
|
+
pj_lock_release(fd->lock);
|
|
247
|
+
printf("EXIT fax_put_frame\n");
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
pos += len;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
pj_lock_release(fd->lock);
|
|
255
|
+
|
|
256
|
+
//printf("EXIT fax_put_frame\n");
|
|
257
|
+
return fd->link.next->put_frame(fd->link.next, frame);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/*
|
|
261
|
+
* Destroy port.
|
|
262
|
+
*/
|
|
263
|
+
static pj_status_t fax_on_destroy(pjmedia_port *this_port)
|
|
264
|
+
{
|
|
265
|
+
printf("fax_on_destroy\n");
|
|
266
|
+
|
|
267
|
+
struct fax_device *fd = (struct fax_device*)this_port;
|
|
268
|
+
|
|
269
|
+
fax_release(&fd->fax);
|
|
270
|
+
|
|
271
|
+
if(fd->lock) pj_lock_destroy(fd->lock);
|
|
272
|
+
return PJ_SUCCESS;
|
|
273
|
+
}
|
|
274
|
+
|
package/src/sip.cpp
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
#include "chainlink_dtmfdet.h"
|
|
27
27
|
#include "chainlink_tonegen.h"
|
|
28
28
|
#include "chainlink_wav_port.h"
|
|
29
|
+
#include "chainlink_fax.h"
|
|
29
30
|
|
|
30
31
|
#include <ctime>
|
|
31
32
|
|
|
@@ -166,6 +167,7 @@ struct Call {
|
|
|
166
167
|
chainlink *wav_player;
|
|
167
168
|
chainlink *tonegen;
|
|
168
169
|
chainlink *dtmfdet;
|
|
170
|
+
chainlink *fax;
|
|
169
171
|
|
|
170
172
|
Transport *transport;
|
|
171
173
|
|
|
@@ -361,6 +363,8 @@ bool prepare_tonegen(Call *c);
|
|
|
361
363
|
bool prepare_wav_player(Call *c, const char *file);
|
|
362
364
|
bool prepare_wav_writer(Call *c, const char *file);
|
|
363
365
|
|
|
366
|
+
bool prepare_fax(Call *c, bool is_sender, const char *file);
|
|
367
|
+
|
|
364
368
|
void prepare_error_event(ostringstream *oss, char *scope, char *details);
|
|
365
369
|
//void prepare_pjsipcall_error_event(ostringstream *oss, char *scope, char *function, pj_status_t s);
|
|
366
370
|
void append_status(ostringstream *oss, pj_status_t s);
|
|
@@ -461,6 +465,21 @@ static void on_inband_dtmf(pjmedia_port *port, void *user_data, char digit){
|
|
|
461
465
|
}
|
|
462
466
|
}
|
|
463
467
|
|
|
468
|
+
static void on_fax_result(pjmedia_port *port, void *user_data, int result){
|
|
469
|
+
if(g_shutting_down) return;
|
|
470
|
+
|
|
471
|
+
long call_id;
|
|
472
|
+
if( !g_call_ids.get_id((long)user_data, call_id) ){
|
|
473
|
+
printf("on_fax_result: Failed to get call_id. Event will not be notified.\n");
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
char evt[256];
|
|
478
|
+
make_evt_fax_result(evt, sizeof(evt), call_id, result);
|
|
479
|
+
dispatch_event(evt);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
|
|
464
483
|
void dispatch_event(const char * evt)
|
|
465
484
|
{
|
|
466
485
|
//addon_log(LOG_LEVEL_DEBUG, "dispach_event called\n");
|
|
@@ -2215,6 +2234,87 @@ int pjw_call_stop_record_wav(long call_id)
|
|
|
2215
2234
|
return 0;
|
|
2216
2235
|
}
|
|
2217
2236
|
|
|
2237
|
+
int pjw_call_start_fax(long call_id, bool is_sender, const char *file)
|
|
2238
|
+
{
|
|
2239
|
+
PJW_LOCK();
|
|
2240
|
+
|
|
2241
|
+
long val;
|
|
2242
|
+
|
|
2243
|
+
if(!g_call_ids.get(call_id, val)){
|
|
2244
|
+
PJW_UNLOCK();
|
|
2245
|
+
set_error("Invalid call_id");
|
|
2246
|
+
return -1;
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
Call *call = (Call*)val;
|
|
2250
|
+
|
|
2251
|
+
if(!call->med_stream)
|
|
2252
|
+
{
|
|
2253
|
+
PJW_UNLOCK();
|
|
2254
|
+
set_error("Media not ready");
|
|
2255
|
+
return -1;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
pj_status_t status;
|
|
2259
|
+
|
|
2260
|
+
pjmedia_port *stream_port;
|
|
2261
|
+
status = pjmedia_stream_get_port(call->med_stream,
|
|
2262
|
+
&stream_port);
|
|
2263
|
+
if(status != PJ_SUCCESS)
|
|
2264
|
+
{
|
|
2265
|
+
PJW_UNLOCK();
|
|
2266
|
+
set_error("pjmedia_stream_get_port failed");
|
|
2267
|
+
return -1;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
if(!prepare_fax(call, is_sender, file)){
|
|
2271
|
+
PJW_UNLOCK();
|
|
2272
|
+
set_error("pjmedia_wav_player_port_create failed");
|
|
2273
|
+
return -1;
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
PJW_UNLOCK();
|
|
2277
|
+
return 0;
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
|
|
2281
|
+
int pjw_call_stop_fax(long call_id)
|
|
2282
|
+
{
|
|
2283
|
+
PJW_LOCK();
|
|
2284
|
+
|
|
2285
|
+
long val;
|
|
2286
|
+
|
|
2287
|
+
if(!g_call_ids.get(call_id, val)){
|
|
2288
|
+
PJW_UNLOCK();
|
|
2289
|
+
set_error("Invalid call_id");
|
|
2290
|
+
return -1;
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
Call *call = (Call*)val;
|
|
2294
|
+
|
|
2295
|
+
pjmedia_port *stream_port;
|
|
2296
|
+
pj_status_t status;
|
|
2297
|
+
status = pjmedia_stream_get_port(call->med_stream,
|
|
2298
|
+
&stream_port);
|
|
2299
|
+
if(status != PJ_SUCCESS) {
|
|
2300
|
+
PJW_UNLOCK();
|
|
2301
|
+
set_error("pjmedia_stream_get_port failed.");
|
|
2302
|
+
return -1;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
if(!prepare_wire(call->inv->pool, &call->fax, PJMEDIA_PIA_SRATE(&stream_port->info), PJMEDIA_PIA_CCNT(&stream_port->info), PJMEDIA_PIA_SPF(&stream_port->info), PJMEDIA_PIA_BITS(&stream_port->info))) {
|
|
2306
|
+
PJW_UNLOCK();
|
|
2307
|
+
set_error("prepare_wire failed.");
|
|
2308
|
+
return -1;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
connect_media_ports(call);
|
|
2312
|
+
|
|
2313
|
+
PJW_UNLOCK();
|
|
2314
|
+
return 0;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
|
|
2218
2318
|
int pjw_call_get_stream_stat(long call_id, char *out_stats){
|
|
2219
2319
|
PJW_LOCK();
|
|
2220
2320
|
|
|
@@ -3645,6 +3745,12 @@ bool init_media_ports(Call *c, unsigned sampling_rate, unsigned channel_count, u
|
|
|
3645
3745
|
if(status != PJ_SUCCESS) return false;
|
|
3646
3746
|
}
|
|
3647
3747
|
|
|
3748
|
+
if(!c->fax) {
|
|
3749
|
+
if(!prepare_wire(c->inv->pool, &c->fax, sampling_rate, channel_count, samples_per_frame, bits_per_sample)) {
|
|
3750
|
+
return false;
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
|
|
3648
3754
|
connect_media_ports(c);
|
|
3649
3755
|
return true;
|
|
3650
3756
|
}
|
|
@@ -3653,7 +3759,8 @@ void connect_media_ports(Call *c) {
|
|
|
3653
3759
|
((chainlink*)c->dtmfdet)->next = (pjmedia_port*)c->tonegen;
|
|
3654
3760
|
((chainlink*)c->tonegen)->next = (pjmedia_port*)c->wav_player;
|
|
3655
3761
|
((chainlink*)c->wav_player)->next = (pjmedia_port*)c->wav_writer;
|
|
3656
|
-
|
|
3762
|
+
((chainlink*)c->wav_writer)->next = (pjmedia_port*)c->fax;
|
|
3763
|
+
((chainlink*)c->fax)->next = c->null_port;
|
|
3657
3764
|
}
|
|
3658
3765
|
|
|
3659
3766
|
bool prepare_tonegen(Call *c) {
|
|
@@ -3740,6 +3847,37 @@ bool prepare_wav_writer(Call *c, const char *file) {
|
|
|
3740
3847
|
return true;
|
|
3741
3848
|
}
|
|
3742
3849
|
|
|
3850
|
+
|
|
3851
|
+
bool prepare_fax(Call *c, bool is_sender, const char *file) {
|
|
3852
|
+
pj_status_t status;
|
|
3853
|
+
|
|
3854
|
+
chainlink *link = (chainlink*)c->fax;
|
|
3855
|
+
|
|
3856
|
+
pjmedia_port *stream_port;
|
|
3857
|
+
status = pjmedia_stream_get_port(c->med_stream,
|
|
3858
|
+
&stream_port);
|
|
3859
|
+
if(status != PJ_SUCCESS) return false;
|
|
3860
|
+
|
|
3861
|
+
status = pjmedia_port_destroy((pjmedia_port*)link);
|
|
3862
|
+
if(status != PJ_SUCCESS) return false;
|
|
3863
|
+
|
|
3864
|
+
status = chainlink_fax_port_create(c->inv->pool,
|
|
3865
|
+
PJMEDIA_PIA_SRATE(&stream_port->info),
|
|
3866
|
+
PJMEDIA_PIA_CCNT(&stream_port->info),
|
|
3867
|
+
PJMEDIA_PIA_SPF(&stream_port->info),
|
|
3868
|
+
PJMEDIA_PIA_BITS(&stream_port->info),
|
|
3869
|
+
on_fax_result,
|
|
3870
|
+
c,
|
|
3871
|
+
c->outgoing,
|
|
3872
|
+
is_sender,
|
|
3873
|
+
file,
|
|
3874
|
+
(pjmedia_port**)&c->fax);
|
|
3875
|
+
if(status != PJ_SUCCESS) return false;
|
|
3876
|
+
|
|
3877
|
+
connect_media_ports(c);
|
|
3878
|
+
return true;
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3743
3881
|
bool prepare_wire(pj_pool_t *pool, chainlink **link, unsigned sampling_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample) {
|
|
3744
3882
|
pj_status_t status;
|
|
3745
3883
|
|
package/src/sip.hpp
CHANGED
|
@@ -33,6 +33,8 @@ int pjw_call_start_record_wav(long call_id, const char *file);
|
|
|
33
33
|
int pjw_call_start_play_wav(long call_id, const char *file);
|
|
34
34
|
int pjw_call_stop_play_wav(long call_id);
|
|
35
35
|
int pjw_call_stop_record_wav(long call_id);
|
|
36
|
+
int pjw_call_start_fax(long call_id, bool is_sender, const char *file);
|
|
37
|
+
int pjw_call_stop_fax(long call_id);
|
|
36
38
|
int pjw_call_get_stream_stat(long call_id, char *out_stats);
|
|
37
39
|
int pjw_call_refer(long call_id, const char *dest_uri, const char *additional_headers, long *out_subscription_id);
|
|
38
40
|
int pjw_call_get_info(long call_id, const char *required_info, char *out_info);
|