/* * This file is part of the libsigrok project. * * Copyright (C) 2014 Bert Vermeulen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #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) { 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) { char *old; old = (char *)&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) { 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 != 0.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(0.0)); return options; } static int cleanup(struct sr_output *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; } SR_PRIV struct sr_output_module output_wav = { .id = "wav", .name = "WAV", .desc = "Microsoft WAV file format", .exts = (const char*[]){"wav", NULL}, .flags = 0, .options = get_options, .init = init, .receive = receive, .cleanup = cleanup, };