From 17bfaca62aaacec71c6da4bd927af051727593b6 Mon Sep 17 00:00:00 2001 From: Bert Vermeulen Date: Sun, 10 Aug 2014 16:57:04 +0200 Subject: [PATCH] input: Introduce new input module API. This is a work in progress. --- Makefile.am | 8 +- include/libsigrok/libsigrok.h | 15 ++ include/libsigrok/proto.h | 15 +- src/backend.c | 12 +- src/input/binary.c | 2 +- src/input/chronovu_la8.c | 2 +- src/input/csv.c | 2 +- src/input/input.c | 431 +++++++++++++++++++++++++++++++++- src/input/vcd.c | 2 +- src/input/wav.c | 344 +++++++++++++++------------ src/libsigrok-internal.h | 77 ++++-- src/output/output.c | 2 +- 12 files changed, 725 insertions(+), 187 deletions(-) diff --git a/Makefile.am b/Makefile.am index e7b63efb..ef4d4b33 100644 --- a/Makefile.am +++ b/Makefile.am @@ -42,12 +42,12 @@ libsigrok_la_SOURCES = \ src/std.c # Input modules +# src/input/binary.c \ +# src/input/chronovu_la8.c \ +# src/input/csv.c \ +# src/input/vcd.c libsigrok_la_SOURCES += \ - src/input/binary.c \ - src/input/chronovu_la8.c \ - src/input/csv.c \ src/input/input.c \ - src/input/vcd.c \ src/input/wav.c # Output modules diff --git a/include/libsigrok/libsigrok.h b/include/libsigrok/libsigrok.h index 1f20e1d6..dd6307de 100644 --- a/include/libsigrok/libsigrok.h +++ b/include/libsigrok/libsigrok.h @@ -443,6 +443,21 @@ struct sr_option { GSList *values; }; +/** Input module metadata keys. */ +enum sr_input_meta_keys { + /** The input filename, if there is one. */ + SR_INPUT_META_FILENAME = 0x01, + /** The input file's size in bytes. */ + SR_INPUT_META_FILESIZE = 0x02, + /** The first 128 bytes of the file, provided as a GString. */ + SR_INPUT_META_HEADER = 0x04, + /** The file's MIME type. */ + SR_INPUT_META_MIMETYPE = 0x08, + + /** The module cannot identify a file without this metadata. */ + SR_INPUT_META_REQUIRED = 0x80, +}; + struct sr_input; struct sr_input_module; struct sr_output; diff --git a/include/libsigrok/proto.h b/include/libsigrok/proto.h index fd6cb7f8..5ca98d40 100644 --- a/include/libsigrok/proto.h +++ b/include/libsigrok/proto.h @@ -124,7 +124,20 @@ SR_API int sr_session_source_remove_channel(struct sr_session *session, /*--- input/input.c ---------------------------------------------------------*/ -SR_API struct sr_input_module **sr_input_list(void); +SR_API const struct sr_input_module **sr_input_list(void); +SR_API const char *sr_input_id_get(const struct sr_input_module *in); +SR_API const char *sr_input_name_get(const struct sr_input_module *in); +SR_API const char *sr_input_description_get(const struct sr_input_module *in); +SR_API const struct sr_input_module *sr_input_find(char *id); +SR_API const struct sr_option *sr_input_options_get(const struct sr_input_module *in); +SR_API void sr_input_options_free(const struct sr_input_module *in); +SR_API struct sr_input *sr_input_new(const struct sr_input_module *imod, + GHashTable *options); +SR_API const struct sr_input *sr_input_scan_buffer(GString *buf); +SR_API const struct sr_input *sr_input_scan_file(const char *filename); +SR_API struct sr_dev_inst *sr_input_dev_inst_get(const struct sr_input *in); +SR_API int sr_input_send(const struct sr_input *in, GString *buf); +SR_API int sr_input_free(const struct sr_input *in); /*--- output/output.c -------------------------------------------------------*/ diff --git a/src/backend.c b/src/backend.c index e6297f54..5f5f4e88 100644 --- a/src/backend.c +++ b/src/backend.c @@ -215,7 +215,7 @@ static int sanity_check_all_drivers(void) static int sanity_check_all_input_modules(void) { int i, errors, ret = SR_OK; - struct sr_input_module **inputs; + const struct sr_input_module **inputs; const char *d; sr_spew("Sanity-checking all input modules."); @@ -230,7 +230,11 @@ static int sanity_check_all_input_modules(void) sr_err("No ID in module %d ('%s').", i, d); errors++; } - if (!inputs[i]->description) { + if (!inputs[i]->name) { + sr_err("No name in module %d ('%s').", i, d); + errors++; + } + if (!inputs[i]->desc) { sr_err("No description in module %d ('%s').", i, d); errors++; } @@ -242,8 +246,8 @@ static int sanity_check_all_input_modules(void) sr_err("No init in module %d ('%s').", i, d); errors++; } - if (!inputs[i]->loadfile) { - sr_err("No loadfile in module %d ('%s').", i, d); + if (!inputs[i]->receive) { + sr_err("No receive in module %d ('%s').", i, d); errors++; } diff --git a/src/input/binary.c b/src/input/binary.c index 846e6bab..91970dbf 100644 --- a/src/input/binary.c +++ b/src/input/binary.c @@ -144,7 +144,7 @@ static int loadfile(struct sr_input *in, const char *filename) SR_PRIV struct sr_input_module input_binary = { .id = "binary", - .description = "Raw binary", + .desc = "Raw binary", .format_match = format_match, .init = init, .loadfile = loadfile, diff --git a/src/input/chronovu_la8.c b/src/input/chronovu_la8.c index 5291eabe..0ac5e54a 100644 --- a/src/input/chronovu_la8.c +++ b/src/input/chronovu_la8.c @@ -199,7 +199,7 @@ static int loadfile(struct sr_input *in, const char *filename) SR_PRIV struct sr_input_module input_chronovu_la8 = { .id = "chronovu-la8", - .description = "ChronoVu LA8", + .desc = "ChronoVu LA8", .format_match = format_match, .init = init, .loadfile = loadfile, diff --git a/src/input/csv.c b/src/input/csv.c index c0316b62..2d53bf6f 100644 --- a/src/input/csv.c +++ b/src/input/csv.c @@ -865,7 +865,7 @@ static int loadfile(struct sr_input *in, const char *filename) SR_PRIV struct sr_input_module input_csv = { .id = "csv", - .description = "Comma-separated values (CSV)", + .desc = "Comma-separated values (CSV)", .format_match = format_match, .init = init, .loadfile = loadfile, diff --git a/src/input/input.c b/src/input/input.c index 21043fcc..9e828c3d 100644 --- a/src/input/input.c +++ b/src/input/input.c @@ -1,7 +1,7 @@ /* * This file is part of the libsigrok project. * - * Copyright (C) 2010-2012 Bert Vermeulen + * 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 @@ -17,9 +17,17 @@ * along with this program. If not, see . */ +#include +#include +#include +#include +#include +#include #include "libsigrok.h" #include "libsigrok-internal.h" +#define LOG_PREFIX "input" + /** * @file * @@ -57,20 +65,427 @@ extern SR_PRIV struct sr_input_module input_vcd; extern SR_PRIV struct sr_input_module input_wav; /* @endcond */ -static struct sr_input_module *input_module_list[] = { - &input_vcd, - &input_chronovu_la8, +static const struct sr_input_module *input_module_list[] = { +// &input_vcd, +// &input_chronovu_la8, &input_wav, - &input_csv, +// &input_csv, /* This one has to be last, because it will take any input. */ - &input_binary, +// &input_binary, NULL, }; -/** @since 0.1.0 */ -SR_API struct sr_input_module **sr_input_list(void) +/** @since 0.4.0 */ +SR_API const struct sr_input_module **sr_input_list(void) { return input_module_list; } +/** + * Returns the specified input module's ID. + * + * @since 0.4.0 + */ +SR_API const char *sr_input_id_get(const struct sr_input_module *o) +{ + if (!o) { + sr_err("Invalid input module NULL!"); + return NULL; + } + + return o->id; +} + +/** + * Returns the specified input module's name. + * + * @since 0.4.0 + */ +SR_API const char *sr_input_name_get(const struct sr_input_module *o) +{ + if (!o) { + sr_err("Invalid input module NULL!"); + return NULL; + } + + return o->name; +} + +/** + * Returns the specified input module's description. + * + * @since 0.4.0 + */ +SR_API const char *sr_input_description_get(const struct sr_input_module *o) +{ + if (!o) { + sr_err("Invalid input module NULL!"); + return NULL; + } + + return o->desc; +} + +/** + * Return the input module with the specified ID, or NULL if no module + * with that id is found. + * + * @since 0.4.0 + */ +SR_API const struct sr_input_module *sr_input_find(char *id) +{ + int i; + + for (i = 0; input_module_list[i]; i++) { + if (!strcmp(input_module_list[i]->id, id)) + return input_module_list[i]; + } + + return NULL; +} + +/** + * Returns a NULL-terminated array of struct sr_option, or NULL if the + * module takes no options. + * + * Each call to this function must be followed by a call to + * sr_input_options_free(). + * + * @since 0.4.0 + */ +SR_API const struct sr_option *sr_input_options_get(const struct sr_input_module *o) +{ + + if (!o || !o->options) + return NULL; + + return o->options(); +} + +/** + * After a call to sr_input_options_get(), this function cleans up all + * the resources allocated by that call. + * + * @since 0.4.0 + */ +SR_API void sr_input_options_free(const struct sr_input_module *o) +{ + struct sr_option *opt; + + if (!o || !o->options) + return; + + for (opt = o->options(); opt->id; opt++) { + if (opt->def) { + g_variant_unref(opt->def); + opt->def = NULL; + } + + if (opt->values) { + g_slist_free_full(opt->values, (GDestroyNotify)g_variant_unref); + opt->values = NULL; + } + } +} + +/** + * Create a new input instance using the specified input module. + * + * This function is used when a client wants to use a specific input + * module to parse a stream. No effort is made to identify the format. + * + * options is a *HashTable with the keys corresponding with + * the module options' id field. The values should be GVariant + * pointers with sunk * references, of the same GVariantType as the option's + * default value. + * + * @since 0.4.0 + */ +SR_API struct sr_input *sr_input_new(const struct sr_input_module *imod, + GHashTable *options) +{ + struct sr_input *in; + struct sr_option *mod_opts; + const GVariantType *gvt; + GHashTable *new_opts; + GHashTableIter iter; + gpointer key, value; + int i; + + in = g_malloc0(sizeof(struct sr_input)); + in->module = imod; + + if (options) { + new_opts = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + (GDestroyNotify)g_variant_unref); + mod_opts = imod->options(); + for (i = 0; mod_opts[i].id; i++) { + if (g_hash_table_lookup_extended(options, mod_opts[i].id, + &key, &value)) { + /* Option not given: insert the default value. */ + gvt = g_variant_get_type(mod_opts[i].def); + if (!g_variant_is_of_type(value, gvt)) { + sr_err("Invalid type for '%s' option.", key); + g_free(in); + return NULL; + } + g_hash_table_insert(new_opts, g_strdup(mod_opts[i].id), + g_variant_ref(value)); + } else { + /* Pass option along. */ + g_hash_table_insert(new_opts, g_strdup(mod_opts[i].id), + g_variant_ref(mod_opts[i].def)); + } + } + + /* Make sure no invalid options were given. */ + g_hash_table_iter_init(&iter, options); + while (g_hash_table_iter_next(&iter, &key, &value)) { + if (!g_hash_table_lookup(new_opts, key)) { + sr_err("Input module '%s' has no option '%s'", imod->id, key); + g_hash_table_destroy(new_opts); + g_free(in); + return NULL; + } + } + } else + new_opts = NULL; + + if (in->module->init && in->module->init(in, new_opts) != SR_OK) { + g_hash_table_destroy(new_opts); + g_free(in); + in = NULL; + } + if (new_opts) + g_hash_table_destroy(new_opts); + + return in; +} + +/* Returns TRUE is all required meta items are available. */ +static gboolean check_required_metadata(const uint8_t *metadata, uint8_t *avail) +{ + int m, a; + uint8_t reqd; + + for (m = 0; metadata[m]; m++) { + if (!metadata[m] & SR_INPUT_META_REQUIRED) + continue; + reqd = metadata[m] & ~SR_INPUT_META_REQUIRED; + for (a = 0; avail[a]; a++) { + if (avail[a] == reqd) + break; + } + if (!avail[a]) + /* Found a required meta item that isn't available. */ + return FALSE; + } + + return TRUE; +} + +/** + * Try to find an input module that can parse the given buffer. + * + * The buffer must contain enough of the beginning of the file for + * the input modules to find a match. This is format-dependent, but + * 128 bytes is normally enough. + * + * If an input module is found, an instance is created and returned. + * Otherwise, NULL is returned. + * + */ +SR_API const struct sr_input *sr_input_scan_buffer(GString *buf) +{ + struct sr_input *in; + const struct sr_input_module *imod; + GHashTable *meta; + unsigned int m, i; + uint8_t mitem, avail_metadata[8]; + + /* No more metadata to be had from a buffer. */ + avail_metadata[0] = SR_INPUT_META_HEADER; + avail_metadata[1] = 0; + + in = NULL; + for (i = 0; input_module_list[i]; i++) { + imod = input_module_list[i]; + if (!imod->metadata[0]) { + /* Module has no metadata for matching so will take + * any input. No point in letting it try to match. */ + continue; + } + if (!check_required_metadata(imod->metadata, avail_metadata)) + /* Cannot satisfy this module's requirements. */ + continue; + + meta = g_hash_table_new(NULL, NULL); + for (m = 0; m < sizeof(imod->metadata); m++) { + mitem = imod->metadata[m] & ~SR_INPUT_META_REQUIRED; + if (mitem == SR_INPUT_META_HEADER) + g_hash_table_insert(meta, GINT_TO_POINTER(mitem), buf); + } + if (g_hash_table_size(meta) == 0) { + /* No metadata for this module, so nothing to match. */ + g_hash_table_destroy(meta); + continue; + } + if (!imod->format_match(meta)) { + /* Module didn't recognize this buffer. */ + g_hash_table_destroy(meta); + continue; + } + g_hash_table_destroy(meta); + + /* Found a matching module. */ + in = sr_input_new(imod, NULL); + in->buf = g_string_new_len(buf->str, buf->len); + break; + } + + return in; +} + +/** + * Try to find an input module that can parse the given file. + * + * If an input module is found, an instance is created and returned. + * Otherwise, NULL is returned. + * + */ +SR_API const struct sr_input *sr_input_scan_file(const char *filename) +{ + struct sr_input *in; + const struct sr_input_module *imod; + GHashTable *meta; + GString *buf; + struct stat st; + unsigned int midx, m, i; + int fd; + ssize_t size; + uint8_t mitem, avail_metadata[8]; + + if (!filename || !filename[0]) { + sr_err("Invalid filename."); + return NULL; + } + + if (!g_file_test(filename, G_FILE_TEST_EXISTS)) { + sr_err("No such file."); + return NULL; + } + + if (stat(filename, &st) < 0) { + sr_err("%s", strerror(errno)); + return NULL; + } + + midx = 0; + avail_metadata[midx++] = SR_INPUT_META_FILENAME; + avail_metadata[midx++] = SR_INPUT_META_FILESIZE; + avail_metadata[midx++] = SR_INPUT_META_HEADER; + /* TODO: MIME type */ + + in = NULL; + buf = NULL; + for (i = 0; input_module_list[i]; i++) { + imod = input_module_list[i]; + if (!imod->metadata[0]) { + /* Module has no metadata for matching so will take + * any input. No point in letting it try to match. */ + continue; + } + if (!check_required_metadata(imod->metadata, avail_metadata)) + /* Cannot satisfy this module's requirements. */ + continue; + + meta = g_hash_table_new(NULL, NULL); + for (m = 0; m < sizeof(imod->metadata); m++) { + mitem = imod->metadata[m] & ~SR_INPUT_META_REQUIRED; + if (mitem == SR_INPUT_META_FILENAME) + g_hash_table_insert(meta, GINT_TO_POINTER(mitem), + (gpointer)filename); + else if (mitem == SR_INPUT_META_FILESIZE) + g_hash_table_insert(meta, GINT_TO_POINTER(mitem), + GINT_TO_POINTER(st.st_size)); + else if (mitem == SR_INPUT_META_HEADER) { + buf = g_string_sized_new(128); + if ((fd = open(filename, O_RDONLY)) < 0) { + sr_err("%s", strerror(errno)); + return NULL; + } + size = read(fd, buf->str, 128); + buf->len = size; + close(fd); + if (size <= 0) { + g_string_free(buf, TRUE); + sr_err("%s", strerror(errno)); + return NULL; + } + g_hash_table_insert(meta, GINT_TO_POINTER(mitem), buf); + g_string_free(buf, TRUE); + } + } + if (g_hash_table_size(meta) == 0) { + /* No metadata for this module, so there's no way it + * can match. */ + g_hash_table_destroy(meta); + continue; + } + if (!imod->format_match(meta)) + /* Module didn't recognize this buffer. */ + continue; + + /* Found a matching module. */ + in = sr_input_new(imod, NULL); + in->buf = g_string_new_len(buf->str, buf->len); + break; + } + if (!in && buf) + g_string_free(buf, TRUE); + + return in; +} + +SR_API struct sr_dev_inst *sr_input_dev_inst_get(const struct sr_input *in) +{ + return in->sdi; +} + +/** + * Send data to the specified input instance. + * + * When an input module instance is created with sr_input_new(), this + * function is used to feed data to the instance. + * + * @since 0.4.0 + */ +SR_API int sr_input_send(const struct sr_input *in, GString *buf) +{ + return in->module->receive(in, buf); +} + +/** + * Free the specified input instance and all associated resources. + * + * @since 0.4.0 + */ +SR_API int sr_input_free(const struct sr_input *in) +{ + int ret; + + if (!in) + return SR_ERR_ARG; + + ret = SR_OK; + if (in->module->cleanup) + ret = in->module->cleanup((struct sr_input *)in); + if (in->buf) + g_string_free(in->buf, TRUE); + g_free((gpointer)in); + + return ret; +} + + /** @} */ diff --git a/src/input/vcd.c b/src/input/vcd.c index fe937567..0c5e7878 100644 --- a/src/input/vcd.c +++ b/src/input/vcd.c @@ -537,7 +537,7 @@ static int loadfile(struct sr_input *in, const char *filename) SR_PRIV struct sr_input_module input_vcd = { .id = "vcd", - .description = "Value Change Dump", + .desc = "Value Change Dump", .format_match = format_match, .init = init, .loadfile = loadfile, diff --git a/src/input/wav.c b/src/input/wav.c index cf061f23..4faa1182 100644 --- a/src/input/wav.c +++ b/src/input/wav.c @@ -30,6 +30,10 @@ /* 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 @@ -37,230 +41,278 @@ #define WAVE_FORMAT_IEEE_FLOAT 3 struct context { + 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; + int fmt_code, samplesize, num_channels, unitsize; - l = strlen(filename); - if (l <= 4 || strcasecmp(filename + l - 4, ".wav")) + if (buf->len < MIN_DATA_CHUNK_OFFSET) { return SR_ERR; + } - 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 = GUINT16_FROM_LE(*(uint16_t *)(buf->str + 20)); + samplerate = GUINT32_FROM_LE(*(uint32_t *)(buf->str + 24)); + samplesize = GUINT16_FROM_LE(*(uint16_t *)(buf->str + 32)); + num_channels = GUINT16_FROM_LE(*(uint16_t *)(buf->str + 22)); + /* TODO div0 */ + unitsize = samplesize / num_channels; - if ((fd = open(filename, O_RDONLY)) == -1) + if (fmt_code == WAVE_FORMAT_PCM) { + if (samplesize != 1 && samplesize != 2 && samplesize != 4) { + sr_err("only 8, 16 or 32 bits per sample supported."); + return SR_ERR; + } + } else if (fmt_code == WAVE_FORMAT_IEEE_FLOAT) { + if (unitsize != 4) { + sr_err("only 32-bit floats supported."); + return SR_ERR; + } + } else { + sr_err("Only PCM and floating point samples are supported."); return SR_ERR; + } - l = read(fd, buf, 40); - close(fd); - if (l != 40) - return SR_ERR; + 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 format_match(const char *filename) +static int format_match(GHashTable *metadata) { - char buf[40]; - uint16_t fmt_code; + GString *buf; - 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 FALSE; - - if (strncmp(buf, "RIFF", 4)) + if (strncmp(buf->str + 8, "WAVE", 4)) return FALSE; - if (strncmp(buf + 8, "WAVE", 4)) + if (strncmp(buf->str + 12, "fmt ", 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) + /* + * Only gets called when we already know this is a WAV file, so + * this parser can log error messages. + */ + if (parse_wav_header(buf, NULL) != SR_OK) return FALSE; return TRUE; } -static int init(struct sr_input *in, const char *filename) +static int init(struct sr_input *in, GHashTable *options) { - struct sr_channel *ch; - struct context *ctx; - char buf[40], channelname[8]; - int i; + (void)options; - if (get_wav_header(filename, buf) != SR_OK) - return SR_ERR; - - 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; - - 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; - } - } - - 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); - } 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 (!isalpha(buf->str[offset + i]) + && !isascii(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 + GUINT32_FROM_LE(*(uint32_t *)(buf->str + offset + 4)); } return offset; } -static int loadfile(struct sr_input *in, const char *filename) +static int initial_receive(const struct sr_input *in) { struct sr_datafeed_packet packet; struct sr_datafeed_meta meta; - struct sr_datafeed_analog analog; + struct sr_channel *ch; struct sr_config *src; - struct context *ctx; - float fdata[CHUNK_SIZE]; - uint64_t sample; - int offset, chunk_samples, samplenum, fd, l, i; - uint8_t buf[CHUNK_SIZE], *s, *d; + struct context *inc; + int i; + char channelname[8]; - ctx = in->sdi->priv; + if (!in->buf) + /* Shouldn't happen. */ + return SR_ERR; + + inc = in->sdi->priv = g_malloc(sizeof(struct context)); + if (parse_wav_header(in->buf, inc) != SR_OK) + return SR_ERR; + + for (i = 0; i < inc->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); + } - /* 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)); + 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); - if ((fd = open(filename, O_RDONLY)) == -1) - return SR_ERR; - if (read(fd, buf, MAX_DATA_CHUNK_OFFSET) < MAX_DATA_CHUNK_OFFSET) - return -1; + return SR_OK; +} - /* 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; +static void send_chunk(const struct sr_input *in, int offset, int num_samples) +{ + struct sr_datafeed_packet packet; + struct sr_datafeed_analog analog; + struct context *inc; + float fdata[CHUNK_SIZE]; + uint64_t sample; + int total_samples, samplenum; + char *s, *d; + inc = in->sdi->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 */ -#ifdef WORDS_BIGENDIAN - for (i = 0; i < ctx->unitsize; i++) - d[i] = s[ctx->unitsize - i]; -#else - memcpy(d, s, ctx->unitsize); -#endif + 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 / 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; } - s += ctx->unitsize; - d += ctx->unitsize; - + } else { + /* BINARY32 float */ +#ifdef WORDS_BIGENDIAN + for (i = 0; i < inc->unitsize; i++) + d[i] = s[inc->unitsize - i]; +#else + memcpy(d, s, inc->unitsize); +#endif } - 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; + 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 receive(const struct sr_input *in, GString *buf) +{ + struct sr_datafeed_packet packet; + struct context *inc; + int offset, chunk_samples, total_samples, processed, max_chunk_samples, num_samples, i; + + if (buf->len == 0) { + /* End of stream. */ + packet.type = SR_DF_END; sr_session_send(in->sdi, &packet); + return SR_OK; } - close(fd); - packet.type = SR_DF_END; - sr_session_send(in->sdi, &packet); + g_string_append_len(in->buf, buf->str, buf->len); + + if (!in->sdi->priv) { + if (initial_receive(in) != SR_OK) + return SR_ERR; + if (in->buf->len < MIN_DATA_CHUNK_OFFSET) { + /* + * Don't even get started until there's enough room + * for the data segment to start. + */ + return SR_OK; + } + } + inc = in->sdi->priv; + + if (!inc->found_data) { + /* Skip past size of 'fmt ' chunk. */ + i = 20 + GUINT32_FROM_LE(*(uint32_t *)(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; } - SR_PRIV struct sr_input_module input_wav = { .id = "wav", - .description = "WAV file", + .name = "WAV", + .desc = "WAV file", + .metadata = { SR_INPUT_META_HEADER | SR_INPUT_META_REQUIRED }, .format_match = format_match, .init = init, - .loadfile = loadfile, + .receive = receive, }; diff --git a/src/libsigrok-internal.h b/src/libsigrok-internal.h index da04c111..0214fffb 100644 --- a/src/libsigrok-internal.h +++ b/src/libsigrok-internal.h @@ -172,37 +172,66 @@ struct sr_context { struct sr_input { /** * A pointer to this input module's 'struct sr_input_module'. - * The frontend can use this to call the module's callbacks. */ - struct sr_input_module *module; - - GHashTable *param; - + const struct sr_input_module *module; + GString *buf; struct sr_dev_inst *sdi; - - void *internal; + void *priv; }; /** Input (file) module driver. */ struct sr_input_module { - /** The unique ID for this input module. Must not be NULL. */ - char *id; - /** - * A short description of the input module, which can (for example) - * be displayed to the user by frontends. Must not be NULL. + * A unique ID for this output module, suitable for use in command-line + * clients, [a-z0-9-]. Must not be NULL. */ - char *description; + const char *id; /** - * Check if this input module can load and parse the specified file. + * A unique name for this output module, suitable for use in GUI + * clients, can contain UTF-8. Must not be NULL. + */ + const char *name; + + /** + * A short description of the output module. Must not be NULL. * - * @param[in] filename The name (and path) of the file to check. + * This can be displayed by frontends, e.g. when selecting the output + * module for saving a file. + */ + const char *desc; + + /** + * Zero-terminated list of metadata items the module needs to be able + * to identify an input stream. Can be all-zero, if the module cannot + * identify streams at all, i.e. has to be forced into use. + * + * Each item is one of: + * SR_INPUT_META_FILENAME + * SR_INPUT_META_FILESIZE + * SR_INPUT_META_HEADER + * SR_INPUT_META_MIMETYPE + * + * If the high bit (SR_INPUT META_REQUIRED) is set, the module cannot + * identify a stream without the given metadata. + */ + const uint8_t metadata[8]; + + /** + * Returns a NULL-terminated list of options this module can take. + * Can be NULL, if the module has no options. + */ + struct sr_option *(*options) (void); + + /** + * Check if this input module can load and parse the specified stream. + * + * @param[in] metadata Metadata the module can use to identify the stream. * * @retval TRUE This module knows the format. * @retval FALSE This module does not know the format. */ - int (*format_match) (const char *filename); + int (*format_match) (GHashTable *metadata); /** * Initialize the input module. @@ -215,7 +244,7 @@ struct sr_input_module { * @retval SR_OK Success * @retval other Negative error code. */ - int (*init) (struct sr_input *in, const char *filename); + int (*init) (struct sr_input *in, GHashTable *options); /** * Load a file, parsing the input according to the file's format. @@ -232,12 +261,22 @@ struct sr_input_module { * @param in A pointer to a valid 'struct sr_input' that the caller * has to allocate and provide to this function. It is also * the responsibility of the caller to free it later. - * @param filename The name (and path) of the file to use. + * @param f The name (and path) of the file to use. * * @retval SR_OK Success * @retval other Negative error code. */ - int (*loadfile) (struct sr_input *in, const char *filename); + int (*receive) (const struct sr_input *in, GString *buf); + + /** + * This function is called after the caller is finished using + * the input module, and can be used to free any internal + * resources the module may keep. + * + * @retval SR_OK Success + * @retval other Negative error code. + */ + int (*cleanup) (struct sr_input *in); }; /** Output module instance. */ diff --git a/src/output/output.c b/src/output/output.c index a2d7c510..0029748b 100644 --- a/src/output/output.c +++ b/src/output/output.c @@ -1,7 +1,7 @@ /* * This file is part of the libsigrok project. * - * Copyright (C) 2010-2012 Bert Vermeulen + * 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