sip-lab 1.41.0 → 1.41.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/DEV.md +14 -0
- package/README.md +6 -13
- package/index.js +1 -1
- package/package.json +1 -1
- package/prebuilds/linux-x64/node.napi.node +0 -0
- package/src/pjmedia/src/pjmedia/bfsk_det.c +115 -102
- package/src/pjmedia/src/pjmedia/bfsk_det2.c +171 -109
package/DEV.md
CHANGED
|
@@ -181,5 +181,19 @@ Then do the actual publish:
|
|
|
181
181
|
```
|
|
182
182
|
npm publish
|
|
183
183
|
```
|
|
184
|
+
If you get something like
|
|
185
|
+
```
|
|
186
|
+
npm timing command:publish Completed in 8775ms
|
|
187
|
+
npm ERR! code E404
|
|
188
|
+
npm ERR! 404 Not Found - PUT https://registry.npmjs.org/sip-lab - Not found
|
|
189
|
+
npm ERR! 404
|
|
190
|
+
npm ERR! 404 'sip-lab@1.41.0' is not in this registry.
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
it means you are not logged in.
|
|
194
|
+
So first do:
|
|
195
|
+
```
|
|
196
|
+
npm login
|
|
197
|
+
```
|
|
184
198
|
|
|
185
199
|
|
package/README.md
CHANGED
|
@@ -11,31 +11,24 @@ See [Documentation](https://github.com/MayamaTakeshi/sip-lab/blob/master/DOC.md)
|
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
|
-
The npm package is built for Debian
|
|
15
|
-
|
|
16
|
-
You can use other debian/ubuntu version but they will require a build of dependencies that will take time (something like 7 minutes but this was measured on my slow PC).
|
|
14
|
+
The npm package is built for Ubuntu/Debian and might work with other linux distros.
|
|
17
15
|
|
|
18
16
|
First install apt packages:
|
|
19
17
|
|
|
20
18
|
```
|
|
21
|
-
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 git wget
|
|
19
|
+
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 git wget bc
|
|
22
20
|
|
|
23
21
|
```
|
|
22
|
+
Obs: in ubuntu you might also need to install libssl1.1
|
|
24
23
|
|
|
25
|
-
Then
|
|
26
|
-
|
|
24
|
+
Then:
|
|
27
25
|
```
|
|
28
|
-
nvm install 19
|
|
29
|
-
nvm use 19
|
|
30
|
-
cd YOUR_NODE_PROJECT_FOLDER
|
|
31
26
|
npm i sip-lab
|
|
32
27
|
```
|
|
33
|
-
Obs: once you install sip-lab, you can switch to other node versions like v20, v21.
|
|
34
|
-
|
|
35
|
-
|
|
36
28
|
Then run some sample script from subfolder samples:
|
|
37
29
|
```
|
|
38
|
-
|
|
30
|
+
cd node_modules/sip-lab
|
|
31
|
+
node ./samples/simple.js
|
|
39
32
|
```
|
|
40
33
|
|
|
41
34
|
The above script has detailed comments.
|
package/index.js
CHANGED
|
@@ -74,7 +74,7 @@ addon.call = {
|
|
|
74
74
|
stop_inband_dtmf_detection: (c_id, params) => { return addon.call_stop_inband_dtmf_detection(c_id, JSON.stringify(params ? params : {})) },
|
|
75
75
|
|
|
76
76
|
start_bfsk_detection: (c_id, params) => { return addon.call_start_bfsk_detection(c_id, JSON.stringify(params ? params : {})) },
|
|
77
|
-
|
|
77
|
+
stop_bfsk_detection: (c_id, params) => { return addon.call_stop_bfsk_detection(c_id, JSON.stringify(params ? params : {})) },
|
|
78
78
|
|
|
79
79
|
start_speech_recog: (c_id, params) => {
|
|
80
80
|
var ps = {}
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
#include <pj/string.h>
|
|
27
27
|
|
|
28
28
|
#include <math.h>
|
|
29
|
+
#include <stdio.h>
|
|
30
|
+
#include <stdlib.h>
|
|
29
31
|
|
|
30
32
|
#define SIGNATURE PJMEDIA_SIGNATURE('b', 'f', 'd', 't')
|
|
31
33
|
#define THIS_FILE "bfsk_det.c"
|
|
@@ -36,24 +38,51 @@
|
|
|
36
38
|
# define TRACE_(expr)
|
|
37
39
|
#endif
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
/*
|
|
42
|
+
* Goertzel-based BFSK detector.
|
|
43
|
+
*
|
|
44
|
+
* CHUNK_SIZE is the number of samples per Goertzel analysis block.
|
|
45
|
+
* Frequency resolution = sample_rate / CHUNK_SIZE.
|
|
46
|
+
*
|
|
47
|
+
* Trade-off:
|
|
48
|
+
* - Larger CHUNK_SIZE → better frequency resolution, but longer analysis
|
|
49
|
+
* window. If the window is longer than one bit period, adjacent bits
|
|
50
|
+
* bleed into each other and detection fails.
|
|
51
|
+
* - Smaller CHUNK_SIZE → poorer frequency resolution, more false detects.
|
|
52
|
+
*
|
|
53
|
+
* Default signal_duration (on_msec / off_msec) used by send_bfsk is 10 ms.
|
|
54
|
+
* At 8000 Hz that is 80 samples per bit period. We therefore need
|
|
55
|
+
* CHUNK_SIZE well below 80 so that we see several chunks per tone burst.
|
|
56
|
+
* 40 samples = 5 ms per chunk → resolution ~200 Hz, fits ~2 chunks per 10 ms
|
|
57
|
+
* tone. This is a reasonable compromise for typical FSK tone pairs that have
|
|
58
|
+
* at least a few hundred Hz separation (e.g. 500/2000 Hz used in tests).
|
|
59
|
+
*/
|
|
60
|
+
#define CHUNK_SIZE 40
|
|
43
61
|
|
|
44
|
-
|
|
62
|
+
/*
|
|
63
|
+
* THRESHOLD: normalized ratio of detected magnitude to reference magnitude.
|
|
64
|
+
* A value of 0.3 means the incoming tone must be at least 30% as strong as
|
|
65
|
+
* a full-scale sine wave at that frequency. Tune upward to reduce false
|
|
66
|
+
* positives in noisy environments, downward to detect weaker signals.
|
|
67
|
+
*/
|
|
68
|
+
#define THRESHOLD 0.3
|
|
69
|
+
|
|
70
|
+
/* Converted by ChatGPT from https://github.com/hackergrrl/goertzel/blob/master/index.js */
|
|
45
71
|
|
|
46
72
|
typedef struct {
|
|
47
73
|
int freq;
|
|
48
74
|
int sampleRate;
|
|
49
75
|
int samplesPerFrame;
|
|
50
|
-
double targetMagnitude;
|
|
76
|
+
double targetMagnitude;
|
|
51
77
|
} goertzel_t;
|
|
52
78
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
79
|
+
/*
|
|
80
|
+
* Compute the expected Goertzel magnitude for a full-scale sine wave at
|
|
81
|
+
* the given frequency and sample rate, using CHUNK_SIZE samples. This is
|
|
82
|
+
* used as a normalization reference so that goertzel_mag() returns a
|
|
83
|
+
* dimensionless ratio in [0, 1] for a matching full-amplitude tone.
|
|
84
|
+
*/
|
|
85
|
+
static double precalcMagnitude(int freq, double rate) {
|
|
57
86
|
double t = 0.0;
|
|
58
87
|
double tstep = 1.0 / rate;
|
|
59
88
|
double samples[CHUNK_SIZE];
|
|
@@ -62,7 +91,7 @@ double precalcMagnitude(int freq, double rate) {
|
|
|
62
91
|
t += tstep;
|
|
63
92
|
}
|
|
64
93
|
|
|
65
|
-
int k = (int)(0.5 + (CHUNK_SIZE * freq) / rate);
|
|
94
|
+
int k = (int)(0.5 + ((double)CHUNK_SIZE * freq) / rate);
|
|
66
95
|
double w = (2 * M_PI / CHUNK_SIZE) * k;
|
|
67
96
|
double c = cos(w);
|
|
68
97
|
double s = sin(w);
|
|
@@ -77,51 +106,39 @@ double precalcMagnitude(int freq, double rate) {
|
|
|
77
106
|
|
|
78
107
|
double real = q1 - q2 * c;
|
|
79
108
|
double imaginary = q2 * s;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return magSquared;
|
|
109
|
+
return real * real + imaginary * imaginary;
|
|
83
110
|
}
|
|
84
111
|
|
|
85
|
-
void goertzel_det_init(goertzel_t *g, int freq, int sample_rate) {
|
|
112
|
+
static void goertzel_det_init(goertzel_t *g, int freq, int sample_rate) {
|
|
86
113
|
g->freq = freq;
|
|
87
114
|
g->sampleRate = sample_rate;
|
|
88
115
|
g->samplesPerFrame = CHUNK_SIZE;
|
|
89
116
|
|
|
90
117
|
assert(g->sampleRate >= g->freq * 2);
|
|
91
118
|
|
|
92
|
-
g->samplesPerFrame = (int)floor(g->samplesPerFrame);
|
|
93
|
-
|
|
94
|
-
// Precompute the target magnitude and store it in the struct
|
|
95
119
|
g->targetMagnitude = precalcMagnitude(g->freq, g->sampleRate);
|
|
96
|
-
// printf("Target Magnitude: %f\n", g->targetMagnitude);
|
|
97
120
|
}
|
|
98
121
|
|
|
99
|
-
|
|
100
|
-
|
|
122
|
+
/*
|
|
123
|
+
* Compute the Goertzel magnitude of the given PCM block and return it as a
|
|
124
|
+
* fraction of the reference magnitude. Input is raw 16-bit little-endian
|
|
125
|
+
* PCM; CHUNK_SIZE samples (2*CHUNK_SIZE bytes) are consumed.
|
|
126
|
+
*/
|
|
127
|
+
static float goertzel_mag(goertzel_t *g, void *samples) {
|
|
101
128
|
float float_samples[CHUNK_SIZE];
|
|
102
|
-
uint8_t *byteBuffer = (uint8_t *)samples;
|
|
103
|
-
for (int i = 0; i < CHUNK_SIZE ; i++) {
|
|
104
|
-
// Combine two consecutive bytes into a 16-bit signed integer (little-endian)
|
|
105
|
-
int16_t sample = byteBuffer[i * 2] | (byteBuffer[i * 2 + 1] << 8);
|
|
106
|
-
float_samples[i] = sample * 2.0f / 0x7FFF;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/*
|
|
129
|
+
uint8_t *byteBuffer = (uint8_t *)samples;
|
|
110
130
|
for (int i = 0; i < CHUNK_SIZE; i++) {
|
|
111
|
-
|
|
131
|
+
int16_t sample = (int16_t)(byteBuffer[i * 2] | (byteBuffer[i * 2 + 1] << 8));
|
|
132
|
+
float_samples[i] = sample / 32767.0f;
|
|
112
133
|
}
|
|
113
|
-
printf("\n");
|
|
114
|
-
*/
|
|
115
|
-
|
|
116
134
|
|
|
117
|
-
int k = (int)(0.5f + (CHUNK_SIZE * g->freq) / g->sampleRate);
|
|
118
|
-
float w = (2.0f * M_PI / CHUNK_SIZE) * k;
|
|
135
|
+
int k = (int)(0.5f + ((float)CHUNK_SIZE * g->freq) / g->sampleRate);
|
|
136
|
+
float w = (2.0f * (float)M_PI / CHUNK_SIZE) * k;
|
|
119
137
|
float c = cosf(w);
|
|
120
138
|
float s = sinf(w);
|
|
121
139
|
float coeff = 2.0f * c;
|
|
122
140
|
|
|
123
141
|
float q0 = 0.0f, q1 = 0.0f, q2 = 0.0f;
|
|
124
|
-
|
|
125
142
|
for (int i = 0; i < CHUNK_SIZE; i++) {
|
|
126
143
|
q0 = coeff * q1 - q2 + float_samples[i];
|
|
127
144
|
q2 = q1;
|
|
@@ -132,23 +149,28 @@ float goertzel_mag(goertzel_t *g, void *samples) {
|
|
|
132
149
|
float imaginary = q2 * s;
|
|
133
150
|
float magSquared = real * real + imaginary * imaginary;
|
|
134
151
|
|
|
135
|
-
float
|
|
136
|
-
//printf("(freq=%i) MagSquared=%f targetMagnitude=%f per=%f\n", g->freq, magSquared, g->targetMagnitude, per);
|
|
137
|
-
return per;
|
|
152
|
+
return (float)(magSquared / g->targetMagnitude);
|
|
138
153
|
}
|
|
139
154
|
|
|
140
155
|
|
|
141
156
|
static pj_status_t bfsk_det_put_frame(pjmedia_port *this_port,
|
|
142
|
-
|
|
157
|
+
pjmedia_frame *frame);
|
|
143
158
|
static pj_status_t bfsk_det_on_destroy(pjmedia_port *this_port);
|
|
144
159
|
|
|
145
160
|
struct bfsk_det
|
|
146
161
|
{
|
|
147
|
-
struct pjmedia_port
|
|
162
|
+
struct pjmedia_port base;
|
|
148
163
|
int clock_rate;
|
|
149
164
|
int freq_zero;
|
|
150
165
|
int freq_one;
|
|
151
166
|
|
|
167
|
+
/*
|
|
168
|
+
* *_in_progress tracks whether the corresponding tone was above threshold
|
|
169
|
+
* in the previous chunk. A bit is reported on the trailing edge (when
|
|
170
|
+
* the tone drops below threshold) so that we emit exactly one event per
|
|
171
|
+
* burst. Only one of zero_in_progress / one_in_progress should be set
|
|
172
|
+
* at a time; the dominant-frequency logic enforces this.
|
|
173
|
+
*/
|
|
152
174
|
int zero_in_progress;
|
|
153
175
|
int one_in_progress;
|
|
154
176
|
|
|
@@ -160,34 +182,30 @@ struct bfsk_det
|
|
|
160
182
|
};
|
|
161
183
|
|
|
162
184
|
PJ_DEF(pj_status_t) pjmedia_bfsk_det_create( pj_pool_t *pool,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
185
|
+
unsigned clock_rate,
|
|
186
|
+
unsigned channel_count,
|
|
187
|
+
unsigned samples_per_frame,
|
|
188
|
+
unsigned bits_per_sample,
|
|
189
|
+
void (*cb)(pjmedia_port*,
|
|
190
|
+
void *user_data,
|
|
191
|
+
int bit),
|
|
192
|
+
void *user_data,
|
|
171
193
|
int freq_zero,
|
|
172
194
|
int freq_one,
|
|
173
|
-
|
|
195
|
+
pjmedia_port **p_port)
|
|
174
196
|
{
|
|
175
|
-
//printf("pjmedia_bfsk_det_create\n");
|
|
176
197
|
struct bfsk_det *det;
|
|
177
|
-
|
|
178
198
|
const pj_str_t name = pj_str("bfsk_det");
|
|
179
199
|
|
|
180
200
|
PJ_ASSERT_RETURN(pool && clock_rate && channel_count &&
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL);
|
|
201
|
+
samples_per_frame && bits_per_sample == 16 &&
|
|
202
|
+
p_port != NULL, PJ_EINVAL);
|
|
185
203
|
|
|
186
204
|
det = PJ_POOL_ZALLOC_T(pool, struct bfsk_det);
|
|
187
|
-
PJ_ASSERT_RETURN(
|
|
205
|
+
PJ_ASSERT_RETURN(det != NULL, PJ_ENOMEM);
|
|
188
206
|
|
|
189
207
|
pjmedia_port_info_init(&det->base.info, &name, SIGNATURE, clock_rate,
|
|
190
|
-
|
|
208
|
+
channel_count, bits_per_sample, samples_per_frame);
|
|
191
209
|
|
|
192
210
|
det->base.put_frame = &bfsk_det_put_frame;
|
|
193
211
|
det->base.on_destroy = &bfsk_det_on_destroy;
|
|
@@ -199,73 +217,67 @@ PJ_DEF(pj_status_t) pjmedia_bfsk_det_create( pj_pool_t *pool,
|
|
|
199
217
|
det->freq_zero = freq_zero;
|
|
200
218
|
det->freq_one = freq_one;
|
|
201
219
|
|
|
202
|
-
float threshold = 0.1;
|
|
203
|
-
|
|
204
220
|
det->goertzel_zero = PJ_POOL_ZALLOC_T(pool, goertzel_t);
|
|
205
|
-
PJ_ASSERT_RETURN(
|
|
221
|
+
PJ_ASSERT_RETURN(det->goertzel_zero != NULL, PJ_ENOMEM);
|
|
206
222
|
|
|
207
223
|
det->goertzel_one = PJ_POOL_ZALLOC_T(pool, goertzel_t);
|
|
208
|
-
PJ_ASSERT_RETURN(
|
|
209
|
-
|
|
210
|
-
int sample_rate = clock_rate;
|
|
211
|
-
|
|
212
|
-
//printf("bfsk_det: clock_rate=%u channel_count=%u samples_per_frame=%u bits_per_frame=%u", clock_rate, channel_count, samples_per_frame, bits_per_sample);
|
|
224
|
+
PJ_ASSERT_RETURN(det->goertzel_one != NULL, PJ_ENOMEM);
|
|
213
225
|
|
|
214
|
-
goertzel_det_init(det->goertzel_zero, det->freq_zero,
|
|
215
|
-
goertzel_det_init(det->goertzel_one,
|
|
226
|
+
goertzel_det_init(det->goertzel_zero, det->freq_zero, (int)clock_rate);
|
|
227
|
+
goertzel_det_init(det->goertzel_one, det->freq_one, (int)clock_rate);
|
|
216
228
|
|
|
217
|
-
*p_port = &det->base;
|
|
229
|
+
*p_port = &det->base;
|
|
218
230
|
return PJ_SUCCESS;
|
|
219
231
|
}
|
|
220
232
|
|
|
221
233
|
static pj_status_t bfsk_det_put_frame(pjmedia_port *this_port,
|
|
222
|
-
|
|
234
|
+
pjmedia_frame *frame)
|
|
223
235
|
{
|
|
224
|
-
|
|
225
|
-
if(frame->type != PJMEDIA_FRAME_TYPE_AUDIO) return PJ_SUCCESS;
|
|
236
|
+
if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) return PJ_SUCCESS;
|
|
226
237
|
|
|
227
238
|
struct bfsk_det *dport = (struct bfsk_det*) this_port;
|
|
228
239
|
|
|
229
|
-
int size = frame->size;
|
|
230
|
-
int bps = PJMEDIA_PIA_BITS(&dport->base.info);
|
|
240
|
+
int size = (int)frame->size;
|
|
231
241
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
for (int i = 0; i < size; i++) {
|
|
239
|
-
printf("%02x", samples[i] & 0xFF);
|
|
240
|
-
printf("%02x", samples[i] >> 8 & 0xFF);
|
|
241
|
-
}
|
|
242
|
-
printf("\n");
|
|
243
|
-
*/
|
|
244
|
-
|
|
245
|
-
for(int i=0 ; i<size/2/CHUNK_SIZE; i++) {
|
|
246
|
-
double zero_power = goertzel_mag(dport->goertzel_zero, &frame->buf[i*2*CHUNK_SIZE]);
|
|
247
|
-
double one_power = goertzel_mag(dport->goertzel_one, &frame->buf[i*2*CHUNK_SIZE]);
|
|
248
|
-
|
|
249
|
-
int zero = zero_power > THRESHOLD;
|
|
250
|
-
int one = one_power > THRESHOLD;
|
|
242
|
+
/* Process CHUNK_SIZE-sample blocks. */
|
|
243
|
+
int num_chunks = size / 2 / CHUNK_SIZE;
|
|
244
|
+
for (int i = 0; i < num_chunks; i++) {
|
|
245
|
+
void *chunk = (uint8_t*)frame->buf + i * 2 * CHUNK_SIZE;
|
|
246
|
+
float zero_power = goertzel_mag(dport->goertzel_zero, chunk);
|
|
247
|
+
float one_power = goertzel_mag(dport->goertzel_one, chunk);
|
|
251
248
|
|
|
252
249
|
/*
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
250
|
+
* For FSK exactly one frequency should be active at a time.
|
|
251
|
+
* If both exceed the threshold, treat the stronger one as active
|
|
252
|
+
* and suppress the other to prevent simultaneous callbacks.
|
|
253
|
+
*/
|
|
254
|
+
int zero, one;
|
|
255
|
+
if (zero_power >= THRESHOLD || one_power >= THRESHOLD) {
|
|
256
|
+
if (zero_power >= one_power) {
|
|
257
|
+
zero = 1;
|
|
258
|
+
one = 0;
|
|
259
|
+
} else {
|
|
260
|
+
zero = 0;
|
|
261
|
+
one = 1;
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
zero = 0;
|
|
265
|
+
one = 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
TRACE_((THIS_FILE, "zero_power=%f one_power=%f zero=%d one=%d",
|
|
269
|
+
zero_power, one_power, zero, one));
|
|
256
270
|
|
|
257
|
-
|
|
258
|
-
if(dport->zero_in_progress && zero
|
|
259
|
-
printf("notifying bit=0\n");
|
|
271
|
+
/* Report bit=0 on trailing edge of zero tone. */
|
|
272
|
+
if (dport->zero_in_progress && !zero) {
|
|
260
273
|
dport->bfsk_cb((pjmedia_port*)dport, dport->bfsk_cb_user_data, 0);
|
|
261
274
|
dport->zero_in_progress = 0;
|
|
262
275
|
} else {
|
|
263
276
|
dport->zero_in_progress = zero;
|
|
264
277
|
}
|
|
265
278
|
|
|
266
|
-
|
|
267
|
-
if(dport->one_in_progress && one
|
|
268
|
-
printf("notifying bit=1\n");
|
|
279
|
+
/* Report bit=1 on trailing edge of one tone. */
|
|
280
|
+
if (dport->one_in_progress && !one) {
|
|
269
281
|
dport->bfsk_cb((pjmedia_port*)dport, dport->bfsk_cb_user_data, 1);
|
|
270
282
|
dport->one_in_progress = 0;
|
|
271
283
|
} else {
|
|
@@ -278,5 +290,6 @@ static pj_status_t bfsk_det_put_frame(pjmedia_port *this_port,
|
|
|
278
290
|
|
|
279
291
|
static pj_status_t bfsk_det_on_destroy(pjmedia_port *this_port)
|
|
280
292
|
{
|
|
293
|
+
PJ_UNUSED_ARG(this_port);
|
|
281
294
|
return PJ_SUCCESS;
|
|
282
295
|
}
|
|
@@ -18,18 +18,34 @@
|
|
|
18
18
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
/*
|
|
22
|
+
* BFSK detector variant 2: uses spandsp's Goertzel implementation.
|
|
23
|
+
*
|
|
24
|
+
* spandsp's goertzel_result() returns the squared DFT magnitude for the
|
|
25
|
+
* configured frequency. For a full-scale sine wave of N samples the
|
|
26
|
+
* expected result is approximately:
|
|
27
|
+
*
|
|
28
|
+
* ((N * 32768.0 / sqrt(2)) ^ 2 [float build]
|
|
29
|
+
*
|
|
30
|
+
* We derive a per-instance threshold from the reference level at creation
|
|
31
|
+
* time so the detector scales correctly with block size and sample rate.
|
|
32
|
+
*/
|
|
33
|
+
|
|
21
34
|
#include <pjmedia/bfsk_det2.h>
|
|
22
35
|
#include <pjmedia/errno.h>
|
|
23
36
|
#include <pjmedia/port.h>
|
|
24
37
|
#include <pj/assert.h>
|
|
25
38
|
#include <pj/pool.h>
|
|
26
39
|
#include <pj/string.h>
|
|
40
|
+
#include <pj/log.h>
|
|
27
41
|
|
|
28
42
|
#include <spandsp.h>
|
|
29
43
|
#include <spandsp/expose.h>
|
|
30
44
|
#include <spandsp/tone_detect.h>
|
|
31
45
|
|
|
32
46
|
#include <math.h>
|
|
47
|
+
#include <stdio.h>
|
|
48
|
+
#include <stdlib.h>
|
|
33
49
|
|
|
34
50
|
#define SIGNATURE PJMEDIA_SIGNATURE('b', 'f', 'd', '2')
|
|
35
51
|
#define THIS_FILE "bfsk_det2.c"
|
|
@@ -40,68 +56,113 @@
|
|
|
40
56
|
# define TRACE_(expr)
|
|
41
57
|
#endif
|
|
42
58
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
/*
|
|
60
|
+
* Number of samples fed to each Goertzel block.
|
|
61
|
+
* 102 samples @ 8000 Hz = ~12.75 ms per block, ~78 Hz resolution.
|
|
62
|
+
* This matches spandsp's DTMF detector block size and is a good trade-off
|
|
63
|
+
* between latency and frequency selectivity for telephony FSK.
|
|
64
|
+
*/
|
|
65
|
+
#define SAMPLES_PER_BLOCK 102
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
* Detection threshold expressed as a fraction of the full-scale reference
|
|
69
|
+
* magnitude. 0.05 = the tone must carry at least 5% of full-scale energy
|
|
70
|
+
* to be declared present. Increase toward 1.0 for noisier environments.
|
|
71
|
+
*/
|
|
72
|
+
#define THRESHOLD_RATIO 0.05f
|
|
46
73
|
|
|
47
74
|
static pj_status_t bfsk_det2_put_frame(pjmedia_port *this_port,
|
|
48
|
-
|
|
75
|
+
pjmedia_frame *frame);
|
|
49
76
|
static pj_status_t bfsk_det2_on_destroy(pjmedia_port *this_port);
|
|
50
77
|
|
|
51
78
|
struct bfsk_det2
|
|
52
79
|
{
|
|
53
|
-
struct pjmedia_port
|
|
80
|
+
struct pjmedia_port base;
|
|
54
81
|
int clock_rate;
|
|
55
82
|
int freq_zero;
|
|
56
83
|
int freq_one;
|
|
57
84
|
|
|
85
|
+
/*
|
|
86
|
+
* *_in_progress: set when the corresponding tone was above threshold in
|
|
87
|
+
* the previous block. A bit is reported on the trailing edge.
|
|
88
|
+
* Mutual exclusion is enforced by the dominant-frequency logic.
|
|
89
|
+
*/
|
|
58
90
|
int zero_in_progress;
|
|
59
91
|
int one_in_progress;
|
|
60
92
|
|
|
61
|
-
|
|
62
|
-
int32_t energy;
|
|
63
|
-
|
|
93
|
+
/* spandsp Goertzel descriptors (static configuration) and states. */
|
|
64
94
|
goertzel_descriptor_t desc_zero;
|
|
65
95
|
goertzel_descriptor_t desc_one;
|
|
96
|
+
goertzel_state_t state_zero;
|
|
97
|
+
goertzel_state_t state_one;
|
|
66
98
|
|
|
67
|
-
|
|
68
|
-
|
|
99
|
+
/*
|
|
100
|
+
* Threshold in the same units as goertzel_result().
|
|
101
|
+
* Computed at create time as THRESHOLD_RATIO * reference_magnitude.
|
|
102
|
+
*/
|
|
103
|
+
#if defined(SPANDSP_USE_FIXED_POINT)
|
|
104
|
+
int32_t threshold;
|
|
105
|
+
#else
|
|
106
|
+
float threshold;
|
|
107
|
+
#endif
|
|
69
108
|
|
|
70
109
|
void (*bfsk_cb)(pjmedia_port*, void*, int);
|
|
71
110
|
void *bfsk_cb_user_data;
|
|
72
111
|
};
|
|
73
112
|
|
|
74
|
-
|
|
113
|
+
/*
|
|
114
|
+
* Compute the Goertzel result for a synthetic full-scale pure sine wave
|
|
115
|
+
* at the given frequency so we can derive a calibrated threshold.
|
|
116
|
+
*/
|
|
117
|
+
#if defined(SPANDSP_USE_FIXED_POINT)
|
|
118
|
+
static int32_t compute_reference_magnitude(int freq, int sample_rate)
|
|
119
|
+
#else
|
|
120
|
+
static float compute_reference_magnitude(int freq, int sample_rate)
|
|
121
|
+
#endif
|
|
122
|
+
{
|
|
123
|
+
goertzel_descriptor_t desc;
|
|
124
|
+
goertzel_state_t state;
|
|
125
|
+
double t = 0.0;
|
|
126
|
+
double tstep = 1.0 / sample_rate;
|
|
127
|
+
int i;
|
|
128
|
+
|
|
129
|
+
make_goertzel_descriptor(&desc, (float)freq, SAMPLES_PER_BLOCK);
|
|
130
|
+
goertzel_init(&state, &desc);
|
|
131
|
+
|
|
132
|
+
for (i = 0; i < SAMPLES_PER_BLOCK; i++) {
|
|
133
|
+
int16_t s = (int16_t)(32767.0 * sin(2.0 * M_PI * freq * t));
|
|
134
|
+
goertzel_sample(&state, s);
|
|
135
|
+
t += tstep;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return goertzel_result(&state);
|
|
139
|
+
}
|
|
75
140
|
|
|
76
141
|
PJ_DEF(pj_status_t) pjmedia_bfsk_det2_create( pj_pool_t *pool,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
142
|
+
unsigned clock_rate,
|
|
143
|
+
unsigned channel_count,
|
|
144
|
+
unsigned samples_per_frame,
|
|
145
|
+
unsigned bits_per_sample,
|
|
146
|
+
void (*cb)(pjmedia_port*,
|
|
147
|
+
void *user_data,
|
|
148
|
+
int bit),
|
|
149
|
+
void *user_data,
|
|
85
150
|
int freq_zero,
|
|
86
151
|
int freq_one,
|
|
87
|
-
|
|
152
|
+
pjmedia_port **p_port)
|
|
88
153
|
{
|
|
89
|
-
//printf("pjmedia_bfsk_det2_create\n");
|
|
90
154
|
struct bfsk_det2 *det;
|
|
91
|
-
|
|
92
155
|
const pj_str_t name = pj_str("bfsk_det2");
|
|
93
156
|
|
|
94
157
|
PJ_ASSERT_RETURN(pool && clock_rate && channel_count &&
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL);
|
|
158
|
+
samples_per_frame && bits_per_sample == 16 &&
|
|
159
|
+
p_port != NULL, PJ_EINVAL);
|
|
99
160
|
|
|
100
161
|
det = PJ_POOL_ZALLOC_T(pool, struct bfsk_det2);
|
|
101
|
-
PJ_ASSERT_RETURN(
|
|
162
|
+
PJ_ASSERT_RETURN(det != NULL, PJ_ENOMEM);
|
|
102
163
|
|
|
103
164
|
pjmedia_port_info_init(&det->base.info, &name, SIGNATURE, clock_rate,
|
|
104
|
-
|
|
165
|
+
channel_count, bits_per_sample, samples_per_frame);
|
|
105
166
|
|
|
106
167
|
det->base.put_frame = &bfsk_det2_put_frame;
|
|
107
168
|
det->base.on_destroy = &bfsk_det2_on_destroy;
|
|
@@ -113,108 +174,108 @@ PJ_DEF(pj_status_t) pjmedia_bfsk_det2_create( pj_pool_t *pool,
|
|
|
113
174
|
det->freq_zero = freq_zero;
|
|
114
175
|
det->freq_one = freq_one;
|
|
115
176
|
|
|
116
|
-
|
|
117
|
-
make_goertzel_descriptor(&det->
|
|
177
|
+
/* Initialise spandsp Goertzel descriptors and states. */
|
|
178
|
+
make_goertzel_descriptor(&det->desc_zero, (float)freq_zero, SAMPLES_PER_BLOCK);
|
|
179
|
+
make_goertzel_descriptor(&det->desc_one, (float)freq_one, SAMPLES_PER_BLOCK);
|
|
118
180
|
|
|
119
|
-
goertzel_init(&det->
|
|
120
|
-
goertzel_init(&det->
|
|
181
|
+
goertzel_init(&det->state_zero, &det->desc_zero);
|
|
182
|
+
goertzel_init(&det->state_one, &det->desc_one);
|
|
121
183
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
184
|
+
/*
|
|
185
|
+
* Derive threshold from the reference magnitude of each frequency.
|
|
186
|
+
* Use the average of the two reference levels so a single threshold
|
|
187
|
+
* applies to both detectors.
|
|
188
|
+
*/
|
|
189
|
+
#if defined(SPANDSP_USE_FIXED_POINT)
|
|
190
|
+
{
|
|
191
|
+
int32_t ref0 = compute_reference_magnitude(freq_zero, (int)clock_rate);
|
|
192
|
+
int32_t ref1 = compute_reference_magnitude(freq_one, (int)clock_rate);
|
|
193
|
+
det->threshold = (int32_t)(THRESHOLD_RATIO * (float)((ref0 + ref1) / 2));
|
|
194
|
+
}
|
|
195
|
+
#else
|
|
196
|
+
{
|
|
197
|
+
float ref0 = compute_reference_magnitude(freq_zero, (int)clock_rate);
|
|
198
|
+
float ref1 = compute_reference_magnitude(freq_one, (int)clock_rate);
|
|
199
|
+
det->threshold = THRESHOLD_RATIO * ((ref0 + ref1) / 2.0f);
|
|
200
|
+
}
|
|
201
|
+
#endif
|
|
128
202
|
|
|
129
|
-
*p_port = &det->base;
|
|
203
|
+
*p_port = &det->base;
|
|
130
204
|
return PJ_SUCCESS;
|
|
131
205
|
}
|
|
132
206
|
|
|
133
207
|
static pj_status_t bfsk_det2_put_frame(pjmedia_port *this_port,
|
|
134
|
-
|
|
208
|
+
pjmedia_frame *frame)
|
|
135
209
|
{
|
|
136
|
-
|
|
137
|
-
if(frame->type != PJMEDIA_FRAME_TYPE_AUDIO) return PJ_SUCCESS;
|
|
210
|
+
if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) return PJ_SUCCESS;
|
|
138
211
|
|
|
139
212
|
struct bfsk_det2 *dport = (struct bfsk_det2*) this_port;
|
|
140
213
|
|
|
141
|
-
|
|
142
|
-
int
|
|
143
|
-
|
|
144
|
-
//printf("p=%x, size=%i clock_rate=%i bits_per_sample=%i\n", frame->buf, size, dport->clock_rate, bps);
|
|
145
|
-
|
|
146
|
-
int16_t * samples = (int16_t*)frame->buf;
|
|
147
|
-
int16_t * num_samples = frame->size/2;
|
|
214
|
+
int16_t *samples = (int16_t*)frame->buf;
|
|
215
|
+
int num_samples = (int)(frame->size / sizeof(int16_t));
|
|
216
|
+
int pos;
|
|
148
217
|
|
|
149
218
|
/*
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
219
|
+
* Feed samples into the Goertzel filters one SAMPLES_PER_BLOCK chunk at
|
|
220
|
+
* a time. Any trailing samples that don't fill a complete block are
|
|
221
|
+
* discarded — this matches how spandsp's own tone detectors behave and
|
|
222
|
+
* avoids carrying partial state across frames.
|
|
223
|
+
*/
|
|
224
|
+
for (pos = 0; pos + SAMPLES_PER_BLOCK <= num_samples; pos += SAMPLES_PER_BLOCK) {
|
|
225
|
+
int j;
|
|
226
|
+
for (j = 0; j < SAMPLES_PER_BLOCK; j++) {
|
|
227
|
+
goertzel_sample(&dport->state_zero, samples[pos + j]);
|
|
228
|
+
goertzel_sample(&dport->state_one, samples[pos + j]);
|
|
229
|
+
}
|
|
157
230
|
|
|
158
231
|
#if defined(SPANDSP_USE_FIXED_POINT)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
int16_t xamp;
|
|
162
|
-
float famp;
|
|
232
|
+
int32_t zero_power = goertzel_result(&dport->state_zero);
|
|
233
|
+
int32_t one_power = goertzel_result(&dport->state_one);
|
|
163
234
|
#else
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
float xamp;
|
|
167
|
-
float famp;
|
|
235
|
+
float zero_power = goertzel_result(&dport->state_zero);
|
|
236
|
+
float one_power = goertzel_result(&dport->state_one);
|
|
168
237
|
#endif
|
|
169
238
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
239
|
+
/* goertzel_result() calls goertzel_reset() internally, so the
|
|
240
|
+
* state is ready for the next block without further action. */
|
|
241
|
+
|
|
242
|
+
/*
|
|
243
|
+
* FSK: at most one frequency should be active at a time.
|
|
244
|
+
* When both exceed the threshold pick the stronger one.
|
|
245
|
+
*/
|
|
246
|
+
int zero, one;
|
|
247
|
+
if (zero_power >= dport->threshold || one_power >= dport->threshold) {
|
|
248
|
+
if (zero_power >= one_power) {
|
|
249
|
+
zero = 1;
|
|
250
|
+
one = 0;
|
|
251
|
+
} else {
|
|
252
|
+
zero = 0;
|
|
253
|
+
one = 1;
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
zero = 0;
|
|
257
|
+
one = 0;
|
|
258
|
+
}
|
|
176
259
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
dport->energy += xamp*xamp;
|
|
188
|
-
#endif
|
|
189
|
-
goertzel_samplex(&dport->goertzel_zero, xamp);
|
|
190
|
-
goertzel_samplex(&dport->goertzel_one, xamp);
|
|
260
|
+
TRACE_((THIS_FILE, "zero_power=%f one_power=%f threshold=%f zero=%d one=%d",
|
|
261
|
+
(double)zero_power, (double)one_power, (double)dport->threshold,
|
|
262
|
+
zero, one));
|
|
263
|
+
|
|
264
|
+
/* Report bit=0 on trailing edge of zero tone. */
|
|
265
|
+
if (dport->zero_in_progress && !zero) {
|
|
266
|
+
dport->bfsk_cb((pjmedia_port*)dport, dport->bfsk_cb_user_data, 0);
|
|
267
|
+
dport->zero_in_progress = 0;
|
|
268
|
+
} else {
|
|
269
|
+
dport->zero_in_progress = zero;
|
|
191
270
|
}
|
|
192
|
-
}
|
|
193
|
-
int32_t zero_power = goertzel_result(&dport->goertzel_zero);
|
|
194
|
-
int32_t one_power = goertzel_result(&dport->goertzel_one);
|
|
195
|
-
|
|
196
|
-
int zero = zero_power > dport->threshold;
|
|
197
|
-
int one = one_power > dport->threshold;
|
|
198
|
-
|
|
199
|
-
printf("zero_power=%f zero_in_progress=%i zero=%i threshold=%f\n", zero_power, dport->zero_in_progress, zero, dport->threshold);
|
|
200
|
-
printf(" one_power=%f one_in_progress=%i one=%i threshold=%f\n", one_power, dport->one_in_progress, one, dport->threshold);
|
|
201
|
-
|
|
202
|
-
// Check for zero signal extinction
|
|
203
|
-
if(dport->zero_in_progress && zero == 0) {
|
|
204
|
-
printf("notifying bit=0\n");
|
|
205
|
-
dport->bfsk_cb((pjmedia_port*)dport, dport->bfsk_cb_user_data, 0);
|
|
206
|
-
dport->zero_in_progress = 0;
|
|
207
|
-
} else {
|
|
208
|
-
dport->zero_in_progress = zero;
|
|
209
|
-
}
|
|
210
271
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
272
|
+
/* Report bit=1 on trailing edge of one tone. */
|
|
273
|
+
if (dport->one_in_progress && !one) {
|
|
274
|
+
dport->bfsk_cb((pjmedia_port*)dport, dport->bfsk_cb_user_data, 1);
|
|
275
|
+
dport->one_in_progress = 0;
|
|
276
|
+
} else {
|
|
277
|
+
dport->one_in_progress = one;
|
|
278
|
+
}
|
|
218
279
|
}
|
|
219
280
|
|
|
220
281
|
return PJ_SUCCESS;
|
|
@@ -222,5 +283,6 @@ static pj_status_t bfsk_det2_put_frame(pjmedia_port *this_port,
|
|
|
222
283
|
|
|
223
284
|
static pj_status_t bfsk_det2_on_destroy(pjmedia_port *this_port)
|
|
224
285
|
{
|
|
286
|
+
PJ_UNUSED_ARG(this_port);
|
|
225
287
|
return PJ_SUCCESS;
|
|
226
288
|
}
|