And two more small changes On Thu, 2017-08-17 at 19:05 +0300, Paul Kocialkowski wrote: > This introduces an ALSA library, with dedicated helpers for handling > playback and capture. It handles ALSA device identification and > configuration as well as a run loop with callback mechanisms for > feeding > output data and handling input data. > > This library paves the way for testing audio going through display > connectors, such as HDMI. > > Signed-off-by: Paul Kocialkowski <paul.kocialkowski@xxxxxxxxxxxxxxx> > --- > configure.ac | 3 + > .../intel-gpu-tools/intel-gpu-tools-docs.xml | 1 + > lib/Makefile.am | 7 + > lib/igt.h | 1 + > lib/igt_alsa.c | 624 > +++++++++++++++++++++ > lib/igt_alsa.h | 60 ++ > 6 files changed, 696 insertions(+) > create mode 100644 lib/igt_alsa.c > create mode 100644 lib/igt_alsa.h > > diff --git a/configure.ac b/configure.ac > index 50aa86b5..e66273a4 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -219,6 +219,9 @@ if test "x$enable_chamelium" = xyes; then > AC_DEFINE(HAVE_CHAMELIUM, 1, [Enable Chamelium support]) > fi > > +PKG_CHECK_MODULES(ALSA, [alsa], [alsa=yes], [alsa=no]) > +AM_CONDITIONAL(HAVE_ALSA, [test "x$alsa" = xyes]) > + > # ------------------------------------------------------------------ > ----------- > # Configuration options > # ------------------------------------------------------------------ > ----------- > diff --git a/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml > b/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml > index c77159cf..0c34e4a5 100644 > --- a/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml > +++ b/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml > @@ -16,6 +16,7 @@ > <chapter> > <title>API Reference</title> > <xi:include href="xml/drmtest.xml"/> > + <xi:include href="xml/igt_alsa.xml"/> > <xi:include href="xml/igt_audio.xml"/> > <xi:include href="xml/igt_aux.xml"/> > <xi:include href="xml/igt_chamelium.xml"/> > diff --git a/lib/Makefile.am b/lib/Makefile.am > index 5ea08314..3ff14f66 100644 > --- a/lib/Makefile.am > +++ b/lib/Makefile.am > @@ -38,6 +38,13 @@ lib_source_list += \ > $(NULL) > endif > > +if HAVE_ALSA > +lib_source_list += \ > + igt_alsa.c \ > + igt_alsa.h \ > + $(NULL) > +endif > + > AM_CPPFLAGS = -I$(top_srcdir) > AM_CFLAGS = \ > $(CWARNFLAGS) \ > diff --git a/lib/igt.h b/lib/igt.h > index a75d2db7..ebf92349 100644 > --- a/lib/igt.h > +++ b/lib/igt.h > @@ -35,6 +35,7 @@ > #include "igt_dummyload.h" > #include "igt_fb.h" > #include "igt_frame.h" > +#include "igt_alsa.h" > #include "igt_audio.h" > #include "igt_gt.h" > #include "igt_kms.h" > diff --git a/lib/igt_alsa.c b/lib/igt_alsa.c > new file mode 100644 > index 00000000..d8bd0873 > --- /dev/null > +++ b/lib/igt_alsa.c > @@ -0,0 +1,624 @@ > +/* > + * 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 <paul.kocialkowski@xxxxxxxxxxxxxxx> > + */ > + > +#include "config.h" > + > +#include <alsa/asoundlib.h> > + > +#include "igt.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; > + int output_sampling_rate; > + int output_channels; > + > + int (*output_callback)(void *data, short *buffer, int > samples); > + void *output_callback_data; > + int output_samples_trigger; > + > + snd_pcm_t *input_handle; > + int input_sampling_rate; > + int input_channels; > + > + int (*input_callback)(void *data, short *buffer, int > samples); > + void *input_callback_data; > + int input_samples_trigger; > +}; > + > +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)); > +} Would be a good idea to add an __attribute__((printf)) here > + > +/** > + * 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; > + > + 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 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; > + int dev; > + int ret; > + > + snd_ctl_card_info_alloca(&card_info); > + snd_pcm_info_alloca(&pcm_info); > + > + card = -1; Just set card = -1 at it's declaration > + > + /* 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) { > + if (skip > 0) { > + skip--; > + continue; > + } > + > + 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_open_input: > + * @alsa: The target alsa structure > + * @device_name: The name of the input device to open > + * > + * Open the ALSA input device whose name matches the provided name > prefix. > + * > + * Returns: An integer equal to zero for success and negative for > failure > + */ > +int alsa_open_input(struct alsa *alsa, const char *device_name) > +{ > + snd_pcm_t *handle; > + char *identifier; > + int ret; > + > + identifier = alsa_resolve_indentifier(device_name, 0); > + > + ret = snd_pcm_open(&handle, device_name, > SND_PCM_STREAM_CAPTURE, > + SND_PCM_NONBLOCK); > + if (ret < 0) > + goto complete; > + > + igt_debug("Opened input %s\n", identifier); > + > + alsa->input_handle = handle; > + > + ret = 0; > + > +complete: > + free(identifier); > + > + return ret; > +} > + > +/** > + * 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_close_output: > + * @alsa: The target alsa structure > + * > + * Close the open ALSA input. > + */ > +void alsa_close_input(struct alsa *alsa) > +{ > + snd_pcm_t *handle = alsa->input_handle; > + if (!handle) > + return; > + > + snd_pcm_close(handle); > + alsa->input_handle = NULL; > +} > + > +static bool alsa_test_configuration(snd_pcm_t *handle, int channels, > + int sampling_rate) > +{ > + snd_pcm_hw_params_t *params; > + int ret; > + > + 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_rate(handle, params, > sampling_rate, 0); > + if (ret < 0) > + return false; > + > + ret = snd_pcm_hw_params_test_channels(handle, params, > channels); > + if (ret < 0) > + return false; > + > + return true; > +} > + > +/** > + * alsa_test_output_configuration: > + * @alsa: The target alsa structure > + * @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, 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, channels, > sampling_rate); > + if (!ret) > + return false; > + } > + > + return true; > +} > + > +/** > + * alsa_test_input_configuration: > + * @alsa: The target alsa structure > + * @channels: The number of channels to test > + * @sampling_rate: The sampling rate to test > + * > + * Test the input configuration specified by @channels and > @sampling_rate > + * for the input device. > + * > + * Returns: A boolean indicating whether the test succeeded > + */ > +bool alsa_test_input_configuration(struct alsa *alsa, int channels, > + int sampling_rate) > +{ > + return alsa_test_configuration(alsa->input_handle, channels, > + sampling_rate); > +} > + > +/** > + * 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, int channels, > + int sampling_rate) > +{ > + snd_pcm_t *handle; > + int ret; > + int i; > + > + for (i = 0; i < alsa->output_handles_count; i++) { > + handle = alsa->output_handles[i]; > + > + ret = snd_pcm_set_params(handle, > SND_PCM_FORMAT_S16_LE, > + SND_PCM_ACCESS_RW_INTERLEAV > ED, > + channels, sampling_rate, 0, > 0); > + igt_assert(ret >= 0); > + } > + > + alsa->output_channels = channels; > + alsa->output_sampling_rate = sampling_rate; > +} > + > +/** > + * alsa_configure_input: > + * @alsa: The target alsa structure > + * @channels: The number of channels to test > + * @sampling_rate: The sampling rate to test > + * > + * Configure the input device with the configuration specified by > @channels > + * and @sampling_rate. > + */ > +void alsa_configure_input(struct alsa *alsa, int channels, > + int sampling_rate) > +{ > + snd_pcm_t *handle; > + int ret; > + > + handle = alsa->input_handle; > + > + ret = snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE, > + SND_PCM_ACCESS_RW_INTERLEAVED, > channels, > + sampling_rate, 0, 0); > + igt_assert(ret >= 0); > + > + alsa->input_channels = channels; > + alsa->input_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, short > *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_register_input_callback: > + * @alsa: The target alsa structure > + * @callback: The callback function to call when input data is > available > + * @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 when input data is > available during > + * a run. The callback is called when @samples_trigger samples are > available. > + * > + * The callback should return an integer equal to zero for success, > negative for > + * failure and positive to indicate that the run should stop. > + */ > +void alsa_register_input_callback(struct alsa *alsa, > + int (*callback)(void *data, short > *buffer, int samples), > + void *callback_data, int > samples_trigger) > +{ > + alsa->input_callback = callback; > + alsa->input_callback_data = callback_data; > + alsa->input_samples_trigger = samples_trigger; > +} > + > +/** > + * alsa_run: > + * @alsa: The target alsa structure > + * @duration_ms: The maximum duration of the run in milliseconds > + * > + * 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; > + short *output_buffer = NULL; > + short *input_buffer = NULL; > + int output_limit; > + int output_total = 0; > + int output_counts[alsa->output_handles_count]; > + bool output_ready = false; > + int output_channels; > + int output_trigger; > + int input_limit; > + int input_total = 0; > + int input_count = 0; > + int input_channels; > + int input_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; > + output_trigger = alsa->output_samples_trigger; > + output_buffer = malloc(sizeof(short) * output_channels * > + output_trigger); > + > + if (alsa->input_callback) { > + input_limit = alsa->input_sampling_rate * > duration_ms / 1000; > + input_trigger = alsa->input_samples_trigger; > + input_channels = alsa->input_channels; > + input_buffer = malloc(sizeof(short) * input_channels > * > + input_trigger); > + } > + > + do { > + reached = true; > + > + if (output_total < output_limit) { > + reached = false; > + > + if (!output_ready) { > + output_ready = true; > + > + for (i = 0; i < alsa- > >output_handles_count; i++) > + output_counts[i] = 0; > + > + ret = alsa->output_callback(alsa- > >output_callback_data, > + output_b > uffer, > + output_t > rigger); > + if (ret < 0) > + goto complete; > + } > + > + for (i = 0; i < alsa->output_handles_count; > i++) { > + handle = alsa->output_handles[i]; > + > + if (output_counts[i] < > output_trigger && > + snd_pcm_avail(handle) > 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], > + count); > + if (ret < 0) { > + ret = > snd_pcm_recover(handle, > + > count, 0); > + if (ret < 0) > + goto > complete; > + } > + > + output_counts[i] += ret; > + } > + } > + > + 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; > + > + } > + > + if (alsa->input_callback && input_total < > input_limit) { > + reached = false; > + > + if (input_count == input_trigger) { > + input_count = 0; > + > + ret = alsa->input_callback(alsa- > >input_callback_data, > + input_buf > fer, > + input_tri > gger); > + if (ret != 0) > + goto complete; > + } > + > + handle = alsa->input_handle; > + > + if (input_count < input_trigger && > + (snd_pcm_avail(handle) > 0 || > input_total == 0)) { > + index = input_count * > input_channels; > + count = input_trigger - input_count; > + avail = snd_pcm_avail(handle); > + > + count = avail > 0 && avail < count ? > avail : > + count; > + > + ret = snd_pcm_readi(handle, > + &input_buffer[in > dex], > + count); > + if (ret == -EAGAIN) { > + ret = 0; > + } else if (ret < 0) { > + ret = > snd_pcm_recover(handle, count, 0); > + if (ret < 0) > + goto complete; > + } > + > + input_count += ret; > + input_total += ret; > + } > + } > + } while (!reached); > + > + ret = 0; > + > +complete: > + if (output_buffer) > + free(output_buffer); > + > + if (input_buffer) > + free(input_buffer); > + > + return ret; > +} > diff --git a/lib/igt_alsa.h b/lib/igt_alsa.h > new file mode 100644 > index 00000000..9911ddde > --- /dev/null > +++ b/lib/igt_alsa.h > @@ -0,0 +1,60 @@ > +/* > + * 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 <paul.kocialkowski@xxxxxxxxxxxxxxx> > + */ > + > +#ifndef IGT_ALSA_H > +#define IGT_ALSA_H > + > +#ifdef HAVE_CONFIG_H > +#include "config.h" > +#endif > + > +#include "igt.h" > +#include <stdbool.h> > + > +struct alsa; > + > +struct alsa *alsa_init(void); > +int alsa_open_output(struct alsa *alsa, const char *device_name); > +int alsa_open_input(struct alsa *alsa, const char *device_name); > +void alsa_close_output(struct alsa *alsa); > +void alsa_close_input(struct alsa *alsa); > +bool alsa_test_output_configuration(struct alsa *alsa, int channels, > + int sampling_rate); > +bool alsa_test_input_configuration(struct alsa *alsa, int channels, > + int sampling_rate); > +void alsa_configure_output(struct alsa *alsa, int channels, > + int sampling_rate); > +void alsa_configure_input(struct alsa *alsa, int channels, > + int sampling_rate); > +void alsa_register_output_callback(struct alsa *alsa, > + int (*callback)(void *data, short > *buffer, int samples), > + void *callback_data, int > samples_trigger); > +void alsa_register_input_callback(struct alsa *alsa, > + int (*callback)(void *data, short > *buffer, int samples), > + void *callback_data, int > samples_trigger); > +int alsa_run(struct alsa *alsa, int duration_ms); > + > +#endif _______________________________________________ Intel-gfx mailing list Intel-gfx@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/intel-gfx