diff --git a/src/asound/bluealsa-pcm.c b/src/asound/bluealsa-pcm.c index d769c13b1..099e4a9dc 100644 --- a/src/asound/bluealsa-pcm.c +++ b/src/asound/bluealsa-pcm.c @@ -38,6 +38,8 @@ #include "shared/log.h" #include "shared/rt.h" +#define NSEC_PER_SEC 1000000000 + struct bluealsa_pcm { snd_pcm_ioplug_t io; @@ -54,8 +56,14 @@ struct bluealsa_pcm { /* event file descriptor */ int event_fd; - /* virtual hardware - ring buffer */ - snd_pcm_uframes_t io_ptr; + /* virtual hardware - ring buffer + * The IO thread is responsible for maintaining the hw pointer, the + * application (ie ioplug) responsible for the appl pointer. These are + * both volatile as they are both written in one thread and read in the + * other. */ + volatile snd_pcm_sframes_t hw_ptr; + char *buffer; + snd_pcm_uframes_t io_hw_boundary; pthread_t io_thread; bool io_started; @@ -67,22 +75,14 @@ struct bluealsa_pcm { /* ALSA operates on frames, we on bytes */ size_t frame_size; - /* In order to see whether the PCM has reached under-run (or over-run), we - * have to know the exact position of the hardware and software pointers. - * To do so, we could use the HW pointer provided by the IO plug structure. - * This pointer is updated by the snd_pcm_hwsync() function, which is not - * thread-safe (total disaster is guaranteed). Since we can not call this - * function, we are going to use our own HW pointer, which can be updated - * safely in our IO thread. */ - snd_pcm_uframes_t io_hw_boundary; - snd_pcm_uframes_t io_hw_ptr; - pthread_mutex_t delay_mutex; struct timespec delay_ts; snd_pcm_uframes_t delay_hw_ptr; unsigned int delay_pcm_nread; bool delay_valid; + /* permit the application to modify the frequency of poll() events */ + volatile snd_pcm_uframes_t avail_min; }; /** @@ -90,6 +90,29 @@ struct bluealsa_pcm { #define debug2(M, ...) \ debug("%s: " M, pcm->ba_pcm.pcm_path, ## __VA_ARGS__) +/* We need to provide our own implementation of snd_pcm_ioplug_hw_avail() + * for alsa-lib versions earlier than 1.1.6 */ +#if SND_LIB_VERSION < 0x010106 +/** + * Frames (playback)/space(capture) available to the virtual hardware. */ +static snd_pcm_uframes_t snd_pcm_ioplug_hw_avail( + const snd_pcm_ioplug_t * const io, + const snd_pcm_uframes_t hw_ptr, + const snd_pcm_uframes_t appl_ptr) { + struct bluealsa_pcm *pcm = io->private_data; + snd_pcm_sframes_t diff; + if (io->stream == SND_PCM_STREAM_PLAYBACK) + diff = appl_ptr - hw_ptr; + else + diff = io->buffer_size - hw_ptr + appl_ptr; + + if (diff < 0) + diff += pcm->io_hw_boundary; + + return diff <= io->buffer_size ? (snd_pcm_uframes_t) diff : 0; +} +#endif + /** * Helper function for closing PCM transport. */ static int close_transport(struct bluealsa_pcm *pcm) { @@ -112,6 +135,27 @@ static void io_thread_cleanup(struct bluealsa_pcm *pcm) { pcm->io_started = false; } +/** + * Some playback applications may start the PCM before they have written the + * first full period of audio data. To match the behavior of the ALSA hw + * plug-in we do not report an underrun in this case. Instead we pause the IO + * thread for the estimated time it should take for a real-time application to + * write the balance of the period. */ +static int playback_wait_first_period(snd_pcm_ioplug_t *io) { + struct bluealsa_pcm *pcm = io->private_data; + while (io->appl_ptr < pcm->avail_min && + (io->state == SND_PCM_STATE_RUNNING || + io->state == SND_PCM_STATE_PREPARED)) { + uint64_t nsec = + (pcm->avail_min - io->appl_ptr) * NSEC_PER_SEC / io->rate; + struct timespec ts = { + .tv_sec = nsec / NSEC_PER_SEC, + .tv_nsec = nsec % NSEC_PER_SEC + }; + nanosleep(&ts, NULL); + } +} + /** * IO thread, which facilitates ring buffer. */ static void *io_thread(snd_pcm_ioplug_t *io) { @@ -134,8 +178,10 @@ static void *io_thread(snd_pcm_ioplug_t *io) { goto fail; } - /* Block I/O until a full period of frames is available. */ - bool started = false; + if (io->stream == SND_PCM_STREAM_PLAYBACK) { + debug2("Waiting for first period of frames"); + playback_wait_first_period(io); + } struct asrsync asrs; asrsync_init(&asrs, io->rate); @@ -143,70 +189,63 @@ static void *io_thread(snd_pcm_ioplug_t *io) { debug2("Starting IO loop: %d", pcm->ba_pcm_fd); for (;;) { - int tmp; - switch (io->state) { - case SND_PCM_STATE_RUNNING: - /* Some playback applications may start the PCM before they have - * written the first full period of audio data. To match the - * behavior of the ALSA hw plug-in we do not report an underrun in - * this case. Instead we pause the IO thread for the estimated time - * it should take for a real-time application to write the balance - * of the period. */ - if (!started && io->stream == SND_PCM_STREAM_PLAYBACK) { - if (io->period_size > io->appl_ptr) { - struct timespec ts = { - .tv_nsec = (io->period_size - io->appl_ptr) * 1000000000 / io->rate }; - debug2("IO thread started with insufficient frames - pausing for %ld ms", ts.tv_nsec / 1000000); - sigtimedwait(&sigset, NULL, &ts); - asrsync_init(&asrs, io->rate); - continue; - } - started = true; - } - break; - case SND_PCM_STATE_DRAINING: - break; - case SND_PCM_STATE_DISCONNECTED: - goto fail; - case SND_PCM_STATE_XRUN: - started = false; - /* fall-through */ - default: + if (pcm->hw_ptr == -1 || io->state == SND_PCM_STATE_PAUSED) { + int tmp; debug2("IO thread paused: %d", io->state); sigwait(&sigset, &tmp); + if (pcm->hw_ptr == -1) + continue; + if (pcm->ba_pcm_fd == -1) + goto fail; + if (io->stream == SND_PCM_STREAM_PLAYBACK) { + debug2("Waiting for first period of frames"); + playback_wait_first_period(io); + } asrsync_init(&asrs, io->rate); debug2("IO thread resumed: %d", io->state); } - snd_pcm_uframes_t io_ptr = pcm->io_ptr; - snd_pcm_uframes_t io_buffer_size = io->buffer_size; - snd_pcm_uframes_t io_hw_ptr = pcm->io_hw_ptr; - snd_pcm_uframes_t io_hw_boundary = pcm->io_hw_boundary; - snd_pcm_uframes_t frames = io->period_size; - const snd_pcm_channel_area_t *areas = snd_pcm_ioplug_mmap_areas(io); - char *buffer = (char *)areas->addr + (areas->first + areas->step * io_ptr) / 8; - char *head = buffer; - ssize_t ret = 0; - size_t len; + /* We update pcm->hw_ptr (ie the value seen by ioplug) only when a + * transfer has been completed. We use a temporary copy during the + * transfer procedure */ + snd_pcm_uframes_t io_hw_ptr = pcm->hw_ptr; + + snd_pcm_uframes_t offset = io_hw_ptr % io->buffer_size; + snd_pcm_uframes_t frames = pcm->avail_min; + char *head = pcm->buffer + offset * pcm->frame_size; /* If the leftover in the buffer is less than a whole period sizes, * adjust the number of frames which should be transfered. It has * turned out, that the buffer might contain fractional number of * periods - it could be an ALSA bug, though, it has to be handled. */ - if (io_buffer_size - io_ptr < frames) - frames = io_buffer_size - io_ptr; + if (io->buffer_size - offset < frames) + frames = io->buffer_size - offset; + + /* Do not try to transfer more frames than are available in the ring + * buffer ! */ + snd_pcm_uframes_t hw_avail = snd_pcm_ioplug_hw_avail(io, io_hw_ptr, + io->appl_ptr); + if (frames > hw_avail) + frames = hw_avail; + + /* there are 2 reasons why the number of available frames may be zero: + * xrun or drained final samples - ioplug will determine which, we just + * need to set the hw pointer to -1 */ + if (frames == 0) { + io_hw_ptr = -1; + goto sync; + } /* IO operation size in bytes */ - len = frames * pcm->frame_size; - - io_ptr += frames; - if (io_ptr >= io_buffer_size) - io_ptr -= io_buffer_size; + size_t len = frames * pcm->frame_size; + /* Increment the hw ptr (with boundary wrap) ready for the next + * iteration. */ io_hw_ptr += frames; - if (io_hw_ptr >= io_hw_boundary) - io_hw_ptr -= io_hw_boundary; + if (io_hw_ptr >= pcm->io_hw_boundary) + io_hw_ptr -= pcm->io_hw_boundary; + ssize_t ret = 0; if (io->stream == SND_PCM_STREAM_CAPTURE) { /* Read the whole period "atomically". This will assure, that frames @@ -228,13 +267,6 @@ static void *io_thread(snd_pcm_ioplug_t *io) { } else { - /* check for under-run and act accordingly */ - if (io_hw_ptr > io->appl_ptr) { - io->state = SND_PCM_STATE_XRUN; - io_ptr = -1; - goto sync; - } - /* Perform atomic write - see the explanation above. */ do { if ((ret = write(pcm->ba_pcm_fd, head, len)) == -1) { @@ -267,8 +299,10 @@ static void *io_thread(snd_pcm_ioplug_t *io) { } sync: - pcm->io_ptr = io_ptr; - pcm->io_hw_ptr = io_hw_ptr; + /* Make the new hw pointer value visible to the ioplug */ + pcm->hw_ptr = io_hw_ptr; + /* Generate poll() event so applcation is made aware of the hw pointer + * change. */ eventfd_write(pcm->event_fd, 1); } @@ -288,13 +322,11 @@ static int bluealsa_start(snd_pcm_ioplug_t *io) { * we might end up with a bunch of IO threads reading or writing to the * same FIFO simultaneously. Instead, just send resume signal. */ if (pcm->io_started) { - io->state = SND_PCM_STATE_RUNNING; pthread_kill(pcm->io_thread, SIGIO); return 0; } /* initialize delay calculation */ - pcm->delay = 0; pcm->delay_valid = false; if (!bluealsa_dbus_pcm_ctrl_send_resume(pcm->ba_pcm_ctrl_fd, NULL)) { @@ -302,19 +334,12 @@ static int bluealsa_start(snd_pcm_ioplug_t *io) { return -errno; } - /* State has to be updated before the IO thread is created - if the state - * does not indicate "running", the IO thread will be suspended until the - * "resume" signal is delivered. This requirement is only (?) theoretical, - * anyhow forewarned is forearmed. */ - snd_pcm_state_t prev_state = io->state; - io->state = SND_PCM_STATE_RUNNING; - + /* Start the IO thread */ pcm->io_started = true; if ((errno = pthread_create(&pcm->io_thread, NULL, PTHREAD_ROUTINE(io_thread), io)) != 0) { debug2("Couldn't create IO thread: %s", strerror(errno)); pcm->io_started = false; - io->state = prev_state; return -errno; } @@ -336,10 +361,8 @@ static int bluealsa_stop(snd_pcm_ioplug_t *io) { if (!bluealsa_dbus_pcm_ctrl_send_drop(pcm->ba_pcm_ctrl_fd, NULL)) return -errno; - /* Although the pcm stream is now stopped, it is still prepared and - * therefore an application that uses start_threshold may try to restart it - * by simply performing an I/O operation. If such an application now calls - * poll() it may be blocked forever unless we generate an event here. */ + /* Applications that call poll() after snd_pcm_drain() will be blocked + * forever unless we generate a poll() event here. */ eventfd_write(pcm->event_fd, 1); return 0; @@ -349,7 +372,11 @@ static snd_pcm_sframes_t bluealsa_pointer(snd_pcm_ioplug_t *io) { struct bluealsa_pcm *pcm = io->private_data; if (pcm->ba_pcm_fd == -1) return -ENODEV; - return pcm->io_ptr; +#ifdef SND_PCM_IOPLUG_FLAG_BOUNDARY_WA + return pcm->hw_ptr; +#else + return pcm->hw_ptr == -1 ? -1 : pcm->hw_ptr % io->buffer_size; +#endif } static int bluealsa_close(snd_pcm_ioplug_t *io) { @@ -377,12 +404,6 @@ static int bluealsa_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params) return -EBUSY; } - /* Indicate that our PCM is ready for writing, even though is is not 100% - * true - IO thread is not running yet. Some weird implementations might - * require PCM to be writable before the snd_pcm_start() call. */ - if (io->stream == SND_PCM_STREAM_PLAYBACK) - eventfd_write(pcm->event_fd, 1); - if (pcm->io.stream == SND_PCM_STREAM_PLAYBACK) { /* By default, the size of the pipe buffer is set to a too large value for * our purpose. On modern Linux system it is 65536 bytes. Large buffer in @@ -394,6 +415,9 @@ static int bluealsa_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params) debug2("FIFO buffer size: %zd", pcm->ba_pcm_buffer_size); } + /* alsa default for avail_min is one period */ + pcm->avail_min = io->period_size; + debug2("Selected HW buffer: %zd periods x %zd bytes %c= %zd bytes", io->buffer_size / io->period_size, pcm->frame_size * io->period_size, io->period_size * (io->buffer_size / io->period_size) == io->buffer_size ? '=' : '<', @@ -414,6 +438,16 @@ static int bluealsa_sw_params(snd_pcm_ioplug_t *io, snd_pcm_sw_params_t *params) struct bluealsa_pcm *pcm = io->private_data; debug2("Initializing SW"); snd_pcm_sw_params_get_boundary(params, &pcm->io_hw_boundary); + snd_pcm_uframes_t avail_min; + snd_pcm_sw_params_get_avail_min(params, &avail_min); + if (avail_min != pcm->avail_min) { + if (avail_min > io->buffer_size) + return -EINVAL; + else { + debug2("changing avail_min: %zu", avail_min); + pcm->avail_min = avail_min; + } + } return 0; } @@ -425,12 +459,18 @@ static int bluealsa_prepare(snd_pcm_ioplug_t *io) { return -ENODEV; /* initialize ring buffer */ - pcm->io_hw_ptr = 0; - pcm->io_ptr = 0; + pcm->hw_ptr = 0; + + /* ioplug allocates and configures its channel area buffer when the + * hw_params are fixed, but after calling bluealsa_hw_params. So this is the + * earliest opportunity for us to safely cache the ring buffer start + * address. */ + const snd_pcm_channel_area_t *areas = snd_pcm_ioplug_mmap_areas(io); + pcm->buffer = (char *)areas->addr + areas->first / 8; /* Indicate that our PCM is ready for i/o, even though is is not 100% - * true - the IO thread is not running yet. Applications using - * snd_pcm_sw_params_set_start_threshold() require PCM to be usable + * true - the IO thread may not be running yet. Applications using + * snd_pcm_sw_params_set_start_threshold() require the PCM to be usable * as soon as it has been prepared. */ eventfd_write(pcm->event_fd, 1); @@ -453,7 +493,6 @@ static int bluealsa_pause(snd_pcm_ioplug_t *io, int enable) { return -errno; if (enable == 0) { - io->state = SND_PCM_STATE_RUNNING; pthread_kill(pcm->io_thread, SIGIO); } @@ -479,6 +518,19 @@ static int bluealsa_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp) { if (pcm->ba_pcm_fd == -1) return -ENODEV; + if (io->state == SND_PCM_STATE_XRUN) { + *delayp = 0; + return -EPIPE; + } + + /* It is not clearly defined what the delay of a pcm that is not running + * should be, and alsaloop fails with the method below. So in this release + * we simply report zero. */ + if (io->state != SND_PCM_STATE_RUNNING) { + *delayp = 0; + return 0; + } + /* Exact calculation of the PCM delay is very hard, if not impossible. For * the sake of simplicity we will make few assumptions and approximations. * In general, the delay is proportional to the number of bytes queued in @@ -488,9 +540,14 @@ static int bluealsa_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp) { snd_pcm_sframes_t delay = 0; if (!pcm->delay_valid) { - /* If we have never transmitted anything then the delay is just based - * on the current buffer length. */ - delay = io->appl_ptr - io->hw_ptr; + /* For playback, if we have never transmitted anything then the delay + * is just based on the current buffer length. + * For capture we always use the current buffer length */ + if (io->stream == SND_PCM_STREAM_PLAYBACK) + delay = snd_pcm_ioplug_hw_avail(io, io->hw_ptr, io->appl_ptr); + else + delay = io->buffer_size - snd_pcm_ioplug_hw_avail(io, io->hw_ptr, + io->appl_ptr); } else { /* Take the gap between the current input pointer in this thread and @@ -505,9 +562,8 @@ static int bluealsa_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp) { struct timespec diff; difftimespec(&now, &pcm->delay_ts, &diff); - delay = io->appl_ptr >= pcm->delay_hw_ptr ? - (io->appl_ptr - pcm->delay_hw_ptr) : - (io->appl_ptr + pcm->io_hw_boundary - pcm->delay_hw_ptr); + delay = snd_pcm_ioplug_hw_avail(io, pcm->delay_hw_ptr, io->appl_ptr); + delay += pcm->delay_pcm_nread / pcm->frame_size; unsigned int tsamples = @@ -519,9 +575,9 @@ static int bluealsa_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp) { } /* data transfer (communication) and encoding/decoding */ - pcm->delay = (io->rate / 100) * pcm->ba_pcm.delay / 100; + delay += (io->rate / 100) * pcm->ba_pcm.delay / 100; - *delayp = delay + pcm->delay + pcm->delay_ex; + *delayp = delay + pcm->delay_ex; return 0; } @@ -552,13 +608,15 @@ static int bluealsa_poll_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfd, static int bluealsa_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfd, unsigned int nfds, unsigned short *revents) { struct bluealsa_pcm *pcm = io->private_data; + *revents = 0; + int ret = 0; if (bluealsa_dbus_connection_poll_dispatch(&pcm->dbus_ctx, &pfd[1], nfds - 1)) while (dbus_connection_dispatch(pcm->dbus_ctx.conn) == DBUS_DISPATCH_DATA_REMAINS) continue; if (pcm->ba_pcm_fd == -1) - return -ENODEV; + goto fail; if (pfd[0].revents & POLLIN) { @@ -568,36 +626,49 @@ static int bluealsa_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfd, if (event & 0xDEAD0000) goto fail; + /* This call synchronizes the ring buffer pointers and updates the + * ioplug state. */ + snd_pcm_uframes_t avail = snd_pcm_avail(io->pcm); + /* ALSA expects that the event will match stream direction, e.g. * playback will not start if the event is for reading. */ *revents = io->stream == SND_PCM_STREAM_CAPTURE ? POLLIN : POLLOUT; - /* Include POLLERR if PCM is not prepared, running or draining. - * Also restore the event trigger as in this state the io thread is - * not active to do it */ - if (io->state != SND_PCM_STATE_PREPARED && - io->state != SND_PCM_STATE_RUNNING && - io->state != SND_PCM_STATE_DRAINING) { - *revents |= POLLERR; - eventfd_write(pcm->event_fd, 1); - } + /* We hold the event fd ready, unless insufficient frames are + * available in the ring buffer */ + bool ready = true; - /* a playback application may write less than start_threshold frames on - * its first write and then wait in poll() forever because the event_fd - * never gets written to again. - * To prevent this possibility, we bump the internal trigger. */ - else if (snd_pcm_stream(io->pcm) == SND_PCM_STREAM_PLAYBACK && - io->state == SND_PCM_STATE_PREPARED) + switch (io->state) { + case SND_PCM_STATE_SETUP: + ready = false; + *revents = 0; + break; + case SND_PCM_STATE_RUNNING: + if (avail < pcm->avail_min) { + ready = false; + *revents = 0; + } + break; + case SND_PCM_STATE_XRUN: + case SND_PCM_STATE_PAUSED: + case SND_PCM_STATE_SUSPENDED: + *revents = POLLERR; + break; + case SND_PCM_STATE_DISCONNECTED: + ready = false; + *revents = POLLERR; + ret = -ENODEV; + break; + case SND_PCM_STATE_PREPARED: + case SND_PCM_STATE_DRAINING: + break; + }; + + if (ready) eventfd_write(pcm->event_fd, 1); - - /* If the event was triggered prematurely, wait for another one. */ - else if (!snd_pcm_avail_update(io->pcm)) - *revents = 0; } - else - *revents = 0; - return 0; + return ret; fail: *revents = POLLERR | POLLHUP; @@ -812,11 +883,14 @@ SND_PCM_PLUGIN_DEFINE_FUNC(bluealsa) { pcm->io.version = SND_PCM_IOPLUG_VERSION; pcm->io.name = "BlueALSA"; pcm->io.flags = SND_PCM_IOPLUG_FLAG_LISTED; +#ifdef SND_PCM_IOPLUG_FLAG_BOUNDARY_WA + pcm->io.flags |= SND_PCM_IOPLUG_FLAG_BOUNDARY_WA; +#endif pcm->io.mmap_rw = 1; pcm->io.callback = &bluealsa_callback; pcm->io.private_data = pcm; -#if SND_LIB_VERSION >= 0x010102 +#if SND_LIB_VERSION <= 0x010103 && SND_LIB_VERSION >= 0x010102 /* ALSA library thread-safe API functionality does not play well with ALSA * IO-plug plug-ins. It causes deadlocks which often make our PCM plug-in * unusable. As a workaround we are going to disable this functionality. */