input/wav: Deal with IEEE float samples in WAV files.

This also skips chunks before the 'data' chunk in WAV files, as
this is quite common.
This commit is contained in:
Bert Vermeulen 2014-08-01 12:02:09 +02:00
parent 364859ac73
commit cb41a838a7
1 changed files with 89 additions and 27 deletions

View File

@ -21,18 +21,27 @@
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <string.h>
#include "libsigrok.h"
#include "libsigrok-internal.h"
#define LOG_PREFIX "input/wav"
/* How many bytes at a time to process and send to the session bus. */
#define CHUNK_SIZE 4096
/* Expect to find the "data" chunk within this offset from the start. */
#define MAX_DATA_CHUNK_OFFSET 256
#define WAVE_FORMAT_PCM 1
#define WAVE_FORMAT_IEEE_FLOAT 3
struct context {
uint64_t samplerate;
int samplesize;
int num_channels;
int unitsize;
int fmt_code;
};
static int get_wav_header(const char *filename, char *buf)
@ -64,6 +73,7 @@ static int get_wav_header(const char *filename, char *buf)
static int format_match(const char *filename)
{
char buf[40];
uint16_t fmt_code;
if (get_wav_header(filename, buf) != SR_OK)
return FALSE;
@ -74,10 +84,9 @@ static int format_match(const char *filename)
return FALSE;
if (strncmp(buf + 12, "fmt ", 4))
return FALSE;
if (GUINT16_FROM_LE(*(uint16_t *)(buf + 20)) != 1)
/* Not PCM. */
return FALSE;
if (strncmp(buf + 36, "data", 4))
fmt_code = GUINT16_FROM_LE(*(uint16_t *)(buf + 20));
if (fmt_code != WAVE_FORMAT_PCM
&& fmt_code != WAVE_FORMAT_IEEE_FLOAT)
return FALSE;
return TRUE;
@ -100,28 +109,58 @@ static int init(struct sr_input *in, const char *filename)
in->sdi = sr_dev_inst_new(0, SR_ST_ACTIVE, NULL, NULL, NULL);
in->sdi->priv = ctx;
ctx->fmt_code = GUINT16_FROM_LE(*(uint16_t *)(buf + 20));
ctx->samplerate = GUINT32_FROM_LE(*(uint32_t *)(buf + 24));
ctx->samplesize = GUINT16_FROM_LE(*(uint16_t *)(buf + 34)) / 8;
if (ctx->samplesize != 1 && ctx->samplesize != 2 && ctx->samplesize != 4) {
ctx->samplesize = GUINT16_FROM_LE(*(uint16_t *)(buf + 32));
ctx->num_channels = GUINT16_FROM_LE(*(uint16_t *)(buf + 22));
ctx->unitsize = ctx->samplesize / ctx->num_channels;
if (ctx->fmt_code == WAVE_FORMAT_PCM) {
if (ctx->samplesize != 1 && ctx->samplesize != 2
&& ctx->samplesize != 4) {
sr_err("only 8, 16 or 32 bits per sample supported.");
return SR_ERR;
}
if ((ctx->num_channels = GUINT16_FROM_LE(*(uint16_t *)(buf + 22))) > 20) {
sr_err("%d channels seems crazy.", ctx->num_channels);
} else {
/* WAVE_FORMAT_IEEE_FLOAT */
if (ctx->samplesize / ctx->num_channels != 4) {
sr_err("only 32-bit floats supported.");
return SR_ERR;
}
}
for (i = 0; i < ctx->num_channels; i++) {
snprintf(channelname, 8, "CH%d", i + 1);
if (!(ch = sr_channel_new(0, SR_CHANNEL_ANALOG, TRUE, channelname)))
return SR_ERR;
ch = sr_channel_new(i, SR_CHANNEL_ANALOG, TRUE, channelname);
in->sdi->channels = g_slist_append(in->sdi->channels, ch);
}
return SR_OK;
}
static int find_data_chunk(uint8_t *buf, int initial_offset)
{
int offset, i;
offset = initial_offset;
while(offset < MAX_DATA_CHUNK_OFFSET) {
if (!memcmp(buf + offset, "data", 4))
/* Skip into the samples. */
return offset + 8;
for (i = 0; i < 4; i++) {
if (!isalpha(buf[offset + i])
&& !isascii(buf[offset + i])
&& !isblank(buf[offset + i]))
/* Doesn't look like a chunk ID. */
return -1;
}
/* Skip past this chunk. */
offset += 8 + GUINT32_FROM_LE(*(uint32_t *)(buf + offset + 4));
}
return offset;
}
static int loadfile(struct sr_input *in, const char *filename)
{
struct sr_datafeed_packet packet;
@ -131,14 +170,15 @@ static int loadfile(struct sr_input *in, const char *filename)
struct context *ctx;
float fdata[CHUNK_SIZE];
uint64_t sample;
int num_samples, chunk_samples, s, c, fd, l;
char buf[CHUNK_SIZE];
int offset, chunk_samples, samplenum, fd, l, i;
uint8_t buf[CHUNK_SIZE], *s, *d;
ctx = in->sdi->priv;
/* Send header packet to the session bus. */
std_session_send_df_header(in->sdi, LOG_PREFIX);
/* Send the samplerate. */
packet.type = SR_DF_META;
packet.payload = &meta;
src = sr_config_new(SR_CONF_SAMPLERATE,
@ -149,32 +189,54 @@ static int loadfile(struct sr_input *in, const char *filename)
if ((fd = open(filename, O_RDONLY)) == -1)
return SR_ERR;
if (read(fd, buf, MAX_DATA_CHUNK_OFFSET) < MAX_DATA_CHUNK_OFFSET)
return -1;
lseek(fd, 40, SEEK_SET);
l = read(fd, buf, 4);
num_samples = GUINT32_FROM_LE((uint32_t)*(buf));
num_samples /= ctx->samplesize / ctx->num_channels;
/* Skip past size of 'fmt ' chunk. */
i = 20 + GUINT32_FROM_LE(*(uint32_t *)(buf + 16));
offset = find_data_chunk(buf, i);
if (offset < 0) {
sr_err("Couldn't find data chunk.");
return SR_ERR;
}
if (lseek(fd, offset, SEEK_SET) == -1)
return SR_ERR;
memset(fdata, 0, CHUNK_SIZE);
while (TRUE) {
if ((l = read(fd, buf, CHUNK_SIZE)) < 1)
break;
chunk_samples = l / ctx->samplesize / ctx->num_channels;
for (s = 0; s < chunk_samples; s++) {
for (c = 0; c < ctx->num_channels; c++) {
chunk_samples = l / ctx->num_channels / ctx->unitsize;
s = buf;
d = (uint8_t *)fdata;
for (samplenum = 0; samplenum < chunk_samples * ctx->num_channels; samplenum++) {
if (ctx->fmt_code == WAVE_FORMAT_PCM) {
sample = 0;
memcpy(&sample, buf + s * ctx->samplesize + c, ctx->samplesize);
memcpy(&sample, s, ctx->unitsize);
switch (ctx->samplesize) {
case 1:
/* 8-bit PCM samples are unsigned. */
fdata[s + c] = (uint8_t)sample / 255.0;
fdata[samplenum] = (uint8_t)sample / 255.0;
break;
case 2:
fdata[s + c] = GINT16_FROM_LE(sample) / 32767.0;
fdata[samplenum] = GINT16_FROM_LE(sample) / 32767.0;
break;
case 4:
fdata[s + c] = GINT32_FROM_LE(sample) / 65535.0;
fdata[samplenum] = GINT32_FROM_LE(sample) / 65535.0;
break;
}
} else {
/* BINARY32 float */
#ifdef WORDS_BIGENDIAN
for (i = 0; i < ctx->unitsize; i++)
d[i] = s[ctx->unitsize - i];
#else
memcpy(d, s, ctx->unitsize);
#endif
}
s += ctx->unitsize;
d += ctx->unitsize;
}
packet.type = SR_DF_ANALOG;
packet.payload = &analog;