]> git.alsa-project.org Git - alsa-utils.git/commitdiff
Add WAV file playback support to speaker-test
authorTakashi Iwai <tiwai@suse.de>
Wed, 30 Nov 2005 17:09:17 +0000 (17:09 +0000)
committerTakashi Iwai <tiwai@suse.de>
Wed, 30 Nov 2005 17:09:17 +0000 (17:09 +0000)
Add WAV file playback support to speaker-test.
The support is still limited to S16_LE, and doesn't refer to csv file yet.

configure.in
speaker-test/speaker-test.1
speaker-test/speaker-test.c

index 13a51d6d06da2221b1dafdb8714c3e23225b14d6..f7969f3e5664b1e5e16bd32ee906568b7815ce72 100644 (file)
@@ -65,10 +65,21 @@ fi
 AC_SUBST(CURSESINC)
 AC_SUBST(CURSESLIB)
 
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+
+eval dir="$datadir"
+case "$dir" in
+/*) ;;
+*) dir="$PWD/$dir"
+esac
+
+dir="$dir/sounds/alsa"
+AC_DEFINE_UNQUOTED(DATADIR, "$dir", [directory containing sample data])
+
 AC_ARG_WITH(testsound,
   [  --with-testsound=file        give the path of test sound file for alsaconf],
   TESTSOUND="$withval",
-  TESTSOUND="/usr/share/sounds/alsa/test.wav")
+  TESTSOUND="$dir/test.wav")
 AC_SUBST(TESTSOUND)
 
 AM_CONFIG_HEADER(include/aconfig.h)
index 07a6485d83132a2ca9b681879c1a2eda33f98b89..2f2a7de76be6efef314dfb22bc4e94e06b7ba814 100644 (file)
@@ -17,7 +17,7 @@ speaker\-test \- command-line speaker test tone generator for ALSA
 .SH SYNOPSIS
 .B speaker\-test
 .BR "" [ \-D | \-\-device 
-.IR FILE ]
+.IR NAME ]
 .BR "" [ \-r | \-\-rate
 .IR RATE ]
 .BR "" [ \-c | \-\-channels
@@ -28,7 +28,12 @@ speaker\-test \- command-line speaker test tone generator for ALSA
 .IR TIME ]
 .BR "" [ \-n | \-\-noise
 .IR TIME ]
-.BR "" [ \-s | \-\-speaker " " "1|2" ]
+.BR "" [ \-s | \-\-speaker
+.IR TYPE ]
+.BR "" [ \-w | \-\-wavfile
+.IR FILE ]
+.BR "" [ \-W | \-\-wavdir
+.IR DIR ]
 .P
 .B speaker\-test
 .B \-\-help
@@ -63,17 +68,30 @@ period size of \fITIME\fP microseconds
 stream of \fIRATE\fP Hz
 
 .TP
-\fB\-t\fP | \fB\-\-test\fP \fB1\fP|\fB2\fP
--t1 means use pink noise (default).
+\fB\-s\fP | \fB\-\-speaker\fP \fBpink\fP|\fBsine\fP|\fBwav\fP
+-tpink means use pink noise (default).
 
 Pink noise is perceptually uniform noise - that is, it sounds like every frequency at once.  If you can hear any tone it may indicate resonances in your speaker system or room.
 
--t2 means use sine wave.
+-tsine means use sine wave.
+
+-twav means to play WAV files, either pre-defined files or given via -w option.
+
+You can pass the number from 1 to 3 as a backward compatibility.
 
 .TP
-\fB\-s\fP | \fB\-\-speaker\fP \fB1\fP|\fB2\fP
+\fB\-s\fP | \fB\-\-speaker\fP \fBpink\fP|\fBsine\fP|\fBwav\fP
 Test speaker 1 or speaker 2 only, rather than both
 
+.TP
+\fB\-w\fP | \fB\-\-wavfile\fP
+Use the given WAV file for the playback instead of pre-defined WAV files.
+
+.TP
+\fB\-W\fP | \fB\-\-wavdir\fP
+Specify the directory containing WAV files for playback.
+
+
 .SH USAGE EXAMPLES
 
 Produce stereo sound from one stereo jack:
index 7e5f3042d43ec8c409833d98eefa7ad884bacfd7..8c7a55f0c1e86becf89d640aec3a309a774ee18e 100644 (file)
 #include <errno.h>
 #include <getopt.h>
 #include <inttypes.h>
+#include <ctype.h>
+#ifdef ENABLE_NLS
 #include <locale.h>
+#endif
 
 #define ALSA_PCM_NEW_HW_PARAMS_API
 #define ALSA_PCM_NEW_SW_PARAMS_API
 #include "aconfig.h"
 #include "gettext.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 */
@@ -63,12 +74,15 @@ static unsigned int       buffer_time = 500000;                 /* ring buffer lengt
 static unsigned int       period_time = 100000;                    /* period time in us */
 #define PERIODS 4
 static double             freq        = 440;                /* sinusoidal wave frequency in Hz */
-static int                test_type   = 1;                  /* Test type. 1 = noise, 2 = sine wave */
+static int                test_type   = TEST_PINK_NOISE;    /* Test type. 1 = noise, 2 = sine wave */
 static pink_noise_t pink;
 static snd_output_t      *output      = NULL;
 static snd_pcm_uframes_t  buffer_size;
 static snd_pcm_uframes_t  period_size;
-static const char        *channel_name[] = {
+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"),
@@ -77,15 +91,14 @@ static const char        *channel_name[] = {
   N_("LFE"),
   N_("Side Left"),
   N_("Side Right"),
-  N_("8"),
-  N_("9"),
-  N_("10"),
-  N_("11"),
-  N_("12"),
-  N_("13"),
-  N_("14"),
-  N_("15"),
-  N_("16")
+  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[] = {
@@ -433,44 +446,244 @@ static int xrun_recovery(snd_pcm_t *handle, int err) {
   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, "cannot allocate\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",
+    "Center.wav"
+    "LFE.wav",
+    "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;
-  uint8_t *ptr;
-  int    err, cptr, n;
-  int    bytes_per_frame=snd_pcm_frames_to_bytes(handle, 1);
+  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;
+    }
+    return err;
+  }
+    
 
   for(n = 0; n < periods; n++) {
-    if(test_type==1)
+    if (test_type == TEST_PINK_NOISE)
       generate_pink_noise(frames, channel, period_size);
     else
       generate_sine(frames, channel, period_size, &phase);
-      
-    ptr = frames;
-    cptr = period_size;
-
-    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 += (err * bytes_per_frame);
-      cptr -= err;
-    }
+    if ((err = write_buffer(handle, frames, period_size)) < 0)
+      return err;
   }
 
   return 0;
@@ -490,8 +703,10 @@ static void help(void)
           "-F,--format sample format\n"
           "-b,--buffer ring buffer size in us\n"
           "-p,--period period size in us\n"
-          "-t,--test   1=use pink noise, 2=use sine wave\n"
+          "-t,--test   pink=use pink noise, sine=use sine wave, wav=WAV file\n"
           "-s,--speaker        single speaker test. Values 1=Left or 2=right\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:"));
@@ -527,6 +742,8 @@ int main(int argc, char *argv[]) {
     {"period",    1, NULL, 'p'},
     {"test",      1, NULL, 't'},
     {"speaker",   1, NULL, 's'},
+    {"wavfile",   1, NULL, 'w'},
+    {"wavdir",    1, NULL, 'W'},
     {NULL,        0, NULL, 0  },
   };
 
@@ -544,7 +761,7 @@ int main(int argc, char *argv[]) {
   while (1) {
     int c;
     
-    if ((c = getopt_long(argc, argv, "hD:r:c:f:F:b:p:t:s:", long_option, NULL)) < 0)
+    if ((c = getopt_long(argc, argv, "hD:r:c:f:F:b:p:t:s:w:W:", long_option, NULL)) < 0)
       break;
     
     switch (c) {
@@ -583,9 +800,22 @@ int main(int argc, char *argv[]) {
       period_time = period_time > 1000000 ? 1000000 : period_time;
       break;
     case 't':
-      test_type = atoi(optarg);
-      test_type = test_type < 1 ? 1 : test_type;
-      test_type = test_type > 2 ? 2 : test_type;
+      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 's':
       speaker = atoi(optarg);
@@ -596,6 +826,12 @@ int main(int argc, char *argv[]) {
         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);
@@ -608,6 +844,9 @@ int main(int argc, char *argv[]) {
     exit(EXIT_SUCCESS);
   }
 
+  if (test_type == TEST_WAV)
+    format = SND_PCM_FORMAT_S16_LE; /* fixed format */
+
   err = snd_output_stdio_attach(&output, stdout, 0);
   if (err < 0) {
     printf(_("Output failed: %s\n"), snd_strerror(err));
@@ -616,11 +855,18 @@ int main(int argc, char *argv[]) {
 
   printf(_("Playback device is %s\n"), device);
   printf(_("Stream parameters are %iHz, %s, %i channels\n"), rate, snd_pcm_format_name(format), channels);
-  if(test_type==1)
+  switch (test_type) {
+  case TEST_PINK_NOISE:
     printf(_("Using 16 octaves of pink noise\n"));
-  else
+    break;
+  case TEST_SINE:
     printf(_("Sine wave rate is %.4fHz\n"), freq);
+    break;
+  case TEST_WAV:
+    printf(_("WAV file(s)\n"));
+    break;
 
+  }
 loop:
   while ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
     printf(_("Playback open error: %d,%s\n"), err,snd_strerror(err));
@@ -641,8 +887,9 @@ loop:
     exit(EXIT_FAILURE);
   }
 
-  frames = malloc((period_size * channels * snd_pcm_format_width(format)) / 8);
-  initialize_pink_noise( &pink, 16);
+  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"));
@@ -651,6 +898,13 @@ loop:
   if (speaker==0) {
     while (1) {
 
+      if (test_type == TEST_WAV) {
+       for (chn = 0; chn < channels; chn++) {
+         if (setup_wav_file(chn) < 0)
+           exit(EXIT_FAILURE);
+       }
+      }
+
       gettimeofday(&tv1, NULL);
       for(chn = 0; chn < channels; chn++) {
        int channel=chn;
@@ -687,6 +941,11 @@ loop:
       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);