input: Introduce new input module API.
This is a work in progress.
This commit is contained in:
parent
d514d35dab
commit
17bfaca62a
|
@ -42,12 +42,12 @@ libsigrok_la_SOURCES = \
|
||||||
src/std.c
|
src/std.c
|
||||||
|
|
||||||
# Input modules
|
# Input modules
|
||||||
|
# src/input/binary.c \
|
||||||
|
# src/input/chronovu_la8.c \
|
||||||
|
# src/input/csv.c \
|
||||||
|
# src/input/vcd.c
|
||||||
libsigrok_la_SOURCES += \
|
libsigrok_la_SOURCES += \
|
||||||
src/input/binary.c \
|
|
||||||
src/input/chronovu_la8.c \
|
|
||||||
src/input/csv.c \
|
|
||||||
src/input/input.c \
|
src/input/input.c \
|
||||||
src/input/vcd.c \
|
|
||||||
src/input/wav.c
|
src/input/wav.c
|
||||||
|
|
||||||
# Output modules
|
# Output modules
|
||||||
|
|
|
@ -443,6 +443,21 @@ struct sr_option {
|
||||||
GSList *values;
|
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;
|
||||||
struct sr_input_module;
|
struct sr_input_module;
|
||||||
struct sr_output;
|
struct sr_output;
|
||||||
|
|
|
@ -124,7 +124,20 @@ SR_API int sr_session_source_remove_channel(struct sr_session *session,
|
||||||
|
|
||||||
/*--- input/input.c ---------------------------------------------------------*/
|
/*--- 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 -------------------------------------------------------*/
|
/*--- output/output.c -------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
|
@ -215,7 +215,7 @@ static int sanity_check_all_drivers(void)
|
||||||
static int sanity_check_all_input_modules(void)
|
static int sanity_check_all_input_modules(void)
|
||||||
{
|
{
|
||||||
int i, errors, ret = SR_OK;
|
int i, errors, ret = SR_OK;
|
||||||
struct sr_input_module **inputs;
|
const struct sr_input_module **inputs;
|
||||||
const char *d;
|
const char *d;
|
||||||
|
|
||||||
sr_spew("Sanity-checking all input modules.");
|
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);
|
sr_err("No ID in module %d ('%s').", i, d);
|
||||||
errors++;
|
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);
|
sr_err("No description in module %d ('%s').", i, d);
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
|
@ -242,8 +246,8 @@ static int sanity_check_all_input_modules(void)
|
||||||
sr_err("No init in module %d ('%s').", i, d);
|
sr_err("No init in module %d ('%s').", i, d);
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
if (!inputs[i]->loadfile) {
|
if (!inputs[i]->receive) {
|
||||||
sr_err("No loadfile in module %d ('%s').", i, d);
|
sr_err("No receive in module %d ('%s').", i, d);
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -144,7 +144,7 @@ static int loadfile(struct sr_input *in, const char *filename)
|
||||||
|
|
||||||
SR_PRIV struct sr_input_module input_binary = {
|
SR_PRIV struct sr_input_module input_binary = {
|
||||||
.id = "binary",
|
.id = "binary",
|
||||||
.description = "Raw binary",
|
.desc = "Raw binary",
|
||||||
.format_match = format_match,
|
.format_match = format_match,
|
||||||
.init = init,
|
.init = init,
|
||||||
.loadfile = loadfile,
|
.loadfile = loadfile,
|
||||||
|
|
|
@ -199,7 +199,7 @@ static int loadfile(struct sr_input *in, const char *filename)
|
||||||
|
|
||||||
SR_PRIV struct sr_input_module input_chronovu_la8 = {
|
SR_PRIV struct sr_input_module input_chronovu_la8 = {
|
||||||
.id = "chronovu-la8",
|
.id = "chronovu-la8",
|
||||||
.description = "ChronoVu LA8",
|
.desc = "ChronoVu LA8",
|
||||||
.format_match = format_match,
|
.format_match = format_match,
|
||||||
.init = init,
|
.init = init,
|
||||||
.loadfile = loadfile,
|
.loadfile = loadfile,
|
||||||
|
|
|
@ -865,7 +865,7 @@ static int loadfile(struct sr_input *in, const char *filename)
|
||||||
|
|
||||||
SR_PRIV struct sr_input_module input_csv = {
|
SR_PRIV struct sr_input_module input_csv = {
|
||||||
.id = "csv",
|
.id = "csv",
|
||||||
.description = "Comma-separated values (CSV)",
|
.desc = "Comma-separated values (CSV)",
|
||||||
.format_match = format_match,
|
.format_match = format_match,
|
||||||
.init = init,
|
.init = init,
|
||||||
.loadfile = loadfile,
|
.loadfile = loadfile,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* This file is part of the libsigrok project.
|
* This file is part of the libsigrok project.
|
||||||
*
|
*
|
||||||
* Copyright (C) 2010-2012 Bert Vermeulen <bert@biot.com>
|
* Copyright (C) 2014 Bert Vermeulen <bert@biot.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,9 +17,17 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
#include "libsigrok.h"
|
#include "libsigrok.h"
|
||||||
#include "libsigrok-internal.h"
|
#include "libsigrok-internal.h"
|
||||||
|
|
||||||
|
#define LOG_PREFIX "input"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
*
|
*
|
||||||
|
@ -57,20 +65,427 @@ extern SR_PRIV struct sr_input_module input_vcd;
|
||||||
extern SR_PRIV struct sr_input_module input_wav;
|
extern SR_PRIV struct sr_input_module input_wav;
|
||||||
/* @endcond */
|
/* @endcond */
|
||||||
|
|
||||||
static struct sr_input_module *input_module_list[] = {
|
static const struct sr_input_module *input_module_list[] = {
|
||||||
&input_vcd,
|
// &input_vcd,
|
||||||
&input_chronovu_la8,
|
// &input_chronovu_la8,
|
||||||
&input_wav,
|
&input_wav,
|
||||||
&input_csv,
|
// &input_csv,
|
||||||
/* This one has to be last, because it will take any input. */
|
/* This one has to be last, because it will take any input. */
|
||||||
&input_binary,
|
// &input_binary,
|
||||||
NULL,
|
NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @since 0.1.0 */
|
/** @since 0.4.0 */
|
||||||
SR_API struct sr_input_module **sr_input_list(void)
|
SR_API const struct sr_input_module **sr_input_list(void)
|
||||||
{
|
{
|
||||||
return input_module_list;
|
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.
|
||||||
|
*
|
||||||
|
* <code>options</code> is a *HashTable with the keys corresponding with
|
||||||
|
* the module options' <code>id</code> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|
|
@ -537,7 +537,7 @@ static int loadfile(struct sr_input *in, const char *filename)
|
||||||
|
|
||||||
SR_PRIV struct sr_input_module input_vcd = {
|
SR_PRIV struct sr_input_module input_vcd = {
|
||||||
.id = "vcd",
|
.id = "vcd",
|
||||||
.description = "Value Change Dump",
|
.desc = "Value Change Dump",
|
||||||
.format_match = format_match,
|
.format_match = format_match,
|
||||||
.init = init,
|
.init = init,
|
||||||
.loadfile = loadfile,
|
.loadfile = loadfile,
|
||||||
|
|
344
src/input/wav.c
344
src/input/wav.c
|
@ -30,6 +30,10 @@
|
||||||
|
|
||||||
/* How many bytes at a time to process and send to the session bus. */
|
/* How many bytes at a time to process and send to the session bus. */
|
||||||
#define CHUNK_SIZE 4096
|
#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. */
|
/* Expect to find the "data" chunk within this offset from the start. */
|
||||||
#define MAX_DATA_CHUNK_OFFSET 256
|
#define MAX_DATA_CHUNK_OFFSET 256
|
||||||
|
|
||||||
|
@ -37,230 +41,278 @@
|
||||||
#define WAVE_FORMAT_IEEE_FLOAT 3
|
#define WAVE_FORMAT_IEEE_FLOAT 3
|
||||||
|
|
||||||
struct context {
|
struct context {
|
||||||
|
int fmt_code;
|
||||||
uint64_t samplerate;
|
uint64_t samplerate;
|
||||||
int samplesize;
|
int samplesize;
|
||||||
int num_channels;
|
int num_channels;
|
||||||
int unitsize;
|
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;
|
uint64_t samplerate;
|
||||||
int fd, l;
|
int fmt_code, samplesize, num_channels, unitsize;
|
||||||
|
|
||||||
l = strlen(filename);
|
if (buf->len < MIN_DATA_CHUNK_OFFSET) {
|
||||||
if (l <= 4 || strcasecmp(filename + l - 4, ".wav"))
|
|
||||||
return SR_ERR;
|
return SR_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
if (stat(filename, &st) == -1)
|
fmt_code = GUINT16_FROM_LE(*(uint16_t *)(buf->str + 20));
|
||||||
return SR_ERR;
|
samplerate = GUINT32_FROM_LE(*(uint32_t *)(buf->str + 24));
|
||||||
if (st.st_size <= 45)
|
samplesize = GUINT16_FROM_LE(*(uint16_t *)(buf->str + 32));
|
||||||
/* Minimum size of header + 1 8-bit mono PCM sample. */
|
num_channels = GUINT16_FROM_LE(*(uint16_t *)(buf->str + 22));
|
||||||
return SR_ERR;
|
/* 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;
|
return SR_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
l = read(fd, buf, 40);
|
if (inc) {
|
||||||
close(fd);
|
inc->fmt_code = fmt_code;
|
||||||
if (l != 40)
|
inc->samplerate = samplerate;
|
||||||
return SR_ERR;
|
inc->samplesize = samplesize;
|
||||||
|
inc->num_channels = num_channels;
|
||||||
|
inc->unitsize = unitsize;
|
||||||
|
inc->found_data = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
return SR_OK;
|
return SR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int format_match(const char *filename)
|
static int format_match(GHashTable *metadata)
|
||||||
{
|
{
|
||||||
char buf[40];
|
GString *buf;
|
||||||
uint16_t fmt_code;
|
|
||||||
|
|
||||||
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;
|
return FALSE;
|
||||||
|
if (strncmp(buf->str + 8, "WAVE", 4))
|
||||||
if (strncmp(buf, "RIFF", 4))
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
if (strncmp(buf + 8, "WAVE", 4))
|
if (strncmp(buf->str + 12, "fmt ", 4))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
if (strncmp(buf + 12, "fmt ", 4))
|
/*
|
||||||
return FALSE;
|
* Only gets called when we already know this is a WAV file, so
|
||||||
fmt_code = GUINT16_FROM_LE(*(uint16_t *)(buf + 20));
|
* this parser can log error messages.
|
||||||
if (fmt_code != WAVE_FORMAT_PCM
|
*/
|
||||||
&& fmt_code != WAVE_FORMAT_IEEE_FLOAT)
|
if (parse_wav_header(buf, NULL) != SR_OK)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
return TRUE;
|
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;
|
(void)options;
|
||||||
struct context *ctx;
|
|
||||||
char buf[40], channelname[8];
|
|
||||||
int i;
|
|
||||||
|
|
||||||
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 = 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;
|
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;
|
offset = initial_offset;
|
||||||
while(offset < MAX_DATA_CHUNK_OFFSET) {
|
while(offset < MIN(MAX_DATA_CHUNK_OFFSET, buf->len)) {
|
||||||
if (!memcmp(buf + offset, "data", 4))
|
if (!memcmp(buf->str + offset, "data", 4))
|
||||||
/* Skip into the samples. */
|
/* Skip into the samples. */
|
||||||
return offset + 8;
|
return offset + 8;
|
||||||
for (i = 0; i < 4; i++) {
|
for (i = 0; i < 4; i++) {
|
||||||
if (!isalpha(buf[offset + i])
|
if (!isalpha(buf->str[offset + i])
|
||||||
&& !isascii(buf[offset + i])
|
&& !isascii(buf->str[offset + i])
|
||||||
&& !isblank(buf[offset + i]))
|
&& !isblank(buf->str[offset + i]))
|
||||||
/* Doesn't look like a chunk ID. */
|
/* Doesn't look like a chunk ID. */
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
/* Skip past this chunk. */
|
/* 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;
|
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_packet packet;
|
||||||
struct sr_datafeed_meta meta;
|
struct sr_datafeed_meta meta;
|
||||||
struct sr_datafeed_analog analog;
|
struct sr_channel *ch;
|
||||||
struct sr_config *src;
|
struct sr_config *src;
|
||||||
struct context *ctx;
|
struct context *inc;
|
||||||
float fdata[CHUNK_SIZE];
|
int i;
|
||||||
uint64_t sample;
|
char channelname[8];
|
||||||
int offset, chunk_samples, samplenum, fd, l, i;
|
|
||||||
uint8_t buf[CHUNK_SIZE], *s, *d;
|
|
||||||
|
|
||||||
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);
|
std_session_send_df_header(in->sdi, LOG_PREFIX);
|
||||||
|
|
||||||
/* Send the samplerate. */
|
|
||||||
packet.type = SR_DF_META;
|
packet.type = SR_DF_META;
|
||||||
packet.payload = &meta;
|
packet.payload = &meta;
|
||||||
src = sr_config_new(SR_CONF_SAMPLERATE,
|
src = sr_config_new(SR_CONF_SAMPLERATE, g_variant_new_uint64(inc->samplerate));
|
||||||
g_variant_new_uint64(ctx->samplerate));
|
|
||||||
meta.config = g_slist_append(NULL, src);
|
meta.config = g_slist_append(NULL, src);
|
||||||
sr_session_send(in->sdi, &packet);
|
sr_session_send(in->sdi, &packet);
|
||||||
sr_config_free(src);
|
sr_config_free(src);
|
||||||
|
|
||||||
if ((fd = open(filename, O_RDONLY)) == -1)
|
return SR_OK;
|
||||||
return SR_ERR;
|
}
|
||||||
if (read(fd, buf, MAX_DATA_CHUNK_OFFSET) < MAX_DATA_CHUNK_OFFSET)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
/* Skip past size of 'fmt ' chunk. */
|
static void send_chunk(const struct sr_input *in, int offset, int num_samples)
|
||||||
i = 20 + GUINT32_FROM_LE(*(uint32_t *)(buf + 16));
|
{
|
||||||
offset = find_data_chunk(buf, i);
|
struct sr_datafeed_packet packet;
|
||||||
if (offset < 0) {
|
struct sr_datafeed_analog analog;
|
||||||
sr_err("Couldn't find data chunk.");
|
struct context *inc;
|
||||||
return SR_ERR;
|
float fdata[CHUNK_SIZE];
|
||||||
}
|
uint64_t sample;
|
||||||
if (lseek(fd, offset, SEEK_SET) == -1)
|
int total_samples, samplenum;
|
||||||
return SR_ERR;
|
char *s, *d;
|
||||||
|
|
||||||
|
inc = in->sdi->priv;
|
||||||
|
|
||||||
|
s = in->buf->str + offset;
|
||||||
|
d = (char *)fdata;
|
||||||
memset(fdata, 0, CHUNK_SIZE);
|
memset(fdata, 0, CHUNK_SIZE);
|
||||||
while (TRUE) {
|
total_samples = num_samples * inc->num_channels;
|
||||||
if ((l = read(fd, buf, CHUNK_SIZE)) < 1)
|
for (samplenum = 0; samplenum < total_samples; samplenum++) {
|
||||||
break;
|
if (inc->fmt_code == WAVE_FORMAT_PCM) {
|
||||||
chunk_samples = l / ctx->num_channels / ctx->unitsize;
|
sample = 0;
|
||||||
s = buf;
|
memcpy(&sample, s, inc->unitsize);
|
||||||
d = (uint8_t *)fdata;
|
switch (inc->samplesize) {
|
||||||
for (samplenum = 0; samplenum < chunk_samples * ctx->num_channels; samplenum++) {
|
case 1:
|
||||||
if (ctx->fmt_code == WAVE_FORMAT_PCM) {
|
/* 8-bit PCM samples are unsigned. */
|
||||||
sample = 0;
|
fdata[samplenum] = (uint8_t)sample / 255.0;
|
||||||
memcpy(&sample, s, ctx->unitsize);
|
break;
|
||||||
switch (ctx->samplesize) {
|
case 2:
|
||||||
case 1:
|
fdata[samplenum] = GINT16_FROM_LE(sample) / 32767.0;
|
||||||
/* 8-bit PCM samples are unsigned. */
|
break;
|
||||||
fdata[samplenum] = (uint8_t)sample / 255.0;
|
case 4:
|
||||||
break;
|
fdata[samplenum] = GINT32_FROM_LE(sample) / 65535.0;
|
||||||
case 2:
|
break;
|
||||||
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
|
|
||||||
}
|
}
|
||||||
s += ctx->unitsize;
|
} else {
|
||||||
d += ctx->unitsize;
|
/* 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;
|
s += inc->unitsize;
|
||||||
packet.payload = &analog;
|
d += inc->unitsize;
|
||||||
analog.channels = in->sdi->channels;
|
}
|
||||||
analog.num_samples = chunk_samples;
|
packet.type = SR_DF_ANALOG;
|
||||||
analog.mq = 0;
|
packet.payload = &analog;
|
||||||
analog.unit = 0;
|
analog.channels = in->sdi->channels;
|
||||||
analog.data = fdata;
|
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);
|
sr_session_send(in->sdi, &packet);
|
||||||
|
return SR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
close(fd);
|
g_string_append_len(in->buf, buf->str, buf->len);
|
||||||
packet.type = SR_DF_END;
|
|
||||||
sr_session_send(in->sdi, &packet);
|
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;
|
return SR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SR_PRIV struct sr_input_module input_wav = {
|
SR_PRIV struct sr_input_module input_wav = {
|
||||||
.id = "wav",
|
.id = "wav",
|
||||||
.description = "WAV file",
|
.name = "WAV",
|
||||||
|
.desc = "WAV file",
|
||||||
|
.metadata = { SR_INPUT_META_HEADER | SR_INPUT_META_REQUIRED },
|
||||||
.format_match = format_match,
|
.format_match = format_match,
|
||||||
.init = init,
|
.init = init,
|
||||||
.loadfile = loadfile,
|
.receive = receive,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -172,37 +172,66 @@ struct sr_context {
|
||||||
struct sr_input {
|
struct sr_input {
|
||||||
/**
|
/**
|
||||||
* A pointer to this input module's 'struct sr_input_module'.
|
* 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;
|
const struct sr_input_module *module;
|
||||||
|
GString *buf;
|
||||||
GHashTable *param;
|
|
||||||
|
|
||||||
struct sr_dev_inst *sdi;
|
struct sr_dev_inst *sdi;
|
||||||
|
void *priv;
|
||||||
void *internal;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Input (file) module driver. */
|
/** Input (file) module driver. */
|
||||||
struct sr_input_module {
|
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)
|
* A unique ID for this output module, suitable for use in command-line
|
||||||
* be displayed to the user by frontends. Must not be NULL.
|
* 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 TRUE This module knows the format.
|
||||||
* @retval FALSE This module does not know 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.
|
* Initialize the input module.
|
||||||
|
@ -215,7 +244,7 @@ struct sr_input_module {
|
||||||
* @retval SR_OK Success
|
* @retval SR_OK Success
|
||||||
* @retval other Negative error code.
|
* @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.
|
* 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
|
* @param in A pointer to a valid 'struct sr_input' that the caller
|
||||||
* has to allocate and provide to this function. It is also
|
* has to allocate and provide to this function. It is also
|
||||||
* the responsibility of the caller to free it later.
|
* 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 SR_OK Success
|
||||||
* @retval other Negative error code.
|
* @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. */
|
/** Output module instance. */
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* This file is part of the libsigrok project.
|
* This file is part of the libsigrok project.
|
||||||
*
|
*
|
||||||
* Copyright (C) 2010-2012 Bert Vermeulen <bert@biot.com>
|
* Copyright (C) 2014 Bert Vermeulen <bert@biot.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|
Loading…
Reference in New Issue