]> sigrok.org Git - libsigrok.git/blobdiff - src/output/wav.c
kingst-la2016: fix segfault that often occurs when a capture is aborted
[libsigrok.git] / src / output / wav.c
index 1fed39a0419ce614d192f345ac1f6009e936cb10..732fe976f0df420c2cae8b8e4b9a935ca0201b45 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "libsigrok.h"
+#include <config.h>
+#include <string.h>
+#include <libsigrok/libsigrok.h>
 #include "libsigrok-internal.h"
 
 #define LOG_PREFIX "output/wav"
 
+/* Minimum/maximum number of samples per channel to put in a data chunk */
+#define MIN_DATA_CHUNK_SAMPLES 10
+
+struct out_context {
+       double scale;
+       gboolean header_done;
+       uint64_t samplerate;
+       int num_channels;
+       GSList *channels;
+       int chanbuf_size;
+       int *chanbuf_used;
+       uint8_t **chanbuf;
+       float *fdata;
+};
+
+static int realloc_chanbufs(const struct sr_output *o, int size)
+{
+       struct out_context *outc;
+       int i;
+
+       outc = o->priv;
+       for (i = 0; i < outc->num_channels; i++) {
+               if (!(outc->chanbuf[i] = g_try_realloc(outc->chanbuf[i], sizeof(float) * size))) {
+                       sr_err("Unable to allocate enough output buffer memory.");
+                       return SR_ERR;
+               }
+               outc->chanbuf_used[i] = 0;
+       }
+       outc->chanbuf_size = size;
+
+       return SR_OK;
+}
+
+static int flush_chanbufs(const struct sr_output *o, GString *out)
+{
+       struct out_context *outc;
+       int num_samples, i, j;
+       char *buf, *bufp;
+
+       outc = o->priv;
+
+       /* Any one of them will do. */
+       num_samples = outc->chanbuf_used[0];
+       if (!(buf = g_try_malloc(4 * num_samples * outc->num_channels))) {
+               sr_err("Unable to allocate enough interleaved output buffer memory.");
+               return SR_ERR;
+       }
+
+       bufp = buf;
+       for (i = 0; i < num_samples; i++) {
+               for (j = 0; j < outc->num_channels; j++) {
+                       memcpy(bufp, outc->chanbuf[j] + i * 4, 4);
+                       bufp += 4;
+               }
+       }
+       g_string_append_len(out, buf, 4 * num_samples * outc->num_channels);
+       g_free(buf);
+
+       for (i = 0; i < outc->num_channels; i++)
+               outc->chanbuf_used[i] = 0;
+
+       return SR_OK;
+}
+
 static int init(struct sr_output *o, GHashTable *options)
 {
-       (void)o;
-       (void)options;
+       struct out_context *outc;
+       struct sr_channel *ch;
+       GSList *l;
+
+       outc = g_malloc0(sizeof(struct out_context));
+       o->priv = outc;
+       outc->scale = g_variant_get_double(g_hash_table_lookup(options, "scale"));
+
+       for (l = o->sdi->channels; l; l = l->next) {
+               ch = l->data;
+               if (ch->type != SR_CHANNEL_ANALOG)
+                       continue;
+               if (!ch->enabled)
+                       continue;
+               outc->channels = g_slist_append(outc->channels, ch);
+               outc->num_channels++;
+       }
+
+       outc->chanbuf = g_malloc0(sizeof(float *) * outc->num_channels);
+       outc->chanbuf_used = g_malloc0(sizeof(int) * outc->num_channels);
+
+       /* Start off the interleaved buffer with 100 samples/channel. */
+       realloc_chanbufs(o, 100);
 
        return SR_OK;
 }
 
+static void add_data_chunk(const struct sr_output *o, GString *gs)
+{
+       struct out_context *outc;
+       char tmp[4];
+
+       outc = o->priv;
+       g_string_append(gs, "fmt ");
+       /* Remaining chunk size */
+       WL32(tmp, 0x12);
+       g_string_append_len(gs, tmp, 4);
+       /* Format code 3 = IEEE float */
+       WL16(tmp, 0x0003);
+       g_string_append_len(gs, tmp, 2);
+       /* Number of channels */
+       WL16(tmp, outc->num_channels);
+       g_string_append_len(gs, tmp, 2);
+       /* Samplerate */
+       WL32(tmp, outc->samplerate);
+       g_string_append_len(gs, tmp, 4);
+       /* Byterate, using 32-bit floats. */
+       WL32(tmp, outc->samplerate * outc->num_channels * 4);
+       g_string_append_len(gs, tmp, 4);
+       /* Blockalign */
+       WL16(tmp, outc->num_channels * 4);
+       g_string_append_len(gs, tmp, 2);
+       /* Bits per sample */
+       WL16(tmp, 32);
+       g_string_append_len(gs, tmp, 2);
+       WL16(tmp, 0);
+       g_string_append_len(gs, tmp, 2);
+
+       g_string_append(gs, "data");
+       /* Data chunk size, max it out. */
+       WL32(tmp, 0xffffffff);
+       g_string_append_len(gs, tmp, 4);
+}
+
+static GString *gen_header(const struct sr_output *o)
+{
+       struct out_context *outc;
+       GVariant *gvar;
+       GString *header;
+       char tmp[4];
+
+       outc = o->priv;
+       if (outc->samplerate == 0) {
+               if (sr_config_get(o->sdi->driver, o->sdi, NULL, SR_CONF_SAMPLERATE,
+                               &gvar) == SR_OK) {
+                       outc->samplerate = g_variant_get_uint64(gvar);
+                       g_variant_unref(gvar);
+               }
+       }
+
+       header = g_string_sized_new(512);
+       g_string_append(header, "RIFF");
+       /* Total size. Max out the field. */
+       WL32(tmp, 0xffffffff);
+       g_string_append_len(header, tmp, 4);
+       g_string_append(header, "WAVE");
+       add_data_chunk(o, header);
+
+       return header;
+}
+
+/*
+ * Stores the float in little-endian BINARY32 IEEE-754 2008 format.
+ */
+static void float_to_le(uint8_t *buf, float value)
+{
+       uint8_t *old;
+
+       old = (uint8_t *)&value;
+#ifdef WORDS_BIGENDIAN
+       buf[0] = old[3];
+       buf[1] = old[2];
+       buf[2] = old[1];
+       buf[3] = old[0];
+#else
+       buf[0] = old[0];
+       buf[1] = old[1];
+       buf[2] = old[2];
+       buf[3] = old[3];
+#endif
+}
+
+/*
+ * Returns the number of samples used in the current channel buffers,
+ * or -1 if they're not all the same.
+ */
+static int check_chanbuf_size(const struct sr_output *o)
+{
+       struct out_context *outc;
+       int size, i;
+
+       outc = o->priv;
+       size = 0;
+       for (i = 0; i < outc->num_channels; i++) {
+               if (size == 0) {
+                       if (outc->chanbuf_used[i] == 0) {
+                               /* Nothing in all the buffers yet. */
+                               size = -1;
+                               break;
+                       } else {
+                               /* New high water mark. */
+                               size = outc->chanbuf_used[i];
+                       }
+               } else if (outc->chanbuf_used[i] != size) {
+                       /* All channel buffers are not equally full yet. */
+                       size = -1;
+                       break;
+               }
+       }
+
+       return size;
+}
+
 static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet,
                GString **out)
 {
-       (void)o;
-       (void)packet;
-       (void)out;
+       struct out_context *outc;
+       const struct sr_datafeed_meta *meta;
+       const struct sr_datafeed_analog *analog;
+       const struct sr_config *src;
+       struct sr_channel *ch;
+       GSList *l;
+       const GSList *channels;
+       float f;
+       int num_channels, num_samples, size, *chan_idx, idx, i, j, ret;
+       float *data;
+       uint8_t *buf;
+
+       *out = NULL;
+       if (!o || !o->sdi || !(outc = o->priv))
+               return SR_ERR_ARG;
+
+       switch (packet->type) {
+       case SR_DF_META:
+               meta = packet->payload;
+               for (l = meta->config; l; l = l->next) {
+                       src = l->data;
+                       if (src->key != SR_CONF_SAMPLERATE)
+                               continue;
+                       outc->samplerate = g_variant_get_uint64(src->data);
+               }
+               break;
+       case SR_DF_ANALOG:
+               if (!outc->header_done) {
+                       *out = gen_header(o);
+                       outc->header_done = TRUE;
+               } else {
+                       *out = g_string_sized_new(512);
+               }
+
+               analog = packet->payload;
+               num_samples = analog->num_samples;
+               channels = analog->meaning->channels;
+               num_channels = g_slist_length(analog->meaning->channels);
+               if (!(data = g_try_realloc(outc->fdata, sizeof(float) * num_samples * num_channels)))
+                       return SR_ERR_MALLOC;
+               outc->fdata = data;
+               ret = sr_analog_to_float(analog, data);
+               if (ret != SR_OK)
+                       return ret;
+
+               if (num_samples == 0)
+                       return SR_OK;
+
+               if (num_channels > outc->num_channels) {
+                       sr_err("Packet has %d channels, but only %d were enabled.",
+                                       num_channels, outc->num_channels);
+                       return SR_ERR;
+               }
+
+               if (num_samples > outc->chanbuf_size) {
+                       if (realloc_chanbufs(o, analog->num_samples) != SR_OK)
+                               return SR_ERR_MALLOC;
+               }
+
+               /* Index the channels in this packet, so we can interleave quicker. */
+               chan_idx = g_malloc(sizeof(int) * outc->num_channels);
+               for (i = 0; i < num_channels; i++) {
+                       ch = g_slist_nth_data((GSList *) channels, i);
+                       chan_idx[i] = g_slist_index(outc->channels, ch);
+               }
+
+               for (i = 0; i < num_samples; i++) {
+                       for (j = 0; j < num_channels; j++) {
+                               idx = chan_idx[j];
+                               buf = outc->chanbuf[idx] + outc->chanbuf_used[idx]++ * 4;
+                               f = data[i * num_channels + j];
+                               if (outc->scale != 1.0)
+                                       f /= outc->scale;
+                               float_to_le(buf, f);
+                       }
+               }
+               g_free(chan_idx);
+
+               size = check_chanbuf_size(o);
+               if (size > MIN_DATA_CHUNK_SAMPLES)
+                       if (flush_chanbufs(o, *out) != SR_OK)
+                               return SR_ERR;
+               break;
+       case SR_DF_END:
+               size = check_chanbuf_size(o);
+               if (size > 0) {
+                       *out = g_string_sized_new(4 * size * outc->num_channels);
+                       if (flush_chanbufs(o, *out) != SR_OK)
+                               return SR_ERR;
+               }
+               break;
+       }
 
        return SR_OK;
 }
 
+static struct sr_option options[] = {
+       { "scale", "Scale", "Scale values by factor", NULL, NULL },
+       ALL_ZERO
+};
+
+static const struct sr_option *get_options(void)
+{
+       if (!options[0].def)
+               options[0].def = g_variant_ref_sink(g_variant_new_double(1.0));
+
+       return options;
+}
+
 static int cleanup(struct sr_output *o)
 {
-       (void)o;
+       struct out_context *outc;
+       int i;
+
+       outc = o->priv;
+       g_slist_free(outc->channels);
+       g_variant_unref(options[0].def);
+       for (i = 0; i < outc->num_channels; i++)
+               g_free(outc->chanbuf[i]);
+       g_free(outc->chanbuf_used);
+       g_free(outc->chanbuf);
+       g_free(outc->fdata);
+       g_free(outc);
+       o->priv = NULL;
 
        return SR_OK;
 }
@@ -50,9 +367,11 @@ static int cleanup(struct sr_output *o)
 SR_PRIV struct sr_output_module output_wav = {
        .id = "wav",
        .name = "WAV",
-       .desc = "WAVE PCM sound module",
+       .desc = "Microsoft WAV file format data",
+       .exts = (const char*[]){"wav", NULL},
+       .flags = 0,
+       .options = get_options,
        .init = init,
        .receive = receive,
        .cleanup = cleanup,
 };
-