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:
parent
364859ac73
commit
cb41a838a7
110
src/input/wav.c
110
src/input/wav.c
|
@ -21,18 +21,27 @@
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <ctype.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "libsigrok.h"
|
#include "libsigrok.h"
|
||||||
#include "libsigrok-internal.h"
|
#include "libsigrok-internal.h"
|
||||||
|
|
||||||
#define LOG_PREFIX "input/wav"
|
#define LOG_PREFIX "input/wav"
|
||||||
|
|
||||||
|
/* How many bytes at a time to process and send to the session bus. */
|
||||||
#define CHUNK_SIZE 4096
|
#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 {
|
struct context {
|
||||||
uint64_t samplerate;
|
uint64_t samplerate;
|
||||||
int samplesize;
|
int samplesize;
|
||||||
int num_channels;
|
int num_channels;
|
||||||
|
int unitsize;
|
||||||
|
int fmt_code;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int get_wav_header(const char *filename, char *buf)
|
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)
|
static int format_match(const char *filename)
|
||||||
{
|
{
|
||||||
char buf[40];
|
char buf[40];
|
||||||
|
uint16_t fmt_code;
|
||||||
|
|
||||||
if (get_wav_header(filename, buf) != SR_OK)
|
if (get_wav_header(filename, buf) != SR_OK)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
@ -74,10 +84,9 @@ static int format_match(const char *filename)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
if (strncmp(buf + 12, "fmt ", 4))
|
if (strncmp(buf + 12, "fmt ", 4))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
if (GUINT16_FROM_LE(*(uint16_t *)(buf + 20)) != 1)
|
fmt_code = GUINT16_FROM_LE(*(uint16_t *)(buf + 20));
|
||||||
/* Not PCM. */
|
if (fmt_code != WAVE_FORMAT_PCM
|
||||||
return FALSE;
|
&& fmt_code != WAVE_FORMAT_IEEE_FLOAT)
|
||||||
if (strncmp(buf + 36, "data", 4))
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
return TRUE;
|
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 = sr_dev_inst_new(0, SR_ST_ACTIVE, NULL, NULL, NULL);
|
||||||
in->sdi->priv = ctx;
|
in->sdi->priv = ctx;
|
||||||
|
|
||||||
|
ctx->fmt_code = GUINT16_FROM_LE(*(uint16_t *)(buf + 20));
|
||||||
ctx->samplerate = GUINT32_FROM_LE(*(uint32_t *)(buf + 24));
|
ctx->samplerate = GUINT32_FROM_LE(*(uint32_t *)(buf + 24));
|
||||||
ctx->samplesize = GUINT16_FROM_LE(*(uint16_t *)(buf + 34)) / 8;
|
ctx->samplesize = GUINT16_FROM_LE(*(uint16_t *)(buf + 32));
|
||||||
if (ctx->samplesize != 1 && ctx->samplesize != 2 && ctx->samplesize != 4) {
|
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.");
|
sr_err("only 8, 16 or 32 bits per sample supported.");
|
||||||
return SR_ERR;
|
return SR_ERR;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
if ((ctx->num_channels = GUINT16_FROM_LE(*(uint16_t *)(buf + 22))) > 20) {
|
/* WAVE_FORMAT_IEEE_FLOAT */
|
||||||
sr_err("%d channels seems crazy.", ctx->num_channels);
|
if (ctx->samplesize / ctx->num_channels != 4) {
|
||||||
|
sr_err("only 32-bit floats supported.");
|
||||||
return SR_ERR;
|
return SR_ERR;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < ctx->num_channels; i++) {
|
for (i = 0; i < ctx->num_channels; i++) {
|
||||||
snprintf(channelname, 8, "CH%d", i + 1);
|
snprintf(channelname, 8, "CH%d", i + 1);
|
||||||
if (!(ch = sr_channel_new(0, SR_CHANNEL_ANALOG, TRUE, channelname)))
|
ch = sr_channel_new(i, SR_CHANNEL_ANALOG, TRUE, channelname);
|
||||||
return SR_ERR;
|
|
||||||
in->sdi->channels = g_slist_append(in->sdi->channels, ch);
|
in->sdi->channels = g_slist_append(in->sdi->channels, ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SR_OK;
|
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)
|
static int loadfile(struct sr_input *in, const char *filename)
|
||||||
{
|
{
|
||||||
struct sr_datafeed_packet packet;
|
struct sr_datafeed_packet packet;
|
||||||
|
@ -131,14 +170,15 @@ static int loadfile(struct sr_input *in, const char *filename)
|
||||||
struct context *ctx;
|
struct context *ctx;
|
||||||
float fdata[CHUNK_SIZE];
|
float fdata[CHUNK_SIZE];
|
||||||
uint64_t sample;
|
uint64_t sample;
|
||||||
int num_samples, chunk_samples, s, c, fd, l;
|
int offset, chunk_samples, samplenum, fd, l, i;
|
||||||
char buf[CHUNK_SIZE];
|
uint8_t buf[CHUNK_SIZE], *s, *d;
|
||||||
|
|
||||||
ctx = in->sdi->priv;
|
ctx = in->sdi->priv;
|
||||||
|
|
||||||
/* Send header packet to the session bus. */
|
/* Send header packet to the session bus. */
|
||||||
std_session_send_df_header(in->sdi, LOG_PREFIX);
|
std_session_send_df_header(in->sdi, LOG_PREFIX);
|
||||||
|
|
||||||
|
/* Send the samplerate. */
|
||||||
packet.type = SR_DF_META;
|
packet.type = SR_DF_META;
|
||||||
packet.payload = &meta;
|
packet.payload = &meta;
|
||||||
src = sr_config_new(SR_CONF_SAMPLERATE,
|
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)
|
if ((fd = open(filename, O_RDONLY)) == -1)
|
||||||
return SR_ERR;
|
return SR_ERR;
|
||||||
|
if (read(fd, buf, MAX_DATA_CHUNK_OFFSET) < MAX_DATA_CHUNK_OFFSET)
|
||||||
|
return -1;
|
||||||
|
|
||||||
lseek(fd, 40, SEEK_SET);
|
/* Skip past size of 'fmt ' chunk. */
|
||||||
l = read(fd, buf, 4);
|
i = 20 + GUINT32_FROM_LE(*(uint32_t *)(buf + 16));
|
||||||
num_samples = GUINT32_FROM_LE((uint32_t)*(buf));
|
offset = find_data_chunk(buf, i);
|
||||||
num_samples /= ctx->samplesize / ctx->num_channels;
|
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) {
|
while (TRUE) {
|
||||||
if ((l = read(fd, buf, CHUNK_SIZE)) < 1)
|
if ((l = read(fd, buf, CHUNK_SIZE)) < 1)
|
||||||
break;
|
break;
|
||||||
chunk_samples = l / ctx->samplesize / ctx->num_channels;
|
chunk_samples = l / ctx->num_channels / ctx->unitsize;
|
||||||
for (s = 0; s < chunk_samples; s++) {
|
s = buf;
|
||||||
for (c = 0; c < ctx->num_channels; c++) {
|
d = (uint8_t *)fdata;
|
||||||
|
for (samplenum = 0; samplenum < chunk_samples * ctx->num_channels; samplenum++) {
|
||||||
|
if (ctx->fmt_code == WAVE_FORMAT_PCM) {
|
||||||
sample = 0;
|
sample = 0;
|
||||||
memcpy(&sample, buf + s * ctx->samplesize + c, ctx->samplesize);
|
memcpy(&sample, s, ctx->unitsize);
|
||||||
switch (ctx->samplesize) {
|
switch (ctx->samplesize) {
|
||||||
case 1:
|
case 1:
|
||||||
/* 8-bit PCM samples are unsigned. */
|
/* 8-bit PCM samples are unsigned. */
|
||||||
fdata[s + c] = (uint8_t)sample / 255.0;
|
fdata[samplenum] = (uint8_t)sample / 255.0;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
fdata[s + c] = GINT16_FROM_LE(sample) / 32767.0;
|
fdata[samplenum] = GINT16_FROM_LE(sample) / 32767.0;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
fdata[s + c] = GINT32_FROM_LE(sample) / 65535.0;
|
fdata[samplenum] = GINT32_FROM_LE(sample) / 65535.0;
|
||||||
break;
|
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.type = SR_DF_ANALOG;
|
||||||
packet.payload = &analog;
|
packet.payload = &analog;
|
||||||
|
|
Loading…
Reference in New Issue