mirror of
https://github.com/Ryujinx/libsoundio.git
synced 2024-12-23 07:25:33 +00:00
dummy: implement prebuffering
This commit is contained in:
parent
69764e1afa
commit
511fcafc3b
|
@ -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.
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue