| /* 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 <alsa/asoundlib.h> |
| #include <limits.h> |
| #include <stdlib.h> |
| #include <syslog.h> |
| |
| #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 |
| |
| /* Limit the number of channels supported for devices: b/158509536 */ |
| #define TEMP_CHANNEL_LIMIT 20 |
| |
| /* 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) { |
| // Pull the max channel count and use that. |
| unsigned int max_channels = 0; |
| rc = snd_pcm_hw_params_get_channels_max(params, &max_channels); |
| if (rc < 0) { |
| syslog(LOG_WARNING, "No valid channel counts found."); |
| return -EINVAL; |
| } else if (max_channels > TEMP_CHANNEL_LIMIT) { |
| syslog(LOG_WARNING, "Can't support so many channels."); |
| return -EINVAL; |
| } else { |
| (*channel_counts)[0] = (size_t)max_channels; |
| (*channel_counts)[1] = 0; |
| } |
| } |
| |
| 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 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; |
| } |
| |
| 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; |
| } |