]> sigrok.org Git - libsigrok.git/blob - src/output/srzip.c
output/srzip: queue samples before ZIP operation
[libsigrok.git] / src / output / srzip.c
1 /*
2  * This file is part of the libsigrok project.
3  *
4  * Copyright (C) 2014 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 <config.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <glib.h>
25 #include <glib/gstdio.h>
26 #include <zip.h>
27 #include <libsigrok/libsigrok.h>
28 #include "libsigrok-internal.h"
29
30 #define LOG_PREFIX "output/srzip"
31 #define CHUNK_SIZE (4 * 1024 * 1024)
32
33 struct out_context {
34         gboolean zip_created;
35         uint64_t samplerate;
36         char *filename;
37         size_t first_analog_index;
38         size_t analog_ch_count;
39         gint *analog_index_map;
40         struct logic_buff {
41                 size_t unit_size;
42                 size_t alloc_size;
43                 uint8_t *samples;
44                 size_t fill_size;
45         } logic_buff;
46         struct analog_buff {
47                 size_t alloc_size;
48                 float *samples;
49                 size_t fill_size;
50         } *analog_buff;
51 };
52
53 static int init(struct sr_output *o, GHashTable *options)
54 {
55         struct out_context *outc;
56
57         (void)options;
58
59         if (!o->filename || o->filename[0] == '\0') {
60                 sr_info("srzip output module requires a file name, cannot save.");
61                 return SR_ERR_ARG;
62         }
63
64         outc = g_malloc0(sizeof(struct out_context));
65         outc->filename = g_strdup(o->filename);
66         o->priv = outc;
67
68         return SR_OK;
69 }
70
71 static int zip_create(const struct sr_output *o)
72 {
73         struct out_context *outc;
74         struct zip *zipfile;
75         struct zip_source *versrc, *metasrc;
76         struct sr_channel *ch;
77         size_t ch_nr;
78         size_t alloc_size;
79         GVariant *gvar;
80         GKeyFile *meta;
81         GSList *l;
82         const char *devgroup;
83         char *s, *metabuf;
84         gsize metalen;
85         guint logic_channels, enabled_logic_channels;
86         guint enabled_analog_channels;
87         guint index;
88
89         outc = o->priv;
90
91         if (outc->samplerate == 0 && sr_config_get(o->sdi->driver, o->sdi, NULL,
92                                         SR_CONF_SAMPLERATE, &gvar) == SR_OK) {
93                 outc->samplerate = g_variant_get_uint64(gvar);
94                 g_variant_unref(gvar);
95         }
96
97         /* Quietly delete it first, libzip wants replace ops otherwise. */
98         g_unlink(outc->filename);
99         zipfile = zip_open(outc->filename, ZIP_CREATE, NULL);
100         if (!zipfile)
101                 return SR_ERR;
102
103         /* "version" */
104         versrc = zip_source_buffer(zipfile, "2", 1, FALSE);
105         if (zip_add(zipfile, "version", versrc) < 0) {
106                 sr_err("Error saving version into zipfile: %s",
107                         zip_strerror(zipfile));
108                 zip_source_free(versrc);
109                 zip_discard(zipfile);
110                 return SR_ERR;
111         }
112
113         /* init "metadata" */
114         meta = g_key_file_new();
115
116         g_key_file_set_string(meta, "global", "sigrok version",
117                         sr_package_version_string_get());
118
119         devgroup = "device 1";
120
121         logic_channels = 0;
122         enabled_logic_channels = 0;
123         enabled_analog_channels = 0;
124         for (l = o->sdi->channels; l; l = l->next) {
125                 ch = l->data;
126
127                 switch (ch->type) {
128                 case SR_CHANNEL_LOGIC:
129                         if (ch->enabled)
130                                 enabled_logic_channels++;
131                         logic_channels++;
132                         break;
133                 case SR_CHANNEL_ANALOG:
134                         if (ch->enabled)
135                                 enabled_analog_channels++;
136                         break;
137                 }
138         }
139
140         /* When reading the file, the first index of the analog channels
141          * can only be deduced through the "total probes" count, so the
142          * first analog index must follow the last logic one, enabled or not. */
143         if (enabled_logic_channels > 0)
144                 outc->first_analog_index = logic_channels + 1;
145         else
146                 outc->first_analog_index = 1;
147
148         /* Only set capturefile and probes if we will actually save logic data. */
149         if (enabled_logic_channels > 0) {
150                 g_key_file_set_string(meta, devgroup, "capturefile", "logic-1");
151                 g_key_file_set_integer(meta, devgroup, "total probes", logic_channels);
152         }
153
154         s = sr_samplerate_string(outc->samplerate);
155         g_key_file_set_string(meta, devgroup, "samplerate", s);
156         g_free(s);
157
158         g_key_file_set_integer(meta, devgroup, "total analog", enabled_analog_channels);
159
160         outc->analog_ch_count = enabled_analog_channels;
161         alloc_size = sizeof(gint) * outc->analog_ch_count;
162         outc->analog_index_map = g_malloc0(alloc_size);
163
164         index = 0;
165         for (l = o->sdi->channels; l; l = l->next) {
166                 ch = l->data;
167                 if (!ch->enabled)
168                         continue;
169
170                 s = NULL;
171                 switch (ch->type) {
172                 case SR_CHANNEL_LOGIC:
173                         ch_nr = ch->index + 1;
174                         s = g_strdup_printf("probe%zu", ch_nr);
175                         break;
176                 case SR_CHANNEL_ANALOG:
177                         ch_nr = outc->first_analog_index + index;
178                         outc->analog_index_map[index] = ch->index;
179                         s = g_strdup_printf("analog%zu", ch_nr);
180                         index++;
181                         break;
182                 }
183                 if (s) {
184                         g_key_file_set_string(meta, devgroup, s, ch->name);
185                         g_free(s);
186                 }
187         }
188
189         /*
190          * Allocate one samples buffer for all logic channels, and
191          * several samples buffers for the analog channels. Allocate
192          * buffers of CHUNK_SIZE size (in bytes), and determine the
193          * sample counts from the respective channel counts and data
194          * type widths.
195          *
196          * These buffers are intended to reduce the number of ZIP
197          * archive update calls, and decouple the srzip output module
198          * from implementation details in other acquisition device
199          * drivers and input modules.
200          */
201         alloc_size = CHUNK_SIZE;
202         outc->logic_buff.unit_size = logic_channels;
203         outc->logic_buff.unit_size += 8 - 1;
204         outc->logic_buff.unit_size /= 8;
205         outc->logic_buff.samples = g_try_malloc0(alloc_size);
206         if (!outc->logic_buff.samples)
207                 return SR_ERR_MALLOC;
208         alloc_size /= outc->logic_buff.unit_size;
209         outc->logic_buff.alloc_size = alloc_size;
210         outc->logic_buff.fill_size = 0;
211
212         alloc_size = sizeof(outc->analog_buff[0]) * outc->analog_ch_count;
213         outc->analog_buff = g_malloc0(alloc_size);
214         for (index = 0; index < outc->analog_ch_count; index++) {
215                 alloc_size = CHUNK_SIZE;
216                 outc->analog_buff[index].samples = g_try_malloc0(alloc_size);
217                 if (!outc->analog_buff[index].samples)
218                         return SR_ERR_MALLOC;
219                 alloc_size /= sizeof(outc->analog_buff[0].samples[0]);
220                 outc->analog_buff[index].alloc_size = alloc_size;
221                 outc->analog_buff[index].fill_size = 0;
222         }
223
224         metabuf = g_key_file_to_data(meta, &metalen, NULL);
225         g_key_file_free(meta);
226
227         metasrc = zip_source_buffer(zipfile, metabuf, metalen, FALSE);
228         if (zip_add(zipfile, "metadata", metasrc) < 0) {
229                 sr_err("Error saving metadata into zipfile: %s",
230                         zip_strerror(zipfile));
231                 zip_source_free(metasrc);
232                 zip_discard(zipfile);
233                 g_free(metabuf);
234                 return SR_ERR;
235         }
236
237         if (zip_close(zipfile) < 0) {
238                 sr_err("Error saving zipfile: %s", zip_strerror(zipfile));
239                 zip_discard(zipfile);
240                 g_free(metabuf);
241                 return SR_ERR;
242         }
243         g_free(metabuf);
244
245         return SR_OK;
246 }
247
248 /**
249  * Append a block of logic data to an srzip archive.
250  *
251  * @param[in] o Output module instance.
252  * @param[in] buf Logic data samples as byte sequence.
253  * @param[in] unitsize Logic data unit size (bytes per sample).
254  * @param[in] length Byte sequence length (in bytes, not samples).
255  *
256  * @returns SR_OK et al error codes.
257  */
258 static int zip_append(const struct sr_output *o,
259         uint8_t *buf, size_t unitsize, size_t length)
260 {
261         struct out_context *outc;
262         struct zip *archive;
263         struct zip_source *logicsrc;
264         int64_t i, num_files;
265         struct zip_stat zs;
266         struct zip_source *metasrc;
267         GKeyFile *kf;
268         GError *error;
269         uint64_t chunk_num;
270         const char *entry_name;
271         char *metabuf;
272         gsize metalen;
273         char *chunkname;
274         unsigned int next_chunk_num;
275
276         if (!length)
277                 return SR_OK;
278
279         outc = o->priv;
280         if (!(archive = zip_open(outc->filename, 0, NULL)))
281                 return SR_ERR;
282
283         if (zip_stat(archive, "metadata", 0, &zs) < 0) {
284                 sr_err("Failed to open metadata: %s", zip_strerror(archive));
285                 zip_discard(archive);
286                 return SR_ERR;
287         }
288         kf = sr_sessionfile_read_metadata(archive, &zs);
289         if (!kf) {
290                 zip_discard(archive);
291                 return SR_ERR_DATA;
292         }
293         /*
294          * If the file was only initialized but doesn't yet have any
295          * data it in, it won't have a unitsize field in metadata yet.
296          */
297         error = NULL;
298         metabuf = NULL;
299         if (!g_key_file_has_key(kf, "device 1", "unitsize", &error)) {
300                 if (error && error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) {
301                         sr_err("Failed to check unitsize key: %s", error->message);
302                         g_error_free(error);
303                         g_key_file_free(kf);
304                         zip_discard(archive);
305                         return SR_ERR;
306                 }
307                 g_clear_error(&error);
308
309                 /* Add unitsize field. */
310                 g_key_file_set_integer(kf, "device 1", "unitsize", unitsize);
311                 metabuf = g_key_file_to_data(kf, &metalen, NULL);
312                 metasrc = zip_source_buffer(archive, metabuf, metalen, FALSE);
313
314                 if (zip_replace(archive, zs.index, metasrc) < 0) {
315                         sr_err("Failed to replace metadata: %s",
316                                 zip_strerror(archive));
317                         g_key_file_free(kf);
318                         zip_source_free(metasrc);
319                         zip_discard(archive);
320                         g_free(metabuf);
321                         return SR_ERR;
322                 }
323         }
324         g_key_file_free(kf);
325
326         next_chunk_num = 1;
327         num_files = zip_get_num_entries(archive, 0);
328         for (i = 0; i < num_files; i++) {
329                 entry_name = zip_get_name(archive, i, 0);
330                 if (!entry_name || strncmp(entry_name, "logic-1", 7) != 0)
331                         continue;
332                 if (entry_name[7] == '\0') {
333                         /*
334                          * This file has no extra chunks, just a single
335                          * "logic-1". Rename it to "logic-1-1" and continue
336                          * with chunk 2.
337                          */
338                         if (zip_rename(archive, i, "logic-1-1") < 0) {
339                                 sr_err("Failed to rename 'logic-1' to 'logic-1-1': %s",
340                                         zip_strerror(archive));
341                                 zip_discard(archive);
342                                 g_free(metabuf);
343                                 return SR_ERR;
344                         }
345                         next_chunk_num = 2;
346                         break;
347                 } else if (entry_name[7] == '-') {
348                         chunk_num = g_ascii_strtoull(entry_name + 8, NULL, 10);
349                         if (chunk_num < G_MAXINT && chunk_num >= next_chunk_num)
350                                 next_chunk_num = chunk_num + 1;
351                 }
352         }
353
354         if (length % unitsize != 0) {
355                 sr_warn("Chunk size %zu not a multiple of the"
356                         " unit size %zu.", length, unitsize);
357         }
358         logicsrc = zip_source_buffer(archive, buf, length, FALSE);
359         chunkname = g_strdup_printf("logic-1-%u", next_chunk_num);
360         i = zip_add(archive, chunkname, logicsrc);
361         g_free(chunkname);
362         if (i < 0) {
363                 sr_err("Failed to add chunk 'logic-1-%u': %s",
364                         next_chunk_num, zip_strerror(archive));
365                 zip_source_free(logicsrc);
366                 zip_discard(archive);
367                 g_free(metabuf);
368                 return SR_ERR;
369         }
370         if (zip_close(archive) < 0) {
371                 sr_err("Error saving session file: %s", zip_strerror(archive));
372                 zip_discard(archive);
373                 g_free(metabuf);
374                 return SR_ERR;
375         }
376         g_free(metabuf);
377
378         return SR_OK;
379 }
380
381 /**
382  * Queue a block of logic data for srzip archive writes.
383  *
384  * @param[in] o Output module instance.
385  * @param[in] buf Logic data samples as byte sequence.
386  * @param[in] unitsize Logic data unit size (bytes per sample).
387  * @param[in] length Number of bytes of sample data.
388  * @param[in] flush Force ZIP archive update (queue by default).
389  *
390  * @returns SR_OK et al error codes.
391  */
392 static int zip_append_queue(const struct sr_output *o,
393         uint8_t *buf, size_t unitsize, size_t length, gboolean flush)
394 {
395         struct out_context *outc;
396         struct logic_buff *buff;
397         size_t send_size, remain, copy_size;
398         uint8_t *wrptr, *rdptr;
399         int ret;
400
401         outc = o->priv;
402         buff = &outc->logic_buff;
403         if (length && unitsize != buff->unit_size)
404                 return SR_ERR_ARG;
405
406         /*
407          * Queue most recently received samples to the local buffer.
408          * Flush to the ZIP archive when the buffer space is exhausted.
409          */
410         rdptr = buf;
411         send_size = length / buff->unit_size;
412         while (send_size) {
413                 remain = buff->alloc_size - buff->fill_size;
414                 if (remain) {
415                         wrptr = &buff->samples[buff->fill_size * buff->unit_size];
416                         copy_size = MIN(send_size, remain);
417                         send_size -= copy_size;
418                         buff->fill_size += copy_size;
419                         memcpy(wrptr, rdptr, copy_size * buff->unit_size);
420                         rdptr += copy_size * buff->unit_size;
421                         remain -= copy_size;
422                 }
423                 if (send_size && !remain) {
424                         ret = zip_append(o, buff->samples, buff->unit_size,
425                                 buff->fill_size * buff->unit_size);
426                         if (ret != SR_OK)
427                                 return ret;
428                         buff->fill_size = 0;
429                         remain = buff->alloc_size - buff->fill_size;
430                 }
431         }
432
433         /* Flush to the ZIP archive if the caller wants us to. */
434         if (flush && buff->fill_size) {
435                 ret = zip_append(o, buff->samples, buff->unit_size,
436                         buff->fill_size * buff->unit_size);
437                 if (ret != SR_OK)
438                         return ret;
439                 buff->fill_size = 0;
440         }
441
442         return SR_OK;
443 }
444
445 /**
446  * Append analog data of a channel to an srzip archive.
447  *
448  * @param[in] o Output module instance.
449  * @param[in] values Sample data as array of floating point values.
450  * @param[in] count Number of samples (float items, not bytes).
451  * @param[in] ch_nr 1-based channel number.
452  *
453  * @returns SR_OK et al error codes.
454  */
455 static int zip_append_analog(const struct sr_output *o,
456         const float *values, size_t count, size_t ch_nr)
457 {
458         struct out_context *outc;
459         struct zip *archive;
460         struct zip_source *analogsrc;
461         int64_t i, num_files;
462         size_t size;
463         struct zip_stat zs;
464         uint64_t chunk_num;
465         const char *entry_name;
466         char *basename;
467         gsize baselen;
468         char *chunkname;
469         unsigned int next_chunk_num;
470
471         outc = o->priv;
472
473         if (!(archive = zip_open(outc->filename, 0, NULL)))
474                 return SR_ERR;
475
476         if (zip_stat(archive, "metadata", 0, &zs) < 0) {
477                 sr_err("Failed to open metadata: %s", zip_strerror(archive));
478                 zip_discard(archive);
479                 return SR_ERR;
480         }
481
482         basename = g_strdup_printf("analog-1-%zu", ch_nr);
483         baselen = strlen(basename);
484         next_chunk_num = 1;
485         num_files = zip_get_num_entries(archive, 0);
486         for (i = 0; i < num_files; i++) {
487                 entry_name = zip_get_name(archive, i, 0);
488                 if (!entry_name || strncmp(entry_name, basename, baselen) != 0) {
489                         continue;
490                 } else if (entry_name[baselen] == '-') {
491                         chunk_num = g_ascii_strtoull(entry_name + baselen + 1, NULL, 10);
492                         if (chunk_num < G_MAXINT && chunk_num >= next_chunk_num)
493                                 next_chunk_num = chunk_num + 1;
494                 }
495         }
496
497         size = sizeof(values[0]) * count;
498         analogsrc = zip_source_buffer(archive, values, size, FALSE);
499         chunkname = g_strdup_printf("%s-%u", basename, next_chunk_num);
500         i = zip_add(archive, chunkname, analogsrc);
501         if (i < 0) {
502                 sr_err("Failed to add chunk '%s': %s", chunkname, zip_strerror(archive));
503                 g_free(chunkname);
504                 g_free(basename);
505                 zip_source_free(analogsrc);
506                 zip_discard(archive);
507                 return SR_ERR;
508         }
509         g_free(chunkname);
510         if (zip_close(archive) < 0) {
511                 sr_err("Error saving session file: %s", zip_strerror(archive));
512                 g_free(basename);
513                 zip_discard(archive);
514                 return SR_ERR;
515         }
516
517         g_free(basename);
518
519         return SR_OK;
520 }
521
522 /**
523  * Queue analog data of a channel for srzip archive writes.
524  *
525  * @param[in] o Output module instance.
526  * @param[in] analog Sample data (session feed packet format).
527  * @param[in] flush Force ZIP archive update (queue by default).
528  *
529  * @returns SR_OK et al error codes.
530  */
531 static int zip_append_analog_queue(const struct sr_output *o,
532         const struct sr_datafeed_analog *analog, gboolean flush)
533 {
534         struct out_context *outc;
535         const struct sr_channel *ch;
536         size_t idx, nr;
537         struct analog_buff *buff;
538         float *values, *wrptr, *rdptr;
539         size_t send_size, remain, copy_size;
540         int ret;
541
542         outc = o->priv;
543
544         /* Is this the DF_END flush call without samples submission? */
545         if (!analog && flush) {
546                 for (idx = 0; idx < outc->analog_ch_count; idx++) {
547                         nr = outc->first_analog_index + idx;
548                         buff = &outc->analog_buff[idx];
549                         if (!buff->fill_size)
550                                 continue;
551                         ret = zip_append_analog(o,
552                                 buff->samples, buff->fill_size, nr);
553                         if (ret != SR_OK)
554                                 return ret;
555                         buff->fill_size = 0;
556                 }
557                 return SR_OK;
558         }
559
560         /* Lookup index and number of the analog channel. */
561         /* TODO: support packets covering multiple channels */
562         if (g_slist_length(analog->meaning->channels) != 1) {
563                 sr_err("Analog packets covering multiple channels not supported yet");
564                 return SR_ERR;
565         }
566         ch = g_slist_nth_data(analog->meaning->channels, 0);
567         for (idx = 0; idx < outc->analog_ch_count; idx++) {
568                 if (outc->analog_index_map[idx] == ch->index)
569                         break;
570         }
571         if (idx == outc->analog_ch_count)
572                 return SR_ERR_ARG;
573         nr = outc->first_analog_index + idx;
574         buff = &outc->analog_buff[idx];
575
576         /* Convert the analog data to an array of float values. */
577         values = g_try_malloc0(analog->num_samples * sizeof(values[0]));
578         if (!values)
579                 return SR_ERR_MALLOC;
580         ret = sr_analog_to_float(analog, values);
581         if (ret != SR_OK) {
582                 g_free(values);
583                 return ret;
584         }
585
586         /*
587          * Queue most recently received samples to the local buffer.
588          * Flush to the ZIP archive when the buffer space is exhausted.
589          */
590         rdptr = values;
591         send_size = analog->num_samples;
592         while (send_size) {
593                 remain = buff->alloc_size - buff->fill_size;
594                 if (remain) {
595                         wrptr = &buff->samples[buff->fill_size];
596                         copy_size = MIN(send_size, remain);
597                         send_size -= copy_size;
598                         buff->fill_size += copy_size;
599                         memcpy(wrptr, rdptr, copy_size * sizeof(values[0]));
600                         rdptr += copy_size;
601                         remain -= copy_size;
602                 }
603                 if (send_size && !remain) {
604                         ret = zip_append_analog(o,
605                                 buff->samples, buff->fill_size, nr);
606                         if (ret != SR_OK) {
607                                 g_free(values);
608                                 return ret;
609                         }
610                         buff->fill_size = 0;
611                         remain = buff->alloc_size - buff->fill_size;
612                 }
613         }
614         g_free(values);
615
616         /* Flush to the ZIP archive if the caller wants us to. */
617         if (flush && buff->fill_size) {
618                 ret = zip_append_analog(o, buff->samples, buff->fill_size, nr);
619                 if (ret != SR_OK)
620                         return ret;
621                 buff->fill_size = 0;
622         }
623
624         return SR_OK;
625 }
626
627 static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet,
628                 GString **out)
629 {
630         struct out_context *outc;
631         const struct sr_datafeed_meta *meta;
632         const struct sr_datafeed_logic *logic;
633         const struct sr_datafeed_analog *analog;
634         const struct sr_config *src;
635         GSList *l;
636         int ret;
637
638         *out = NULL;
639         if (!o || !o->sdi || !(outc = o->priv))
640                 return SR_ERR_ARG;
641
642         switch (packet->type) {
643         case SR_DF_META:
644                 meta = packet->payload;
645                 for (l = meta->config; l; l = l->next) {
646                         src = l->data;
647                         if (src->key != SR_CONF_SAMPLERATE)
648                                 continue;
649                         outc->samplerate = g_variant_get_uint64(src->data);
650                 }
651                 break;
652         case SR_DF_LOGIC:
653                 if (!outc->zip_created) {
654                         if ((ret = zip_create(o)) != SR_OK)
655                                 return ret;
656                         outc->zip_created = TRUE;
657                 }
658                 logic = packet->payload;
659                 ret = zip_append_queue(o, logic->data,
660                         logic->unitsize, logic->length, FALSE);
661                 if (ret != SR_OK)
662                         return ret;
663                 break;
664         case SR_DF_ANALOG:
665                 if (!outc->zip_created) {
666                         if ((ret = zip_create(o)) != SR_OK)
667                                 return ret;
668                         outc->zip_created = TRUE;
669                 }
670                 analog = packet->payload;
671                 ret = zip_append_analog_queue(o, analog, FALSE);
672                 if (ret != SR_OK)
673                         return ret;
674                 break;
675         case SR_DF_END:
676                 if (outc->zip_created) {
677                         ret = zip_append_queue(o, NULL, 0, 0, TRUE);
678                         if (ret != SR_OK)
679                                 return ret;
680                         ret = zip_append_analog_queue(o, NULL, TRUE);
681                         if (ret != SR_OK)
682                                 return ret;
683                 }
684                 break;
685         }
686
687         return SR_OK;
688 }
689
690 static struct sr_option options[] = {
691         ALL_ZERO
692 };
693
694 static const struct sr_option *get_options(void)
695 {
696         return options;
697 }
698
699 static int cleanup(struct sr_output *o)
700 {
701         struct out_context *outc;
702         size_t idx;
703
704         outc = o->priv;
705
706         g_free(outc->analog_index_map);
707         g_free(outc->filename);
708         g_free(outc->logic_buff.samples);
709         for (idx = 0; idx < outc->analog_ch_count; idx++)
710                 g_free(outc->analog_buff[idx].samples);
711         g_free(outc->analog_buff);
712
713         g_free(outc);
714         o->priv = NULL;
715
716         return SR_OK;
717 }
718
719 SR_PRIV struct sr_output_module output_srzip = {
720         .id = "srzip",
721         .name = "srzip",
722         .desc = "srzip session file format data",
723         .exts = (const char*[]){"sr", NULL},
724         .flags = SR_OUTPUT_INTERNAL_IO_HANDLING,
725         .options = get_options,
726         .init = init,
727         .receive = receive,
728         .cleanup = cleanup,
729 };