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,