]> sigrok.org Git - libsigrok.git/blobdiff - src/input/wav.c
Build: Set local include directories in Makefile.am
[libsigrok.git] / src / input / wav.c
index 2e6662556c00444a2e7a4da9f573e6c11104f7b9..f2d5db0dfcc21075293c3d9350573aff820410e8 100644 (file)
 #include <fcntl.h>
 #include <ctype.h>
 #include <string.h>
-#include "libsigrok.h"
+#include <stdint.h>
+#include <libsigrok/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
+
+/* Minimum size of header + 1 8-bit mono PCM sample. */
+#define MIN_DATA_CHUNK_OFFSET    45
+
 /* 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
+#define WAVE_FORMAT_PCM_         0x0001
+#define WAVE_FORMAT_IEEE_FLOAT_  0x0003
+#define WAVE_FORMAT_EXTENSIBLE_  0xfffe
 
 struct context {
+       gboolean started;
+       int fmt_code;
        uint64_t samplerate;
        int samplesize;
        int num_channels;
        int unitsize;
-       int fmt_code;
+       gboolean found_data;
 };
 
-static int get_wav_header(const char *filename, char *buf)
+static int parse_wav_header(GString *buf, struct context *inc)
 {
-       struct stat st;
-       int fd, l;
+       uint64_t samplerate;
+       unsigned int fmt_code, samplesize, num_channels, unitsize;
 
-       l = strlen(filename);
-       if (l <= 4 || strcasecmp(filename + l - 4, ".wav"))
-               return SR_ERR;
+       if (buf->len < MIN_DATA_CHUNK_OFFSET)
+               return SR_ERR_NA;
 
-       if (stat(filename, &st) == -1)
-               return SR_ERR;
-       if (st.st_size <= 45)
-               /* Minimum size of header + 1 8-bit mono PCM sample. */
-               return SR_ERR;
+       fmt_code = RL16(buf->str + 20);
+       samplerate = RL32(buf->str + 24);
 
-       if ((fd = open(filename, O_RDONLY)) == -1)
+       samplesize = RL16(buf->str + 32);
+       num_channels = RL16(buf->str + 22);
+       if (num_channels == 0)
                return SR_ERR;
+       unitsize = samplesize / num_channels;
+       if (unitsize != 1 && unitsize != 2 && unitsize != 4) {
+               sr_err("Only 8, 16 or 32 bits per sample supported.");
+               return SR_ERR_DATA;
+       }
 
-       l = read(fd, buf, 40);
-       close(fd);
-       if (l != 40)
-               return SR_ERR;
+       if (fmt_code == WAVE_FORMAT_PCM_) {
+       } else if (fmt_code == WAVE_FORMAT_IEEE_FLOAT_) {
+               if (unitsize != 4) {
+                       sr_err("only 32-bit floats supported.");
+                       return SR_ERR_DATA;
+               }
+       } else if (fmt_code == WAVE_FORMAT_EXTENSIBLE_) {
+               if (buf->len < 70)
+                       /* Not enough for extensible header and next chunk. */
+                       return SR_ERR_NA;
 
-       return SR_OK;
-}
+               if (RL16(buf->str + 16) != 40) {
+                       sr_err("WAV extensible format chunk must be 40 bytes.");
+                       return SR_ERR;
+               }
+               if (RL16(buf->str + 36) != 22) {
+                       sr_err("WAV extension must be 22 bytes.");
+                       return SR_ERR;
+               }
+               if (RL16(buf->str + 34) != RL16(buf->str + 38)) {
+                       sr_err("Reduced valid bits per sample not supported.");
+                       return SR_ERR_DATA;
+               }
+               /* Real format code is the first two bytes of the GUID. */
+               fmt_code = RL16(buf->str + 44);
+               if (fmt_code != WAVE_FORMAT_PCM_ && fmt_code != WAVE_FORMAT_IEEE_FLOAT_) {
+                       sr_err("Only PCM and floating point samples are supported.");
+                       return SR_ERR_DATA;
+               }
+               if (fmt_code == WAVE_FORMAT_IEEE_FLOAT_ && unitsize != 4) {
+                       sr_err("only 32-bit floats supported.");
+                       return SR_ERR_DATA;
+               }
+       } else {
+               sr_err("Only PCM and floating point samples are supported.");
+               return SR_ERR_DATA;
+       }
 
-static int format_match(const char *filename)
-{
-       char buf[40];
-       uint16_t fmt_code;
-
-       if (get_wav_header(filename, buf) != SR_OK)
-               return FALSE;
-
-       if (strncmp(buf, "RIFF", 4))
-               return FALSE;
-       if (strncmp(buf + 8, "WAVE", 4))
-               return FALSE;
-       if (strncmp(buf + 12, "fmt ", 4))
-               return FALSE;
-       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;
+       if (inc) {
+               inc->fmt_code = fmt_code;
+               inc->samplerate = samplerate;
+               inc->samplesize = samplesize;
+               inc->num_channels = num_channels;
+               inc->unitsize = unitsize;
+               inc->found_data = FALSE;
+       }
+
+       return SR_OK;
 }
 
-static int init(struct sr_input *in, const char *filename)
+static int format_match(GHashTable *metadata)
 {
-       struct sr_channel *ch;
-       struct context *ctx;
-       char buf[40], channelname[8];
-       int i;
+       GString *buf;
+       int ret;
 
-       if (get_wav_header(filename, buf) != SR_OK)
+       buf = g_hash_table_lookup(metadata, GINT_TO_POINTER(SR_INPUT_META_HEADER));
+       if (strncmp(buf->str, "RIFF", 4))
                return SR_ERR;
+       if (strncmp(buf->str + 8, "WAVE", 4))
+               return SR_ERR;
+       if (strncmp(buf->str + 12, "fmt ", 4))
+               return SR_ERR;
+       /*
+        * Only gets called when we already know this is a WAV file, so
+        * this parser can log error messages.
+        */
+       if ((ret = parse_wav_header(buf, NULL)) != SR_OK)
+               return ret;
 
-       if (!(ctx = g_try_malloc0(sizeof(struct context))))
-               return SR_ERR_MALLOC;
-
-       /* Create a virtual device. */
-       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 + 32));
-       ctx->num_channels = GUINT16_FROM_LE(*(uint16_t *)(buf + 22));
-       ctx->unitsize = ctx->samplesize / ctx->num_channels;
+       return SR_OK;
+}
 
-       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;
-               }
-       } else {
-               /* WAVE_FORMAT_IEEE_FLOAT */
-               if (ctx->samplesize / ctx->num_channels != 4) {
-                       sr_err("only 32-bit floats supported.");
-                       return SR_ERR;
-               }
-       }
+static int init(struct sr_input *in, GHashTable *options)
+{
+       (void)options;
 
-       for (i = 0; i < ctx->num_channels; i++) {
-               snprintf(channelname, 8, "CH%d", i + 1);
-               ch = sr_channel_new(i, SR_CHANNEL_ANALOG, TRUE, channelname);
-               in->sdi->channels = g_slist_append(in->sdi->channels, ch);
-       }
+       in->sdi = g_malloc0(sizeof(struct sr_dev_inst));
+       in->priv = g_malloc0(sizeof(struct context));
 
        return SR_OK;
 }
 
-static int find_data_chunk(uint8_t *buf, int initial_offset)
+static int find_data_chunk(GString *buf, int initial_offset)
 {
-       int offset, i;
+       unsigned int offset, i;
 
        offset = initial_offset;
-       while(offset < MAX_DATA_CHUNK_OFFSET) {
-               if (!memcmp(buf + offset, "data", 4))
+       while (offset < MIN(MAX_DATA_CHUNK_OFFSET, buf->len)) {
+               if (!memcmp(buf->str + 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]))
+                       if (!isalnum(buf->str[offset + i])
+                                       && !isblank(buf->str[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));
+               offset += 8 + RL32(buf->str + offset + 4);
        }
 
        return offset;
 }
 
-static int loadfile(struct sr_input *in, const char *filename)
+static void send_chunk(const struct sr_input *in, int offset, int num_samples)
 {
        struct sr_datafeed_packet packet;
-       struct sr_datafeed_meta meta;
        struct sr_datafeed_analog analog;
-       struct sr_config *src;
-       struct context *ctx;
+       struct context *inc;
        float fdata[CHUNK_SIZE];
        uint64_t sample;
-       int offset, chunk_samples, samplenum, fd, l, i;
-       uint8_t buf[CHUNK_SIZE], *s, *d;
+       int total_samples, samplenum;
+       char *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,
-                       g_variant_new_uint64(ctx->samplerate));
-       meta.config = g_slist_append(NULL, src);
-       sr_session_send(in->sdi, &packet);
-       sr_config_free(src);
-
-       if ((fd = open(filename, O_RDONLY)) == -1)
-               return SR_ERR;
-       if (read(fd, buf, MAX_DATA_CHUNK_OFFSET) < MAX_DATA_CHUNK_OFFSET)
-               return -1;
-
-       /* 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;
+       inc = in->priv;
 
+       s = in->buf->str + offset;
+       d = (char *)fdata;
        memset(fdata, 0, CHUNK_SIZE);
-       while (TRUE) {
-               if ((l = read(fd, buf, CHUNK_SIZE)) < 1)
-                       break;
-               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, s, ctx->unitsize);
-                               switch (ctx->samplesize) {
-                               case 1:
-                                       /* 8-bit PCM samples are unsigned. */
-                                       fdata[samplenum] = (uint8_t)sample / 255.0;
-                                       break;
-                               case 2:
-                                       fdata[samplenum] = GINT16_FROM_LE(sample) / 32767.0;
-                                       break;
-                               case 4:
-                                       fdata[samplenum] = GINT32_FROM_LE(sample) / 65535.0;
-                                       break;
-                               }
-                       } else {
-                               /* BINARY32 float */
+       total_samples = num_samples * inc->num_channels;
+       for (samplenum = 0; samplenum < total_samples; samplenum++) {
+               if (inc->fmt_code == WAVE_FORMAT_PCM_) {
+                       sample = 0;
+                       memcpy(&sample, s, inc->unitsize);
+                       switch (inc->samplesize) {
+                       case 1:
+                               /* 8-bit PCM samples are unsigned. */
+                               fdata[samplenum] = (uint8_t)sample / (float)255;
+                               break;
+                       case 2:
+                               fdata[samplenum] = RL16S(&sample) / (float)INT16_MAX;
+                               break;
+                       case 4:
+                               fdata[samplenum] = RL32S(&sample) / (float)INT32_MAX;
+                               break;
+                       }
+               } else {
+                       /* BINARY32 float */
 #ifdef WORDS_BIGENDIAN
-                               for (i = 0; i < ctx->unitsize; i++)
-                                       d[i] = s[ctx->unitsize - i];
+                       int i;
+                       for (i = 0; i < inc->unitsize; i++)
+                               d[i] = s[inc->unitsize - 1 - i];
 #else
-                               memcpy(d, s, ctx->unitsize);
+                       memcpy(d, s, inc->unitsize);
 #endif
-                       }
-                       s += ctx->unitsize;
-                       d += ctx->unitsize;
+               }
+               s += inc->unitsize;
+               d += inc->unitsize;
+       }
+       packet.type = SR_DF_ANALOG;
+       packet.payload = &analog;
+       analog.channels = in->sdi->channels;
+       analog.num_samples = num_samples;
+       analog.mq = 0;
+       analog.mqflags = 0;
+       analog.unit = 0;
+       analog.data = fdata;
+       sr_session_send(in->sdi, &packet);
+}
 
+static int process_buffer(struct sr_input *in)
+{
+       struct context *inc;
+       struct sr_datafeed_packet packet;
+       struct sr_datafeed_meta meta;
+       struct sr_config *src;
+       int offset, chunk_samples, total_samples, processed, max_chunk_samples;
+       int num_samples, i;
+       char channelname[8];
+
+       inc = in->priv;
+       if (!inc->started) {
+               for (i = 0; i < inc->num_channels; i++) {
+                       snprintf(channelname, 8, "CH%d", i + 1);
+                       sr_channel_new(in->sdi, i, SR_CHANNEL_ANALOG, TRUE, channelname);
                }
-               packet.type = SR_DF_ANALOG;
-               packet.payload = &analog;
-               analog.channels = in->sdi->channels;
-               analog.num_samples = chunk_samples;
-               analog.mq = 0;
-               analog.unit = 0;
-               analog.data = fdata;
+
+               std_session_send_df_header(in->sdi, LOG_PREFIX);
+
+               packet.type = SR_DF_META;
+               packet.payload = &meta;
+               src = sr_config_new(SR_CONF_SAMPLERATE, g_variant_new_uint64(inc->samplerate));
+               meta.config = g_slist_append(NULL, src);
                sr_session_send(in->sdi, &packet);
+               sr_config_free(src);
+
+               inc->started = TRUE;
        }
 
-       close(fd);
-       packet.type = SR_DF_END;
-       sr_session_send(in->sdi, &packet);
+       if (!inc->found_data) {
+               /* Skip past size of 'fmt ' chunk. */
+               i = 20 + RL32(in->buf->str + 16);
+               offset = find_data_chunk(in->buf, i);
+               if (offset < 0) {
+                       if (in->buf->len > MAX_DATA_CHUNK_OFFSET) {
+                               sr_err("Couldn't find data chunk.");
+                               return SR_ERR;
+                       }
+               }
+               inc->found_data = TRUE;
+       } else
+               offset = 0;
+
+       /* Round off up to the last channels * unitsize boundary. */
+       chunk_samples = (in->buf->len - offset) / inc->num_channels / inc->unitsize;
+       max_chunk_samples = CHUNK_SIZE / inc->num_channels / inc->unitsize;
+       processed = 0;
+       total_samples = chunk_samples;
+       while (processed < total_samples) {
+               if (chunk_samples > max_chunk_samples)
+                       num_samples = max_chunk_samples;
+               else
+                       num_samples = chunk_samples;
+               send_chunk(in, offset, num_samples);
+               offset += num_samples * inc->unitsize;
+               chunk_samples -= num_samples;
+               processed += num_samples;
+       }
+
+       if ((unsigned int)offset < in->buf->len) {
+               /*
+                * The incoming buffer wasn't processed completely. Stash
+                * the leftover data for next time.
+                */
+               g_string_erase(in->buf, 0, offset);
+       } else
+               g_string_truncate(in->buf, 0);
 
        return SR_OK;
 }
 
+static int receive(struct sr_input *in, GString *buf)
+{
+       struct context *inc;
+       int ret;
+
+       g_string_append_len(in->buf, buf->str, buf->len);
+
+       if (in->buf->len < MIN_DATA_CHUNK_OFFSET) {
+               /*
+                * Don't even try until there's enough room
+                * for the data segment to start.
+                */
+               return SR_OK;
+       }
+
+       inc = in->priv;
+       if (!in->sdi_ready) {
+               if ((ret = parse_wav_header(in->buf, inc)) == SR_ERR_NA)
+                       /* Not enough data yet. */
+                       return SR_OK;
+               else if (ret != SR_OK)
+                       return ret;
+
+               /* sdi is ready, notify frontend. */
+               in->sdi_ready = TRUE;
+               return SR_OK;
+       }
+
+       ret = process_buffer(in);
+
+       return ret;
+}
+
+static int end(struct sr_input *in)
+{
+       struct sr_datafeed_packet packet;
+       struct context *inc;
+       int ret;
+
+       if (in->sdi_ready)
+               ret = process_buffer(in);
+       else
+               ret = SR_OK;
 
-SR_PRIV struct sr_input_format input_wav = {
+       inc = in->priv;
+       if (inc->started) {
+               packet.type = SR_DF_END;
+               sr_session_send(in->sdi, &packet);
+       }
+
+       return ret;
+}
+
+SR_PRIV struct sr_input_module input_wav = {
        .id = "wav",
-       .description = "WAV file",
+       .name = "WAV",
+       .desc = "WAV file",
+       .exts = (const char*[]){"wav", NULL},
+       .metadata = { SR_INPUT_META_HEADER | SR_INPUT_META_REQUIRED },
        .format_match = format_match,
        .init = init,
-       .loadfile = loadfile,
+       .receive = receive,
+       .end = end,
 };
-