]> sigrok.org Git - libsigrok.git/blame - src/input/wav.c
input: make sr_input{_module} opaque to clients.
[libsigrok.git] / src / input / wav.c
CommitLineData
1d36b4d2 1/*
50985c20 2 * This file is part of the libsigrok project.
1d36b4d2
BV
3 *
4 * Copyright (C) 2013 Bert Vermeulen <bert@biot.com>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <sys/types.h>
21#include <sys/stat.h>
22#include <unistd.h>
23#include <fcntl.h>
cb41a838 24#include <ctype.h>
1d36b4d2
BV
25#include <string.h>
26#include "libsigrok.h"
27#include "libsigrok-internal.h"
28
3544f848 29#define LOG_PREFIX "input/wav"
1d36b4d2 30
cb41a838 31/* How many bytes at a time to process and send to the session bus. */
1d36b4d2 32#define CHUNK_SIZE 4096
cb41a838
BV
33/* Expect to find the "data" chunk within this offset from the start. */
34#define MAX_DATA_CHUNK_OFFSET 256
35
36#define WAVE_FORMAT_PCM 1
37#define WAVE_FORMAT_IEEE_FLOAT 3
1d36b4d2
BV
38
39struct context {
40 uint64_t samplerate;
41 int samplesize;
42 int num_channels;
cb41a838
BV
43 int unitsize;
44 int fmt_code;
1d36b4d2
BV
45};
46
47static int get_wav_header(const char *filename, char *buf)
48{
49 struct stat st;
50 int fd, l;
51
52 l = strlen(filename);
53 if (l <= 4 || strcasecmp(filename + l - 4, ".wav"))
54 return SR_ERR;
55
56 if (stat(filename, &st) == -1)
57 return SR_ERR;
58 if (st.st_size <= 45)
59 /* Minimum size of header + 1 8-bit mono PCM sample. */
60 return SR_ERR;
61
62 if ((fd = open(filename, O_RDONLY)) == -1)
63 return SR_ERR;
64
65 l = read(fd, buf, 40);
66 close(fd);
67 if (l != 40)
68 return SR_ERR;
69
70 return SR_OK;
71}
72
73static int format_match(const char *filename)
74{
75 char buf[40];
cb41a838 76 uint16_t fmt_code;
1d36b4d2
BV
77
78 if (get_wav_header(filename, buf) != SR_OK)
79 return FALSE;
80
81 if (strncmp(buf, "RIFF", 4))
82 return FALSE;
83 if (strncmp(buf + 8, "WAVE", 4))
84 return FALSE;
85 if (strncmp(buf + 12, "fmt ", 4))
86 return FALSE;
cb41a838
BV
87 fmt_code = GUINT16_FROM_LE(*(uint16_t *)(buf + 20));
88 if (fmt_code != WAVE_FORMAT_PCM
89 && fmt_code != WAVE_FORMAT_IEEE_FLOAT)
1d36b4d2
BV
90 return FALSE;
91
92 return TRUE;
93}
94
95static int init(struct sr_input *in, const char *filename)
96{
ba7dd8bb 97 struct sr_channel *ch;
1d36b4d2 98 struct context *ctx;
ba7dd8bb 99 char buf[40], channelname[8];
1d36b4d2
BV
100 int i;
101
102 if (get_wav_header(filename, buf) != SR_OK)
103 return SR_ERR;
104
105 if (!(ctx = g_try_malloc0(sizeof(struct context))))
106 return SR_ERR_MALLOC;
107
108 /* Create a virtual device. */
109 in->sdi = sr_dev_inst_new(0, SR_ST_ACTIVE, NULL, NULL, NULL);
110 in->sdi->priv = ctx;
111
cb41a838 112 ctx->fmt_code = GUINT16_FROM_LE(*(uint16_t *)(buf + 20));
1d36b4d2 113 ctx->samplerate = GUINT32_FROM_LE(*(uint32_t *)(buf + 24));
cb41a838
BV
114 ctx->samplesize = GUINT16_FROM_LE(*(uint16_t *)(buf + 32));
115 ctx->num_channels = GUINT16_FROM_LE(*(uint16_t *)(buf + 22));
116 ctx->unitsize = ctx->samplesize / ctx->num_channels;
117
118 if (ctx->fmt_code == WAVE_FORMAT_PCM) {
119 if (ctx->samplesize != 1 && ctx->samplesize != 2
120 && ctx->samplesize != 4) {
121 sr_err("only 8, 16 or 32 bits per sample supported.");
122 return SR_ERR;
123 }
124 } else {
125 /* WAVE_FORMAT_IEEE_FLOAT */
126 if (ctx->samplesize / ctx->num_channels != 4) {
127 sr_err("only 32-bit floats supported.");
128 return SR_ERR;
129 }
1d36b4d2
BV
130 }
131
132 for (i = 0; i < ctx->num_channels; i++) {
ba7dd8bb 133 snprintf(channelname, 8, "CH%d", i + 1);
cb41a838 134 ch = sr_channel_new(i, SR_CHANNEL_ANALOG, TRUE, channelname);
ba7dd8bb 135 in->sdi->channels = g_slist_append(in->sdi->channels, ch);
1d36b4d2
BV
136 }
137
138 return SR_OK;
139}
140
cb41a838
BV
141static int find_data_chunk(uint8_t *buf, int initial_offset)
142{
143 int offset, i;
144
145 offset = initial_offset;
146 while(offset < MAX_DATA_CHUNK_OFFSET) {
147 if (!memcmp(buf + offset, "data", 4))
148 /* Skip into the samples. */
149 return offset + 8;
150 for (i = 0; i < 4; i++) {
151 if (!isalpha(buf[offset + i])
152 && !isascii(buf[offset + i])
153 && !isblank(buf[offset + i]))
154 /* Doesn't look like a chunk ID. */
155 return -1;
156 }
157 /* Skip past this chunk. */
158 offset += 8 + GUINT32_FROM_LE(*(uint32_t *)(buf + offset + 4));
159 }
160
161 return offset;
162}
163
1d36b4d2
BV
164static int loadfile(struct sr_input *in, const char *filename)
165{
166 struct sr_datafeed_packet packet;
167 struct sr_datafeed_meta meta;
168 struct sr_datafeed_analog analog;
169 struct sr_config *src;
170 struct context *ctx;
171 float fdata[CHUNK_SIZE];
172 uint64_t sample;
cb41a838
BV
173 int offset, chunk_samples, samplenum, fd, l, i;
174 uint8_t buf[CHUNK_SIZE], *s, *d;
1d36b4d2
BV
175
176 ctx = in->sdi->priv;
177
178 /* Send header packet to the session bus. */
29a27196 179 std_session_send_df_header(in->sdi, LOG_PREFIX);
1d36b4d2 180
cb41a838 181 /* Send the samplerate. */
1d36b4d2
BV
182 packet.type = SR_DF_META;
183 packet.payload = &meta;
ec4063b8
BV
184 src = sr_config_new(SR_CONF_SAMPLERATE,
185 g_variant_new_uint64(ctx->samplerate));
1d36b4d2
BV
186 meta.config = g_slist_append(NULL, src);
187 sr_session_send(in->sdi, &packet);
ec4063b8 188 sr_config_free(src);
1d36b4d2
BV
189
190 if ((fd = open(filename, O_RDONLY)) == -1)
191 return SR_ERR;
cb41a838
BV
192 if (read(fd, buf, MAX_DATA_CHUNK_OFFSET) < MAX_DATA_CHUNK_OFFSET)
193 return -1;
194
195 /* Skip past size of 'fmt ' chunk. */
196 i = 20 + GUINT32_FROM_LE(*(uint32_t *)(buf + 16));
197 offset = find_data_chunk(buf, i);
198 if (offset < 0) {
199 sr_err("Couldn't find data chunk.");
200 return SR_ERR;
201 }
202 if (lseek(fd, offset, SEEK_SET) == -1)
203 return SR_ERR;
1d36b4d2 204
cb41a838 205 memset(fdata, 0, CHUNK_SIZE);
1d36b4d2
BV
206 while (TRUE) {
207 if ((l = read(fd, buf, CHUNK_SIZE)) < 1)
208 break;
cb41a838
BV
209 chunk_samples = l / ctx->num_channels / ctx->unitsize;
210 s = buf;
211 d = (uint8_t *)fdata;
212 for (samplenum = 0; samplenum < chunk_samples * ctx->num_channels; samplenum++) {
213 if (ctx->fmt_code == WAVE_FORMAT_PCM) {
1d36b4d2 214 sample = 0;
cb41a838 215 memcpy(&sample, s, ctx->unitsize);
1d36b4d2
BV
216 switch (ctx->samplesize) {
217 case 1:
218 /* 8-bit PCM samples are unsigned. */
cb41a838 219 fdata[samplenum] = (uint8_t)sample / 255.0;
1d36b4d2
BV
220 break;
221 case 2:
cb41a838 222 fdata[samplenum] = GINT16_FROM_LE(sample) / 32767.0;
1d36b4d2
BV
223 break;
224 case 4:
cb41a838 225 fdata[samplenum] = GINT32_FROM_LE(sample) / 65535.0;
1d36b4d2
BV
226 break;
227 }
cb41a838
BV
228 } else {
229 /* BINARY32 float */
230#ifdef WORDS_BIGENDIAN
231 for (i = 0; i < ctx->unitsize; i++)
232 d[i] = s[ctx->unitsize - i];
233#else
234 memcpy(d, s, ctx->unitsize);
235#endif
1d36b4d2 236 }
cb41a838
BV
237 s += ctx->unitsize;
238 d += ctx->unitsize;
239
1d36b4d2
BV
240 }
241 packet.type = SR_DF_ANALOG;
242 packet.payload = &analog;
ba7dd8bb 243 analog.channels = in->sdi->channels;
1d36b4d2
BV
244 analog.num_samples = chunk_samples;
245 analog.mq = 0;
246 analog.unit = 0;
247 analog.data = fdata;
248 sr_session_send(in->sdi, &packet);
249 }
250
251 close(fd);
252 packet.type = SR_DF_END;
253 sr_session_send(in->sdi, &packet);
254
255 return SR_OK;
256}
257
258
d4c93774 259SR_PRIV struct sr_input_module input_wav = {
1d36b4d2
BV
260 .id = "wav",
261 .description = "WAV file",
262 .format_match = format_match,
263 .init = init,
264 .loadfile = loadfile,
265};
266