/* * Copyright © 2017 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * Authors: * Paul Kocialkowski */ #include "config.h" #include #include "igt_alsa.h" #include "igt_aux.h" #include "igt_core.h" #include "igt_eld.h" #define HANDLES_MAX 8 /** * SECTION:igt_alsa * @short_description: Library with ALSA helpers * @title: ALSA * @include: igt_alsa.h * * This library contains helpers for ALSA playback and capture. */ struct alsa { snd_pcm_t *output_handles[HANDLES_MAX]; int output_handles_count; snd_pcm_format_t output_format; int output_sampling_rate; int output_channels; int (*output_callback)(void *data, void *buffer, int samples); void *output_callback_data; int output_samples_trigger; }; /** * alsa_has_exclusive_access: * Check whether ALSA has exclusive access to audio devices. Fails if * PulseAudio is running. */ bool alsa_has_exclusive_access(void) { if (igt_is_process_running("pulseaudio")) { igt_warn("alsa doesn't have exclusive access to audio devices\n"); igt_warn("It seems that PulseAudio is running. Audio tests " "need direct access to audio devices, so PulseAudio " "needs to be stopped. You can do so by running " "`pulseaudio --kill`. Also make sure to add " "autospawn=no to /etc/pulse/client.conf\n"); return false; } return true; } static void alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...) { if (err) igt_debug("[ALSA] %s: %s\n", function, snd_strerror(err)); } /** * alsa_init: * Allocate and initialize an alsa structure and configure the error handler. * * Returns: A newly-allocated alsa structure */ struct alsa *alsa_init(void) { struct alsa *alsa; if (!alsa_has_exclusive_access()) { return NULL; } alsa = malloc(sizeof(struct alsa)); memset(alsa, 0, sizeof(struct alsa)); /* Redirect errors to igt_debug instead of stderr. */ snd_lib_error_set_handler(alsa_error_handler); return alsa; } static bool alsa_dev_has_igt_eld(snd_ctl_t *ctl, int dev) { snd_hctl_t *handle; snd_hctl_elem_t *elem; snd_ctl_elem_info_t *info; snd_ctl_elem_value_t *value; const char *name; snd_ctl_elem_type_t type; unsigned int count; const char *eld; bool is_igt, eld_found; igt_assert(snd_hctl_open_ctl(&handle, ctl) == 0); igt_assert(snd_hctl_load(handle) == 0); igt_assert(snd_ctl_elem_info_malloc(&info) == 0); igt_assert(snd_ctl_elem_value_malloc(&value) == 0); is_igt = eld_found = false; for (elem = snd_hctl_first_elem(handle); elem; elem = snd_hctl_elem_next(elem)) { igt_assert(snd_hctl_elem_info(elem, info) == 0); name = snd_ctl_elem_info_get_name(info); type = snd_ctl_elem_info_get_type(info); count = snd_ctl_elem_info_get_count(info); if (strcmp(name, "ELD") != 0) continue; igt_assert(type == SND_CTL_ELEM_TYPE_BYTES); if (snd_ctl_elem_info_get_device(info) != dev) continue; eld_found = true; igt_assert(snd_hctl_elem_read(elem, value) == 0); if (count == 0) { igt_debug("ELD found for device %s,%d, but it's empty " "(the screen is probably disconnected)\n", snd_hctl_name(handle), dev); break; } igt_debug("ELD found for device %s,%d\n", snd_hctl_name(handle), dev); eld = snd_ctl_elem_value_get_bytes(value); is_igt = eld_is_igt(eld, count); break; } if (!eld_found) { /* ELDs are probably not supported */ igt_debug("ELD not found for device %s,%d, skipping ELD check\n", snd_hctl_name(handle), dev); is_igt = true; } snd_ctl_elem_info_free(info); snd_ctl_elem_value_free(value); snd_hctl_free(handle); return is_igt; } static char *alsa_resolve_indentifier(const char *device_name, int skip) { snd_ctl_card_info_t *card_info; snd_pcm_info_t *pcm_info; snd_ctl_t *handle = NULL; const char *pcm_name; char *identifier = NULL; char name[32]; int card = -1; int dev; int ret; snd_ctl_card_info_alloca(&card_info); snd_pcm_info_alloca(&pcm_info); /* First try to open the device as-is. */ if (!skip) { ret = snd_ctl_open(&handle, device_name, 0); if (!ret) { identifier = strdup(device_name); goto resolved; } } do { ret = snd_card_next(&card); if (ret < 0 || card < 0) break; snprintf(name, sizeof(name), "hw:%d", card); ret = snd_ctl_open(&handle, name, 0); if (ret < 0) continue; ret = snd_ctl_card_info(handle, card_info); if (ret < 0) { snd_ctl_close(handle); handle = NULL; continue; } dev = -1; do { ret = snd_ctl_pcm_next_device(handle, &dev); if (ret < 0 || dev < 0) break; snd_pcm_info_set_device(pcm_info, dev); snd_pcm_info_set_subdevice(pcm_info, 0); ret = snd_ctl_pcm_info(handle, pcm_info); if (ret < 0) continue; pcm_name = snd_pcm_info_get_name(pcm_info); if (!pcm_name) continue; ret = strncmp(device_name, pcm_name, strlen(device_name)); if (ret != 0) continue; if (!alsa_dev_has_igt_eld(handle, dev)) { igt_debug("Device hw:%d,%d matches the name " "but doesn't have an IGT ELD\n", card, dev); continue; } if (skip > 0) { skip--; continue; } igt_debug("Matched device \"%s\" (hw:%d,%d)\n", pcm_name, card, dev); snprintf(name, sizeof(name), "hw:%d,%d", card, dev); identifier = strdup(name); goto resolved; } while (dev >= 0); snd_ctl_close(handle); handle = NULL; } while (card >= 0); resolved: if (handle) snd_ctl_close(handle); return identifier; } /** * alsa_open_output: * @alsa: The target alsa structure * @device_name: The name prefix of the output device(s) to open * * Open ALSA output devices whose name prefixes match the provided name prefix. * * Returns: An integer equal to zero for success and negative for failure */ int alsa_open_output(struct alsa *alsa, const char *device_name) { snd_pcm_t *handle; char *identifier; int skip; int index; int ret; skip = alsa->output_handles_count; index = alsa->output_handles_count; while (index < HANDLES_MAX) { identifier = alsa_resolve_indentifier(device_name, skip++); if (!identifier) break; ret = snd_pcm_open(&handle, identifier, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (ret < 0) { free(identifier); continue; } igt_debug("Opened output %s\n", identifier); alsa->output_handles[index++] = handle; free(identifier); } if (index == 0) return -1; alsa->output_handles_count = index; return 0; } /** * alsa_close_output: * @alsa: The target alsa structure * * Close all the open ALSA outputs. */ void alsa_close_output(struct alsa *alsa) { snd_pcm_t *handle; int i; for (i = 0; i < alsa->output_handles_count; i++) { handle = alsa->output_handles[i]; if (!handle) continue; snd_pcm_close(handle); alsa->output_handles[i] = NULL; } alsa->output_handles_count = 0; alsa->output_callback = NULL; } static bool alsa_test_configuration(snd_pcm_t *handle, snd_pcm_format_t fmt, int channels, int sampling_rate) { snd_pcm_hw_params_t *params; int ret; unsigned int min_channels, max_channels; unsigned int min_rate, max_rate; int min_rate_dir, max_rate_dir; snd_pcm_hw_params_alloca(¶ms); ret = snd_pcm_hw_params_any(handle, params); if (ret < 0) return false; ret = snd_pcm_hw_params_test_format(handle, params, fmt); if (ret < 0) { igt_debug("Output device doesn't support the format %s\n", snd_pcm_format_name(fmt)); return false; } ret = snd_pcm_hw_params_test_rate(handle, params, sampling_rate, 0); if (ret < 0) { snd_pcm_hw_params_get_rate_min(params, &min_rate, &min_rate_dir); snd_pcm_hw_params_get_rate_max(params, &max_rate, &max_rate_dir); igt_debug("Output device supports rates between %u and %u, " "requested %d\n", min_rate, max_rate, sampling_rate); return false; } ret = snd_pcm_hw_params_test_channels(handle, params, channels); if (ret < 0) { snd_pcm_hw_params_get_channels_min(params, &min_channels); snd_pcm_hw_params_get_channels_max(params, &max_channels); igt_debug("Output device supports between %u and " "%u channels, requested %d\n", min_channels, max_channels, channels); return false; } return true; } /** * alsa_test_output_configuration: * @alsa: The target alsa structure * @fmt: The format to test * @channels: The number of channels to test * @sampling_rate: The sampling rate to test * * Test the output configuration specified by @channels and @sampling_rate * for the output devices. * * Returns: A boolean indicating whether the test succeeded */ bool alsa_test_output_configuration(struct alsa *alsa, snd_pcm_format_t fmt, int channels, int sampling_rate) { snd_pcm_t *handle; bool ret; int i; for (i = 0; i < alsa->output_handles_count; i++) { handle = alsa->output_handles[i]; ret = alsa_test_configuration(handle, fmt, channels, sampling_rate); if (!ret) return false; } return true; } /** * alsa_configure_output: * @alsa: The target alsa structure * @channels: The number of channels to test * @sampling_rate: The sampling rate to test * * Configure the output devices with the configuration specified by @channels * and @sampling_rate. */ void alsa_configure_output(struct alsa *alsa, snd_pcm_format_t fmt, int channels, int sampling_rate) { snd_pcm_t *handle; int ret; int i; int soft_resample = 0; /* Don't allow ALSA to resample */ unsigned int latency = 0; for (i = 0; i < alsa->output_handles_count; i++) { handle = alsa->output_handles[i]; ret = snd_pcm_set_params(handle, fmt, SND_PCM_ACCESS_RW_INTERLEAVED, channels, sampling_rate, soft_resample, latency); igt_assert(ret >= 0); } alsa->output_format = fmt; alsa->output_channels = channels; alsa->output_sampling_rate = sampling_rate; } /** * alsa_register_output_callback: * @alsa: The target alsa structure * @callback: The callback function to call to fill output data * @callback_data: The data pointer to pass to the callback function * @samples_trigger: The required number of samples to trigger the callback * * Register a callback function to be called to fill output data during a run. * The callback is called when @samples_trigger samples are required. * * The callback should return an integer equal to zero for success and negative * for failure. */ void alsa_register_output_callback(struct alsa *alsa, int (*callback)(void *data, void *buffer, int samples), void *callback_data, int samples_trigger) { alsa->output_callback = callback; alsa->output_callback_data = callback_data; alsa->output_samples_trigger = samples_trigger; } /** * alsa_run: * @alsa: The target alsa structure * @duration_ms: The maximum duration of the run in milliseconds, or -1 for an * infinite duration. * * Run ALSA playback and capture on the input and output devices for at * most @duration_ms milliseconds, calling the registered callbacks when needed. * * Returns: An integer equal to zero for success, positive for a stop caused * by the input callback and negative for failure */ int alsa_run(struct alsa *alsa, int duration_ms) { snd_pcm_t *handle; char *output_buffer = NULL; int output_limit; int output_total = 0; int output_counts[alsa->output_handles_count]; bool output_ready = false; int output_channels; int bytes_per_sample; int output_trigger; bool reached; int index; int count; int avail; int i; int ret; output_limit = alsa->output_sampling_rate * duration_ms / 1000; output_channels = alsa->output_channels; bytes_per_sample = snd_pcm_format_physical_width(alsa->output_format) / 8; output_trigger = alsa->output_samples_trigger; output_buffer = malloc(output_channels * output_trigger * bytes_per_sample); do { reached = true; if (output_limit < 0 || output_total < output_limit) { reached = false; if (!output_ready) { for (i = 0; i < alsa->output_handles_count; i++) output_counts[i] = 0; ret = alsa->output_callback(alsa->output_callback_data, output_buffer, output_trigger); if (ret < 0) goto complete; } for (i = 0; i < alsa->output_handles_count; i++) { handle = alsa->output_handles[i]; ret = snd_pcm_avail(handle); if (output_counts[i] < output_trigger && ret > 0) { index = output_counts[i] * output_channels; count = output_trigger - output_counts[i]; avail = snd_pcm_avail(handle); count = avail < count ? avail : count; ret = snd_pcm_writei(handle, &output_buffer[index * bytes_per_sample], count); if (ret < 0) { ret = snd_pcm_recover(handle, ret, 0); if (ret < 0) { igt_debug("snd_pcm_recover after snd_pcm_writei failed"); goto complete; } } output_counts[i] += ret; } else if (output_counts[i] < output_trigger && ret < 0) { ret = snd_pcm_recover(handle, ret, 0); if (ret < 0) { igt_debug("snd_pcm_recover failed"); goto complete; } } } output_ready = false; for (i = 0; i < alsa->output_handles_count; i++) if (output_counts[i] < output_trigger) output_ready = true; if (!output_ready) output_total += output_trigger; } } while (!reached); ret = 0; complete: free(output_buffer); return ret; }