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