sip-lab 1.40.1 → 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.
@@ -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
- #include <math.h>
40
- #include <stdio.h>
41
- #include <stdlib.h>
42
- #include <stdbool.h>
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
- // Converted by ChatGPT from https://github.com/hackergrrl/goertzel/blob/master/index.js
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; // Store the precomputed magnitude
76
+ double targetMagnitude;
51
77
  } goertzel_t;
52
78
 
53
- #define CHUNK_SIZE 16
54
- #define THRESHOLD 0.9
55
-
56
- double precalcMagnitude(int freq, double rate) {
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
- double magSquared = real * real + imaginary * imaginary;
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
- float goertzel_mag(goertzel_t *g, void *samples) {
100
- // Allocate an array of floats to hold normalized samples
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; // Cast the void* to a byte array (uint8_t *)
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
- printf("%f,", float_samples[i]);
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 per = magSquared / g->targetMagnitude;
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
- pjmedia_frame *frame);
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 base;
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
- unsigned clock_rate,
164
- unsigned channel_count,
165
- unsigned samples_per_frame,
166
- unsigned bits_per_sample,
167
- void (*cb)(pjmedia_port*,
168
- void *user_data,
169
- int bit),
170
- void *user_data,
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
- pjmedia_port **p_port)
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
- samples_per_frame && bits_per_sample == 16 &&
182
- p_port != NULL, PJ_EINVAL);
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(pool != NULL, PJ_ENOMEM);
205
+ PJ_ASSERT_RETURN(det != NULL, PJ_ENOMEM);
188
206
 
189
207
  pjmedia_port_info_init(&det->base.info, &name, SIGNATURE, clock_rate,
190
- channel_count, bits_per_sample, samples_per_frame);
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(pool != NULL, PJ_ENOMEM);
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(pool != NULL, PJ_ENOMEM);
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, sample_rate);
215
- goertzel_det_init(det->goertzel_one, det->freq_one, sample_rate);
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
- pjmedia_frame *frame)
234
+ pjmedia_frame *frame)
223
235
  {
224
- //printf("bfsk_det put_frame\n");
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
- //printf("p=%x, size=%i clock_rate=%i bits_per_sample=%i\n", frame->buf, size, dport->clock_rate, bps);
233
-
234
- int16_t * samples = (int16_t*)frame->buf;
235
-
236
- /*
237
- printf("Buffer contents:\n");
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
- printf("zero_power=%f zero_in_progress=%i zero=%i threshold=%f\n", zero_power, dport->zero_in_progress, zero, THRESHOLD);
254
- printf(" one_power=%f one_in_progress=%i one=%i threshold=%f\n", one_power, dport->one_in_progress, one, THRESHOLD);
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
- // Check for zero signal extinction
258
- if(dport->zero_in_progress && zero == 0) {
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
- // Check for one signal extinction
267
- if(dport->one_in_progress && one == 0) {
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
  }