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
|
||||
|
||||
# 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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 -------------------------------------------------------*/
|
||||
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* 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
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#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-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.
|
||||
*
|
||||
* <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 = {
|
||||
.id = "vcd",
|
||||
.description = "Value Change Dump",
|
||||
.desc = "Value Change Dump",
|
||||
.format_match = format_match,
|
||||
.init = init,
|
||||
.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. */
|
||||
#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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
Loading…
Reference in New Issue