| /* |
| * Copyright (C) 2000-2004 James Courtier-Dutton |
| * Copyright (C) 2005 Nathan Hurst |
| * |
| * This file is part of the speaker-test tool. |
| * |
| * This small program sends a simple sinusoidal wave to your speakers. |
| * |
| * speaker-test is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * speaker-test is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA |
| * |
| * |
| * Main program by James Courtier-Dutton (including some source code fragments from the alsa project.) |
| * Some cleanup from Daniel Caujolle-Bert <segfault@club-internet.fr> |
| * Pink noise option added Nathan Hurst, |
| * based on generator by Phil Burk (pink.c) |
| * |
| * Changelog: |
| * 0.0.8 Added support for pink noise output. |
| * Changelog: |
| * 0.0.7 Added support for more than 6 channels. |
| * Changelog: |
| * 0.0.6 Added support for different sample formats. |
| * |
| * $Id: speaker_test.c,v 1.00 2003/11/26 19:43:38 jcdutton Exp $ |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sched.h> |
| #include <errno.h> |
| #include <getopt.h> |
| #include <inttypes.h> |
| #include <ctype.h> |
| #include <byteswap.h> |
| #include <signal.h> |
| |
| #define ALSA_PCM_NEW_HW_PARAMS_API |
| #define ALSA_PCM_NEW_SW_PARAMS_API |
| #include <alsa/asoundlib.h> |
| #include <sys/time.h> |
| #include <math.h> |
| #include "pink.h" |
| #include "aconfig.h" |
| #include "gettext.h" |
| #include "version.h" |
| |
| #ifdef ENABLE_NLS |
| #include <locale.h> |
| #endif |
| |
| #ifdef SND_CHMAP_API_VERSION |
| #define CONFIG_SUPPORT_CHMAP 1 |
| #endif |
| |
| enum { |
| TEST_PINK_NOISE = 1, |
| TEST_SINE, |
| TEST_WAV, |
| TEST_PATTERN, |
| }; |
| |
| #define MAX_CHANNELS 16 |
| |
| #if __BYTE_ORDER == __LITTLE_ENDIAN |
| #define COMPOSE_ID(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24)) |
| #define LE_SHORT(v) (v) |
| #define LE_INT(v) (v) |
| #define BE_SHORT(v) bswap_16(v) |
| #define BE_INT(v) bswap_32(v) |
| #else /* __BIG_ENDIAN */ |
| #define COMPOSE_ID(a,b,c,d) ((d) | ((c)<<8) | ((b)<<16) | ((a)<<24)) |
| #define LE_SHORT(v) bswap_16(v) |
| #define LE_INT(v) bswap_32(v) |
| #define BE_SHORT(v) (v) |
| #define BE_INT(v) (v) |
| #endif |
| |
| static char *device = "default"; /* playback device */ |
| static snd_pcm_format_t format = SND_PCM_FORMAT_S16; /* sample format */ |
| static unsigned int rate = 48000; /* stream rate */ |
| static unsigned int channels = 1; /* count of channels */ |
| static unsigned int speaker = 0; /* count of channels */ |
| static unsigned int buffer_time = 0; /* ring buffer length in us */ |
| static unsigned int period_time = 0; /* period time in us */ |
| static unsigned int nperiods = 4; /* number of periods */ |
| static double freq = 440.0; /* sinusoidal wave frequency in Hz */ |
| static int test_type = TEST_PINK_NOISE; /* Test type. 1 = noise, 2 = sine wave */ |
| static pink_noise_t pink; |
| static snd_pcm_uframes_t buffer_size; |
| static snd_pcm_uframes_t period_size; |
| static const char *given_test_wav_file = NULL; |
| static char *wav_file_dir = SOUNDSDIR; |
| static int debug = 0; |
| static int in_aborting = 0; |
| static snd_pcm_t *pcm_handle = NULL; |
| |
| #ifdef CONFIG_SUPPORT_CHMAP |
| static snd_pcm_chmap_t *channel_map; |
| static int channel_map_set; |
| #endif |
| |
| static const char *const channel_name[MAX_CHANNELS] = { |
| /* 0 */ N_("Front Left"), |
| /* 1 */ N_("Front Right"), |
| /* 2 */ N_("Rear Left"), |
| /* 3 */ N_("Rear Right"), |
| /* 4 */ N_("Center"), |
| /* 5 */ N_("LFE"), |
| /* 6 */ N_("Side Left"), |
| /* 7 */ N_("Side Right"), |
| /* 8 */ N_("Channel 9"), |
| /* 9 */ N_("Channel 10"), |
| /* 10 */ N_("Channel 11"), |
| /* 11 */ N_("Channel 12"), |
| /* 12 */ N_("Channel 13"), |
| /* 13 */ N_("Channel 14"), |
| /* 14 */ N_("Channel 15"), |
| /* 15 */ N_("Channel 16") |
| }; |
| |
| static const int channels4[] = { |
| 0, /* Front Left */ |
| 1, /* Front Right */ |
| 3, /* Rear Right */ |
| 2, /* Rear Left */ |
| }; |
| static const int channels6[] = { |
| 0, /* Front Left */ |
| 4, /* Center */ |
| 1, /* Front Right */ |
| 3, /* Rear Right */ |
| 2, /* Rear Left */ |
| 5, /* LFE */ |
| }; |
| static const int channels8[] = { |
| 0, /* Front Left */ |
| 4, /* Center */ |
| 1, /* Front Right */ |
| 7, /* Side Right */ |
| 3, /* Rear Right */ |
| 2, /* Rear Left */ |
| 6, /* Side Left */ |
| 5, /* LFE */ |
| }; |
| |
| static int get_mapped_channel(int chn) |
| { |
| #ifdef CONFIG_SUPPORT_CHMAP |
| static const int maps[MAX_CHANNELS] = { |
| SND_CHMAP_FL, |
| SND_CHMAP_FR, |
| SND_CHMAP_RL, |
| SND_CHMAP_RR, |
| SND_CHMAP_FC, |
| SND_CHMAP_LFE, |
| SND_CHMAP_SL, |
| SND_CHMAP_SR, |
| }; |
| |
| if (channel_map && maps[chn]) { |
| int i; |
| for (i = 0; i < channel_map->channels; i++) { |
| if (channel_map->pos[i] == maps[chn]) |
| return i; |
| } |
| } |
| #endif |
| return chn; |
| } |
| |
| static int get_speaker_channel(int chn) |
| { |
| #ifdef CONFIG_SUPPORT_CHMAP |
| if (channel_map_set) |
| return chn; |
| #endif |
| |
| switch (channels) { |
| case 4: |
| chn = channels4[chn]; |
| break; |
| case 6: |
| chn = channels6[chn]; |
| break; |
| case 8: |
| chn = channels8[chn]; |
| break; |
| } |
| |
| return get_mapped_channel(chn); |
| } |
| |
| static const char *get_channel_name(int chn) |
| { |
| #ifdef CONFIG_SUPPORT_CHMAP |
| if (channel_map_set && chn < channel_map->channels) { |
| const char *name = snd_pcm_chmap_long_name(channel_map->pos[chn]); |
| return name ? name : "Unknown"; |
| } |
| #endif |
| return gettext(channel_name[chn]); |
| } |
| |
| static const int supported_formats[] = { |
| SND_PCM_FORMAT_S8, |
| SND_PCM_FORMAT_S16_LE, |
| SND_PCM_FORMAT_S16_BE, |
| SND_PCM_FORMAT_FLOAT_LE, |
| SND_PCM_FORMAT_S32_LE, |
| SND_PCM_FORMAT_S32_BE, |
| -1 |
| }; |
| |
| static void generate_sine(uint8_t *frames, int channel, int count, double *_phase) { |
| double phase = *_phase; |
| double max_phase = 1.0 / freq; |
| double step = 1.0 / (double)rate; |
| double res; |
| float fres; |
| int chn; |
| int32_t ires; |
| int8_t *samp8 = (int8_t*) frames; |
| int16_t *samp16 = (int16_t*) frames; |
| int32_t *samp32 = (int32_t*) frames; |
| float *samp_f = (float*) frames; |
| |
| while (count-- > 0) { |
| for(chn=0;chn<channels;chn++) { |
| switch (format) { |
| case SND_PCM_FORMAT_S8: |
| if (chn==channel) { |
| res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0x03fffffff; /* Don't use MAX volume */ |
| ires = res; |
| *samp8++ = ires >> 24; |
| } else { |
| *samp8++ = 0; |
| } |
| break; |
| case SND_PCM_FORMAT_S16_LE: |
| if (chn==channel) { |
| res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0x03fffffff; /* Don't use MAX volume */ |
| ires = res; |
| *samp16++ = LE_SHORT(ires >> 16); |
| } else { |
| *samp16++ = 0; |
| } |
| break; |
| case SND_PCM_FORMAT_S16_BE: |
| if (chn==channel) { |
| res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0x03fffffff; /* Don't use MAX volume */ |
| ires = res; |
| *samp16++ = BE_SHORT(ires >> 16); |
| } else { |
| *samp16++ = 0; |
| } |
| break; |
| case SND_PCM_FORMAT_FLOAT_LE: |
| if (chn==channel) { |
| res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0.75 ; /* Don't use MAX volume */ |
| fres = res; |
| *samp_f++ = fres; |
| } else { |
| *samp_f++ = 0.0; |
| } |
| break; |
| case SND_PCM_FORMAT_S32_LE: |
| if (chn==channel) { |
| res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0x03fffffff; /* Don't use MAX volume */ |
| ires = res; |
| *samp32++ = LE_INT(ires); |
| } else { |
| *samp32++ = 0; |
| } |
| break; |
| case SND_PCM_FORMAT_S32_BE: |
| if (chn==channel) { |
| res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0x03fffffff; /* Don't use MAX volume */ |
| ires = res; |
| *samp32++ = BE_INT(ires); |
| } else { |
| *samp32++ = 0; |
| } |
| break; |
| default: |
| ; |
| } |
| } |
| |
| phase += step; |
| if (phase >= max_phase) |
| phase -= max_phase; |
| } |
| |
| *_phase = phase; |
| } |
| |
| /* Pink noise is a better test than sine wave because we can tell |
| * where pink noise is coming from more easily that a sine wave. |
| */ |
| |
| |
| static void generate_pink_noise( uint8_t *frames, int channel, int count) { |
| double res; |
| int chn; |
| int32_t ires; |
| int8_t *samp8 = (int8_t*) frames; |
| int16_t *samp16 = (int16_t*) frames; |
| int32_t *samp32 = (int32_t*) frames; |
| |
| while (count-- > 0) { |
| for(chn=0;chn<channels;chn++) { |
| switch (format) { |
| case SND_PCM_FORMAT_S8: |
| if (chn==channel) { |
| res = generate_pink_noise_sample(&pink) * 0x03fffffff; /* Don't use MAX volume */ |
| ires = res; |
| *samp8++ = ires >> 24; |
| } else { |
| *samp8++ = 0; |
| } |
| break; |
| case SND_PCM_FORMAT_S16_LE: |
| if (chn==channel) { |
| res = generate_pink_noise_sample(&pink) * 0x03fffffff; /* Don't use MAX volume */ |
| ires = res; |
| *samp16++ = LE_SHORT(ires >> 16); |
| } else { |
| *samp16++ = 0; |
| } |
| break; |
| case SND_PCM_FORMAT_S16_BE: |
| if (chn==channel) { |
| res = generate_pink_noise_sample(&pink) * 0x03fffffff; /* Don't use MAX volume */ |
| ires = res; |
| *samp16++ = BE_SHORT(ires >> 16); |
| } else { |
| *samp16++ = 0; |
| } |
| break; |
| case SND_PCM_FORMAT_S32_LE: |
| if (chn==channel) { |
| res = generate_pink_noise_sample(&pink) * 0x03fffffff; /* Don't use MAX volume */ |
| ires = res; |
| *samp32++ = LE_INT(ires); |
| } else { |
| *samp32++ = 0; |
| } |
| break; |
| case SND_PCM_FORMAT_S32_BE: |
| if (chn==channel) { |
| res = generate_pink_noise_sample(&pink) * 0x03fffffff; /* Don't use MAX volume */ |
| ires = res; |
| *samp32++ = BE_INT(ires); |
| } else { |
| *samp32++ = 0; |
| } |
| break; |
| default: |
| ; |
| } |
| } |
| } |
| } |
| |
| /* |
| * useful for tests |
| */ |
| static void generate_pattern(uint8_t *frames, int channel, int count, int *_pattern) { |
| int pattern = *_pattern; |
| int chn; |
| int8_t *samp8 = (int8_t*) frames; |
| int16_t *samp16 = (int16_t*) frames; |
| int32_t *samp32 = (int32_t*) frames; |
| float *samp_f = (float*) frames; |
| |
| while (count-- > 0) { |
| for(chn=0;chn<channels;chn++,pattern++) { |
| switch (format) { |
| case SND_PCM_FORMAT_S8: |
| if (chn==channel) { |
| *samp8++ = pattern & 0xff; |
| } else { |
| *samp8++ = 0; |
| } |
| break; |
| case SND_PCM_FORMAT_S16_LE: |
| if (chn==channel) { |
| *samp16++ = LE_SHORT(pattern & 0xfffff); |
| } else { |
| *samp16++ = 0; |
| } |
| break; |
| case SND_PCM_FORMAT_S16_BE: |
| if (chn==channel) { |
| *samp16++ = BE_SHORT(pattern & 0xffff); |
| } else { |
| *samp16++ = 0; |
| } |
| break; |
| case SND_PCM_FORMAT_FLOAT_LE: |
| if (chn==channel) { |
| *samp_f++ = LE_INT(((double)pattern) / INT32_MAX); |
| } else { |
| *samp_f++ = 0.0; |
| } |
| break; |
| case SND_PCM_FORMAT_S32_LE: |
| if (chn==channel) { |
| *samp32++ = LE_INT(pattern); |
| } else { |
| *samp32++ = 0; |
| } |
| break; |
| case SND_PCM_FORMAT_S32_BE: |
| if (chn==channel) { |
| *samp32++ = BE_INT(pattern); |
| } else { |
| *samp32++ = 0; |
| } |
| break; |
| default: |
| ; |
| } |
| } |
| } |
| |
| *_pattern = pattern; |
| } |
| |
| static int set_hwparams(snd_pcm_t *handle, snd_pcm_hw_params_t *params, snd_pcm_access_t access) { |
| unsigned int rrate; |
| int err; |
| snd_pcm_uframes_t period_size_min; |
| snd_pcm_uframes_t period_size_max; |
| snd_pcm_uframes_t buffer_size_min; |
| snd_pcm_uframes_t buffer_size_max; |
| |
| /* choose all parameters */ |
| err = snd_pcm_hw_params_any(handle, params); |
| if (err < 0) { |
| fprintf(stderr, _("Broken configuration for playback: no configurations available: %s\n"), snd_strerror(err)); |
| return err; |
| } |
| |
| /* set the interleaved read/write format */ |
| err = snd_pcm_hw_params_set_access(handle, params, access); |
| if (err < 0) { |
| fprintf(stderr, _("Access type not available for playback: %s\n"), snd_strerror(err)); |
| return err; |
| } |
| |
| /* set the sample format */ |
| err = snd_pcm_hw_params_set_format(handle, params, format); |
| if (err < 0) { |
| fprintf(stderr, _("Sample format not available for playback: %s\n"), snd_strerror(err)); |
| return err; |
| } |
| |
| /* set the count of channels */ |
| err = snd_pcm_hw_params_set_channels(handle, params, channels); |
| if (err < 0) { |
| fprintf(stderr, _("Channels count (%i) not available for playbacks: %s\n"), channels, snd_strerror(err)); |
| return err; |
| } |
| |
| /* set the stream rate */ |
| rrate = rate; |
| err = snd_pcm_hw_params_set_rate(handle, params, rate, 0); |
| if (err < 0) { |
| fprintf(stderr, _("Rate %iHz not available for playback: %s\n"), rate, snd_strerror(err)); |
| return err; |
| } |
| |
| if (rrate != rate) { |
| fprintf(stderr, _("Rate doesn't match (requested %iHz, get %iHz, err %d)\n"), rate, rrate, err); |
| return -EINVAL; |
| } |
| |
| printf(_("Rate set to %iHz (requested %iHz)\n"), rrate, rate); |
| /* set the buffer time */ |
| err = snd_pcm_hw_params_get_buffer_size_min(params, &buffer_size_min); |
| err = snd_pcm_hw_params_get_buffer_size_max(params, &buffer_size_max); |
| err = snd_pcm_hw_params_get_period_size_min(params, &period_size_min, NULL); |
| err = snd_pcm_hw_params_get_period_size_max(params, &period_size_max, NULL); |
| printf(_("Buffer size range from %lu to %lu\n"),buffer_size_min, buffer_size_max); |
| printf(_("Period size range from %lu to %lu\n"),period_size_min, period_size_max); |
| if (period_time > 0) { |
| printf(_("Requested period time %u us\n"), period_time); |
| err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, NULL); |
| if (err < 0) { |
| fprintf(stderr, _("Unable to set period time %u us for playback: %s\n"), |
| period_time, snd_strerror(err)); |
| return err; |
| } |
| } |
| if (buffer_time > 0) { |
| printf(_("Requested buffer time %u us\n"), buffer_time); |
| err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, NULL); |
| if (err < 0) { |
| fprintf(stderr, _("Unable to set buffer time %u us for playback: %s\n"), |
| buffer_time, snd_strerror(err)); |
| return err; |
| } |
| } |
| if (! buffer_time && ! period_time) { |
| buffer_size = buffer_size_max; |
| if (! period_time) |
| buffer_size = (buffer_size / nperiods) * nperiods; |
| printf(_("Using max buffer size %lu\n"), buffer_size); |
| err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size); |
| if (err < 0) { |
| fprintf(stderr, _("Unable to set buffer size %lu for playback: %s\n"), |
| buffer_size, snd_strerror(err)); |
| return err; |
| } |
| } |
| if (! buffer_time || ! period_time) { |
| printf(_("Periods = %u\n"), nperiods); |
| err = snd_pcm_hw_params_set_periods_near(handle, params, &nperiods, NULL); |
| if (err < 0) { |
| fprintf(stderr, _("Unable to set nperiods %u for playback: %s\n"), |
| nperiods, snd_strerror(err)); |
| return err; |
| } |
| } |
| |
| /* write the parameters to device */ |
| err = snd_pcm_hw_params(handle, params); |
| if (err < 0) { |
| fprintf(stderr, _("Unable to set hw params for playback: %s\n"), snd_strerror(err)); |
| return err; |
| } |
| |
| snd_pcm_hw_params_get_buffer_size(params, &buffer_size); |
| snd_pcm_hw_params_get_period_size(params, &period_size, NULL); |
| printf(_("was set period_size = %lu\n"),period_size); |
| printf(_("was set buffer_size = %lu\n"),buffer_size); |
| if (2*period_size > buffer_size) { |
| fprintf(stderr, _("buffer to small, could not use\n")); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams) { |
| int err; |
| |
| /* get the current swparams */ |
| err = snd_pcm_sw_params_current(handle, swparams); |
| if (err < 0) { |
| fprintf(stderr, _("Unable to determine current swparams for playback: %s\n"), snd_strerror(err)); |
| return err; |
| } |
| |
| /* start the transfer when a buffer is full */ |
| err = snd_pcm_sw_params_set_start_threshold(handle, swparams, buffer_size); |
| if (err < 0) { |
| fprintf(stderr, _("Unable to set start threshold mode for playback: %s\n"), snd_strerror(err)); |
| return err; |
| } |
| |
| /* allow the transfer when at least period_size frames can be processed */ |
| err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_size); |
| if (err < 0) { |
| fprintf(stderr, _("Unable to set avail min for playback: %s\n"), snd_strerror(err)); |
| return err; |
| } |
| |
| /* write the parameters to the playback device */ |
| err = snd_pcm_sw_params(handle, swparams); |
| if (err < 0) { |
| fprintf(stderr, _("Unable to set sw params for playback: %s\n"), snd_strerror(err)); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_SUPPORT_CHMAP |
| static int config_chmap(snd_pcm_t *handle, const char *mapstr) |
| { |
| int err; |
| |
| if (mapstr) { |
| channel_map = snd_pcm_chmap_parse_string(mapstr); |
| if (!channel_map) { |
| fprintf(stderr, _("Unable to parse channel map string: %s\n"), mapstr); |
| return -EINVAL; |
| } |
| err = snd_pcm_set_chmap(handle, channel_map); |
| if (err < 0) { |
| fprintf(stderr, _("Unable to set channel map: %s\n"), mapstr); |
| return err; |
| } |
| channel_map_set = 1; |
| return 0; |
| } |
| |
| channel_map = snd_pcm_get_chmap(handle); |
| return 0; |
| } |
| #endif |
| |
| /* |
| * Underrun and suspend recovery |
| */ |
| |
| static int xrun_recovery(snd_pcm_t *handle, int err) { |
| if (err == -EPIPE) { /* under-run */ |
| err = snd_pcm_prepare(handle); |
| if (err < 0) |
| fprintf(stderr, _("Can't recovery from underrun, prepare failed: %s\n"), snd_strerror(err)); |
| return 0; |
| } |
| else if (err == -ESTRPIPE) { |
| |
| while ((err = snd_pcm_resume(handle)) == -EAGAIN) |
| sleep(1); /* wait until the suspend flag is released */ |
| |
| if (err < 0) { |
| err = snd_pcm_prepare(handle); |
| if (err < 0) |
| fprintf(stderr, _("Can't recovery from suspend, prepare failed: %s\n"), snd_strerror(err)); |
| } |
| |
| return 0; |
| } |
| |
| return err; |
| } |
| |
| /* |
| * Handle WAV files |
| */ |
| |
| static const char *wav_file[MAX_CHANNELS]; |
| static int wav_file_size[MAX_CHANNELS]; |
| |
| struct wave_header { |
| struct { |
| uint32_t magic; |
| uint32_t length; |
| uint32_t type; |
| } hdr; |
| struct { |
| uint32_t type; |
| uint32_t length; |
| } chunk1; |
| struct { |
| uint16_t format; |
| uint16_t channels; |
| uint32_t rate; |
| uint32_t bytes_per_sec; |
| uint16_t sample_size; |
| uint16_t sample_bits; |
| } body; |
| struct { |
| uint32_t type; |
| uint32_t length; |
| } chunk; |
| }; |
| |
| #define WAV_RIFF COMPOSE_ID('R','I','F','F') |
| #define WAV_WAVE COMPOSE_ID('W','A','V','E') |
| #define WAV_FMT COMPOSE_ID('f','m','t',' ') |
| #define WAV_DATA COMPOSE_ID('d','a','t','a') |
| #define WAV_PCM_CODE 1 |
| |
| static const char *search_for_file(const char *name) |
| { |
| char *file; |
| if (*name == '/') |
| return strdup(name); |
| file = malloc(strlen(wav_file_dir) + strlen(name) + 2); |
| if (file) |
| sprintf(file, "%s/%s", wav_file_dir, name); |
| return file; |
| } |
| |
| static int check_wav_file(int channel, const char *name) |
| { |
| struct wave_header header; |
| int fd; |
| |
| wav_file[channel] = search_for_file(name); |
| if (! wav_file[channel]) { |
| fprintf(stderr, _("No enough memory\n")); |
| return -ENOMEM; |
| } |
| |
| if ((fd = open(wav_file[channel], O_RDONLY)) < 0) { |
| fprintf(stderr, _("Cannot open WAV file %s\n"), wav_file[channel]); |
| return -EINVAL; |
| } |
| if (read(fd, &header, sizeof(header)) < (int)sizeof(header)) { |
| fprintf(stderr, _("Invalid WAV file %s\n"), wav_file[channel]); |
| goto error; |
| } |
| |
| if (header.hdr.magic != WAV_RIFF || header.hdr.type != WAV_WAVE) { |
| fprintf(stderr, _("Not a WAV file: %s\n"), wav_file[channel]); |
| goto error; |
| } |
| if (header.body.format != LE_SHORT(WAV_PCM_CODE)) { |
| fprintf(stderr, _("Unsupported WAV format %d for %s\n"), |
| LE_SHORT(header.body.format), wav_file[channel]); |
| goto error; |
| } |
| if (header.body.channels != LE_SHORT(1)) { |
| fprintf(stderr, _("%s is not a mono stream (%d channels)\n"), |
| wav_file[channel], LE_SHORT(header.body.channels)); |
| goto error; |
| } |
| if (header.body.rate != LE_INT(rate)) { |
| fprintf(stderr, _("Sample rate doesn't match (%d) for %s\n"), |
| LE_INT(header.body.rate), wav_file[channel]); |
| goto error; |
| } |
| if (header.body.sample_bits != LE_SHORT(16)) { |
| fprintf(stderr, _("Unsupported sample format bits %d for %s\n"), |
| LE_SHORT(header.body.sample_bits), wav_file[channel]); |
| goto error; |
| } |
| if (header.chunk.type != WAV_DATA) { |
| fprintf(stderr, _("Invalid WAV file %s\n"), wav_file[channel]); |
| goto error; |
| } |
| wav_file_size[channel] = LE_INT(header.chunk.length); |
| close(fd); |
| return 0; |
| |
| error: |
| close(fd); |
| return -EINVAL; |
| } |
| |
| static int setup_wav_file(int chn) |
| { |
| static const char *const wavs[MAX_CHANNELS] = { |
| "Front_Left.wav", |
| "Front_Right.wav", |
| "Rear_Left.wav", |
| "Rear_Right.wav", |
| "Front_Center.wav", |
| "Rear_Center.wav", /* FIXME: should be "Bass" or so */ |
| "Side_Left.wav", |
| "Side_Right.wav", |
| "Channel_9.wav", |
| "Channel_10.wav", |
| "Channel_11.wav", |
| "Channel_12.wav", |
| "Channel_13.wav", |
| "Channel_14.wav", |
| "Channel_15.wav", |
| "Channel_16.wav" |
| }; |
| |
| if (given_test_wav_file) |
| return check_wav_file(chn, given_test_wav_file); |
| |
| #ifdef CONFIG_SUPPORT_CHMAP |
| if (channel_map_set && chn < channel_map->channels) { |
| int channel = channel_map->pos[chn] - SND_CHMAP_FL; |
| if (channel >= 0 && channel < MAX_CHANNELS) |
| return check_wav_file(chn, wavs[channel]); |
| } |
| #endif |
| |
| return check_wav_file(chn, wavs[chn]); |
| } |
| |
| static int read_wav(uint16_t *buf, int channel, int offset, int bufsize) |
| { |
| static FILE *wavfp = NULL; |
| int size; |
| |
| if (in_aborting) |
| return -EFAULT; |
| |
| if (! wav_file[channel]) { |
| fprintf(stderr, _("Undefined channel %d\n"), channel); |
| return -EINVAL; |
| } |
| |
| if (offset >= wav_file_size[channel]) |
| return 0; /* finished */ |
| |
| if (! offset) { |
| if (wavfp) |
| fclose(wavfp); |
| wavfp = fopen(wav_file[channel], "r"); |
| if (! wavfp) |
| return -errno; |
| if (fseek(wavfp, sizeof(struct wave_header), SEEK_SET) < 0) |
| return -errno; |
| } |
| if (offset + bufsize > wav_file_size[channel]) |
| bufsize = wav_file_size[channel] - offset; |
| bufsize /= channels; |
| for (size = 0; size < bufsize; size += 2) { |
| int chn; |
| for (chn = 0; chn < channels; chn++) { |
| if (chn == channel) { |
| if (fread(buf, 2, 1, wavfp) != 1) |
| return size; |
| } |
| else |
| *buf = 0; |
| buf++; |
| } |
| } |
| return size; |
| } |
| |
| |
| /* |
| * Transfer method - write only |
| */ |
| |
| static int write_buffer(snd_pcm_t *handle, uint8_t *ptr, int cptr) |
| { |
| int err; |
| |
| while (cptr > 0 && !in_aborting) { |
| |
| err = snd_pcm_writei(handle, ptr, cptr); |
| |
| if (err == -EAGAIN) |
| continue; |
| |
| if (err < 0) { |
| fprintf(stderr, _("Write error: %d,%s\n"), err, snd_strerror(err)); |
| if ((err = xrun_recovery(handle, err)) < 0) { |
| fprintf(stderr, _("xrun_recovery failed: %d,%s\n"), err, snd_strerror(err)); |
| return err; |
| } |
| break; /* skip one period */ |
| } |
| |
| ptr += snd_pcm_frames_to_bytes(handle, err); |
| cptr -= err; |
| } |
| return 0; |
| } |
| |
| static int write_loop(snd_pcm_t *handle, int channel, int periods, uint8_t *frames) |
| { |
| double phase = 0; |
| int pattern = 0; |
| int err, n; |
| |
| fflush(stdout); |
| if (test_type == TEST_WAV) { |
| int bufsize = snd_pcm_frames_to_bytes(handle, period_size); |
| n = 0; |
| while ((err = read_wav((uint16_t *)frames, channel, n, bufsize)) > 0 && !in_aborting) { |
| n += err; |
| if ((err = write_buffer(handle, frames, |
| snd_pcm_bytes_to_frames(handle, err * channels))) < 0) |
| break; |
| } |
| if (buffer_size > n && !in_aborting) { |
| snd_pcm_drain(handle); |
| snd_pcm_prepare(handle); |
| } |
| return err; |
| } |
| |
| |
| if (periods <= 0) |
| periods = 1; |
| |
| for(n = 0; n < periods && !in_aborting; n++) { |
| if (test_type == TEST_PINK_NOISE) |
| generate_pink_noise(frames, channel, period_size); |
| else if (test_type == TEST_PATTERN) |
| generate_pattern(frames, channel, period_size, &pattern); |
| else |
| generate_sine(frames, channel, period_size, &phase); |
| |
| if ((err = write_buffer(handle, frames, period_size)) < 0) |
| return err; |
| } |
| if (buffer_size > n * period_size && !in_aborting) { |
| snd_pcm_drain(handle); |
| snd_pcm_prepare(handle); |
| } |
| return 0; |
| } |
| |
| static int prg_exit(int code) |
| { |
| if (pcm_handle) |
| snd_pcm_close(pcm_handle); |
| exit(code); |
| return code; |
| } |
| |
| static void signal_handler(int sig) |
| { |
| if (in_aborting) |
| return; |
| |
| in_aborting = 1; |
| |
| if (pcm_handle) |
| snd_pcm_abort(pcm_handle); |
| if (sig == SIGABRT) { |
| pcm_handle = NULL; |
| prg_exit(EXIT_FAILURE); |
| } |
| signal(sig, signal_handler); |
| } |
| |
| static void help(void) |
| { |
| const int *fmt; |
| |
| printf( |
| _("Usage: speaker-test [OPTION]... \n" |
| "-h,--help help\n" |
| "-D,--device playback device\n" |
| "-r,--rate stream rate in Hz\n" |
| "-c,--channels count of channels in stream\n" |
| "-f,--frequency sine wave frequency in Hz\n" |
| "-F,--format sample format\n" |
| "-b,--buffer ring buffer size in us\n" |
| "-p,--period period size in us\n" |
| "-P,--nperiods number of periods\n" |
| "-t,--test pink=use pink noise, sine=use sine wave, wav=WAV file\n" |
| "-l,--nloops specify number of loops to test, 0 = infinite\n" |
| "-s,--speaker single speaker test. Values 1=Left, 2=right, etc\n" |
| "-w,--wavfile Use the given WAV file as a test sound\n" |
| "-W,--wavdir Specify the directory containing WAV files\n" |
| "-m,--chmap Specify the channel map to override\n" |
| "\n")); |
| printf(_("Recognized sample formats are:")); |
| for (fmt = supported_formats; *fmt >= 0; fmt++) { |
| const char *s = snd_pcm_format_name(*fmt); |
| if (s) |
| printf(" %s", s); |
| } |
| |
| printf("\n\n"); |
| } |
| |
| int main(int argc, char *argv[]) { |
| snd_pcm_t *handle; |
| int err, morehelp; |
| snd_pcm_hw_params_t *hwparams; |
| snd_pcm_sw_params_t *swparams; |
| uint8_t *frames; |
| int chn; |
| const int *fmt; |
| double time1,time2,time3; |
| unsigned int n, nloops; |
| struct timeval tv1,tv2; |
| #ifdef CONFIG_SUPPORT_CHMAP |
| const char *chmap = NULL; |
| #endif |
| |
| static const struct option long_option[] = { |
| {"help", 0, NULL, 'h'}, |
| {"device", 1, NULL, 'D'}, |
| {"rate", 1, NULL, 'r'}, |
| {"channels", 1, NULL, 'c'}, |
| {"frequency", 1, NULL, 'f'}, |
| {"format", 1, NULL, 'F'}, |
| {"buffer", 1, NULL, 'b'}, |
| {"period", 1, NULL, 'p'}, |
| {"nperiods", 1, NULL, 'P'}, |
| {"test", 1, NULL, 't'}, |
| {"nloops", 1, NULL, 'l'}, |
| {"speaker", 1, NULL, 's'}, |
| {"wavfile", 1, NULL, 'w'}, |
| {"wavdir", 1, NULL, 'W'}, |
| {"debug", 0, NULL, 'd'}, |
| #ifdef CONFIG_SUPPORT_CHMAP |
| {"chmap", 1, NULL, 'm'}, |
| #endif |
| {NULL, 0, NULL, 0 }, |
| }; |
| |
| #ifdef ENABLE_NLS |
| setlocale(LC_ALL, ""); |
| textdomain(PACKAGE); |
| #endif |
| |
| snd_pcm_hw_params_alloca(&hwparams); |
| snd_pcm_sw_params_alloca(&swparams); |
| |
| nloops = 0; |
| morehelp = 0; |
| |
| printf("\nspeaker-test %s\n\n", SND_UTIL_VERSION_STR); |
| while (1) { |
| int c; |
| |
| if ((c = getopt_long(argc, argv, "hD:r:c:f:F:b:p:P:t:l:s:w:W:d" |
| #ifdef CONFIG_SUPPORT_CHMAP |
| "m:" |
| #endif |
| , long_option, NULL)) < 0) |
| break; |
| |
| switch (c) { |
| case 'h': |
| morehelp++; |
| break; |
| case 'D': |
| device = strdup(optarg); |
| break; |
| case 'F': |
| format = snd_pcm_format_value(optarg); |
| for (fmt = supported_formats; *fmt >= 0; fmt++) |
| if (*fmt == format) |
| break; |
| if (*fmt < 0) { |
| fprintf(stderr, "Format %s is not supported...\n", snd_pcm_format_name(format)); |
| exit(EXIT_FAILURE); |
| } |
| break; |
| case 'r': |
| rate = atoi(optarg); |
| rate = rate < 4000 ? 4000 : rate; |
| rate = rate > 384000 ? 384000 : rate; |
| break; |
| case 'c': |
| channels = atoi(optarg); |
| channels = channels < 1 ? 1 : channels; |
| channels = channels > 1024 ? 1024 : channels; |
| break; |
| case 'f': |
| freq = atof(optarg); |
| freq = freq < 30.0 ? 30.0 : freq; |
| freq = freq > 5000.0 ? 5000.0 : freq; |
| break; |
| case 'b': |
| buffer_time = atoi(optarg); |
| buffer_time = buffer_time > 1000000 ? 1000000 : buffer_time; |
| break; |
| case 'p': |
| period_time = atoi(optarg); |
| period_time = period_time > 1000000 ? 1000000 : period_time; |
| break; |
| case 'P': |
| nperiods = atoi(optarg); |
| if (nperiods < 2 || nperiods > 1024) { |
| fprintf(stderr, _("Invalid number of periods %d\n"), nperiods); |
| exit(1); |
| } |
| break; |
| case 't': |
| if (*optarg == 'p') |
| test_type = TEST_PINK_NOISE; |
| else if (*optarg == 's') |
| test_type = TEST_SINE; |
| else if (*optarg == 'w') |
| test_type = TEST_WAV; |
| else if (*optarg == 't') |
| test_type = TEST_PATTERN; |
| else if (isdigit(*optarg)) { |
| test_type = atoi(optarg); |
| if (test_type < TEST_PINK_NOISE || test_type > TEST_PATTERN) { |
| fprintf(stderr, _("Invalid test type %s\n"), optarg); |
| exit(1); |
| } |
| } else { |
| fprintf(stderr, _("Invalid test type %s\n"), optarg); |
| exit(1); |
| } |
| break; |
| case 'l': |
| nloops = atoi(optarg); |
| break; |
| case 's': |
| speaker = atoi(optarg); |
| speaker = speaker < 1 ? 0 : speaker; |
| speaker = speaker > channels ? 0 : speaker; |
| if (speaker==0) { |
| fprintf(stderr, _("Invalid parameter for -s option.\n")); |
| exit(EXIT_FAILURE); |
| } |
| break; |
| case 'w': |
| given_test_wav_file = optarg; |
| break; |
| case 'W': |
| wav_file_dir = optarg; |
| break; |
| case 'd': |
| debug = 1; |
| break; |
| #ifdef CONFIG_SUPPORT_CHMAP |
| case 'm': |
| chmap = optarg; |
| break; |
| #endif |
| default: |
| fprintf(stderr, _("Unknown option '%c'\n"), c); |
| exit(EXIT_FAILURE); |
| break; |
| } |
| } |
| |
| if (morehelp) { |
| help(); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (test_type == TEST_WAV) |
| format = SND_PCM_FORMAT_S16_LE; /* fixed format */ |
| |
| printf(_("Playback device is %s\n"), device); |
| printf(_("Stream parameters are %iHz, %s, %i channels\n"), rate, snd_pcm_format_name(format), channels); |
| switch (test_type) { |
| case TEST_PINK_NOISE: |
| printf(_("Using 16 octaves of pink noise\n")); |
| break; |
| case TEST_SINE: |
| printf(_("Sine wave rate is %.4fHz\n"), freq); |
| break; |
| case TEST_WAV: |
| printf(_("WAV file(s)\n")); |
| break; |
| |
| } |
| |
| signal(SIGINT, signal_handler); |
| signal(SIGTERM, signal_handler); |
| signal(SIGABRT, signal_handler); |
| |
| if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { |
| printf(_("Playback open error: %d,%s\n"), err,snd_strerror(err)); |
| prg_exit(EXIT_FAILURE); |
| } |
| pcm_handle = handle; |
| |
| if ((err = set_hwparams(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { |
| printf(_("Setting of hwparams failed: %s\n"), snd_strerror(err)); |
| prg_exit(EXIT_FAILURE); |
| } |
| if ((err = set_swparams(handle, swparams)) < 0) { |
| printf(_("Setting of swparams failed: %s\n"), snd_strerror(err)); |
| prg_exit(EXIT_FAILURE); |
| } |
| |
| #ifdef CONFIG_SUPPORT_CHMAP |
| err = config_chmap(handle, chmap); |
| if (err < 0) |
| prg_exit(EXIT_FAILURE); |
| #endif |
| |
| if (debug) { |
| snd_output_t *log; |
| err = snd_output_stdio_attach(&log, stderr, 0); |
| if (err >= 0) { |
| snd_pcm_dump(handle, log); |
| snd_output_close(log); |
| } |
| } |
| |
| frames = malloc(snd_pcm_frames_to_bytes(handle, period_size)); |
| if (test_type == TEST_PINK_NOISE) |
| initialize_pink_noise(&pink, 16); |
| |
| if (frames == NULL) { |
| fprintf(stderr, _("No enough memory\n")); |
| prg_exit(EXIT_FAILURE); |
| } |
| |
| if (speaker==0) { |
| |
| if (test_type == TEST_WAV) { |
| for (chn = 0; chn < channels; chn++) { |
| if (setup_wav_file(get_speaker_channel(chn)) < 0) |
| prg_exit(EXIT_FAILURE); |
| } |
| } |
| |
| for (n = 0; (! nloops || n < nloops) && !in_aborting; n++) { |
| |
| gettimeofday(&tv1, NULL); |
| for(chn = 0; chn < channels; chn++) { |
| int channel = get_speaker_channel(chn); |
| printf(" %d - %s\n", channel, get_channel_name(channel)); |
| |
| err = write_loop(handle, channel, ((rate*3)/period_size), frames); |
| |
| if (err < 0) { |
| fprintf(stderr, _("Transfer failed: %s\n"), snd_strerror(err)); |
| prg_exit(EXIT_SUCCESS); |
| } |
| } |
| gettimeofday(&tv2, NULL); |
| time1 = (double)tv1.tv_sec + ((double)tv1.tv_usec / 1000000.0); |
| time2 = (double)tv2.tv_sec + ((double)tv2.tv_usec / 1000000.0); |
| time3 = time2 - time1; |
| printf(_("Time per period = %lf\n"), time3 ); |
| } |
| } else { |
| chn = get_speaker_channel(speaker - 1); |
| |
| if (test_type == TEST_WAV) { |
| if (setup_wav_file(chn) < 0) |
| prg_exit(EXIT_FAILURE); |
| } |
| |
| printf(" - %s\n", get_channel_name(chn)); |
| err = write_loop(handle, chn, ((rate*5)/period_size), frames); |
| |
| if (err < 0) { |
| fprintf(stderr, _("Transfer failed: %s\n"), snd_strerror(err)); |
| } |
| } |
| |
| |
| free(frames); |
| |
| return prg_exit(EXIT_SUCCESS); |
| } |