alsa-utils/speaker-test/speaker-test.c
David Henningsson 7ac9508c4e speaker-test: Fix wrong wave file played back in chmap setup
When explicitly specifying channel maps, we need to use that when
loading wave files so the correct file is played back.

Signed-off-by: David Henningsson <david.henningsson@canonical.com>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2013-06-14 14:05:23 +02:00

1235 lines
32 KiB
C

/*
* 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);
}