mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-11-10 03:35:42 +01:00
9dd3e2801c
The number of periods to play in pink and sine modes could be calculated as zero, which results in just silence. Make the minimal value 1.
976 lines
26 KiB
C
976 lines
26 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>
|
|
#ifdef ENABLE_NLS
|
|
#include <locale.h>
|
|
#endif
|
|
#include <byteswap.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"
|
|
|
|
enum {
|
|
TEST_PINK_NOISE = 1,
|
|
TEST_SINE,
|
|
TEST_WAV
|
|
};
|
|
|
|
#define MAX_CHANNELS 16
|
|
|
|
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; /* 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 = DATADIR;
|
|
|
|
static const char *channel_name[MAX_CHANNELS] = {
|
|
N_("Front Left"),
|
|
N_("Front Right"),
|
|
N_("Rear Left"),
|
|
N_("Rear Right"),
|
|
N_("Center"),
|
|
N_("LFE"),
|
|
N_("Side Left"),
|
|
N_("Side Right"),
|
|
N_("Channel 9"),
|
|
N_("Channel 10"),
|
|
N_("Channel 11"),
|
|
N_("Channel 12"),
|
|
N_("Channel 13"),
|
|
N_("Channel 14"),
|
|
N_("Channel 15"),
|
|
N_("Channel 16")
|
|
};
|
|
|
|
static const int channels4[] = {
|
|
0,
|
|
1,
|
|
3,
|
|
2
|
|
};
|
|
static const int channels6[] = {
|
|
0,
|
|
4,
|
|
1,
|
|
3,
|
|
2,
|
|
5
|
|
};
|
|
static const int channels8[] = {
|
|
0,
|
|
4,
|
|
1,
|
|
7,
|
|
3,
|
|
2,
|
|
6,
|
|
5
|
|
};
|
|
|
|
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;
|
|
int sample_size_bits = snd_pcm_format_width(format);
|
|
|
|
while (count-- > 0) {
|
|
//res = sin((phase * 2 * M_PI) / max_phase - M_PI) * 32767;
|
|
//res = sin((phase * 2 * M_PI) / max_phase - M_PI) * 32767;
|
|
//res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0x03fffffff; /* Don't use MAX volume */
|
|
//if (res > 0) res = 10000;
|
|
//if (res < 0) res = -10000;
|
|
|
|
/* printf("%e\n",res); */
|
|
//ires = res;
|
|
//ires = ((16 - (count & 0xf)) <<24);
|
|
//ires = 0;
|
|
|
|
for(chn=0;chn<channels;chn++) {
|
|
if (sample_size_bits == 8) {
|
|
if (chn==channel) {
|
|
res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0x03fffffff; /* Don't use MAX volume */
|
|
ires = res;
|
|
*samp8++ = ires >> 24;
|
|
//*samp8++ = 0x12;
|
|
} else {
|
|
*samp8++ = 0;
|
|
}
|
|
} else if (sample_size_bits == 16) {
|
|
if (chn==channel) {
|
|
res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0x03fffffff; /* Don't use MAX volume */
|
|
ires = res;
|
|
*samp16++ = ires >>16;
|
|
//*samp16++ = 0x1234;
|
|
} else {
|
|
//*samp16++ = (ires >>16)+1;
|
|
*samp16++ = 0;
|
|
}
|
|
} else if ((sample_size_bits == 32) && (format == 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;
|
|
//*samp32++ = 0xF2345678;
|
|
//printf("res=%lf, ires=%d 0x%x, samp32=0x%x\n",res,ires, ires, samp32[-1]);
|
|
} else {
|
|
//*samp32++ = ires+0x10000;
|
|
//*samp32++ = ires;
|
|
*samp_f++ = 0.0;
|
|
}
|
|
} else if ((sample_size_bits == 32) && (format != SND_PCM_FORMAT_FLOAT_LE)) {
|
|
if (chn==channel) {
|
|
res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0x03fffffff; /* Don't use MAX volume */
|
|
ires = res;
|
|
*samp32++ = ires;
|
|
//*samp32++ = 0xF2345678;
|
|
//printf("res=%lf, ires=%d 0x%x, samp32=0x%x\n",res,ires, ires, samp32[-1]);
|
|
} else {
|
|
//*samp32++ = ires+0x10000;
|
|
//*samp32++ = ires;
|
|
*samp32++ = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
float fres;
|
|
int8_t *samp8 = (int8_t*) frames;
|
|
int16_t *samp16 = (int16_t*) frames;
|
|
int32_t *samp32 = (int32_t*) frames;
|
|
float *samp_f = (float*) frames;
|
|
int sample_size_bits = snd_pcm_format_width(format);
|
|
|
|
while (count-- > 0) {
|
|
for(chn=0;chn<channels;chn++) {
|
|
if (sample_size_bits == 8) {
|
|
if (chn==channel) {
|
|
res = generate_pink_noise_sample(&pink) * 0x03fffffff; /* Don't use MAX volume */
|
|
ires = res;
|
|
*samp8++ = ires >> 24;
|
|
} else {
|
|
*samp8++ = 0;
|
|
}
|
|
} else if (sample_size_bits == 16) {
|
|
if (chn==channel) {
|
|
res = generate_pink_noise_sample(&pink) * 0x03fffffff; /* Don't use MAX volume */
|
|
ires = res;
|
|
*samp16++ = ires >>16;
|
|
} else {
|
|
*samp16++ = 0;
|
|
}
|
|
} else if ((sample_size_bits == 32) && (format == SND_PCM_FORMAT_FLOAT_LE)) {
|
|
if (chn==channel) {
|
|
res = generate_pink_noise_sample(&pink) * 0.75; /* Don't use MAX volume */
|
|
fres = res;
|
|
*samp_f++ = fres;
|
|
} else {
|
|
*samp_f++ = 0.0;
|
|
}
|
|
} else if ((sample_size_bits == 32) && (format != SND_PCM_FORMAT_FLOAT_LE)) {
|
|
if (chn==channel) {
|
|
res = generate_pink_noise_sample(&pink) * 0x03fffffff; /* Don't use MAX volume */
|
|
ires = res;
|
|
*samp32++ = ires;
|
|
} else {
|
|
*samp32++ = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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) {
|
|
printf(_("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) {
|
|
printf(_("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) {
|
|
printf(_("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) {
|
|
printf(_("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) {
|
|
printf(_("Rate %iHz not available for playback: %s\n"), rate, snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
if (rrate != rate) {
|
|
printf(_("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) {
|
|
printf(_("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) {
|
|
printf(_("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) {
|
|
printf(_("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) {
|
|
printf(_("Unable to set nperiods %u for playback: %s\n"),
|
|
nperiods, 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) {
|
|
printf(_("buffer to small, could not use\n"));
|
|
return err;
|
|
}
|
|
|
|
/* write the parameters to device */
|
|
err = snd_pcm_hw_params(handle, params);
|
|
if (err < 0) {
|
|
printf(_("Unable to set hw params for playback: %s\n"), snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
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) {
|
|
printf(_("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) {
|
|
printf(_("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) {
|
|
printf(_("Unable to set avail min for playback: %s\n"), snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
/* align all transfers to 1 sample */
|
|
err = snd_pcm_sw_params_set_xfer_align(handle, swparams, 1);
|
|
if (err < 0) {
|
|
printf(_("Unable to set transfer align 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) {
|
|
printf(_("Unable to set sw params for playback: %s\n"), snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
printf(_("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)
|
|
printf(_("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;
|
|
};
|
|
|
|
#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)
|
|
#else
|
|
#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)
|
|
#endif
|
|
|
|
#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 *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);
|
|
else
|
|
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 (! 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) {
|
|
|
|
err = snd_pcm_writei(handle, ptr, cptr);
|
|
|
|
if (err == -EAGAIN)
|
|
continue;
|
|
|
|
if (err < 0) {
|
|
printf(_("Write error: %d,%s\n"), err, snd_strerror(err));
|
|
if (xrun_recovery(handle, err) < 0) {
|
|
printf(_("xrun_recovery failed: %d,%s\n"), err, snd_strerror(err));
|
|
return -1;
|
|
}
|
|
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 err, n;
|
|
|
|
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) {
|
|
n += err;
|
|
if ((err = write_buffer(handle, frames,
|
|
snd_pcm_bytes_to_frames(handle, err * channels))) < 0)
|
|
break;
|
|
}
|
|
if (buffer_size > n) {
|
|
snd_pcm_drain(handle);
|
|
snd_pcm_prepare(handle);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
|
|
if (periods <= 0)
|
|
periods = 1;
|
|
|
|
for(n = 0; n < periods; n++) {
|
|
if (test_type == TEST_PINK_NOISE)
|
|
generate_pink_noise(frames, channel, period_size);
|
|
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) {
|
|
snd_pcm_drain(handle);
|
|
snd_pcm_prepare(handle);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void help(void)
|
|
{
|
|
int k;
|
|
|
|
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"
|
|
"\n"));
|
|
#if 1
|
|
printf(_("Recognized sample formats are:"));
|
|
for (k = 0; k < SND_PCM_FORMAT_LAST; ++k) {
|
|
const char *s = snd_pcm_format_name(k);
|
|
if (s)
|
|
printf(" %s", s);
|
|
}
|
|
|
|
printf("\n\n");
|
|
#endif
|
|
|
|
}
|
|
|
|
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;
|
|
double time1,time2,time3;
|
|
unsigned int n, nloops;
|
|
struct timeval tv1,tv2;
|
|
|
|
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'},
|
|
{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:", 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);
|
|
break;
|
|
case 'r':
|
|
rate = atoi(optarg);
|
|
rate = rate < 4000 ? 4000 : rate;
|
|
rate = rate > 196000 ? 196000 : rate;
|
|
break;
|
|
case 'c':
|
|
channels = atoi(optarg);
|
|
channels = channels < 1 ? 1 : channels;
|
|
channels = channels > 1024 ? 1024 : channels;
|
|
break;
|
|
case 'f':
|
|
freq = atoi(optarg);
|
|
freq = freq < 50 ? 50 : freq;
|
|
freq = freq > 5000 ? 5000 : 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 (isdigit(*optarg)) {
|
|
test_type = atoi(optarg);
|
|
if (test_type < TEST_PINK_NOISE || test_type > TEST_WAV) {
|
|
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) {
|
|
printf(_("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;
|
|
default:
|
|
printf(_("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;
|
|
|
|
}
|
|
|
|
while ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
|
|
printf(_("Playback open error: %d,%s\n"), err,snd_strerror(err));
|
|
sleep(1);
|
|
}
|
|
|
|
if ((err = set_hwparams(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
|
|
printf(_("Setting of hwparams failed: %s\n"), snd_strerror(err));
|
|
snd_pcm_close(handle);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if ((err = set_swparams(handle, swparams)) < 0) {
|
|
printf(_("Setting of swparams failed: %s\n"), snd_strerror(err));
|
|
snd_pcm_close(handle);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
frames = malloc(snd_pcm_frames_to_bytes(handle, period_size));
|
|
if (test_type == TEST_PINK_NOISE)
|
|
initialize_pink_noise(&pink, 16);
|
|
|
|
if (frames == NULL) {
|
|
printf(_("No enough memory\n"));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (speaker==0) {
|
|
|
|
if (test_type == TEST_WAV) {
|
|
for (chn = 0; chn < channels; chn++) {
|
|
if (setup_wav_file(chn) < 0)
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
for (n = 0; ! nloops || n < nloops; n++) {
|
|
|
|
gettimeofday(&tv1, NULL);
|
|
for(chn = 0; chn < channels; chn++) {
|
|
int channel=chn;
|
|
if (channels == 4) {
|
|
channel=channels4[chn];
|
|
}
|
|
if (channels == 6) {
|
|
channel=channels6[chn];
|
|
}
|
|
if (channels == 8) {
|
|
channel=channels8[chn];
|
|
}
|
|
printf(" %d - %s\n", channel, gettext(channel_name[channel]));
|
|
|
|
err = write_loop(handle, channel, ((rate*3)/period_size), frames);
|
|
|
|
if (err < 0) {
|
|
printf(_("Transfer failed: %s\n"), snd_strerror(err));
|
|
free(frames);
|
|
snd_pcm_close(handle);
|
|
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 {
|
|
if (test_type == TEST_WAV) {
|
|
if (setup_wav_file(speaker - 1) < 0)
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
printf(" - %s\n", gettext(channel_name[speaker-1]));
|
|
err = write_loop(handle, speaker-1, ((rate*5)/period_size), frames);
|
|
|
|
if (err < 0) {
|
|
printf(_("Transfer failed: %s\n"), snd_strerror(err));
|
|
}
|
|
}
|
|
|
|
|
|
free(frames);
|
|
snd_pcm_close(handle);
|
|
|
|
exit(EXIT_SUCCESS);
|
|
}
|