55
66package libsio
77
8- // #cgo CFLAGS: -std=c11
8+ // #cgo CFLAGS: -std=c11 -DLIBSIO
99// #include "cb.h"
1010import "C"
1111import (
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.
128134func 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
305321func (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.
322337func (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.
0 commit comments