shithub: sox

Download patch

ref: 9be087e6db2fc207507b4ab09e3da9e186a19d79
parent: 8099559f373645b98fa4cc800fbed0aec0a0705f
author: robs <robs>
date: Sat Sep 12 06:36:00 EDT 2009

MS wave audio record support from Doug Cook

--- a/ChangeLog
+++ b/ChangeLog
@@ -38,7 +38,7 @@
 
 Audio device drivers:
 
-  o Add native windows audio output driver. (Pavel Karneliuk)
+  o Add native windows audio driver. (Pavel Karneliuk, Doug Cook)
   o Try default ALSA record encoding parameters if those given
     are invalid (would previously fail).  (robs)
 
--- a/soxformat.7
+++ b/soxformat.7
@@ -654,10 +654,7 @@
 option with the output file options.
 .TP
 \fBwaveaudio\fR (optional)
-Wave Audio device driver; supports only playing of audio.
-This is a native Windows audio driver.  The OSS driver is also
-often used when compiled with Cygwin under Windows since it
-has read and write support.
+MS-Windows native audio device driver.
 If a file name is specified with this driver, it is ignored.  Examples:
 .EX
 	sox infile -t waveaudio
--- a/src/waveaudio.c
+++ b/src/waveaudio.c
@@ -1,6 +1,4 @@
-/* libSoX (c) 2009 SoX contributors
- * Copyright (c) 2009 Pavel Karneliuk pavel_karneliuk@users.sourceforge.net
- * Implementation of audio output driver for Windows
+/* libSoX device driver: MS-Windows audio   (c) 2009 SoX contributors
  *
  * This library is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as published by
@@ -18,172 +16,337 @@
  */
 
 #include "sox_i.h"
-#ifdef HAVE_WAVEAUDIO
 
-#include <assert.h>
 #include <windows.h>
 #include <mmsystem.h>
 
-#define num_buffers 16
-typedef struct {
-    HWAVEOUT    waveout;
-    WAVEFORMATEX  format;
+/* 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
+ * 4 if you want to be extra-safe.
+ */
+#define num_buffers 4
 
-    HGLOBAL handle_data;
-    HGLOBAL handle_wavheader;
+typedef struct waveaudio_priv_t
+{
+  /* Handle to the input device (microphone, line in, etc.). NULL if playing. */
+  HWAVEIN hin;
 
-    HPSTR   ptr_data[num_buffers];
-    WAVEHDR*   ptr_wavheader;
+  /* Handle to the output device (speakers, line out, etc.). NULL if recording. */
+  HWAVEOUT hout;
 
-    HANDLE need_more_data_blocks;
-} priv_t;
+  /* Event that becomes signaled when a the system has finished processing a buffer. */
+  HANDLE block_finished_event;
 
-void CALLBACK waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR instance, DWORD_PTR Param1, DWORD_PTR Param2);
+  /* Data transfer buffers. The lpData member of the first buffer points at
+   * data[buf_len*sample_size*0], the second buffer's lpData points
+   * data[buf_len*sample_size*1], etc. The dwUser field contains the number
+   * of samples of this buffer that have already been processed.
+   */
+  WAVEHDR headers[num_buffers];
 
-int setup_write(sox_format_t* ft)
-{
-    DWORD buf_len;
-    int i;
-    priv_t *priv = (priv_t *)ft->priv;
-    if(priv == NULL) return SOX_EOF;
+  /* The combined data area shared by all transfer buffers. */
+  char * data;
 
-    ft->signal.precision = 16;
+  /* The number of samples that can fit into one transfer buffer. */
+  size_t buf_len;
 
-    priv->format.wFormatTag = WAVE_FORMAT_PCM;
-    priv->format.nChannels = ft->signal.channels;
-    priv->format.nSamplesPerSec = ft->signal.rate;
-    priv->format.wBitsPerSample = sizeof(int16_t)*8;
-    priv->format.nAvgBytesPerSec = priv->format.nSamplesPerSec * priv->format.wBitsPerSample/8;
-    priv->format.nBlockAlign = (priv->format.nChannels*priv->format.wBitsPerSample)/8;
-    priv->format.cbSize = 0;
+  /* Index of the buffer that we're currently processing. For playback, this is the buffer
+   * that will receive the next samples. For recording, this is the buffer from which we'll
+   * be getting the next samples. If no buffers are ready for processing, this is the buffer
+   * that will be the next to become ready.
+   */
+  unsigned current;
 
-    buf_len = sox_globals.bufsiz * sizeof(int16_t);
+  /* If there has been an error, this has the Win32 error code. Otherwise, this is 0. */
+  DWORD error;
+} priv_t;
 
-    priv->handle_data = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, buf_len * num_buffers);
-    priv->ptr_data[0]      = (HPSTR) GlobalLock(priv->handle_data);
-    priv->handle_wavheader         = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, (DWORD)sizeof(WAVEHDR) * num_buffers);
-    priv->ptr_wavheader = (WAVEHDR*) GlobalLock(priv->handle_wavheader);
-    memset(priv->ptr_wavheader, 0, sizeof(WAVEHDR) * num_buffers);
-    for(i=0; i<num_buffers; i++)
-    {
-        priv->ptr_data[i] = priv->ptr_data[0] + buf_len*i;
+static void fail(sox_format_t* ft, DWORD code, const char* context)
+{
+  char message[256];
+  DWORD formatMessageOk = FormatMessageA(
+    FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+    NULL,
+    code,
+    0,
+    message,
+    sizeof(message) / sizeof(message[0]),
+    NULL);
+  if (formatMessageOk)
+    lsx_fail_errno(ft, SOX_EOF, "WaveAudio %s failed with code %d: %s", context, code, message);
+  else
+    lsx_fail_errno(ft, SOX_EOF, "WaveAudio %s failed with unrecognized MMSYSERR code %d.", context, code);
+}
 
-        priv->ptr_wavheader[i].lpData = priv->ptr_data[i];
-        priv->ptr_wavheader[i].dwBufferLength = buf_len;
-        priv->ptr_wavheader[i].dwFlags = 0L;
-        priv->ptr_wavheader[i].dwLoops = 0L;
+static int stop(sox_format_t* ft)
+{
+  priv_t *priv = (priv_t*)ft->priv;
+  if (priv == NULL) return SOX_EOF;
+
+  if (priv->hin)
+  {
+    priv->error = waveInReset(priv->hin);
+    priv->error = waveInClose(priv->hin);
+  }
+  
+  if (priv->hout && !priv->error)
+  {
+    while ((priv->error = waveOutClose(priv->hout)) == WAVERR_STILLPLAYING)
+    {
+      WaitForSingleObject(priv->block_finished_event, INFINITE);
     }
+  }
+  else if (priv->hout)
+  {
+    priv->error = waveOutReset(priv->hout);
+    priv->error = waveOutClose(priv->hout);
+  }
 
-    priv->waveout = NULL;
+  if (priv->block_finished_event)
+    CloseHandle(priv->block_finished_event);
 
-    waveOutOpen((LPHWAVEOUT)&priv->waveout, WAVE_MAPPER, &priv->format,
-                               (DWORD_PTR)waveOutProc, (DWORD_PTR)priv, CALLBACK_FUNCTION);
+  if (priv->data)
+    free(priv->data);
 
-    priv->need_more_data_blocks = CreateEvent(NULL, TRUE, TRUE, NULL);
-
-    return priv->waveout ? SOX_SUCCESS : SOX_EOF;
+  return SOX_SUCCESS;
 }
 
-int stop_write(sox_format_t* ft)
+static int start(sox_format_t* ft)
 {
-    priv_t *priv = (priv_t *)ft->priv;
-    if(priv == NULL) return SOX_EOF;
+  size_t i;
+  UINT dev;
+  WAVEFORMATEX fmt;
+  int recording = ft->mode == 'r';
+  priv_t *priv = (priv_t*)ft->priv;
+  if (priv == NULL) return SOX_EOF;
 
-    while(WAVERR_STILLPLAYING == waveOutClose(priv->waveout))
+  /* Handle AUDIODEV device selection:
+   * NULL, blank, or "default" gets you the default device (WAVE_MAPPER = -1).
+   * 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))
+  {
+    dev = WAVE_MAPPER;
+  }
+  else
+  {
+    WAVEINCAPSA incaps;
+    WAVEOUTCAPSA outcaps;
+    const char *dev_name;
+    char* dev_num_end;
+    dev = strtoul(ft->filename, &dev_num_end, 0);
+    if (dev_num_end[0] == 0)
     {
-#if 0
-        /* terminate */
-        if( priv->is_cancelled() )
+      if (recording)
+        priv->error = waveInGetDevCapsA(dev, &incaps, sizeof(incaps));
+      else
+        priv->error = waveOutGetDevCapsA(dev, &outcaps, sizeof(outcaps));
+
+      if (priv->error)
+      {
+        lsx_fail_errno(ft, ENODEV, "WaveAudio was unable to find the AUDIODEV %s device \"%s\".", recording ? "input" : "output", ft->filename);
+        return SOX_EOF;
+      }
+    }
+    else
+    {
+      UINT dev_count = recording ? waveInGetNumDevs() : waveOutGetNumDevs();
+      for (dev = -1; dev == WAVE_MAPPER || dev < dev_count; dev++)
+      {
+        if (recording)
         {
-            waveOutReset(priv->waveout);
+          priv->error = waveInGetDevCapsA(dev, &incaps, sizeof(incaps));
+          dev_name = incaps.szPname;
         }
-        else priv->update_pos();
-#endif
-        Sleep(50);
+        else
+        {
+          priv->error = waveOutGetDevCapsA(dev, &outcaps, sizeof(outcaps));
+          dev_name = outcaps.szPname;
+        }
+
+        if (priv->error)
+        {
+          fail(ft, priv->error, recording ? "waveInGetDevCapsA" : "waveOutGetDevCapsA");
+          return SOX_EOF;
+        }
+
+        if (!strncasecmp(ft->filename, dev_name, 31))
+          break;
+      }
+
+      if (dev == dev_count)
+      {
+        lsx_fail_errno(ft, ENODEV, "WaveAudio was unable to find the AUDIODEV %s device \"%s\".", recording ? "input" : "output", ft->filename);
+        return SOX_EOF;
+      }
     }
+  }
 
-    CloseHandle(priv->need_more_data_blocks);
+  priv->buf_len = sox_globals.bufsiz;
+  priv->data = lsx_malloc(priv->buf_len * sizeof(int16_t) * num_buffers);
+  if (!priv->data)
+  {
+    lsx_fail_errno(ft, SOX_ENOMEM, "Out of memory.");
+    return SOX_EOF;
+  }
 
-    GlobalUnlock(priv->handle_wavheader);
-    GlobalFree(priv->handle_wavheader);
+  priv->block_finished_event = CreateEventA(NULL, FALSE, FALSE, NULL);
+  if (!priv->block_finished_event)
+  {
+    priv->error = GetLastError();
+    fail(ft, priv->error, "CreateEventA");
+    stop(ft);
+    return SOX_EOF;
+  }
 
-    GlobalUnlock(priv->handle_data);
-    GlobalFree(priv->handle_data);
+  ft->signal.precision = 16;
+  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;
 
-    return SOX_SUCCESS;
-}
+  if (recording)
+    priv->error = waveInOpen(&priv->hin, dev, &fmt, (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);
 
-static size_t write(sox_format_t * ft, const sox_sample_t* buf, size_t len)
-{
-    int i;
-    int clips = 0;
-    size_t j;
-    WAVEHDR* header = NULL;
-    MMRESULT res = 0;
-    priv_t *priv = (priv_t *)ft->priv;
-    if(priv == NULL) return SOX_EOF;
+  if (priv->error != MMSYSERR_NOERROR)
+  {
+    fail(ft, priv->error, recording ? "waveInOpen" : "waveOutOpen");
+    stop(ft);
+    return SOX_EOF;
+  }
 
-    while(header == NULL)
+  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);
+
+    if (recording)
+      priv->error = waveInPrepareHeader(priv->hin, &priv->headers[i], sizeof(priv->headers[i]));
+    else
+      priv->error = waveOutPrepareHeader(priv->hout, &priv->headers[i], sizeof(priv->headers[i]));
+
+    if (priv->error != MMSYSERR_NOERROR)
     {
-        /* find first free header */
-        for(i=0; i<num_buffers; i++)
-        {
-            if(priv->ptr_wavheader[i].dwFlags == 0 || priv->ptr_wavheader[i].dwFlags & WHDR_DONE )
-            {
-                header = &priv->ptr_wavheader[i];
-                break;
-            }
-        }
+      fail(ft, priv->error, recording ? "waveInPrepareHeader" : "waveOutPrepareHeader");
+      stop(ft);
+      return SOX_EOF;
+    }
 
-        if(header == NULL) /* not found free data blocks */
-        {
-            while(WAIT_TIMEOUT == WaitForSingleObject(priv->need_more_data_blocks, 50))
-            {
-/*                if(task->is_cancelled())
-                {
-                    waveOutReset(task->waveout);
-                    *osamp = 0;
-                    return SOX_SUCCESS;
-                }
-                else priv->update_pos();*/
-            }
-            ResetEvent(priv->need_more_data_blocks);
-        }
+    if (recording)
+    {
+      priv->error = waveInAddBuffer(priv->hin, &priv->headers[i], sizeof(priv->headers[i]));
+      if (priv->error != MMSYSERR_NOERROR)
+      {
+        fail(ft, priv->error, "waveInAddBuffer");
+        stop(ft);
+        return SOX_EOF;
+      }
     }
+  }
 
-    /* put ibuf into data block for playback */
-    if(header)
+  if (recording)
+  {
+    priv->error = waveInStart(priv->hin);
+    if (priv->error != MMSYSERR_NOERROR)
     {
-        res = waveOutUnprepareHeader(priv->waveout, header, sizeof(WAVEHDR));
-        assert(MMSYSERR_NOERROR == res);
+      fail(ft, priv->error, "waveInStart");
+      stop(ft);
+      return SOX_EOF;
+    }
+  }
 
-        for(j=0; j< len; ++j)
-        {
-            SOX_SAMPLE_LOCALS;
-            ((int16_t *)header->lpData)[j] = SOX_SAMPLE_TO_SIGNED_16BIT(buf[j],clips);
-        }
+  return SOX_SUCCESS;
+}
 
-        header->dwBufferLength = len * sizeof(int16_t);
-        header->dwFlags = 0;
+static size_t read(sox_format_t * ft, sox_sample_t* buf, size_t len)
+{
+  size_t copied = 0;
+  priv_t *priv = (priv_t*)ft->priv;
+  if (priv == NULL) return SOX_EOF;
 
-        res = waveOutPrepareHeader(priv->waveout, header, sizeof(WAVEHDR));
-        assert(MMSYSERR_NOERROR == res);
+  while (!priv->error && copied < len)
+  {
+    LPWAVEHDR header = &priv->headers[priv->current];
+    if (0 == (header->dwFlags & WHDR_INQUEUE) ||
+      0 != (header->dwFlags & WHDR_DONE))
+    {
+      size_t length = header->dwBytesRecorded / sizeof(int16_t);
+      size_t ready = min(len - copied, length - header->dwUser);
+      size_t i;
 
-        waveOutWrite(priv->waveout, header, sizeof(WAVEHDR));
-        assert(MMSYSERR_NOERROR == res);
+      for (i = 0; i < ready; ++i)
+      {
+        SOX_SAMPLE_LOCALS;
+        buf[copied++] = SOX_SIGNED_16BIT_TO_SAMPLE(((int16_t *)header->lpData)[header->dwUser++], dummy);
+      }
+
+      if (header->dwUser == length)
+      {
+        priv->error = waveInAddBuffer(priv->hin, header, sizeof(*header));
+        priv->current = (priv->current + 1) % num_buffers;
+        priv->headers[priv->current].dwUser = 0;
+        if (priv->error)
+        {
+          fail(ft, priv->error, "waveInAddBuffer");
+          copied = 0;
+        }
+      }
     }
+    else
+    {
+      WaitForSingleObject(priv->block_finished_event, INFINITE);
+    }
+  }
 
-    return len;
+  return copied;
 }
 
-void CALLBACK waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR instance, DWORD_PTR Param1, DWORD_PTR Param2)
+static size_t write(sox_format_t * ft, const sox_sample_t* buf, size_t len)
 {
-    priv_t* priv = (priv_t*) instance;
-    /* unlock Sox pipeline if some data block has been processed*/
-    if( uMsg == WOM_DONE )
+  unsigned clips = 0;
+  size_t copied = 0;
+  priv_t *priv = (priv_t*)ft->priv;
+  if (priv == NULL) return SOX_EOF;
+
+  while (!priv->error && copied < len)
+  {
+    LPWAVEHDR header = &priv->headers[priv->current];
+    if (0 == (header->dwFlags & WHDR_INQUEUE) ||
+      0 != (header->dwFlags & WHDR_DONE))
     {
-        SetEvent(priv->need_more_data_blocks);
+      size_t ready = min(len - copied, priv->buf_len - header->dwUser);
+      size_t i;
+
+      for (i = 0; i < ready; ++i)
+      {
+        SOX_SAMPLE_LOCALS;
+        ((int16_t *)header->lpData)[header->dwUser++] = SOX_SAMPLE_TO_SIGNED_16BIT(buf[copied++], clips);
+      }
+
+      header->dwBufferLength = header->dwUser * sizeof(int16_t);
+      priv->error = waveOutWrite(priv->hout, header, sizeof(*header));
+      priv->current = (priv->current + 1) % num_buffers;
+      priv->headers[priv->current].dwUser = 0;
+
+      if (priv->error)
+      {
+        fail(ft, priv->error, "waveOutWrite");
+        copied = 0;
+      }
     }
+    else
+    {
+      WaitForSingleObject(priv->block_finished_event, INFINITE);
+    }
+  }
+
+  return copied;
 }
 
 LSX_FORMAT_HANDLER(waveaudio)
@@ -191,12 +354,11 @@
   static const char * const names[] = {"waveaudio", NULL};
   static unsigned const write_encodings[] = { SOX_ENCODING_UNKNOWN, 16, 0, 0};
   static sox_format_handler_t const handler = {SOX_LIB_VERSION_CODE,
-    "Windows Multimedia Audio Output", names, 
-    SOX_FILE_DEVICE | SOX_FILE_NOSTDIO,
-    NULL, NULL, NULL,
-    setup_write, write, stop_write,
-    NULL, write_encodings, NULL, sizeof(priv_t)
+  "Windows Multimedia Audio", names, 
+  SOX_FILE_DEVICE | SOX_FILE_NOSTDIO,
+  start, read, stop,
+  start, write, stop,
+  NULL, write_encodings, NULL, sizeof(priv_t)
   };
   return &handler;
 }
-#endif