Skip to content

Commit b055012

Browse files
authored
Merge pull request #25 from wsc1/master
cb cross package, deadlines
2 parents 021b1b1 + cfe16ae commit b055012

File tree

9 files changed

+295
-90
lines changed

9 files changed

+295
-90
lines changed

libsio/cb.c

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212

1313
#include "cb.h"
1414

15+
// forward decl.
16+
static void toGoAndBack(Cb *cb);
17+
static void inCb(Cb *cb, void *in, int nf);
18+
static void outCb(Cb *cb, void *out, int *nf);
19+
static void duplexCb(Cb *cb, void *out, int *nf, void *in, int isz);
1520

1621
Cb * newCb(int bufSz) {
1722
Cb * cb = (Cb*) malloc(sizeof(Cb));
@@ -27,32 +32,17 @@ Cb * newCb(int bufSz) {
2732
cb->time.tv_sec = 0;
2833
cb->time.tv_nsec = 1000;
2934
cb->inGo = 0;
30-
fprintf(stderr, "cb c addr %p inGo c addr on new: %p\n", cb, &cb->inGo);
35+
cb->inCb = inCb;
36+
cb->outCb = outCb;
37+
cb->duplexCb = duplexCb;
3138
return cb;
3239
}
3340

3441
void freeCb(Cb *cb) {
3542
free(cb);
3643
}
3744

38-
void * getIn(Cb *cb) {
39-
return cb->in;
40-
}
41-
42-
int getInF(Cb *cb) {
43-
return cb->inF;
44-
}
4545

46-
void * getOut(Cb *cb) {
47-
return cb->out;
48-
}
49-
50-
int getOutF(Cb *cb) {
51-
return cb->outF;
52-
}
53-
54-
// forward decl.
55-
static void toGoAndBack(Cb *cb);
5646

5747
/*
5848
From Ian Lance Taylor: in C11 stdatomic terms Go atomic.CompareAndSwap is like
@@ -70,20 +60,20 @@ control an airplane. But for audio, we'll give it a shot.
7060
* inCb is written to be called in an audio i/o callback API, as described
7161
* in cb.md, for capture.
7262
*/
73-
void inCb(Cb *cb, void *in, int nF) {
63+
static void inCb(Cb *cb, void *in, int nF) {
7464
cb->in = in;
7565
cb->inF = nF;
7666
toGoAndBack(cb);
7767
}
7868

79-
void outCb(Cb *cb, void *out, int *nF) {
69+
static void outCb(Cb *cb, void *out, int *nF) {
8070
cb->out = out;
8171
cb->outF = *nF;
8272
toGoAndBack(cb);
8373
*nF = cb->outF;
8474
}
8575

86-
void duplexCb(Cb *cb, void *out, int *onF, void *in, int inF) {
76+
static void duplexCb(Cb *cb, void *out, int *onF, void *in, int inF) {
8777
cb->in = in;
8878
cb->inF = inF;
8979
cb->out = out;

libsio/cb.go

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55

66
package libsio
77

8-
// #cgo CFLAGS: -std=c11
8+
// #cgo CFLAGS: -std=c11 -DLIBSIO
99
// #include "cb.h"
1010
import "C"
1111
import (
1212
"errors"
13+
"fmt"
1314
"io"
1415
"runtime"
1516
"sync/atomic"
@@ -123,6 +124,11 @@ type MissedDeadline struct {
123124
OffBy time.Duration
124125
}
125126

127+
// String for convenience.
128+
func (m *MissedDeadline) String() string {
129+
return fmt.Sprintf("missed frame %d by %s\n", m.Frame, m.OffBy)
130+
}
131+
126132
// NewCb creates a new Cb for the specified form (channels + sample rate)
127133
// sample codec and buffer size b in frames.
128134
func NewCb(v sound.Form, sco sample.Codec, b int) *Cb {
@@ -166,9 +172,9 @@ func (r *Cb) LastMissed() bool {
166172
// By default, this is equal to the buffer size. As a result,
167173
// if the minimum number of frames exchanged is less than the
168174
// buffer size, it should be set with SetMinCbFrames. A value
169-
// of 0 is acceptable if the value is unknown.
175+
// of 1 is acceptable if the value is unknown.
170176
//
171-
// This has an effect on CPU utilisation, as deadlines
177+
// This has an effect on CPU utilisation, as sleep deadlines
172178
// are calculated with respect to the minimum number of
173179
// frames that may be exchanged with the underlying API.
174180
// So if the minimum is significantly less than the buffer frame size,
@@ -202,6 +208,7 @@ func (r *Cb) Receive(d []float64) (int, error) {
202208
bps := r.sco.Bytes()
203209
var nf, onf int // frame counter and overlap frame count
204210
var cbBuf []byte // cast from C pointer callback data
211+
205212
for start < nF {
206213
if err := r.fromC(addr); err != nil {
207214
return 0, ErrCApiLost
@@ -215,8 +222,9 @@ func (r *Cb) Receive(d []float64) (int, error) {
215222
}
216223
return 0, io.EOF
217224
}
225+
218226
if start == 0 && r.frames == 0 {
219-
r.setOrgTime(-nf)
227+
r.setOrgTime(0)
220228
}
221229

222230
// in case the C cb doesn't align to the buffer size
@@ -243,6 +251,7 @@ func (r *Cb) Receive(d []float64) (int, error) {
243251
}
244252
start += nf
245253
r.frames += int64(nf)
254+
r.checkDeadline(r.frames + int64(len(r.over)))
246255
}
247256
r.il.Deinter(d[:start*nC])
248257
return start, nil
@@ -269,6 +278,7 @@ func (r *Cb) Send(d []float64) error {
269278
var nf int
270279
var cbBuf []byte
271280
for start < nF {
281+
r.checkDeadline(r.frames)
272282
if err := r.fromC(addr); err != nil {
273283
return ErrCApiLost
274284
}
@@ -295,12 +305,18 @@ func (r *Cb) Send(d []float64) error {
295305
if err := r.toC(addr); err != nil {
296306
return ErrCApiLost
297307
}
298-
start += nf
299308
r.frames += int64(nf)
309+
start += nf
300310
}
301311
return nil
302312
}
303313

314+
// C returns a pointer to the C.Cb which does the C callbacks for
315+
// r.
316+
func (r *Cb) C() unsafe.Pointer {
317+
return unsafe.Pointer(r.c)
318+
}
319+
304320
// TBD
305321
func (r *Cb) SendReceive(out, in []float64) (int, error) {
306322
return 0, nil
@@ -316,33 +332,42 @@ func (r *Cb) setOrgTime(nf int) {
316332
r.orgTime = time.Now().Add(d)
317333
}
318334

319-
// sleep only if underlying API is regular w.r.t.
320-
// supplied buffer sizes and buffer size is bigger
321-
// than OS latency jitter.
335+
// maybeSleep sleeps only if the minimum buffer size is bigger than estimated
336+
// OS latency jitter. see sleepSlack above.
322337
func (r *Cb) maybeSleep() {
323338
if r.frames == 0 {
324339
return
325340
}
326-
if r.minCbf == 0 {
327-
trg := r.orgTime.Add(time.Duration(int64(r.bsz)+r.frames) * r.frameDur)
328-
deadline := time.Until(trg)
329-
if deadline < 0 {
330-
r.misses = append(r.misses, MissedDeadline{r.frames, deadline})
331-
}
332-
return
333-
}
334-
trg := r.orgTime.Add(time.Duration(int64(r.minCbf)+r.frames) * r.frameDur)
341+
trg := r.orgTime.Add(time.Duration(int64(r.bsz)+r.frames) * r.frameDur)
335342
deadline := time.Until(trg)
336-
if deadline < 0 {
337-
r.misses = append(r.misses, MissedDeadline{r.frames, deadline})
338-
return
339-
}
340343
if deadline <= sleepSlack {
341344
return
342345
}
343346
time.Sleep(deadline - sleepSlack)
344347
}
345348

349+
// checkDeadline checks whether r has missed a deadline according to the sample rate
350+
// and the number of sample frames exchanged.
351+
//
352+
// checkDeadline only works after some samples have been exchanged with the underlying
353+
// API. It is called before exchanging subsequent samples to ensure that the exchange
354+
// occurs before the real time represented by previously exchanged samples.
355+
//
356+
// Since the underlying API may allow us be late from time to time like this, a missed
357+
// deadline does not necessarily imply that we have caused glitching. No missed
358+
// deadlines does imply the underlying API should have the opportunity to proceed
359+
// without glitching.
360+
func (r *Cb) checkDeadline(nf int64) {
361+
if r.frames == 0 {
362+
return
363+
}
364+
trg := r.orgTime.Add(time.Duration(nf+1) * r.frameDur)
365+
deadline := time.Until(trg)
366+
if deadline < 0 {
367+
r.misses = append(r.misses, MissedDeadline{nf, -deadline})
368+
}
369+
}
370+
346371
// ErrCApiLost can be returned if the thread running the C API
347372
// is somehow killed or the hardware causes the callbacks to
348373
// block.

libsio/cb.h

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,38 @@
88
#include <stdint.h>
99
#include <time.h>
1010

11+
// Cb holds data for exchanging information between a C or OS callback on a
12+
// dedicated thread foreign to the calling application and Go.
13+
//
1114
typedef struct Cb {
12-
int bufSz;
13-
_Atomic uint32_t inGo;
14-
void * in;
15-
int inF;
16-
void * out;
17-
int outF;
18-
struct timespec time;
15+
int bufSz; // in frames
16+
_Atomic uint32_t inGo; // whether we've passed of the buffer to go and stlll await it to finish
17+
void * in; // input buffer
18+
int inF; // input number of sample frames
19+
void * out; // output buffer
20+
int outF; // output number of sample frames
21+
struct timespec time; // for throttling a bit when there's a long wait.
22+
23+
24+
// function pointers below are used to give access to callbacks to
25+
// other Go packaages. They can can access a Cb * as a go unsafe.Pointer by importing
26+
// libsio, and then C code in that package can call these function pointers.
27+
// It doesn't seem possible to share C function definitions between packages by cgo alone,
28+
// so we came up with this mechanism. These fields are populated by newCb.
29+
void (*inCb)(struct Cb *cb, void *in, int nf);
30+
void (*outCb)(struct Cb *cb, void *out, int *nf);
31+
void (*duplexCb)(struct Cb *cb, void *out, int *onf, void *in, int inf);
1932
} Cb;
2033

34+
#ifdef LIBSIO
2135

36+
// only libsio can access these functions via cgo package "C".
2237
Cb * newCb(int bufSz);
2338

2439
void freeCb(Cb *cb);
25-
void * getIn(Cb *cb);
26-
int getInF(Cb *cb);
27-
void * getOut(Cb *cb);
28-
int getOutF(Cb *cb);
29-
void inCb(Cb *cb, void *in, int nf);
30-
void outCb(Cb *cb, void *out, int *nf);
31-
void duplexCb(Cb *cb, void *out, int *nf, void *in, int isz);
3240
void closeCb(Cb *cb);
3341

42+
#endif
43+
3444

3545
#endif

libsio/cb_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ func TestCbCapture(t *testing.T) {
2727
} else if n != b {
2828
t.Errorf("expected %d got %d\n", b, n)
2929
}
30+
if cb.LastMissed() {
31+
fmt.Printf("misses:\n")
32+
ms := cb.LastMisses()
33+
for i := range ms {
34+
fmt.Printf("\t%s\n", &ms[i])
35+
}
36+
}
3037
}
3138
}
3239

@@ -44,5 +51,12 @@ func TestCbPlay(t *testing.T) {
4451
if err != nil {
4552
t.Error(err)
4653
}
54+
if cb.LastMissed() {
55+
fmt.Printf("misses:\n")
56+
ms := cb.LastMisses()
57+
for i := range ms {
58+
fmt.Printf("\t%s\n", &ms[i])
59+
}
60+
}
4761
}
4862
}

libsio/runcb.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,13 @@ package libsio
5252
// sleepTime.tv_sec = 0;
5353
// sleepTime.tv_nsec = 500000L;
5454
// int of;
55+
// Cb *cb = rcb->cb;
5556
// for (int i = 0; i < rcb->n; i++) {
5657
// if (rcb->input) {
57-
// inCb(rcb->cb, rcb->buf, rcb->bf);
58+
// cb->inCb(cb, rcb->buf, rcb->bf);
5859
// } else {
5960
// of = rcb->bf;
60-
// outCb(rcb->cb, rcb->buf, &of);
61+
// cb->outCb(cb, rcb->buf, &of);
6162
// }
6263
// nanosleep(&sleepTime, NULL);
6364
// }

ports/darwin/aqs_darwin.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source
2+
// code is governed by a license that can be found in the License file.
3+
14
package darwin
25

36
import (

0 commit comments

Comments
 (0)