X-Git-Url: https://sigrok.org/gitweb/?a=blobdiff_plain;f=src%2Foutput%2Fwav.c;h=732fe976f0df420c2cae8b8e4b9a935ca0201b45;hb=2622b4297fd4cc4bed5c06bb6ae0aaa8b40e0ece;hp=1fed39a0419ce614d192f345ac1f6009e936cb10;hpb=afaa75b98c8beca03d67d28dacbffee819c2f70b;p=libsigrok.git diff --git a/src/output/wav.c b/src/output/wav.c index 1fed39a0..732fe976 100644 --- a/src/output/wav.c +++ b/src/output/wav.c @@ -17,32 +17,349 @@ * along with this program. If not, see . */ -#include "libsigrok.h" +#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) { - (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, }; -