shithub: sox

Download patch

ref: 78e008ae4a3ee1ba9ef2d0d56959369006b48dc2
parent: e8f0f3cb3908916824aa7125393ddd27597ae840
author: idigdoug <idigdoug>
date: Sat Feb 12 03:59:32 EST 2011

Add support for 24 and 32-bit samples to waveaudio driver.

--- a/ChangeLog
+++ b/ChangeLog
@@ -30,6 +30,8 @@
   o Fix immediate segfault on OSX while attempting to record.  May also
     prevent segfaults on playing that some people reported.  (Adam Fritzler)
   o Add support for 32-bit samples to OSS driver. (Eric Lammerts)
+  o Add support for 24 and 32-bit samples to waveaudio (Win32) driver.
+    (Doug Cook)
 
 Effects:
 
--- a/src/waveaudio.c
+++ b/src/waveaudio.c
@@ -19,6 +19,7 @@
 
 #include <windows.h>
 #include <mmsystem.h>
+#include <mmreg.h>
 
 /* Larger means more latency (difference between the status line and the audio you hear),
  * but it means lower chance of stuttering/glitching. 2 buffers is usually enough. Use
@@ -57,6 +58,9 @@
    */
   unsigned current;
 
+  /* Width of a sample in bytes: 1, 2, 3, or 4. */
+  unsigned sample_width;
+
   /* If there has been an error, this has the Win32 error code. Otherwise, this is 0. */
   DWORD error;
 } priv_t;
@@ -111,14 +115,64 @@
   return SOX_SUCCESS;
 }
 
+static int check_format(
+    WAVEFORMATEXTENSIBLE* pfmt,
+    int recording,
+    unsigned dev,
+    unsigned channels,
+    unsigned width,
+    unsigned hertz)
+{
+  static unsigned char const SubformatPcm[] = "\x01\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71";
+  const unsigned bytewidth = width > 24 ? 4 : width > 16 ? 3 : width > 8 ? 2 : 1;
+  const int extend = channels > 2 || bytewidth > 2;
+  DWORD error;
+  pfmt->Format.wFormatTag = extend ? WAVE_FORMAT_EXTENSIBLE : WAVE_FORMAT_PCM;
+  pfmt->Format.nChannels = channels;
+  pfmt->Format.nSamplesPerSec = hertz;
+  pfmt->Format.nAvgBytesPerSec = channels * bytewidth * hertz;
+  pfmt->Format.nBlockAlign = channels * bytewidth;
+  pfmt->Format.wBitsPerSample = bytewidth * 8;
+  pfmt->Format.cbSize = extend ? 22 : 0;
+  pfmt->Samples.wValidBitsPerSample = width;
+  pfmt->dwChannelMask = 0;
+  memcpy(&pfmt->SubFormat, SubformatPcm, 16);
+  if (recording)
+    error = waveInOpen(0, dev, &pfmt->Format, 0, 0, WAVE_FORMAT_QUERY);
+  else
+    error = waveOutOpen(0, dev, &pfmt->Format, 0, 0, WAVE_FORMAT_QUERY);
+  return error == MMSYSERR_NOERROR;
+}
+
+static int negotiate_format(sox_format_t* ft, WAVEFORMATEXTENSIBLE* pfmt, unsigned dev)
+{
+  int recording = ft->mode == 'r';
+
+  unsigned precision = ft->encoding.bits_per_sample;
+  if (precision > 32)
+    precision = 32;
+  else if (precision < 8)
+    precision = 8;
+
+  while (precision > 0)
+  {
+    if (check_format(pfmt, recording, dev, ft->signal.channels, precision, (unsigned)ft->signal.rate))
+      return 1;
+    precision = (precision - 1) & ~0x7;
+  }
+
+  return 0;
+}
+
 static int start(sox_format_t* ft)
 {
   size_t i;
   UINT dev;
-  WAVEFORMATEX fmt;
+  WAVEFORMATEXTENSIBLE fmt;
   int recording = ft->mode == 'r';
   priv_t *priv = (priv_t*)ft->priv;
   if (priv == NULL) return SOX_EOF;
+  memset(&fmt, 0, sizeof(fmt));
 
   /* Handle AUDIODEV device selection:
    * NULL, blank, or "default" gets you the default device (WAVE_MAPPER = -1).
@@ -125,7 +179,7 @@
    * An integer value gets you the device with that device number, if it exists (counting starts at 0).
    * A string gets you the device with that name, if it exists.
    */
-  if (ft->filename == 0 || ft->filename == 0 || !strcasecmp("default", ft->filename))
+  if (ft->filename == 0 || ft->filename[0] == 0 || !strcasecmp("default", ft->filename))
   {
     dev = WAVE_MAPPER;
   }
@@ -152,7 +206,7 @@
     else
     {
       UINT dev_count = recording ? waveInGetNumDevs() : waveOutGetNumDevs();
-      for (dev = -1; dev == WAVE_MAPPER || dev < dev_count; dev++)
+      for (dev = (UINT)-1; dev == WAVE_MAPPER || dev < dev_count; dev++)
       {
         if (recording)
         {
@@ -183,8 +237,26 @@
     }
   }
 
+  if (!negotiate_format(ft, &fmt, dev))
+  {
+    lsx_fail_errno(ft, ENODEV, "WaveAudio was unable to negotiate a sample format.");
+    return SOX_EOF;
+  }
+
+  priv->sample_width = fmt.Format.wBitsPerSample / 8;
+  ft->signal.precision = fmt.Samples.wValidBitsPerSample;
+  ft->signal.channels = fmt.Format.nChannels;
+  lsx_report(
+      "WaveAudio negotiated %s device %d with %uHz %uCh %uprec %uwidth.",
+      recording ? "input" : "output",
+      (int)dev,
+      (unsigned)fmt.Format.nSamplesPerSec,
+      (unsigned)fmt.Format.nChannels,
+      (unsigned)fmt.Samples.wValidBitsPerSample,
+      (unsigned)fmt.Format.wBitsPerSample);
+
   priv->buf_len = sox_globals.bufsiz;
-  priv->data = lsx_malloc(priv->buf_len * sizeof(int16_t) * num_buffers);
+  priv->data = lsx_malloc(priv->buf_len * priv->sample_width * num_buffers);
   if (!priv->data)
   {
     lsx_fail_errno(ft, SOX_ENOMEM, "Out of memory.");
@@ -200,21 +272,10 @@
     return SOX_EOF;
   }
 
-  /* Allow simulation of 8-bit audio playback. */
-  ft->signal.precision = ft->signal.precision >= 16 ? 16 : 8;
-
-  fmt.wFormatTag = WAVE_FORMAT_PCM;
-  fmt.nChannels = ft->signal.channels;
-  fmt.nSamplesPerSec = (DWORD)ft->signal.rate;
-  fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * sizeof(int16_t);
-  fmt.nBlockAlign = fmt.nChannels*sizeof(int16_t);
-  fmt.wBitsPerSample = sizeof(int16_t)*8;
-  fmt.cbSize = 0;
-
   if (recording)
-    priv->error = waveInOpen(&priv->hin, dev, &fmt, (DWORD_PTR)priv->block_finished_event, 0, CALLBACK_EVENT);
+    priv->error = waveInOpen(&priv->hin, dev, &fmt.Format, (DWORD_PTR)priv->block_finished_event, 0, CALLBACK_EVENT);
   else
-    priv->error = waveOutOpen(&priv->hout, dev, &fmt, (DWORD_PTR)priv->block_finished_event, 0, CALLBACK_EVENT);
+    priv->error = waveOutOpen(&priv->hout, dev, &fmt.Format, (DWORD_PTR)priv->block_finished_event, 0, CALLBACK_EVENT);
 
   if (priv->error != MMSYSERR_NOERROR)
   {
@@ -225,8 +286,8 @@
 
   for (i = 0; i < num_buffers; i++)
   {
-    priv->headers[i].lpData = priv->data + priv->buf_len * sizeof(int16_t) * i;
-    priv->headers[i].dwBufferLength = priv->buf_len * sizeof(int16_t);
+    priv->headers[i].lpData = priv->data + priv->buf_len * priv->sample_width * i;
+    priv->headers[i].dwBufferLength = priv->buf_len * priv->sample_width;
 
     if (recording)
       priv->error = waveInPrepareHeader(priv->hin, &priv->headers[i], sizeof(priv->headers[i]));
@@ -278,13 +339,38 @@
     if (0 == (header->dwFlags & WHDR_INQUEUE) ||
       0 != (header->dwFlags & WHDR_DONE))
     {
-      size_t length = header->dwBytesRecorded / sizeof(int16_t);
+      size_t length = header->dwBytesRecorded / priv->sample_width;
       size_t ready = min(len - copied, length - header->dwUser);
       size_t i;
 
-      for (i = 0; i < ready; ++i)
+      switch (priv->sample_width)
       {
-        buf[copied++] = SOX_SIGNED_16BIT_TO_SAMPLE(((int16_t *)header->lpData)[header->dwUser++], dummy);
+      case 1:
+          for (i = 0; i < ready; ++i)
+          {
+            buf[copied++] = SOX_UNSIGNED_8BIT_TO_SAMPLE(((uint8_t *)header->lpData)[header->dwUser++], dummy);
+          }
+          break;
+      case 2:
+          for (i = 0; i < ready; ++i)
+          {
+            buf[copied++] = SOX_SIGNED_16BIT_TO_SAMPLE(((int16_t *)header->lpData)[header->dwUser++], dummy);
+          }
+          break;
+      case 3:
+          for (i = 0; i < ready; ++i)
+          {
+            int24_t x = *(UNALIGNED int24_t*)(header->lpData + header->dwUser * 3);
+            buf[copied++] = SOX_SIGNED_24BIT_TO_SAMPLE(x, dummy);
+            header->dwUser++;
+          }
+          break;
+      case 4:
+          for (i = 0; i < ready; ++i)
+          {
+            buf[copied++] = SOX_SIGNED_32BIT_TO_SAMPLE(((int32_t *)header->lpData)[header->dwUser++], dummy);
+          }
+          break;
       }
 
       if (header->dwUser == length)
@@ -324,26 +410,43 @@
       size_t ready = min(len - copied, priv->buf_len - header->dwUser);
       size_t i;
 
-      if (ft->signal.precision != 8)
+      switch (priv->sample_width)
       {
-        /* Normal case: Play with 16-bit resolution. */
-        for (i = 0; i < ready; ++i)
-        {
-          SOX_SAMPLE_LOCALS;
-          ((int16_t *)header->lpData)[header->dwUser++] = SOX_SAMPLE_TO_SIGNED_16BIT(buf[copied++], clips);
-        }
+      case 1:
+          for (i = 0; i < ready; ++i)
+          {
+            SOX_SAMPLE_LOCALS;
+            ((uint8_t *)header->lpData)[header->dwUser++] = SOX_SAMPLE_TO_UNSIGNED_8BIT(buf[copied++], clips);
+          }
+          break;
+      case 2:
+          for (i = 0; i < ready; ++i)
+          {
+            SOX_SAMPLE_LOCALS;
+            ((int16_t *)header->lpData)[header->dwUser++] = SOX_SAMPLE_TO_SIGNED_16BIT(buf[copied++], clips);
+          }
+          break;
+      case 3:
+          for (i = 0; i < ready; ++i)
+          {
+            SOX_SAMPLE_LOCALS;
+            unsigned char* pdata = (unsigned char*)header->lpData + header->dwUser * 3;
+            int24_t x = SOX_SAMPLE_TO_SIGNED_24BIT(buf[copied++], clips);
+            *pdata++ = x;
+            *pdata++ = x >> 8;
+            *pdata++ = x >> 16;
+            header->dwUser++;
+          }
+          break;
+      case 4:
+          for (i = 0; i < ready; ++i)
+          {
+            ((int32_t *)header->lpData)[header->dwUser++] = SOX_SAMPLE_TO_SIGNED_32BIT(buf[copied++], clips);
+          }
+          break;
       }
-      else
-      {
-        /* Special case: Simulate 8-bit audio playback. */
-        for (i = 0; i < ready; ++i)
-        {
-          SOX_SAMPLE_LOCALS;
-          ((int16_t *)header->lpData)[header->dwUser++] = SOX_SAMPLE_TO_SIGNED_8BIT(buf[copied++], clips) << 8;
-        }
-      }
 
-      header->dwBufferLength = header->dwUser * sizeof(int16_t);
+      header->dwBufferLength = header->dwUser * priv->sample_width;
       priv->error = waveOutWrite(priv->hout, header, sizeof(*header));
       priv->current = (priv->current + 1) % num_buffers;
       priv->headers[priv->current].dwUser = 0;
@@ -367,8 +470,7 @@
 {
   static const char * const names[] = {"waveaudio", NULL};
   static unsigned const write_encodings[] = {
-    SOX_ENCODING_SIGN2, 16, 0,
-    SOX_ENCODING_SIGN2, 8, 0,
+    SOX_ENCODING_SIGN2, 16, 24, 32, 8, 0,
     0};
   static sox_format_handler_t const handler = {SOX_LIB_VERSION_CODE,
   "Windows Multimedia Audio", names,