X-Git-Url: https://code.delx.au/pulseaudio/blobdiff_plain/638f9a561e8353ae9bd7e07e4f42e4c4f53f617e..454ca62b235a2f9089e3780b14ae01397515081e:/src/modules/module-solaris.c diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c index 0920d25e..b4fa7348 100644 --- a/src/modules/module-solaris.c +++ b/src/modules/module-solaris.c @@ -28,12 +28,9 @@ #include #include #include -#include #include #include -#include #include -#include #include #include @@ -41,14 +38,12 @@ #include #include -#include #include #include #include #include #include -#include #include #include #include @@ -60,6 +55,7 @@ #include #include #include +#include #include "module-solaris-symdef.h" @@ -79,7 +75,7 @@ PA_MODULE_USAGE( "rate= " "buffer_length= " "channel_map="); -PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_LOAD_ONCE(false); struct userdata { pa_core *core; @@ -104,12 +100,14 @@ struct userdata { pa_rtpoll_item *rtpoll_item; pa_module *module; - pa_bool_t sink_suspended, source_suspended; + bool sink_suspended, source_suspended; uint32_t play_samples_msw, record_samples_msw; uint32_t prev_playback_samples, prev_record_samples; int32_t minimum_request; + + pa_smoother *smoother; }; static const char* const valid_modargs[] = { @@ -133,6 +131,9 @@ static const char* const valid_modargs[] = { #define MAX_RENDER_HZ (300) /* This render rate limit imposes a minimum latency, but without it we waste too much CPU time. */ +#define MAX_BUFFER_SIZE (128 * 1024) +/* An attempt to buffer more than 128 KB causes write() to fail with errno == EAGAIN. */ + static uint64_t get_playback_buffered_bytes(struct userdata *u) { audio_info_t info; uint64_t played_bytes; @@ -145,7 +146,12 @@ static uint64_t get_playback_buffered_bytes(struct userdata *u) { /* Handle wrap-around of the device's sample counter, which is a uint_32. */ if (u->prev_playback_samples > info.play.samples) { - /* Unfortunately info.play.samples can sometimes go backwards, even before it wraps! */ + /* + * Unfortunately info.play.samples can sometimes go backwards, even before it wraps! + * The bug seems to be absent on Solaris x86 nv117 with audio810 driver, at least on this (UP) machine. + * The bug is present on a different (SMP) machine running Solaris x86 nv103 with audioens driver. + * An earlier revision of this file mentions the same bug independently (unknown configuration). + */ if (u->prev_playback_samples + info.play.samples < 240000) { ++u->play_samples_msw; } else { @@ -155,7 +161,12 @@ static uint64_t get_playback_buffered_bytes(struct userdata *u) { u->prev_playback_samples = info.play.samples; played_bytes = (((uint64_t)u->play_samples_msw << 32) + info.play.samples) * u->frame_size; - return u->written_bytes - played_bytes; + pa_smoother_put(u->smoother, pa_rtclock_now(), pa_bytes_to_usec(played_bytes, &u->sink->sample_spec)); + + if (u->written_bytes > played_bytes) + return u->written_bytes - played_bytes; + else + return 0; } static pa_usec_t sink_get_latency(struct userdata *u, pa_sample_spec *ss) { @@ -294,8 +305,8 @@ static int auto_format(int fd, int mode, pa_sample_spec *ss) { info.record.encoding = AUDIO_ENCODING_LINEAR; break; default: - pa_log("AUDIO_SETINFO: Unsupported sample format."); - return -1; + pa_log("AUDIO_SETINFO: Unsupported sample format."); + return -1; } } @@ -314,7 +325,7 @@ static int open_audio_device(struct userdata *u, pa_sample_spec *ss) { pa_assert(u); pa_assert(ss); - if ((u->fd = open(u->device_name, u->mode | O_NONBLOCK)) < 0) { + if ((u->fd = pa_open_cloexec(u->device_name, u->mode | O_NONBLOCK, 0)) < 0) { pa_log_warn("open %s failed (%s)", u->device_name, pa_cstrerror(errno)); return -1; } @@ -340,7 +351,7 @@ static int suspend(struct userdata *u) { pa_log_info("Suspending..."); - ioctl(u->fd, AUDIO_DRAIN, NULL); + ioctl(u->fd, I_FLUSH, FLUSHRW); pa_close(u->fd); u->fd = -1; @@ -387,24 +398,28 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); + pa_smoother_pause(u->smoother, pa_rtclock_now()); + if (!u->source || u->source_suspended) { if (suspend(u) < 0) return -1; } - u->sink_suspended = TRUE; + u->sink_suspended = true; break; case PA_SINK_IDLE: case PA_SINK_RUNNING: if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + pa_smoother_resume(u->smoother, pa_rtclock_now(), true); + if (!u->source || u->source_suspended) { if (unsuspend(u) < 0) return -1; u->sink->get_volume(u->sink); u->sink->get_mute(u->sink); } - u->sink_suspended = FALSE; + u->sink_suspended = false; } break; @@ -441,7 +456,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off if (suspend(u) < 0) return -1; } - u->source_suspended = TRUE; + u->source_suspended = true; break; case PA_SOURCE_IDLE: @@ -453,7 +468,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off return -1; u->source->get_volume(u->source); } - u->source_suspended = FALSE; + u->source_suspended = false; } break; @@ -479,7 +494,7 @@ static void sink_set_volume(pa_sink *s) { if (u->fd >= 0) { AUDIO_INITINFO(&info); - info.play.gain = pa_cvolume_max(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; + info.play.gain = pa_cvolume_max(&s->real_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; assert(info.play.gain <= AUDIO_MAX_GAIN); if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { @@ -501,8 +516,7 @@ static void sink_get_volume(pa_sink *s) { if (ioctl(u->fd, AUDIO_GETINFO, &info) < 0) pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); else - pa_cvolume_set(&s->virtual_volume, s->sample_spec.channels, - info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); + pa_cvolume_set(&s->real_volume, s->sample_spec.channels, info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); } } @@ -515,7 +529,7 @@ static void source_set_volume(pa_source *s) { if (u->fd >= 0) { AUDIO_INITINFO(&info); - info.play.gain = pa_cvolume_max(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; + info.play.gain = pa_cvolume_max(&s->real_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; assert(info.play.gain <= AUDIO_MAX_GAIN); if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { @@ -537,8 +551,7 @@ static void source_get_volume(pa_source *s) { if (ioctl(u->fd, AUDIO_GETINFO, &info) < 0) pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); else - pa_cvolume_set(&s->virtual_volume, s->sample_spec.channels, - info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); + pa_cvolume_set(&s->real_volume, s->sample_spec.channels, info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); } } @@ -577,14 +590,21 @@ static void process_rewind(struct userdata *u) { pa_assert(u); - /* Figure out how much we shall rewind and reset the counter */ + if (!PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + pa_sink_process_rewind(u->sink, 0); + return; + } + rewind_nbytes = u->sink->thread_info.rewind_nbytes; - u->sink->thread_info.rewind_nbytes = 0; if (rewind_nbytes > 0) { pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes); rewind_nbytes = PA_MIN(u->memchunk.length, rewind_nbytes); u->memchunk.length -= rewind_nbytes; + if (u->memchunk.length <= 0 && u->memchunk.memblock) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); } @@ -606,16 +626,18 @@ static void thread_func(void *userdata) { pa_thread_mq_install(&u->thread_mq); + pa_smoother_set_time_offset(u->smoother, pa_rtclock_now()); + for (;;) { /* Render some data and write it to the dsp */ + if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) + process_rewind(u); + if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) { - pa_usec_t xtime0; + pa_usec_t xtime0, ysleep_interval, xsleep_interval; uint64_t buffered_bytes; - if (u->sink->thread_info.rewind_requested) - process_rewind(u); - err = ioctl(u->fd, AUDIO_GETINFO, &info); if (err < 0) { pa_log("AUDIO_GETINFO ioctl failed: %s", pa_cstrerror(errno)); @@ -629,12 +651,15 @@ static void thread_func(void *userdata) { info.play.error = 0; if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); + + pa_smoother_reset(u->smoother, pa_rtclock_now(), true); } for (;;) { void *p; ssize_t w; size_t len; + int write_type = 1; /* * Since we cannot modify the size of the output buffer we fake it @@ -651,39 +676,32 @@ static void thread_func(void *userdata) { if (len < (size_t) u->minimum_request) break; - if (u->memchunk.length < len) + if (!u->memchunk.length) pa_sink_render(u->sink, u->sink->thread_info.max_request, &u->memchunk); + len = PA_MIN(u->memchunk.length, len); + p = pa_memblock_acquire(u->memchunk.memblock); - w = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, NULL); + w = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, len, &write_type); pa_memblock_release(u->memchunk.memblock); if (w <= 0) { - switch (errno) { - case EINTR: - continue; - case EAGAIN: - /* If the buffer_size is too big, we get EAGAIN. Avoiding that limit by trial and error - * is not ideal, but I don't know how to get the system to tell me what the limit is. - */ - u->buffer_size = u->buffer_size * 18 / 25; - u->buffer_size -= u->buffer_size % u->frame_size; - u->buffer_size = PA_MAX(u->buffer_size, 2 * u->minimum_request); - pa_sink_set_max_request_within_thread(u->sink, u->buffer_size); - pa_sink_set_max_rewind_within_thread(u->sink, u->buffer_size); - pa_log("EAGAIN. Buffer size is now %u bytes (%llu buffered)", u->buffer_size, buffered_bytes); - break; - default: - pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno)); - goto fail; + if (errno == EINTR) { + continue; + } else if (errno == EAGAIN) { + /* We may have realtime priority so yield the CPU to ensure that fd can become writable again. */ + pa_log_debug("EAGAIN with %llu bytes buffered.", buffered_bytes); + break; + } else { + pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno)); + goto fail; } } else { pa_assert(w % u->frame_size == 0); u->written_bytes += w; - u->memchunk.length -= w; - u->memchunk.index += w; + u->memchunk.length -= w; if (u->memchunk.length <= 0) { pa_memblock_unref(u->memchunk.memblock); pa_memchunk_reset(&u->memchunk); @@ -691,7 +709,9 @@ static void thread_func(void *userdata) { } } - pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec)); + ysleep_interval = pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec); + xsleep_interval = pa_smoother_translate(u->smoother, xtime0, ysleep_interval); + pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + PA_MIN(xsleep_interval, ysleep_interval)); } else pa_rtpoll_set_timer_disabled(u->rtpoll); @@ -758,7 +778,7 @@ static void thread_func(void *userdata) { } /* Hmm, nothing to do. Let's sleep */ - if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + if ((ret = pa_rtpoll_run(u->rtpoll, true)) < 0) goto fail; if (ret == 0) @@ -797,27 +817,27 @@ static void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void pa_log_debug("caught signal"); if (u->sink) { - pa_sink_get_volume(u->sink, TRUE, FALSE); - pa_sink_get_mute(u->sink, TRUE); + pa_sink_get_volume(u->sink, true); + pa_sink_get_mute(u->sink, true); } if (u->source) - pa_source_get_volume(u->source, TRUE); + pa_source_get_volume(u->source, true); } int pa__init(pa_module *m) { struct userdata *u = NULL; - pa_bool_t record = TRUE, playback = TRUE; + bool record = true, playback = true; pa_sample_spec ss; pa_channel_map map; pa_modargs *ma = NULL; uint32_t buffer_length_msec; - int fd; + int fd = -1; pa_sink_new_data sink_new_data; pa_source_new_data source_new_data; char const *name; char *name_buf; - pa_bool_t namereg_fail; + bool namereg_fail; pa_assert(m); @@ -838,6 +858,9 @@ int pa__init(pa_module *m) { u = pa_xnew0(struct userdata, 1); + if (!(u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC * 2, true, true, 10, pa_rtclock_now(), true))) + goto fail; + /* * For a process (or several processes) to use the same audio device for both * record and playback at the same time, the device's mixer must be enabled. @@ -861,7 +884,13 @@ int pa__init(pa_module *m) { } u->buffer_size = pa_usec_to_bytes(1000 * buffer_length_msec, &ss); if (u->buffer_size < 2 * u->minimum_request) { - pa_log("supplied buffer size argument is too small"); + pa_log("buffer_length argument cannot be smaller than %u", + (unsigned)(pa_bytes_to_usec(2 * u->minimum_request, &ss) / 1000)); + goto fail; + } + if (u->buffer_size > MAX_BUFFER_SIZE) { + pa_log("buffer_length argument cannot be greater than %u", + (unsigned)(pa_bytes_to_usec(MAX_BUFFER_SIZE, &ss) / 1000)); goto fail; } @@ -884,11 +913,11 @@ int pa__init(pa_module *m) { if (u->mode != O_WRONLY) { name_buf = NULL; - namereg_fail = TRUE; + namereg_fail = true; if (!(name = pa_modargs_get_value(ma, "source_name", NULL))) { name = name_buf = pa_sprintf_malloc("solaris_input.%s", pa_path_get_filename(u->device_name)); - namereg_fail = FALSE; + namereg_fail = false; } pa_source_new_data_init(&source_new_data); @@ -910,7 +939,7 @@ int pa__init(pa_module *m) { goto fail; } - u->source = pa_source_new(m->core, &source_new_data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|PA_SOURCE_HW_VOLUME_CTRL); + u->source = pa_source_new(m->core, &source_new_data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); pa_source_new_data_done(&source_new_data); pa_xfree(name_buf); @@ -924,19 +953,20 @@ int pa__init(pa_module *m) { pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); + pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->buffer_size, &u->source->sample_spec)); - u->source->get_volume = source_get_volume; - u->source->set_volume = source_set_volume; - u->source->refresh_volume = TRUE; + pa_source_set_get_volume_callback(u->source, source_get_volume); + pa_source_set_set_volume_callback(u->source, source_set_volume); + u->source->refresh_volume = true; } else u->source = NULL; if (u->mode != O_RDONLY) { name_buf = NULL; - namereg_fail = TRUE; + namereg_fail = true; if (!(name = pa_modargs_get_value(ma, "sink_name", NULL))) { name = name_buf = pa_sprintf_malloc("solaris_output.%s", pa_path_get_filename(u->device_name)); - namereg_fail = FALSE; + namereg_fail = false; } pa_sink_new_data_init(&sink_new_data); @@ -957,7 +987,7 @@ int pa__init(pa_module *m) { goto fail; } - u->sink = pa_sink_new(m->core, &sink_new_data, PA_SINK_HARDWARE|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL|PA_SINK_HW_MUTE_CTRL); + u->sink = pa_sink_new(m->core, &sink_new_data, PA_SINK_HARDWARE|PA_SINK_LATENCY); pa_sink_new_data_done(&sink_new_data); pa_assert(u->sink); @@ -966,15 +996,15 @@ int pa__init(pa_module *m) { pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); - - u->sink->get_volume = sink_get_volume; - u->sink->set_volume = sink_set_volume; - u->sink->get_mute = sink_get_mute; - u->sink->set_mute = sink_set_mute; - u->sink->refresh_volume = u->sink->refresh_muted = TRUE; - + pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->buffer_size, &u->sink->sample_spec)); pa_sink_set_max_request(u->sink, u->buffer_size); pa_sink_set_max_rewind(u->sink, u->buffer_size); + + pa_sink_set_get_volume_callback(u->sink, sink_get_volume); + pa_sink_set_set_volume_callback(u->sink, sink_set_volume); + pa_sink_set_get_mute_callback(u->sink, sink_get_mute); + pa_sink_set_set_mute_callback(u->sink, sink_set_mute); + u->sink->refresh_volume = u->sink->refresh_muted = true; } else u->sink = NULL; @@ -986,7 +1016,7 @@ int pa__init(pa_module *m) { else pa_log_warn("Could not register SIGPOLL handler"); - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("solaris", thread_func, u))) { pa_log("Failed to create thread."); goto fail; } @@ -1075,6 +1105,9 @@ void pa__done(pa_module *m) { if (u->fd >= 0) close(u->fd); + if (u->smoother) + pa_smoother_free(u->smoother); + pa_xfree(u->device_name); pa_xfree(u);