output/wav: Initial module implementation.
This commit is contained in:
parent
afaa75b98c
commit
0605f87475
301
src/output/wav.c
301
src/output/wav.c
|
@ -17,32 +17,317 @@
|
||||||
* 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 "libsigrok.h"
|
#include "libsigrok.h"
|
||||||
#include "libsigrok-internal.h"
|
#include "libsigrok-internal.h"
|
||||||
|
|
||||||
#define LOG_PREFIX "output/wav"
|
#define LOG_PREFIX "output/wav"
|
||||||
|
|
||||||
static int init(struct sr_output *o, GHashTable *options)
|
/* Minimum/maximum number of samples per channel to put in a data chunk */
|
||||||
|
#define MIN_DATA_CHUNK_SAMPLES 10
|
||||||
|
|
||||||
|
struct out_context {
|
||||||
|
gboolean header_done;
|
||||||
|
uint64_t samplerate;
|
||||||
|
int num_channels;
|
||||||
|
GSList *channels;
|
||||||
|
int chanbuf_size;
|
||||||
|
int *chanbuf_used;
|
||||||
|
uint8_t **chanbuf;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int realloc_chanbufs(const struct sr_output *o, int size)
|
||||||
{
|
{
|
||||||
(void)o;
|
struct out_context *outc;
|
||||||
(void)options;
|
int i;
|
||||||
|
|
||||||
|
outc = o->priv;
|
||||||
|
for (i = 0; i < outc->num_channels; i++) {
|
||||||
|
if (!(outc->chanbuf[i] = g_try_realloc(outc->chanbuf[i], sizeof(float) * size))) {
|
||||||
|
sr_err("Unable to allocate enough output buffer memory.");
|
||||||
|
return SR_ERR;
|
||||||
|
}
|
||||||
|
outc->chanbuf_used[i] = 0;
|
||||||
|
}
|
||||||
|
outc->chanbuf_size = size;
|
||||||
|
|
||||||
return SR_OK;
|
return SR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int flush_chanbufs(const struct sr_output *o, GString *out)
|
||||||
|
{
|
||||||
|
struct out_context *outc;
|
||||||
|
int size, i, j;
|
||||||
|
char *buf, **bufp;
|
||||||
|
|
||||||
|
outc = o->priv;
|
||||||
|
|
||||||
|
/* Any one of them will do. */
|
||||||
|
size = outc->chanbuf_used[0];
|
||||||
|
if (!(buf = g_try_malloc(4 * size * outc->num_channels))) {
|
||||||
|
sr_err("Unable to allocate enough interleaved output buffer memory.");
|
||||||
|
return SR_ERR;
|
||||||
|
}
|
||||||
|
bufp = g_malloc(sizeof(char *) * outc->num_channels);
|
||||||
|
for (i = 0; i < outc->num_channels; i++)
|
||||||
|
bufp[i] = buf + i * 4;
|
||||||
|
|
||||||
|
for (i = 0; i < size * 4; i += 4) {
|
||||||
|
for (j = 0; j < outc->num_channels; j++) {
|
||||||
|
memcpy(bufp[j], outc->chanbuf[j] + i, 4);
|
||||||
|
bufp[j] += outc->num_channels * 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_string_append_len(out, buf, 4 * size * outc->num_channels);
|
||||||
|
g_free(bufp);
|
||||||
|
g_free(buf);
|
||||||
|
|
||||||
|
for (i = 0; i < outc->num_channels; i++)
|
||||||
|
outc->chanbuf_used[i] = 0;
|
||||||
|
|
||||||
|
return SR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int init(struct sr_output *o, GHashTable *options)
|
||||||
|
{
|
||||||
|
struct out_context *outc;
|
||||||
|
struct sr_channel *ch;
|
||||||
|
GSList *l;
|
||||||
|
|
||||||
|
(void)options;
|
||||||
|
|
||||||
|
outc = g_malloc0(sizeof(struct out_context));
|
||||||
|
o->priv = outc;
|
||||||
|
|
||||||
|
for (l = o->sdi->channels; l; l = l->next) {
|
||||||
|
ch = l->data;
|
||||||
|
if (ch->type != SR_CHANNEL_ANALOG)
|
||||||
|
continue;
|
||||||
|
if (!ch->enabled)
|
||||||
|
continue;
|
||||||
|
outc->channels = g_slist_append(outc->channels, ch);
|
||||||
|
outc->num_channels++;
|
||||||
|
}
|
||||||
|
|
||||||
|
outc->chanbuf = g_malloc0(sizeof(float *) * outc->num_channels);
|
||||||
|
outc->chanbuf_used = g_malloc0(sizeof(int) * outc->num_channels);
|
||||||
|
|
||||||
|
/* Start off the interleaved buffer with 100 samples/channel. */
|
||||||
|
realloc_chanbufs(o, 100);
|
||||||
|
|
||||||
|
return SR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_data_chunk(const struct sr_output *o, GString *gs)
|
||||||
|
{
|
||||||
|
struct out_context *outc;
|
||||||
|
char tmp[4];
|
||||||
|
|
||||||
|
outc = o->priv;
|
||||||
|
g_string_append(gs, "fmt ");
|
||||||
|
/* Remaining chunk size */
|
||||||
|
WL32(tmp, 0x12);
|
||||||
|
g_string_append_len(gs, tmp, 4);
|
||||||
|
/* Format code 3 = IEEE float */
|
||||||
|
WL16(tmp, 0x0003);
|
||||||
|
g_string_append_len(gs, tmp, 2);
|
||||||
|
/* Number of channels */
|
||||||
|
WL16(tmp, outc->num_channels);
|
||||||
|
g_string_append_len(gs, tmp, 2);
|
||||||
|
/* Samplerate */
|
||||||
|
WL32(tmp, outc->samplerate);
|
||||||
|
g_string_append_len(gs, tmp, 4);
|
||||||
|
/* Byterate, using 32-bit floats. */
|
||||||
|
WL32(tmp, outc->samplerate * outc->num_channels * 4);
|
||||||
|
g_string_append_len(gs, tmp, 4);
|
||||||
|
/* Blockalign */
|
||||||
|
WL16(tmp, outc->num_channels * 4);
|
||||||
|
g_string_append_len(gs, tmp, 2);
|
||||||
|
/* Bits per sample */
|
||||||
|
WL16(tmp, 32);
|
||||||
|
g_string_append_len(gs, tmp, 2);
|
||||||
|
WL16(tmp, 0);
|
||||||
|
g_string_append_len(gs, tmp, 2);
|
||||||
|
|
||||||
|
g_string_append(gs, "data");
|
||||||
|
/* Data chunk size, max it out. */
|
||||||
|
WL32(tmp, 0xffffffff);
|
||||||
|
g_string_append_len(gs, tmp, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GString *gen_header(const struct sr_output *o)
|
||||||
|
{
|
||||||
|
struct out_context *outc;
|
||||||
|
GVariant *gvar;
|
||||||
|
GString *header;
|
||||||
|
char tmp[4];
|
||||||
|
|
||||||
|
outc = o->priv;
|
||||||
|
if (outc->samplerate == 0) {
|
||||||
|
if (sr_config_get(o->sdi->driver, o->sdi, NULL, SR_CONF_SAMPLERATE,
|
||||||
|
&gvar) == SR_OK) {
|
||||||
|
outc->samplerate = g_variant_get_uint64(gvar);
|
||||||
|
g_variant_unref(gvar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header = g_string_sized_new(512);
|
||||||
|
g_string_append(header, "RIFF");
|
||||||
|
/* Total size. Max out the field. */
|
||||||
|
WL32(tmp, 0xffffffff);
|
||||||
|
g_string_append_len(header, tmp, 4);
|
||||||
|
g_string_append(header, "WAVE");
|
||||||
|
add_data_chunk(o, header);
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stores the float in little-endian BINARY32 IEEE-754 2008 format.
|
||||||
|
*/
|
||||||
|
static void float_to_le(uint8_t *buf, float value)
|
||||||
|
{
|
||||||
|
char *old;
|
||||||
|
|
||||||
|
old = (char *)&value;
|
||||||
|
#ifdef WORDS_BIGENDIAN
|
||||||
|
buf[0] = old[3];
|
||||||
|
buf[1] = old[2];
|
||||||
|
buf[2] = old[1];
|
||||||
|
buf[3] = old[0];
|
||||||
|
#else
|
||||||
|
buf[0] = old[0];
|
||||||
|
buf[1] = old[1];
|
||||||
|
buf[2] = old[2];
|
||||||
|
buf[3] = old[3];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the number of samples used in the current channel buffers,
|
||||||
|
* or -1 if they're not all the same.
|
||||||
|
*/
|
||||||
|
static int check_chanbuf_size(const struct sr_output *o)
|
||||||
|
{
|
||||||
|
struct out_context *outc;
|
||||||
|
int size, i;
|
||||||
|
|
||||||
|
outc = o->priv;
|
||||||
|
size = 0;
|
||||||
|
for (i = 0; i < outc->num_channels; i++) {
|
||||||
|
if (size == 0) {
|
||||||
|
if (outc->chanbuf_used[i] == 0) {
|
||||||
|
/* Nothing in all the buffers yet. */
|
||||||
|
size = -1;
|
||||||
|
break;
|
||||||
|
} else
|
||||||
|
/* New high water mark. */
|
||||||
|
size = outc->chanbuf_used[i];
|
||||||
|
} else if (outc->chanbuf_used[i] != size) {
|
||||||
|
/* All channel buffers are not equally full yet. */
|
||||||
|
size = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet,
|
static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet,
|
||||||
GString **out)
|
GString **out)
|
||||||
{
|
{
|
||||||
(void)o;
|
struct out_context *outc;
|
||||||
(void)packet;
|
const struct sr_datafeed_meta *meta;
|
||||||
(void)out;
|
const struct sr_datafeed_analog *analog;
|
||||||
|
const struct sr_config *src;
|
||||||
|
struct sr_channel *ch;
|
||||||
|
GSList *l;
|
||||||
|
int num_channels, size, *chan_idx, idx, i, j;
|
||||||
|
uint8_t *buf;
|
||||||
|
|
||||||
|
*out = NULL;
|
||||||
|
if (!o || !o->sdi || !(outc = o->priv))
|
||||||
|
return SR_ERR_ARG;
|
||||||
|
|
||||||
|
switch (packet->type) {
|
||||||
|
case SR_DF_META:
|
||||||
|
meta = packet->payload;
|
||||||
|
for (l = meta->config; l; l = l->next) {
|
||||||
|
src = l->data;
|
||||||
|
if (src->key != SR_CONF_SAMPLERATE)
|
||||||
|
continue;
|
||||||
|
outc->samplerate = g_variant_get_uint64(src->data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SR_DF_ANALOG:
|
||||||
|
if (!outc->header_done) {
|
||||||
|
*out = gen_header(o);
|
||||||
|
outc->header_done = TRUE;
|
||||||
|
} else
|
||||||
|
*out = g_string_sized_new(512);
|
||||||
|
|
||||||
|
analog = packet->payload;
|
||||||
|
if (analog->num_samples == 0)
|
||||||
|
return SR_OK;
|
||||||
|
|
||||||
|
num_channels = g_slist_length(analog->channels);
|
||||||
|
if (num_channels > outc->num_channels) {
|
||||||
|
sr_err("Packet has %d channels, but only %d were enabled.",
|
||||||
|
num_channels, outc->num_channels);
|
||||||
|
return SR_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (analog->num_samples > outc->chanbuf_size) {
|
||||||
|
if (realloc_chanbufs(o, analog->num_samples) != SR_OK)
|
||||||
|
return SR_ERR_MALLOC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Index the channels in this packet, so we can interleave quicker. */
|
||||||
|
chan_idx = g_malloc(sizeof(int) * outc->num_channels);
|
||||||
|
for (i = 0; i < num_channels; i++) {
|
||||||
|
ch = g_slist_nth_data(analog->channels, i);
|
||||||
|
chan_idx[i] = g_slist_index(outc->channels, ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < analog->num_samples; i++) {
|
||||||
|
for (j = 0; j < num_channels; j++) {
|
||||||
|
idx = chan_idx[j];
|
||||||
|
buf = outc->chanbuf[idx] + outc->chanbuf_used[idx]++ * 4;
|
||||||
|
float_to_le(buf, analog->data[i + j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_free(chan_idx);
|
||||||
|
|
||||||
|
size = check_chanbuf_size(o);
|
||||||
|
if (size > MIN_DATA_CHUNK_SAMPLES)
|
||||||
|
if (flush_chanbufs(o, *out) != SR_OK)
|
||||||
|
return SR_ERR;
|
||||||
|
break;
|
||||||
|
case SR_DF_END:
|
||||||
|
size = check_chanbuf_size(o);
|
||||||
|
if (size > 0) {
|
||||||
|
*out = g_string_sized_new(4 * size * outc->num_channels);
|
||||||
|
if (flush_chanbufs(o, *out) != SR_OK)
|
||||||
|
return SR_ERR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return SR_OK;
|
return SR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cleanup(struct sr_output *o)
|
static int cleanup(struct sr_output *o)
|
||||||
{
|
{
|
||||||
(void)o;
|
struct out_context *outc;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
outc = o->priv;
|
||||||
|
g_slist_free(outc->channels);
|
||||||
|
for (i = 0; i < outc->num_channels; i++)
|
||||||
|
g_free(outc->chanbuf[i]);
|
||||||
|
g_free(outc->chanbuf_used);
|
||||||
|
g_free(outc->chanbuf);
|
||||||
|
g_free(outc);
|
||||||
|
o->priv = NULL;
|
||||||
|
|
||||||
return SR_OK;
|
return SR_OK;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +335,7 @@ static int cleanup(struct sr_output *o)
|
||||||
SR_PRIV struct sr_output_module output_wav = {
|
SR_PRIV struct sr_output_module output_wav = {
|
||||||
.id = "wav",
|
.id = "wav",
|
||||||
.name = "WAV",
|
.name = "WAV",
|
||||||
.desc = "WAVE PCM sound module",
|
.desc = "WAVE file format",
|
||||||
.init = init,
|
.init = init,
|
||||||
.receive = receive,
|
.receive = receive,
|
||||||
.cleanup = cleanup,
|
.cleanup = cleanup,
|
||||||
|
|
Loading…
Reference in New Issue