/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include #include #include #include #include "cras_alsa_helpers.h" #include "cras_audio_format.h" #include "cras_util.h" /* Macro to convert between snd_pcm_chmap_position(defined in * alsa-lib since 1.0.27) and CRAS_CHANNEL, values of which are * of the same order but shifted by 3. */ #define CH_TO_ALSA(ch) ((ch) + 3) #define CH_TO_CRAS(ch) ((ch) - 3) /* Assert the channel is defined in CRAS_CHANNELS. */ #define ALSA_CH_VALID(ch) ((ch >= SND_CHMAP_FL) && (ch <= SND_CHMAP_FRC)) /* Time difference between two consecutive underrun logs. */ #define UNDERRUN_LOG_TIME_SECS 30 /* Chances to give mmap_begin to work. */ static const size_t MAX_MMAP_BEGIN_ATTEMPTS = 3; /* Time to sleep between resume attempts. */ static const size_t ALSA_SUSPENDED_SLEEP_TIME_US = 250000; /* What rates should we check for on this dev? * Listed in order of preference. 0 terminalted. */ static const size_t test_sample_rates[] = { 44100, 48000, 32000, 96000, 22050, 16000, 8000, 4000, 192000, 0 }; /* What channel counts shoud be checked on this dev? * Listed in order of preference. 0 terminalted. */ static const size_t test_channel_counts[] = { 10, 6, 4, 2, 1, 8, 0 }; static const snd_pcm_format_t test_formats[] = { SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S24_LE, SND_PCM_FORMAT_S32_LE, SND_PCM_FORMAT_S24_3LE, (snd_pcm_format_t)0 }; /* Looks up the list of channel map for the one can exactly matches * the layout specified in fmt. */ static snd_pcm_chmap_query_t *cras_chmap_caps_match( snd_pcm_chmap_query_t **chmaps, struct cras_audio_format *fmt) { size_t ch, i; int idx, matches; snd_pcm_chmap_query_t **chmap; /* Search for channel map that already matches the order */ for (chmap = chmaps; *chmap; chmap++) { if ((*chmap)->map.channels != fmt->num_channels) continue; matches = 1; for (ch = 0; ch < CRAS_CH_MAX; ch++) { idx = fmt->channel_layout[ch]; if (idx == -1) continue; if ((unsigned)idx >= (*chmap)->map.channels) continue; if ((*chmap)->map.pos[idx] != CH_TO_ALSA(ch)) { matches = 0; break; } } if (matches) return *chmap; } /* Search for channel map that can arbitrarily swap order */ for (chmap = chmaps; *chmap; chmap++) { if ((*chmap)->type == SND_CHMAP_TYPE_FIXED || (*chmap)->map.channels != fmt->num_channels) continue; matches = 1; for (ch = 0; ch < CRAS_CH_MAX; ch++) { idx = fmt->channel_layout[ch]; if (idx == -1) continue; int found = 0; for (i = 0; i < fmt->num_channels; i++) { if ((*chmap)->map.pos[i] == CH_TO_ALSA(ch)) { found = 1; break; } } if (found == 0) { matches = 0; break; } } if (matches && (*chmap)->type == SND_CHMAP_TYPE_VAR) return *chmap; /* Check if channel map is a match by arbitrarily swap * pair order */ matches = 1; for (i = 0; i < fmt->num_channels; i += 2) { ch = CH_TO_CRAS((*chmap)->map.pos[i]); if (fmt->channel_layout[ch] & 0x01) { matches = 0; break; } if (fmt->channel_layout[ch] + 1 != fmt->channel_layout[CH_TO_CRAS( (*chmap)->map.pos[i + 1])]) { matches = 0; break; } } if (matches) return *chmap; } return NULL; } /* When the exact match does not exist, select the best valid * channel map which can be supported by means of channel conversion * matrix. */ static snd_pcm_chmap_query_t *cras_chmap_caps_conv_matrix( snd_pcm_chmap_query_t **chmaps, struct cras_audio_format *fmt) { float **conv_mtx; size_t i; snd_pcm_chmap_query_t **chmap; struct cras_audio_format *conv_fmt; conv_fmt = cras_audio_format_create(fmt->format, fmt->frame_rate, fmt->num_channels); for (chmap = chmaps; *chmap; chmap++) { if ((*chmap)->map.channels != fmt->num_channels) continue; for (i = 0; i < CRAS_CH_MAX; i++) conv_fmt->channel_layout[i] = -1; for (i = 0; i < conv_fmt->num_channels; i++) { if (!ALSA_CH_VALID((*chmap)->map.pos[i])) continue; conv_fmt->channel_layout[CH_TO_CRAS( (*chmap)->map.pos[i])] = i; } /* Examine channel map by test creating a conversion matrix * for each candidate. Once a non-null matrix is created, * that channel map is considered supported and select it as * the best match one. */ conv_mtx = cras_channel_conv_matrix_create(fmt, conv_fmt); if (conv_mtx) { cras_channel_conv_matrix_destroy(conv_mtx, conv_fmt->num_channels); cras_audio_format_destroy(conv_fmt); return *chmap; } } cras_audio_format_destroy(conv_fmt); return NULL; } /* Finds the best channel map for given format and list of channel * map capability. */ static snd_pcm_chmap_query_t *cras_chmap_caps_best( snd_pcm_t *handle, snd_pcm_chmap_query_t **chmaps, struct cras_audio_format *fmt) { snd_pcm_chmap_query_t **chmap; snd_pcm_chmap_query_t *match; match = cras_chmap_caps_match(chmaps, fmt); if (match) return match; match = cras_chmap_caps_conv_matrix(chmaps, fmt); if (match) return match; /* For capture stream, choose the first chmap matching channel * count. Channel positions reported in this chmap will be used * to fill correspond channels into client stream. */ if (snd_pcm_stream(handle) == SND_PCM_STREAM_CAPTURE) for (chmap = chmaps; *chmap; chmap++) if ((*chmap)->map.channels == fmt->num_channels) return *chmap; return NULL; } int cras_alsa_pcm_open(snd_pcm_t **handle, const char *dev, snd_pcm_stream_t stream) { int rc; int retries = 3; static const unsigned int OPEN_RETRY_DELAY_US = 100000; retry_open: rc = snd_pcm_open(handle, dev, stream, SND_PCM_NONBLOCK | SND_PCM_NO_AUTO_RESAMPLE | SND_PCM_NO_AUTO_CHANNELS | SND_PCM_NO_AUTO_FORMAT); if (rc == -EBUSY && --retries) { usleep(OPEN_RETRY_DELAY_US); goto retry_open; } return rc; } int cras_alsa_pcm_close(snd_pcm_t *handle) { return snd_pcm_close(handle); } int cras_alsa_pcm_start(snd_pcm_t *handle) { return snd_pcm_start(handle); } int cras_alsa_pcm_drain(snd_pcm_t *handle) { return snd_pcm_drain(handle); } int cras_alsa_resume_appl_ptr(snd_pcm_t *handle, snd_pcm_uframes_t ahead) { int rc; snd_pcm_uframes_t period_frames, buffer_frames; snd_pcm_sframes_t to_move, avail_frames; rc = snd_pcm_avail(handle); if (rc == -EPIPE || rc == -ESTRPIPE) { cras_alsa_attempt_resume(handle); avail_frames = 0; } else if (rc < 0) { syslog(LOG_ERR, "Fail to get avail frames: %s", snd_strerror(rc)); return rc; } else { avail_frames = rc; } rc = snd_pcm_get_params(handle, &buffer_frames, &period_frames); if (rc < 0) { syslog(LOG_ERR, "Fail to get buffer size: %s", snd_strerror(rc)); return rc; } to_move = avail_frames - buffer_frames + ahead; if (to_move > 0) { rc = snd_pcm_forward(handle, to_move); } else if (to_move < 0) { rc = snd_pcm_rewind(handle, -to_move); } else { return 0; } if (rc < 0) { syslog(LOG_ERR, "Fail to resume appl_ptr: %s", snd_strerror(rc)); return rc; } return 0; } int cras_alsa_set_channel_map(snd_pcm_t *handle, struct cras_audio_format *fmt) { size_t i, ch; snd_pcm_chmap_query_t **chmaps; snd_pcm_chmap_query_t *match; if (fmt->num_channels <= 2) return 0; chmaps = snd_pcm_query_chmaps(handle); if (chmaps == NULL) { syslog(LOG_WARNING, "No chmap queried! Skip chmap set"); goto done; } match = cras_chmap_caps_best(handle, chmaps, fmt); if (!match) { syslog(LOG_ERR, "Unable to find the best channel map"); goto done; } /* A channel map could match the layout after channels * pair/arbitrary swapped. Modified the channel positions * before set to HW. */ for (i = 0; i < fmt->num_channels; i++) { for (ch = 0; ch < CRAS_CH_MAX; ch++) if (fmt->channel_layout[ch] == (int)i) break; if (ch != CRAS_CH_MAX) match->map.pos[i] = CH_TO_ALSA(ch); } if (snd_pcm_set_chmap(handle, &match->map) != 0) syslog(LOG_ERR, "Unable to set channel map"); done: snd_pcm_free_chmaps(chmaps); return 0; } int cras_alsa_get_channel_map(snd_pcm_t *handle, struct cras_audio_format *fmt) { snd_pcm_chmap_query_t **chmaps; snd_pcm_chmap_query_t *match; int rc = 0; size_t i; chmaps = snd_pcm_query_chmaps(handle); if (chmaps == NULL) { rc = -EINVAL; goto done; } match = cras_chmap_caps_best(handle, chmaps, fmt); if (!match) { syslog(LOG_ERR, "Unable to find the best channel map"); rc = -1; goto done; } /* Fill back the selected channel map so channel converter can * handle it. */ for (i = 0; i < CRAS_CH_MAX; i++) fmt->channel_layout[i] = -1; for (i = 0; i < fmt->num_channels; i++) { if (!ALSA_CH_VALID(match->map.pos[i])) continue; fmt->channel_layout[CH_TO_CRAS(match->map.pos[i])] = i; } /* Handle the special channel map {SND_CHMAP_MONO} */ if (match->map.channels == 1 && match->map.pos[0] == SND_CHMAP_MONO) fmt->channel_layout[CRAS_CH_FC] = 0; done: snd_pcm_free_chmaps(chmaps); return rc; } int cras_alsa_fill_properties(snd_pcm_t *handle, size_t **rates, size_t **channel_counts, snd_pcm_format_t **formats) { int rc; size_t i, num_found; snd_pcm_hw_params_t *params; snd_pcm_hw_params_alloca(¶ms); rc = snd_pcm_hw_params_any(handle, params); if (rc < 0) { syslog(LOG_ERR, "snd_pcm_hw_params_any: %s", snd_strerror(rc)); return rc; } *rates = (size_t *)malloc(sizeof(test_sample_rates)); if (*rates == NULL) return -ENOMEM; *channel_counts = (size_t *)malloc(sizeof(test_channel_counts)); if (*channel_counts == NULL) { free(*rates); return -ENOMEM; } *formats = (snd_pcm_format_t *)malloc(sizeof(test_formats)); if (*formats == NULL) { free(*channel_counts); free(*rates); return -ENOMEM; } num_found = 0; for (i = 0; test_sample_rates[i] != 0; i++) { rc = snd_pcm_hw_params_test_rate(handle, params, test_sample_rates[i], 0); if (rc == 0) (*rates)[num_found++] = test_sample_rates[i]; } (*rates)[num_found] = 0; if (num_found == 0) { syslog(LOG_WARNING, "No valid sample rates."); return -EINVAL; } num_found = 0; for (i = 0; test_channel_counts[i] != 0; i++) { rc = snd_pcm_hw_params_test_channels(handle, params, test_channel_counts[i]); if (rc == 0) (*channel_counts)[num_found++] = test_channel_counts[i]; } (*channel_counts)[num_found] = 0; if (num_found == 0) { syslog(LOG_WARNING, "No valid channel counts found."); return -EINVAL; } num_found = 0; for (i = 0; test_formats[i] != 0; i++) { rc = snd_pcm_hw_params_test_format(handle, params, test_formats[i]); if (rc == 0) (*formats)[num_found++] = test_formats[i]; } (*formats)[num_found] = (snd_pcm_format_t)0; if (num_found == 0) { syslog(LOG_WARNING, "No valid sample formats."); return -EINVAL; } return 0; } int cras_alsa_set_hwparams(snd_pcm_t *handle, struct cras_audio_format *format, snd_pcm_uframes_t *buffer_frames, int period_wakeup, unsigned int dma_period_time) { unsigned int rate, ret_rate; int err; snd_pcm_hw_params_t *hwparams; rate = format->frame_rate; snd_pcm_hw_params_alloca(&hwparams); err = snd_pcm_hw_params_any(handle, hwparams); if (err < 0) { syslog(LOG_ERR, "hw_params_any failed %s\n", snd_strerror(err)); return err; } /* Disable hardware resampling. */ err = snd_pcm_hw_params_set_rate_resample(handle, hwparams, 0); if (err < 0) { syslog(LOG_ERR, "Disabling resampling %s\n", snd_strerror(err)); return err; } /* Always interleaved. */ err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED); if (err < 0) { syslog(LOG_ERR, "Setting interleaved %s\n", snd_strerror(err)); return err; } /* If period_wakeup flag is not set, try to disable ALSA wakeups, * we'll keep a timer. */ if (!period_wakeup && snd_pcm_hw_params_can_disable_period_wakeup(hwparams)) { err = snd_pcm_hw_params_set_period_wakeup(handle, hwparams, 0); if (err < 0) syslog(LOG_WARNING, "disabling wakeups %s\n", snd_strerror(err)); } /* Setup the period time so that the hardware pulls the right amount * of data at the right time. */ if (dma_period_time) { int dir = 0; unsigned int original = dma_period_time; err = snd_pcm_hw_params_set_period_time_near( handle, hwparams, &dma_period_time, &dir); if (err < 0) { syslog(LOG_ERR, "could not set period time: %s", snd_strerror(err)); return err; } else if (original != dma_period_time) { syslog(LOG_DEBUG, "period time set to: %u", dma_period_time); } } /* Set the sample format. */ err = snd_pcm_hw_params_set_format(handle, hwparams, format->format); if (err < 0) { syslog(LOG_ERR, "set format %s\n", snd_strerror(err)); return err; } /* Set the stream rate. */ ret_rate = rate; err = snd_pcm_hw_params_set_rate_near(handle, hwparams, &ret_rate, 0); if (err < 0) { syslog(LOG_ERR, "set_rate_near %iHz %s\n", rate, snd_strerror(err)); return err; } if (ret_rate != rate) { syslog(LOG_ERR, "tried for %iHz, settled for %iHz)\n", rate, ret_rate); return -EINVAL; } /* Set the count of channels. */ err = snd_pcm_hw_params_set_channels(handle, hwparams, format->num_channels); if (err < 0) { syslog(LOG_ERR, "set_channels %s\n", snd_strerror(err)); return err; } /* Make sure buffer frames is even, or snd_pcm_hw_params will * return invalid argument error. */ err = snd_pcm_hw_params_get_buffer_size_max(hwparams, buffer_frames); if (err < 0) syslog(LOG_WARNING, "get buffer max %s\n", snd_strerror(err)); *buffer_frames &= ~0x01; err = snd_pcm_hw_params_set_buffer_size_max(handle, hwparams, buffer_frames); if (err < 0) { syslog(LOG_ERR, "set_buffer_size_max %s", snd_strerror(err)); return err; } syslog(LOG_DEBUG, "buffer size set to %u\n", (unsigned int)*buffer_frames); /* Finally, write the parameters to the device. */ err = snd_pcm_hw_params(handle, hwparams); if (err < 0) { syslog(LOG_ERR, "hw_params: %s: rate: %u, ret_rate: %u, " "channel: %zu, format: %u\n", snd_strerror(err), rate, ret_rate, format->num_channels, format->format); return err; } return 0; } int cras_alsa_set_swparams(snd_pcm_t *handle, int *enable_htimestamp) { int err; snd_pcm_sw_params_t *swparams; snd_pcm_uframes_t boundary; snd_pcm_sw_params_alloca(&swparams); err = snd_pcm_sw_params_current(handle, swparams); if (err < 0) { syslog(LOG_ERR, "sw_params_current: %s\n", snd_strerror(err)); return err; } err = snd_pcm_sw_params_get_boundary(swparams, &boundary); if (err < 0) { syslog(LOG_ERR, "get_boundary: %s\n", snd_strerror(err)); return err; } err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, boundary); if (err < 0) { syslog(LOG_ERR, "set_stop_threshold: %s\n", snd_strerror(err)); return err; } /* Don't auto start. */ err = snd_pcm_sw_params_set_start_threshold(handle, swparams, LONG_MAX); if (err < 0) { syslog(LOG_ERR, "set_stop_threshold: %s\n", snd_strerror(err)); return err; } /* Disable period events. */ err = snd_pcm_sw_params_set_period_event(handle, swparams, 0); if (err < 0) { syslog(LOG_ERR, "set_period_event: %s\n", snd_strerror(err)); return err; } if (*enable_htimestamp) { /* Use MONOTONIC_RAW time-stamps. */ err = snd_pcm_sw_params_set_tstamp_type( handle, swparams, SND_PCM_TSTAMP_TYPE_MONOTONIC_RAW); if (err < 0) { syslog(LOG_ERR, "set_tstamp_type: %s\n", snd_strerror(err)); return err; } err = snd_pcm_sw_params_set_tstamp_mode( handle, swparams, SND_PCM_TSTAMP_ENABLE); if (err < 0) { syslog(LOG_ERR, "set_tstamp_mode: %s\n", snd_strerror(err)); return err; } } /* This hack is required because ALSA-LIB does not provide any way to * detect whether MONOTONIC_RAW timestamps are supported by the kernel. * In ALSA-LIB, the code checks the hardware protocol version. */ err = snd_pcm_sw_params(handle, swparams); if (err == -EINVAL && *enable_htimestamp) { *enable_htimestamp = 0; syslog(LOG_WARNING, "MONOTONIC_RAW timestamps are not supported."); err = snd_pcm_sw_params_set_tstamp_type( handle, swparams, SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY); if (err < 0) { syslog(LOG_ERR, "set_tstamp_type: %s\n", snd_strerror(err)); return err; } err = snd_pcm_sw_params_set_tstamp_mode( handle, swparams, SND_PCM_TSTAMP_NONE); if (err < 0) { syslog(LOG_ERR, "set_tstamp_mode: %s\n", snd_strerror(err)); return err; } err = snd_pcm_sw_params(handle, swparams); } if (err < 0) { syslog(LOG_ERR, "sw_params: %s\n", snd_strerror(err)); return err; } return 0; } int cras_alsa_get_avail_frames(snd_pcm_t *handle, snd_pcm_uframes_t buf_size, snd_pcm_uframes_t severe_underrun_frames, const char *dev_name, snd_pcm_uframes_t *avail, struct timespec *tstamp) { snd_pcm_sframes_t frames; int rc = 0; static struct timespec tstamp_last_underrun_log = {.tv_sec = 0, .tv_nsec = 0}; /* Use snd_pcm_avail still to ensure that the hardware pointer is * up to date. Otherwise, we could use the deprecated snd_pcm_hwsync(). * IMO this is a deficiency in the ALSA API. */ frames = snd_pcm_avail(handle); if (frames >= 0) rc = snd_pcm_htimestamp(handle, avail, tstamp); else rc = frames; if (rc == -EPIPE || rc == -ESTRPIPE) { cras_alsa_attempt_resume(handle); rc = 0; goto error; } else if (rc < 0) { syslog(LOG_ERR, "pcm_avail error %s, %s\n", dev_name, snd_strerror(rc)); goto error; } else if (frames > (snd_pcm_sframes_t)buf_size) { struct timespec tstamp_now; clock_gettime(CLOCK_MONOTONIC_RAW, &tstamp_now); /* Limit the log rate. */ if ((tstamp_now.tv_sec - tstamp_last_underrun_log.tv_sec) > UNDERRUN_LOG_TIME_SECS) { syslog(LOG_ERR, "pcm_avail returned frames larger than buf_size: " "%s: %ld > %lu\n", dev_name, frames, buf_size); tstamp_last_underrun_log.tv_sec = tstamp_now.tv_sec; tstamp_last_underrun_log.tv_nsec = tstamp_now.tv_nsec; } if ((frames - (snd_pcm_sframes_t)buf_size) > (snd_pcm_sframes_t)severe_underrun_frames) { rc = -EPIPE; goto error; } else { frames = buf_size; } } *avail = frames; return 0; error: *avail = 0; tstamp->tv_sec = 0; tstamp->tv_nsec = 0; return rc; } int cras_alsa_get_delay_frames(snd_pcm_t *handle, snd_pcm_uframes_t buf_size, snd_pcm_sframes_t *delay) { int rc; rc = snd_pcm_delay(handle, delay); if (rc < 0) return rc; if (*delay > (snd_pcm_sframes_t)buf_size) *delay = buf_size; if (*delay < 0) *delay = 0; return 0; } /* * Attempts to resume a PCM. * Note that this path does not get executed for default playback/capture * stream. Default playback/capture stream are removed from the device * upon suspend, and re-attached to the device after resume. * The only stream that lives across suspend resume is hotword stream. */ int cras_alsa_attempt_resume(snd_pcm_t *handle) { int rc; syslog(LOG_INFO, "System suspended."); while ((rc = snd_pcm_resume(handle)) == -EAGAIN) usleep(ALSA_SUSPENDED_SLEEP_TIME_US); if (rc < 0) { /* * Some devices do not support snd_pcm_resume, that is * acceptable. */ syslog(LOG_INFO, "System suspended, failed to resume %s.", snd_strerror(rc)); rc = snd_pcm_prepare(handle); if (rc < 0) { syslog(LOG_ERR, "Suspended, failed to prepare: %s.", snd_strerror(rc)); } /* * CRAS does not use auto-start (start_threshold = 0), so start * PCM after it is prepared. This is only for hotword stream. */ rc = snd_pcm_start(handle); if (rc < 0) { syslog(LOG_ERR, "Suspended, failed to start: %s.", snd_strerror(rc)); } } return rc; } int cras_alsa_mmap_get_whole_buffer(snd_pcm_t *handle, uint8_t **dst) { snd_pcm_uframes_t offset; /* The purpose of calling cras_alsa_mmap_begin is to get the base * address of the buffer. The requested and retrieved frames are not * meaningful here. * However, we need to set a non-zero requested frames to get a * non-zero retrieved frames. This is to avoid the error checking in * snd_pcm_mmap_begin, where it judges retrieved frames being 0 as a * failure. */ snd_pcm_uframes_t frames = 1; return cras_alsa_mmap_begin(handle, 0, dst, &offset, &frames); } int cras_alsa_mmap_begin(snd_pcm_t *handle, unsigned int format_bytes, uint8_t **dst, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames) { int rc; unsigned int attempts = 0; const snd_pcm_channel_area_t *my_areas; while (attempts++ < MAX_MMAP_BEGIN_ATTEMPTS) { rc = snd_pcm_mmap_begin(handle, &my_areas, offset, frames); if (rc == -ESTRPIPE) { /* First handle suspend/resume. */ rc = cras_alsa_attempt_resume(handle); if (rc < 0) return rc; continue; /* Recovered from suspend, try again. */ } else if (rc < 0) { /* If we can recover, continue and try again. */ if (snd_pcm_recover(handle, rc, 0) == 0) continue; syslog(LOG_INFO, "recover failed begin: %s\n", snd_strerror(rc)); return rc; } /* Available frames could be zero right after input pcm handle * resumed. As for output pcm handle, some error has occurred * when mmap_begin return zero frames, return -EIO for that * case. */ if (snd_pcm_stream(handle) == SND_PCM_STREAM_PLAYBACK && *frames == 0) { syslog(LOG_INFO, "mmap_begin set frames to 0."); return -EIO; } *dst = (uint8_t *)my_areas[0].addr + (*offset) * format_bytes; return 0; } return -EIO; } int cras_alsa_mmap_commit(snd_pcm_t *handle, snd_pcm_uframes_t offset, snd_pcm_uframes_t frames) { int rc; snd_pcm_sframes_t res; res = snd_pcm_mmap_commit(handle, offset, frames); if (res != (snd_pcm_sframes_t)frames) { res = res >= 0 ? (int)-EPIPE : res; if (res == -ESTRPIPE) { /* First handle suspend/resume. */ rc = cras_alsa_attempt_resume(handle); if (rc < 0) return rc; } else { /* If we can recover, continue and try again. */ rc = snd_pcm_recover(handle, res, 0); if (rc < 0) { syslog(LOG_ERR, "mmap_commit: pcm_recover failed: %s\n", snd_strerror(rc)); return rc; } } } return 0; }