diff --git a/spa/plugins/alsa/alsa-pcm-sink.c b/spa/plugins/alsa/alsa-pcm-sink.c index 0eccd1e95..018b01231 100644 --- a/spa/plugins/alsa/alsa-pcm-sink.c +++ b/spa/plugins/alsa/alsa-pcm-sink.c @@ -646,7 +646,7 @@ static int impl_node_process(void *object) SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); input->buffer_id = SPA_ID_INVALID; - spa_alsa_write(this, 0); + spa_alsa_write(this); input->status = SPA_STATUS_OK; } diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index ea10292b7..2b2fc2f96 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -258,7 +258,6 @@ static void sanitize_map(snd_pcm_chmap_t* map) } } - } int @@ -320,13 +319,15 @@ spa_alsa_enum_format(struct state *state, int seq, uint32_t start, uint32_t num, const struct format_info *fi = &format_info[i]; if (snd_pcm_format_mask_test(fmask, fi->format)) { - if (snd_pcm_access_mask_test(amask, SND_PCM_ACCESS_MMAP_INTERLEAVED) && + if ((snd_pcm_access_mask_test(amask, SND_PCM_ACCESS_MMAP_INTERLEAVED) || + snd_pcm_access_mask_test(amask, SND_PCM_ACCESS_RW_INTERLEAVED)) && (state->default_format == 0 || state->default_format == fi->spa_format)) { if (j++ == 0) spa_pod_builder_id(&b, fi->spa_format); spa_pod_builder_id(&b, fi->spa_format); } - if (snd_pcm_access_mask_test(amask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED) && + if ((snd_pcm_access_mask_test(amask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED) || + snd_pcm_access_mask_test(amask, SND_PCM_ACCESS_RW_NONINTERLEAVED)) && fi->spa_pformat != SPA_AUDIO_FORMAT_UNKNOWN && (state->default_format == 0 || state->default_format == fi->spa_pformat)) { if (j++ == 0) @@ -487,6 +488,7 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ int err, dir; snd_pcm_hw_params_t *params; snd_pcm_format_t format; + snd_pcm_access_mask_t *amask; struct spa_audio_info_raw *info = &fmt->info.raw; snd_pcm_t *hndl; unsigned int periods; @@ -511,17 +513,34 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ } /* set the interleaved/planar read/write format */ - CHECK(snd_pcm_hw_params_set_access(hndl, params, + snd_pcm_access_mask_alloca(&amask); + snd_pcm_hw_params_get_access_mask(params, amask); + + if ((err = snd_pcm_hw_params_set_access(hndl, params, planar ? SND_PCM_ACCESS_MMAP_NONINTERLEAVED - : SND_PCM_ACCESS_MMAP_INTERLEAVED), "set_access"); + : SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0) { + spa_log_debug(state->log, NAME" %p: MMAP not possible: %s", state, + snd_strerror(err)); + if ((err = snd_pcm_hw_params_set_access(hndl, params, + planar ? SND_PCM_ACCESS_RW_NONINTERLEAVED + : SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + spa_log_error(state->log, NAME" %p: RW not possible: %s", state, + snd_strerror(err)); + return err; + } + state->use_mmap = false; + } else { + state->use_mmap = true; + } /* disable ALSA wakeups, we use a timer */ if (snd_pcm_hw_params_can_disable_period_wakeup(params)) CHECK(snd_pcm_hw_params_set_period_wakeup(hndl, params, 0), "set_period_wakeup"); /* set the sample format */ - spa_log_debug(state->log, NAME" %p: Stream parameters are %iHz, %s %s, %i channels", + spa_log_debug(state->log, NAME" %p: Stream parameters are %iHz fmt:%s access:%s-%s channels:%i", state, info->rate, snd_pcm_format_name(format), + state->use_mmap ? "mmap" : "rw", planar ? "planar" : "interleaved", info->channels); CHECK(snd_pcm_hw_params_set_format(hndl, params, format), "set_format"); @@ -553,6 +572,7 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ state->channels = info->channels; state->rate = info->rate; state->frame_size = snd_pcm_format_physical_width(format) / 8; + state->planar = planar; state->blocks = 1; if (planar) state->blocks *= info->channels; @@ -589,11 +609,13 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ state->headroom = is_batch ? period_size : 0; periods = state->buffer_frames / state->period_frames; - spa_log_info(state->log, NAME" %s (%s): format:%s %s rate:%d channels:%d " + spa_log_info(state->log, NAME" %s (%s): format:%s access:%s-%s rate:%d channels:%d " "buffer frames %lu, period frames %lu, periods %u, frame_size %zd", state->props.device, state->stream == SND_PCM_STREAM_CAPTURE ? "capture" : "playback", - snd_pcm_format_name(state->format), planar ? "planar" : "interleaved", + snd_pcm_format_name(state->format), + state->use_mmap ? "mmap" : "rw", + planar ? "planar" : "interleaved", state->rate, state->channels, state->buffer_frames, state->period_frames, periods, state->frame_size); @@ -647,6 +669,62 @@ static int set_timeout(struct state *state, uint64_t time) return 0; } +int spa_alsa_silence(struct state *state, snd_pcm_uframes_t silence) +{ + snd_pcm_t *hndl = state->hndl; + const snd_pcm_channel_area_t *my_areas; + snd_pcm_uframes_t frames, offset; + int i, res; + + if (state->use_mmap) { + frames = state->buffer_frames; + + if (SPA_UNLIKELY((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &frames)) < 0)) { + spa_log_error(state->log, NAME" %p: snd_pcm_mmap_begin error: %s", + state, snd_strerror(res)); + return res; + } + silence = SPA_MIN(silence, frames); + + spa_log_trace_fp(state->log, NAME" %p: silence %ld", state, silence); + snd_pcm_areas_silence(my_areas, offset, state->channels, silence, state->format); + + if (SPA_UNLIKELY((res = snd_pcm_mmap_commit(hndl, offset, silence)) < 0)) { + spa_log_error(state->log, NAME" %p: snd_pcm_mmap_commit error: %s", + state, snd_strerror(res)); + return res; + } + } else { + uint8_t buffer[silence * state->frame_size]; + memset(buffer, 0, silence * state->frame_size); + + if (state->planar) { + void *bufs[state->channels]; + for (i = 0; i < state->channels; i++) + bufs[i] = buffer; + snd_pcm_writen(hndl, bufs, silence); + } else { + snd_pcm_writei(hndl, buffer, silence); + } + } + return 0; +} + +static inline int do_start(struct state *state) +{ + int res; + if (SPA_UNLIKELY(!state->alsa_started)) { + spa_log_trace(state->log, NAME" %p: snd_pcm_start", state); + if ((res = snd_pcm_start(state->hndl)) < 0) { + spa_log_error(state->log, NAME" %p: snd_pcm_start: %s", + state, snd_strerror(res)); + return res; + } + state->alsa_started = true; + } + return 0; +} + static int alsa_recover(struct state *state, int err) { int res, st; @@ -700,17 +778,12 @@ recover: } spa_dll_init(&state->dll); state->alsa_recovering = true; + state->alsa_started = false; if (state->stream == SND_PCM_STREAM_CAPTURE) { - if ((res = snd_pcm_start(state->hndl)) < 0) { - spa_log_error(state->log, NAME" %p: snd_pcm_start: %s", - state, snd_strerror(res)); - return res; - } - state->alsa_started = true; + do_start(state); } else { - state->alsa_started = false; - spa_alsa_write(state, state->threshold * 2); + spa_alsa_silence(state, state->threshold * 2); } return 0; } @@ -826,13 +899,13 @@ static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t del return 0; } -int spa_alsa_write(struct state *state, snd_pcm_uframes_t silence) +int spa_alsa_write(struct state *state) { snd_pcm_t *hndl = state->hndl; const snd_pcm_channel_area_t *my_areas; snd_pcm_uframes_t written, frames, offset, off, to_write, total_written; snd_pcm_sframes_t commitres; - int res; + int res = 0; if (SPA_LIKELY(state->position && state->duration != state->position->clock.duration)) { state->duration = state->position->clock.duration; @@ -869,18 +942,22 @@ int spa_alsa_write(struct state *state, snd_pcm_uframes_t silence) total_written = 0; again: - frames = state->buffer_frames; - if (SPA_UNLIKELY((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &frames)) < 0)) { - spa_log_error(state->log, NAME" %p: snd_pcm_mmap_begin error: %s", - state, snd_strerror(res)); - return res; - } - spa_log_trace_fp(state->log, NAME" %p: begin %ld %ld %d %ld", - state, offset, frames, state->threshold, silence); - silence = SPA_MIN(silence, frames); + frames = state->buffer_frames; + if (state->use_mmap) { + if (SPA_UNLIKELY((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &frames)) < 0)) { + spa_log_error(state->log, NAME" %p: snd_pcm_mmap_begin error: %s", + state, snd_strerror(res)); + return res; + } + spa_log_trace_fp(state->log, NAME" %p: begin %ld %ld %d", + state, offset, frames, state->threshold); + off = offset; + } else { + off = 0; + } + to_write = frames; - off = offset; written = 0; while (!spa_list_is_empty(&state->ready) && to_write > 0) { @@ -907,14 +984,35 @@ again: l0 = SPA_MIN(n_bytes, maxsize - offs); l1 = n_bytes - l0; - for (i = 0; i < b->buf->n_datas; i++) { - dst = SPA_MEMBER(my_areas[i].addr, off * state->frame_size, uint8_t); - src = d[i].data; + if (SPA_LIKELY(state->use_mmap)) { + for (i = 0; i < b->buf->n_datas; i++) { + dst = SPA_MEMBER(my_areas[i].addr, off * state->frame_size, uint8_t); + src = d[i].data; - spa_memcpy(dst, src + offs, l0); - if (SPA_UNLIKELY(l1 > 0)) - spa_memcpy(dst + l0, src, l1); + spa_memcpy(dst, src + offs, l0); + if (SPA_UNLIKELY(l1 > 0)) + spa_memcpy(dst + l0, src, l1); + } + } else { + if (state->planar) { + void *bufs[b->buf->n_datas]; + + for (i = 0; i < b->buf->n_datas; i++) + bufs[i] = SPA_MEMBER(d[i].data, offs, void); + snd_pcm_writen(hndl, bufs, l0 / state->frame_size); + if (SPA_UNLIKELY(l1 > 0)) { + for (i = 0; i < b->buf->n_datas; i++) + bufs[i] = d[i].data; + snd_pcm_writen(hndl, bufs, l1 / state->frame_size); + } + } else { + src = d[0].data; + snd_pcm_writei(hndl, src + offs, l0 / state->frame_size); + if (SPA_UNLIKELY(l1 > 0)) + snd_pcm_writei(hndl, src, l1 / state->frame_size); + } } + state->ready_offset += n_bytes; if (state->ready_offset >= size) { @@ -930,32 +1028,24 @@ again: written += n_frames; off += n_frames; to_write -= n_frames; - if (silence > n_frames) - silence -= n_frames; - else - silence = 0; - } - - if (SPA_UNLIKELY(silence > 0)) { - spa_log_trace_fp(state->log, NAME" %p: silence %ld", state, silence); - snd_pcm_areas_silence(my_areas, off, state->channels, silence, state->format); - written += silence; } spa_log_trace_fp(state->log, NAME" %p: commit %ld %ld %"PRIi64, state, offset, written, state->sample_count); total_written += written; - if (SPA_UNLIKELY((commitres = snd_pcm_mmap_commit(hndl, offset, written)) < 0)) { - spa_log_error(state->log, NAME" %p: snd_pcm_mmap_commit error: %s", - state, snd_strerror(commitres)); - if (commitres != -EPIPE && commitres != -ESTRPIPE) - return res; - } + if (state->use_mmap) { + if (SPA_UNLIKELY((commitres = snd_pcm_mmap_commit(hndl, offset, written)) < 0)) { + spa_log_error(state->log, NAME" %p: snd_pcm_mmap_commit error: %s", + state, snd_strerror(commitres)); + if (commitres != -EPIPE && commitres != -ESTRPIPE) + return res; + } - if (commitres > 0 && written != (snd_pcm_uframes_t) commitres) { - spa_log_warn(state->log, NAME" %p: mmap_commit wrote %ld instead of %ld", - state, commitres, written); + if (commitres > 0 && written != (snd_pcm_uframes_t) commitres) { + spa_log_warn(state->log, NAME" %p: mmap_commit wrote %ld instead of %ld", + state, commitres, written); + } } if (!spa_list_is_empty(&state->ready) && written > 0) @@ -963,15 +1053,9 @@ again: state->sample_count += total_written; - if (SPA_UNLIKELY(!state->alsa_started && total_written > 0)) { - spa_log_trace(state->log, NAME" %p: snd_pcm_start %lu", state, written); - if ((res = snd_pcm_start(hndl)) < 0) { - spa_log_error(state->log, NAME" %p: snd_pcm_start: %s", - state, snd_strerror(res)); - return res; - } - state->alsa_started = true; - } + if (SPA_UNLIKELY(!state->alsa_started && total_written > 0)) + do_start(state); + return 0; } @@ -1035,15 +1119,23 @@ push_frames(struct state *state, d[i].chunk->stride = state->frame_size; } } else { + void *bufs[b->buf->n_datas]; for (i = 0; i < b->buf->n_datas; i++) { - memset(d[i].data, 0, n_bytes); + bufs[i] = d[i].data; d[i].chunk->offset = 0; d[i].chunk->size = n_bytes; d[i].chunk->stride = state->frame_size; } + if (state->planar) { + snd_pcm_readn(state->hndl, bufs, total_frames); + } else { + snd_pcm_readi(state->hndl, bufs[0], total_frames); + } } spa_list_append(&state->ready, &b->link); } + if (my_areas == NULL) + snd_pcm_rewind(state->hndl, keep); return total_frames - keep; } @@ -1055,7 +1147,7 @@ int spa_alsa_read(struct state *state, snd_pcm_uframes_t silence) const snd_pcm_channel_area_t *my_areas; snd_pcm_uframes_t read, frames, offset; snd_pcm_sframes_t commitres; - int res; + int res = 0; if (state->position) { if (state->duration != state->position->clock.duration) { @@ -1111,33 +1203,38 @@ int spa_alsa_read(struct state *state, snd_pcm_uframes_t silence) if (frames == 0) frames = state->threshold + state->delay; - - to_read = state->buffer_frames; - if ((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &to_read)) < 0) { - spa_log_error(state->log, NAME" %p: snd_pcm_mmap_begin error: %s", - state, snd_strerror(res)); - return res; + if (state->use_mmap) { + to_read = state->buffer_frames; + if ((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &to_read)) < 0) { + spa_log_error(state->log, NAME" %p: snd_pcm_mmap_begin error: %s", + state, snd_strerror(res)); + return res; + } + spa_log_trace_fp(state->log, NAME" %p: begin offs:%ld frames:%ld to_read:%ld thres:%d", state, + offset, frames, to_read, state->threshold); + } else { + my_areas = NULL; + offset = 0; } - spa_log_trace_fp(state->log, NAME" %p: begin offs:%ld frames:%ld to_read:%ld thres:%d", state, - offset, frames, to_read, state->threshold); - read = push_frames(state, my_areas, offset, frames, state->delay); - spa_log_trace_fp(state->log, NAME" %p: commit offs:%ld read:%ld count:%"PRIi64, state, - offset, read, state->sample_count); total_read += read; - if ((commitres = snd_pcm_mmap_commit(hndl, offset, read)) < 0) { - spa_log_error(state->log, NAME" %p: snd_pcm_mmap_commit error: %s", - state, snd_strerror(commitres)); - if (commitres != -EPIPE && commitres != -ESTRPIPE) - return res; - } + if (state->use_mmap) { + spa_log_trace_fp(state->log, NAME" %p: commit offs:%ld read:%ld count:%"PRIi64, state, + offset, read, state->sample_count); + if ((commitres = snd_pcm_mmap_commit(hndl, offset, read)) < 0) { + spa_log_error(state->log, NAME" %p: snd_pcm_mmap_commit error: %s", + state, snd_strerror(commitres)); + if (commitres != -EPIPE && commitres != -ESTRPIPE) + return res; + } - if (commitres > 0 && read != (snd_pcm_uframes_t) commitres) { - spa_log_warn(state->log, NAME" %p: mmap_commit read %ld instead of %ld", - state, commitres, read); + if (commitres > 0 && read != (snd_pcm_uframes_t) commitres) { + spa_log_warn(state->log, NAME" %p: mmap_commit read %ld instead of %ld", + state, commitres, read); + } } state->sample_count += total_read; @@ -1169,7 +1266,7 @@ static int handle_play(struct state *state, uint64_t nsec, res = spa_node_call_ready(&state->callbacks, SPA_STATUS_NEED_DATA); } else { - res = spa_alsa_write(state, 0); + res = spa_alsa_write(state); } return res; } @@ -1349,17 +1446,12 @@ int spa_alsa_start(struct state *state) reset_buffers(state); state->alsa_sync = true; state->alsa_recovering = false; + state->alsa_started = false; if (state->stream == SND_PCM_STREAM_PLAYBACK) { - state->alsa_started = false; - spa_alsa_write(state, state->threshold * 2); + spa_alsa_silence(state, state->threshold * 2); } else { - if ((err = snd_pcm_start(state->hndl)) < 0) { - spa_log_error(state->log, NAME" %p: snd_pcm_start: %s", state, - snd_strerror(err)); - return err; - } - state->alsa_started = true; + do_start(state); } set_timers(state); diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 55574362b..8f823c724 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -159,6 +159,8 @@ struct state { unsigned int following:1; unsigned int matching:1; unsigned int resample:1; + unsigned int use_mmap:1; + unsigned int planar:1; int64_t sample_count; @@ -186,7 +188,7 @@ int spa_alsa_reassign_follower(struct state *state); int spa_alsa_pause(struct state *state); int spa_alsa_close(struct state *state); -int spa_alsa_write(struct state *state, snd_pcm_uframes_t silence); +int spa_alsa_write(struct state *state); int spa_alsa_read(struct state *state, snd_pcm_uframes_t silence); void spa_alsa_recycle_buffer(struct state *state, uint32_t buffer_id);