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
0. pipe record to playback example working with dummy osx, windows
0. pipe record to playback example working with ALSA linux
0. expose prebuf
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. look at microphone example and determine if fewer memcpys can be done
with the audio data
- pulseaudio has peek() drop() which sucks, but what if libsoundio lets you
specify how much to peek() and if you don't peek all of it, save the
unused to a buffer for you.
- test that sending the frame count to begin read works with PulseAudio
0. add len arguments to APIs that have char *
0. Test in an app that needs to synchronize video to test the
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) {
char *exe = argv[0];
enum SoundIoBackend backend = SoundIoBackendNone;
@ -109,6 +114,7 @@ int main(int argc, char **argv) {
struct SoundIoOutStream *outstream = soundio_outstream_create(device);
outstream->format = SoundIoFormatFloat32NE;
outstream->write_callback = write_callback;
outstream->underflow_callback = underflow_callback;
if ((err = soundio_outstream_open(outstream)))
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->buffer_duration / 2.0, device->period_duration_max);
}
if (outstream->prebuf_duration == -1.0)
outstream->prebuf_duration = outstream->buffer_duration;
SoundIoOutStreamAlsa *osa = create<SoundIoOutStreamAlsa>();
if (!osa) {
@ -1144,7 +1146,8 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
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);
return SoundIoErrorOpeningDevice;
}

View file

@ -21,6 +21,10 @@ struct SoundIoOutStreamDummy {
atomic_flag abort_flag;
int buffer_frame_count;
struct SoundIoRingBuffer ring_buffer;
int prebuf_frame_count;
int prebuf_frames_left;
long frames_consumed;
double playback_start_time;
SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS];
};
@ -46,35 +50,47 @@ static void playback_thread_run(void *arg) {
SoundIoOutStream *outstream = &os->pub;
SoundIoOutStreamDummy *osd = (SoundIoOutStreamDummy *)os->backend_data;
long frames_consumed = 0;
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()) {
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 next_period = start_time +
ceil(time_passed / outstream->period_duration) * outstream->period_duration;
double relative_time = next_period - now;
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_frames = free_bytes / instream->bytes_per_frame;
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;
soundio_ring_buffer_advance_write_ptr(&isd->ring_buffer, byte_count);
frames_consumed += write_count;
if (frames_left > 0)
isd->hole_size += frames_left;
if (write_count > 0)
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;
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();
if (!osd->cond) {
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) {
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);
}
@ -265,6 +280,13 @@ static int outstream_end_write_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate
SoundIoOutStream *outstream = &os->pub;
int byte_count = frame_count * outstream->bytes_per_frame;
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;
}

View file

@ -696,14 +696,20 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
ospa->buffer_attr.minreq = 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;
if (outstream->buffer_duration > 0.0) {
int buffer_length = 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.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);
@ -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);
outstream->buffer_duration = (attr->maxlength /
(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);
@ -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 buffer_length = 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;
}

View file

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

View file

@ -327,6 +327,11 @@ struct SoundIoOutStream {
// sets `PA_STREAM_ADJUST_LATENCY` and is the value used for `fragsize`.
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.
void *userdata;
// In this callback, you call `soundio_outstream_begin_write` and