dummy: implement prebuffering

This commit is contained in:
Andrew Kelley 2015-07-23 16:49:44 -07:00
parent 69764e1afa
commit 511fcafc3b
7 changed files with 78 additions and 39 deletions

View file

@ -231,7 +231,6 @@ view `coverage/index.html` in a browser.
## Roadmap ## Roadmap
0. pipe record to playback example working with dummy osx, windows
0. pipe record to playback example working with ALSA linux 0. pipe record to playback example working with ALSA linux
0. expose prebuf 0. expose prebuf
0. why does pulseaudio microphone use up all the CPU? 0. why does pulseaudio microphone use up all the CPU?
@ -248,9 +247,7 @@ view `coverage/index.html` in a browser.
0. Integrate into libgroove and test with Groove Basin 0. Integrate into libgroove and test with Groove Basin
0. look at microphone example and determine if fewer memcpys can be done 0. look at microphone example and determine if fewer memcpys can be done
with the audio data with the audio data
- pulseaudio has peek() drop() which sucks, but what if libsoundio lets you - test that sending the frame count to begin read works with PulseAudio
specify how much to peek() and if you don't peek all of it, save the
unused to a buffer for you.
0. add len arguments to APIs that have char * 0. add len arguments to APIs that have char *
0. Test in an app that needs to synchronize video to test the 0. Test in an app that needs to synchronize video to test the
latency/synchronization API. latency/synchronization API.

View file

@ -70,6 +70,11 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra
} }
} }
static void underflow_callback(struct SoundIoOutStream *outstream) {
static int count = 0;
fprintf(stderr, "underflow %d\n", count++);
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
char *exe = argv[0]; char *exe = argv[0];
enum SoundIoBackend backend = SoundIoBackendNone; enum SoundIoBackend backend = SoundIoBackendNone;
@ -109,6 +114,7 @@ int main(int argc, char **argv) {
struct SoundIoOutStream *outstream = soundio_outstream_create(device); struct SoundIoOutStream *outstream = soundio_outstream_create(device);
outstream->format = SoundIoFormatFloat32NE; outstream->format = SoundIoFormatFloat32NE;
outstream->write_callback = write_callback; outstream->write_callback = write_callback;
outstream->underflow_callback = underflow_callback;
if ((err = soundio_outstream_open(outstream))) if ((err = soundio_outstream_open(outstream)))
panic("unable to open device: %s", soundio_strerror(err)); panic("unable to open device: %s", soundio_strerror(err));

View file

@ -1029,6 +1029,8 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
outstream->period_duration = clamp(device->period_duration_min, outstream->period_duration = clamp(device->period_duration_min,
outstream->buffer_duration / 2.0, device->period_duration_max); outstream->buffer_duration / 2.0, device->period_duration_max);
} }
if (outstream->prebuf_duration == -1.0)
outstream->prebuf_duration = outstream->buffer_duration;
SoundIoOutStreamAlsa *osa = create<SoundIoOutStreamAlsa>(); SoundIoOutStreamAlsa *osa = create<SoundIoOutStreamAlsa>();
if (!osa) { if (!osa) {
@ -1144,7 +1146,8 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
if ((err = snd_pcm_sw_params_set_start_threshold(osa->handle, swparams, buffer_size_frames)) < 0) { snd_pcm_uframes_t prebuf_frames = ceil(outstream->prebuf_duration * (double)outstream->sample_rate);
if ((err = snd_pcm_sw_params_set_start_threshold(osa->handle, swparams, prebuf_frames)) < 0) {
outstream_destroy_alsa(si, os); outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }

View file

@ -21,6 +21,10 @@ struct SoundIoOutStreamDummy {
atomic_flag abort_flag; atomic_flag abort_flag;
int buffer_frame_count; int buffer_frame_count;
struct SoundIoRingBuffer ring_buffer; struct SoundIoRingBuffer ring_buffer;
int prebuf_frame_count;
int prebuf_frames_left;
long frames_consumed;
double playback_start_time;
SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS];
}; };
@ -46,35 +50,47 @@ static void playback_thread_run(void *arg) {
SoundIoOutStream *outstream = &os->pub; SoundIoOutStream *outstream = &os->pub;
SoundIoOutStreamDummy *osd = (SoundIoOutStreamDummy *)os->backend_data; SoundIoOutStreamDummy *osd = (SoundIoOutStreamDummy *)os->backend_data;
long frames_consumed = 0;
double start_time = soundio_os_get_time(); double start_time = soundio_os_get_time();
osd->playback_start_time = start_time;
osd->frames_consumed = 0;
int fill_bytes = soundio_ring_buffer_fill_count(&osd->ring_buffer);
int free_bytes = soundio_ring_buffer_capacity(&osd->ring_buffer) - fill_bytes;
int fill_frames = fill_bytes / outstream->bytes_per_frame;
int free_frames = free_bytes / outstream->bytes_per_frame;
osd->prebuf_frames_left = osd->prebuf_frame_count - fill_frames;
outstream->write_callback(outstream, free_frames);
while (osd->abort_flag.test_and_set()) { while (osd->abort_flag.test_and_set()) {
double now = soundio_os_get_time(); double now = soundio_os_get_time();
double total_time = now - start_time;
long total_frames = total_time * outstream->sample_rate;
int frames_to_kill = total_frames - frames_consumed;
int fill_count = soundio_ring_buffer_fill_count(&osd->ring_buffer);
int frames_in_buffer = fill_count / outstream->bytes_per_frame;
int read_count = min(frames_to_kill, frames_in_buffer);
int frames_left = frames_to_kill - read_count;
int byte_count = read_count * outstream->bytes_per_frame;
soundio_ring_buffer_advance_read_ptr(&osd->ring_buffer, byte_count);
frames_consumed += read_count;
if (frames_left > 0) {
outstream->underflow_callback(outstream);
// TODO delete this and simulate prebuf
soundio_ring_buffer_advance_write_ptr(&osd->ring_buffer,
soundio_ring_buffer_free_count(&osd->ring_buffer));
} else if (read_count > 0) {
outstream->write_callback(outstream, read_count);
}
now = soundio_os_get_time();
double time_passed = now - start_time; double time_passed = now - start_time;
double next_period = start_time + double next_period = start_time +
ceil(time_passed / outstream->period_duration) * outstream->period_duration; ceil(time_passed / outstream->period_duration) * outstream->period_duration;
double relative_time = next_period - now; double relative_time = next_period - now;
soundio_os_cond_timed_wait(osd->cond, nullptr, relative_time); soundio_os_cond_timed_wait(osd->cond, nullptr, relative_time);
int fill_bytes = soundio_ring_buffer_fill_count(&osd->ring_buffer);
int free_bytes = soundio_ring_buffer_capacity(&osd->ring_buffer) - fill_bytes;
int fill_frames = fill_bytes / outstream->bytes_per_frame;
int free_frames = free_bytes / outstream->bytes_per_frame;
if (osd->prebuf_frames_left > 0) {
outstream->write_callback(outstream, free_frames);
continue;
}
double total_time = soundio_os_get_time() - osd->playback_start_time;
long total_frames = total_time * outstream->sample_rate;
int frames_to_kill = total_frames - osd->frames_consumed;
int read_count = min(frames_to_kill, fill_frames);
int byte_count = read_count * outstream->bytes_per_frame;
soundio_ring_buffer_advance_read_ptr(&osd->ring_buffer, byte_count);
osd->frames_consumed += read_count;
if (frames_to_kill > read_count) {
osd->prebuf_frames_left = osd->prebuf_frame_count;
outstream->underflow_callback(outstream);
} else if (free_frames > 0) {
outstream->write_callback(outstream, free_frames);
}
} }
} }
@ -93,12 +109,11 @@ static void capture_thread_run(void *arg) {
int free_bytes = soundio_ring_buffer_free_count(&isd->ring_buffer); int free_bytes = soundio_ring_buffer_free_count(&isd->ring_buffer);
int free_frames = free_bytes / instream->bytes_per_frame; int free_frames = free_bytes / instream->bytes_per_frame;
int write_count = min(frames_to_kill, free_frames); int write_count = min(frames_to_kill, free_frames);
int frames_left = frames_to_kill - write_count; int frames_left = max(frames_to_kill - write_count, 0);
int byte_count = write_count * instream->bytes_per_frame; int byte_count = write_count * instream->bytes_per_frame;
soundio_ring_buffer_advance_write_ptr(&isd->ring_buffer, byte_count); soundio_ring_buffer_advance_write_ptr(&isd->ring_buffer, byte_count);
frames_consumed += write_count; frames_consumed += write_count;
if (frames_left > 0)
isd->hole_size += frames_left; isd->hole_size += frames_left;
if (write_count > 0) if (write_count > 0)
instream->read_callback(instream, write_count); instream->read_callback(instream, write_count);
@ -194,6 +209,11 @@ static int outstream_open_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
osd->buffer_frame_count = actual_capacity / outstream->bytes_per_frame; osd->buffer_frame_count = actual_capacity / outstream->bytes_per_frame;
outstream->buffer_duration = osd->buffer_frame_count / (double) outstream->sample_rate; outstream->buffer_duration = osd->buffer_frame_count / (double) outstream->sample_rate;
if (outstream->prebuf_duration == -1.0)
outstream->prebuf_duration = outstream->buffer_duration;
outstream->prebuf_duration = min(outstream->prebuf_duration, outstream->buffer_duration);
osd->prebuf_frame_count = ceil(outstream->prebuf_duration * (double) outstream->sample_rate);
osd->cond = soundio_os_cond_create(); osd->cond = soundio_os_cond_create();
if (!osd->cond) { if (!osd->cond) {
outstream_destroy_dummy(si, os); outstream_destroy_dummy(si, os);
@ -225,11 +245,6 @@ static int outstream_pause_dummy(struct SoundIoPrivate *si, struct SoundIoOutStr
} }
static int outstream_start_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { static int outstream_start_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
SoundIoOutStreamDummy *osd = (SoundIoOutStreamDummy *)os->backend_data;
// TODO delete this and simulate prebuf
soundio_ring_buffer_advance_write_ptr(&osd->ring_buffer, soundio_ring_buffer_free_count(&osd->ring_buffer));
return outstream_pause_dummy(si, os, false); return outstream_pause_dummy(si, os, false);
} }
@ -265,6 +280,13 @@ static int outstream_end_write_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate
SoundIoOutStream *outstream = &os->pub; SoundIoOutStream *outstream = &os->pub;
int byte_count = frame_count * outstream->bytes_per_frame; int byte_count = frame_count * outstream->bytes_per_frame;
soundio_ring_buffer_advance_write_ptr(&osd->ring_buffer, byte_count); soundio_ring_buffer_advance_write_ptr(&osd->ring_buffer, byte_count);
if (osd->prebuf_frames_left > 0) {
osd->prebuf_frames_left -= frame_count;
if (osd->prebuf_frames_left <= 0) {
osd->playback_start_time = soundio_os_get_time();
osd->frames_consumed = 0;
}
}
return 0; return 0;
} }

View file

@ -696,14 +696,20 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
ospa->buffer_attr.minreq = UINT32_MAX; ospa->buffer_attr.minreq = UINT32_MAX;
ospa->buffer_attr.fragsize = UINT32_MAX; ospa->buffer_attr.fragsize = UINT32_MAX;
if (outstream->buffer_duration > 0.0) {
int bytes_per_second = outstream->bytes_per_frame * outstream->sample_rate; int bytes_per_second = outstream->bytes_per_frame * outstream->sample_rate;
if (outstream->buffer_duration > 0.0) {
int buffer_length = outstream->bytes_per_frame * int buffer_length = outstream->bytes_per_frame *
ceil(outstream->buffer_duration * bytes_per_second / (double)outstream->bytes_per_frame); ceil(outstream->buffer_duration * bytes_per_second / (double)outstream->bytes_per_frame);
ospa->buffer_attr.maxlength = buffer_length; ospa->buffer_attr.maxlength = buffer_length;
ospa->buffer_attr.tlength = buffer_length; ospa->buffer_attr.tlength = buffer_length;
} }
if (outstream->prebuf_duration >= 0.0) {
int prebuf_length = outstream->bytes_per_frame *
ceil(outstream->prebuf_duration * bytes_per_second / (double)outstream->bytes_per_frame);
ospa->buffer_attr.prebuf = prebuf_length;
}
pa_threaded_mainloop_unlock(sipa->main_loop); pa_threaded_mainloop_unlock(sipa->main_loop);
@ -733,6 +739,8 @@ static int outstream_start_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
const pa_buffer_attr *attr = pa_stream_get_buffer_attr(ospa->stream); const pa_buffer_attr *attr = pa_stream_get_buffer_attr(ospa->stream);
outstream->buffer_duration = (attr->maxlength / outstream->buffer_duration = (attr->maxlength /
(double)outstream->bytes_per_frame) / (double)outstream->sample_rate; (double)outstream->bytes_per_frame) / (double)outstream->sample_rate;
outstream->prebuf_duration = (attr->prebuf /
(double)outstream->bytes_per_frame) / (double)outstream->sample_rate;
pa_threaded_mainloop_unlock(sipa->main_loop); pa_threaded_mainloop_unlock(sipa->main_loop);
@ -903,10 +911,6 @@ static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
int bytes_per_second = instream->bytes_per_frame * instream->sample_rate; int bytes_per_second = instream->bytes_per_frame * instream->sample_rate;
int buffer_length = instream->bytes_per_frame * int buffer_length = instream->bytes_per_frame *
ceil(instream->period_duration * bytes_per_second / (double)instream->bytes_per_frame); ceil(instream->period_duration * bytes_per_second / (double)instream->bytes_per_frame);
ispa->buffer_attr.maxlength = UINT32_MAX;
ispa->buffer_attr.tlength = UINT32_MAX;
ispa->buffer_attr.prebuf = UINT32_MAX;
ispa->buffer_attr.minreq = UINT32_MAX;
ispa->buffer_attr.fragsize = buffer_length; ispa->buffer_attr.fragsize = buffer_length;
} }

View file

@ -351,6 +351,8 @@ struct SoundIoOutStream *soundio_outstream_create(struct SoundIoDevice *device)
outstream->error_callback = default_outstream_error_callback; outstream->error_callback = default_outstream_error_callback;
outstream->underflow_callback = default_underflow_callback; outstream->underflow_callback = default_underflow_callback;
outstream->prebuf_duration = -1.0;
return outstream; return outstream;
} }

View file

@ -327,6 +327,11 @@ struct SoundIoOutStream {
// sets `PA_STREAM_ADJUST_LATENCY` and is the value used for `fragsize`. // sets `PA_STREAM_ADJUST_LATENCY` and is the value used for `fragsize`.
double period_duration; double period_duration;
// How many seconds need to be in the buffer before playback will commence.
// If a buffer underflow occurs, this prebuffering will be again enabled.
// This value defaults to being the same as `buffer_duration`.
double prebuf_duration;
// Defaults to NULL. Put whatever you want here. // Defaults to NULL. Put whatever you want here.
void *userdata; void *userdata;
// In this callback, you call `soundio_outstream_begin_write` and // In this callback, you call `soundio_outstream_begin_write` and