Skip to content

Commit 20a682c

Browse files
committed
Use total_frames to check for ringbuffer over-/underflow
1 parent 6ad3033 commit 20a682c

File tree

4 files changed

+53
-27
lines changed

4 files changed

+53
-27
lines changed

examples/fetch_stats.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,17 @@
1414

1515

1616
def print_stats(obj):
17-
print(' blocks:', obj.stats.blocks)
18-
print(' overflows:', obj.stats.input_overflows)
17+
print(' blocks:', obj.stats.blocks)
18+
print(' overflows:', obj.stats.input_overflows)
19+
20+
21+
def print_action(action):
22+
print(' type:', next(
23+
k for k, v in vars(rtmixer).items() if v == action.type))
24+
print(' requested time:', action.requested_time)
25+
print(' actual time:', action.actual_time)
26+
print(' total frames:', action.total_frames)
27+
print(' done frames:', action.done_frames)
1928

2029

2130
stream = rtmixer.Recorder(
@@ -31,23 +40,31 @@ def print_stats(obj):
3140
assert stream.stats.input_overflows == 0
3241

3342
with stream:
34-
print('waiting a few seconds')
43+
print('waiting a few seconds ...')
3544
sd.sleep(3 * 1000)
3645
print('checking stats:')
3746
action = stream.fetch_and_reset_stats()
3847
stream.wait(action)
3948
print_stats(action)
40-
print('starting recording to buffer')
49+
print('starting recording to buffer ...')
4150
action = stream.record_buffer(buffer, channels=channels)
42-
# TODO: check if recording started successfully
51+
# TODO: check if recording started successfully?
4352
stream.wait(action)
53+
print('result:')
54+
print_action(action)
4455
print('stats from finished recording:')
4556
print_stats(action)
46-
print('starting recording to ringbuffer')
57+
print('starting recording to ringbuffer ...')
4758
action = stream.record_ringbuffer(ringbuffer, channels=channels)
48-
# TODO: check if recording started successfully
49-
# TODO: record ringbuffer, but don't read from it (detect overflow)
50-
sd.sleep(3 * 1000)
59+
# TODO: check if recording started successfully?
60+
# NB: We are writing to the ringbuffer, but we are not reading from it,
61+
# which will lead to an overflow
62+
print('waiting for ring buffer to fill up ...')
63+
stream.wait(action)
64+
print('result:')
65+
print_action(action)
66+
print('recording was stopped because of ringbuffer overflow:',
67+
action.done_frames != action.total_frames)
5168
print('checking stats:')
5269
action = stream.fetch_and_reset_stats()
5370
stream.wait(action)

examples/sampler.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,15 @@ def on_key_release(self, event):
7979
# TODO: fade out (both recording and playback)?
8080
assert sample.action is not None
8181
if sample.action.type == rtmixer.RECORD_RINGBUFFER:
82-
# TODO: check for errors/xruns? check for rinbuffer overflow?
8382
# Stop recording
83+
sample.keep_alive = sample.action
8484
sample.action = self.stream.cancel(sample.action)
8585
# A CANCEL action is returned which is checked by poll_ringbuffer()
8686
elif sample.action.type == rtmixer.PLAY_BUFFER:
8787
# TODO: check for errors/xruns?
8888
# Stop playback (if still running)
8989
if sample.action in self.stream.actions:
90+
sample.keep_alive = sample.action
9091
sample.action = self.stream.cancel(sample.action)
9192
# TODO: do something with sample.action?
9293
elif sample.action.type == rtmixer.CANCEL:
@@ -99,17 +100,22 @@ def poll_ringbuffer(self, sample):
99100
assert sample.action is not None
100101
assert sample.action.type in (rtmixer.RECORD_RINGBUFFER,
101102
rtmixer.CANCEL)
102-
# TODO: check for errors? is everything still working OK?
103-
# TODO: check for xruns?
104-
chunk = sample.ringbuffer.read()
105-
if chunk:
106-
sample.buffer.extend(chunk)
107-
108103
if sample.action not in self.stream.actions:
109104
# Recording is finished
110-
# TODO: check for errors in CANCEL action?
111105
self.rec_counter -= 1
106+
if sample.action.type == rtmixer.CANCEL:
107+
# TODO: check for errors in CANCEL action?
108+
action = sample.action.action
109+
else:
110+
action = sample.action
111+
assert action.type == rtmixer.RECORD_RINGBUFFER
112+
if action.done_frames != action.total_frames:
113+
print('error while recording (ringbuffer too short?)')
114+
# TODO: check for xruns?
115+
# NB: We make sure that the ringbuffer is emptied after recording:
116+
sample.buffer.extend(sample.ringbuffer.read())
112117
else:
118+
sample.buffer.extend(sample.ringbuffer.read())
113119
# Set polling rate based on input latency (which may change!):
114120
self.after(int(self.stream.latency[0] * 1000),
115121
self.poll_ringbuffer, sample)

src/rtmixer.c

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ void remove_action(struct action** addr, const struct state* state)
2828
, &action, 1);
2929
if (written != 1)
3030
{
31-
// TODO: do something? Stop callback? Log error (in "state")?
31+
// TODO: Stop callback! Unrecoverable error! Log error (in "state")?
3232
printf("result queue is full\n");
3333
}
3434
}
@@ -37,6 +37,8 @@ void get_stats(PaStreamCallbackFlags flags, struct stats* stats)
3737
{
3838
stats->blocks++;
3939

40+
// TODO: store min/max block size?
41+
4042
if (flags & paInputUnderflow) { stats->input_underflows++; }
4143
if (flags & paInputOverflow) { stats->input_overflows++; }
4244
if (flags & paOutputUnderflow) { stats->output_underflows++; }
@@ -116,6 +118,9 @@ int callback(const void* input, void* output, frame_t frameCount
116118

117119
get_new_actions(state);
118120

121+
// TODO: store min/max available space in result_q?
122+
// TODO: use worst case from before/after the "while" loop?
123+
119124
struct action** actionaddr = &(state->actions);
120125
while (*actionaddr)
121126
{
@@ -185,7 +190,8 @@ int callback(const void* input, void* output, frame_t frameCount
185190
{
186191
// Removal is scheduled before playback/recording begins
187192

188-
// TODO: save some status information?
193+
// TODO: save some more status information?
194+
delinquent->total_frames = 0;
189195
remove_action(i, state);
190196
break;
191197
}
@@ -199,9 +205,9 @@ int callback(const void* input, void* output, frame_t frameCount
199205
}
200206
}
201207

202-
if (delinquent->total_frames + delinquent_offset > offset)
208+
if (delinquent->total_frames == ULONG_MAX
209+
|| delinquent->total_frames + delinquent_offset > offset)
203210
{
204-
CALLBACK_ASSERT(offset >= delinquent_offset);
205211
delinquent->total_frames = offset - delinquent_offset;
206212
}
207213
else
@@ -382,9 +388,6 @@ int callback(const void* input, void* output, frame_t frameCount
382388
if (totalsize < (ring_buffer_size_t)frames)
383389
{
384390
// Ring buffer is empty or full
385-
386-
// TODO: store some information in action?
387-
388391
remove_action(actionaddr, state);
389392
continue;
390393
}

src/rtmixer.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,16 @@ struct action
3535
bool allow_belated;
3636
const PaTime requested_time;
3737
PaTime actual_time;
38-
struct action* next;
38+
struct action* next; // Used to create singly linked list of actions
3939
union {
4040
float* const buffer;
4141
struct PaUtilRingBuffer* const ringbuffer;
4242
struct action* const action; // Used in CANCEL
4343
};
4444
frame_t total_frames;
4545
frame_t done_frames;
46-
// TODO: something to store the result of the action?
4746
struct stats stats;
48-
// TODO: queue usage: store smallest available write/read size?
47+
// TODO: ringbuffer usage: store smallest available write/read size?
4948
const frame_t channels; // Size of the following array
5049
const frame_t mapping[]; // "flexible array member"
5150
};
@@ -59,6 +58,7 @@ struct state
5958
struct PaUtilRingBuffer* const result_q; // Q for results and cmd disposal
6059
struct action* actions; // Singly linked list of actions
6160
struct stats stats;
61+
// TODO: result_q usage?
6262
};
6363

6464
int callback(const void* input, void* output, frame_t frameCount

0 commit comments

Comments
 (0)