demux: hls: add adaptative based hls demuxer
[vlc.git] / modules / demux / image.c
1 /*****************************************************************************
2  * image.c: Image demuxer
3  *****************************************************************************
4  * Copyright (C) 2010 Laurent Aimar
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_demux.h>
35 #include <vlc_image.h>
36 #include "mxpeg_helper.h"
37
38 /*****************************************************************************
39  * Module descriptor
40  *****************************************************************************/
41 static int  Open (vlc_object_t *);
42 static void Close(vlc_object_t *);
43
44 #define ID_TEXT N_("ES ID")
45 #define ID_LONGTEXT N_( \
46     "Set the ID of the elementary stream")
47
48 #define GROUP_TEXT N_("Group")
49 #define GROUP_LONGTEXT N_(\
50     "Set the group of the elementary stream")
51
52 #define DECODE_TEXT N_("Decode")
53 #define DECODE_LONGTEXT N_( \
54     "Decode at the demuxer stage")
55
56 #define CHROMA_TEXT N_("Forced chroma")
57 #define CHROMA_LONGTEXT N_( \
58     "If non empty and image-decode is true, the image will be " \
59     "converted to the specified chroma.")
60
61 #define DURATION_TEXT N_("Duration in seconds")
62 #define DURATION_LONGTEXT N_( \
63     "Duration in seconds before simulating an end of file. " \
64     "A negative value means an unlimited play time.")
65
66 #define FPS_TEXT N_("Frame rate")
67 #define FPS_LONGTEXT N_( \
68     "Frame rate of the elementary stream produced.")
69
70 #define RT_TEXT N_("Real-time")
71 #define RT_LONGTEXT N_( \
72     "Use real-time mode suitable for being used as a master input and " \
73     "real-time input slaves.")
74
75 vlc_module_begin()
76     set_description(N_("Image demuxer"))
77     set_shortname(N_("Image"))
78     set_category(CAT_INPUT)
79     set_subcategory(SUBCAT_INPUT_DEMUX)
80     add_integer("image-id", -1, ID_TEXT, ID_LONGTEXT, true)
81         change_safe()
82     add_integer("image-group", 0, GROUP_TEXT, GROUP_LONGTEXT, true)
83         change_safe()
84     add_bool("image-decode", true, DECODE_TEXT, DECODE_LONGTEXT, true)
85         change_safe()
86     add_string("image-chroma", "", CHROMA_TEXT, CHROMA_LONGTEXT, true)
87         change_safe()
88     add_float("image-duration", 10, DURATION_TEXT, DURATION_LONGTEXT, false)
89         change_safe()
90     add_string("image-fps", "10/1", FPS_TEXT, FPS_LONGTEXT, true)
91         change_safe()
92     add_bool("image-realtime", false, RT_TEXT, RT_LONGTEXT, true)
93         change_safe()
94     set_capability("demux", 10)
95     set_callbacks(Open, Close)
96 vlc_module_end()
97
98 /*****************************************************************************
99  * Local prototypes
100  *****************************************************************************/
101 struct demux_sys_t
102 {
103     block_t     *data;
104     es_out_id_t *es;
105     mtime_t     duration;
106     bool        is_realtime;
107     mtime_t     pts_origin;
108     mtime_t     pts_next;
109     date_t        pts;
110 };
111
112 static block_t *Load(demux_t *demux)
113 {
114     const int     max_size = 4096 * 4096 * 8;
115     const int64_t size = stream_Size(demux->s);
116     if (size < 0 || size > max_size) {
117         msg_Err(demux, "Rejecting image based on its size (%"PRId64" > %d)", size, max_size);
118         return NULL;
119     }
120
121     if (size > 0)
122         return stream_Block(demux->s, size);
123     /* TODO */
124     return NULL;
125 }
126
127 static block_t *Decode(demux_t *demux,
128                        video_format_t *fmt, vlc_fourcc_t chroma, block_t *data)
129 {
130     image_handler_t *handler = image_HandlerCreate(demux);
131     if (!handler) {
132         block_Release(data);
133         return NULL;
134     }
135
136     video_format_t decoded;
137     video_format_Init(&decoded, chroma);
138
139     picture_t *image = image_Read(handler, data, fmt, &decoded);
140     image_HandlerDelete(handler);
141
142     if (!image)
143         return NULL;
144
145     video_format_Clean(fmt);
146     *fmt = decoded;
147
148     size_t size = 0;
149     for (int i = 0; i < image->i_planes; i++)
150         size += image->p[i].i_pitch * image->p[i].i_lines;
151
152     data = block_Alloc(size);
153     if (!data) {
154         picture_Release(image);
155         return NULL;
156     }
157
158     size_t offset = 0;
159     for (int i = 0; i < image->i_planes; i++) {
160         const plane_t *src = &image->p[i];
161         for (int y = 0; y < src->i_visible_lines; y++) {
162             memcpy(&data->p_buffer[offset],
163                    &src->p_pixels[y * src->i_pitch],
164                    src->i_visible_pitch);
165             offset += src->i_visible_pitch;
166         }
167     }
168
169     picture_Release(image);
170     return data;
171 }
172
173 static int Demux(demux_t *demux)
174 {
175     demux_sys_t *sys = demux->p_sys;
176
177     if (!sys->data)
178         return 0;
179
180     mtime_t deadline;
181     const mtime_t pts_first = sys->pts_origin + date_Get(&sys->pts);
182     if (sys->pts_next > VLC_TS_INVALID) {
183         deadline = sys->pts_next;
184     } else if (sys->is_realtime) {
185         deadline = mdate();
186         const mtime_t max_wait = CLOCK_FREQ / 50;
187         if (deadline + max_wait < pts_first) {
188             es_out_Control(demux->out, ES_OUT_SET_PCR, deadline);
189             /* That's ugly, but not yet easily fixable */
190             mwait(deadline + max_wait);
191             return 1;
192         }
193     } else {
194         deadline = 1 + pts_first;
195     }
196
197     for (;;) {
198         const mtime_t pts = sys->pts_origin + date_Get(&sys->pts);
199         if (sys->duration >= 0 && pts >= sys->pts_origin + sys->duration)
200             return 0;
201
202         if (pts >= deadline)
203             return 1;
204
205         block_t *data = block_Duplicate(sys->data);
206         if (!data)
207             return -1;
208
209         data->i_dts =
210         data->i_pts = VLC_TS_0 + pts;
211         es_out_Control(demux->out, ES_OUT_SET_PCR, data->i_pts);
212         es_out_Send(demux->out, sys->es, data);
213
214         date_Increment(&sys->pts, 1);
215     }
216 }
217
218 static int Control(demux_t *demux, int query, va_list args)
219 {
220     demux_sys_t *sys = demux->p_sys;
221
222     switch (query) {
223     case DEMUX_GET_POSITION: {
224         double *position = va_arg(args, double *);
225         if (sys->duration > 0)
226             *position = date_Get(&sys->pts) / (double)sys->duration;
227         else
228             *position = 0;
229         return VLC_SUCCESS;
230     }
231     case DEMUX_SET_POSITION: {
232         if (sys->duration < 0 || sys->is_realtime)
233             return VLC_EGENERIC;
234         double position = va_arg(args, double);
235         date_Set(&sys->pts, position * sys->duration);
236         return VLC_SUCCESS;
237     }
238     case DEMUX_GET_TIME: {
239         int64_t *time = va_arg(args, int64_t *);
240         *time = sys->pts_origin + date_Get(&sys->pts);
241         return VLC_SUCCESS;
242     }
243     case DEMUX_SET_TIME: {
244         if (sys->duration < 0 || sys->is_realtime)
245             return VLC_EGENERIC;
246         int64_t time = va_arg(args, int64_t);
247         date_Set(&sys->pts, VLC_CLIP(time - sys->pts_origin, 0, sys->duration));
248         return VLC_SUCCESS;
249     }
250     case DEMUX_SET_NEXT_DEMUX_TIME: {
251         int64_t pts_next = VLC_TS_0 + va_arg(args, int64_t);
252         if (sys->pts_next <= VLC_TS_INVALID)
253             sys->pts_origin = pts_next;
254         sys->pts_next = pts_next;
255         return VLC_SUCCESS;
256     }
257     case DEMUX_GET_LENGTH: {
258         int64_t *length = va_arg(args, int64_t *);
259         *length = __MAX(sys->duration, 0);
260         return VLC_SUCCESS;
261     }
262     case DEMUX_GET_FPS: {
263         double *fps = va_arg(args, double *);
264         *fps = (double)sys->pts.i_divider_num / sys->pts.i_divider_den;
265         return VLC_SUCCESS;
266     }
267     case DEMUX_GET_META:
268     case DEMUX_HAS_UNSUPPORTED_META:
269     case DEMUX_GET_ATTACHMENTS:
270     default:
271         return VLC_EGENERIC;
272     }
273 }
274
275 static bool IsBmp(stream_t *s)
276 {
277     const uint8_t *header;
278     if (stream_Peek(s, &header, 18) < 18)
279         return false;
280     if (memcmp(header, "BM", 2) &&
281         memcmp(header, "BA", 2) &&
282         memcmp(header, "CI", 2) &&
283         memcmp(header, "CP", 2) &&
284         memcmp(header, "IC", 2) &&
285         memcmp(header, "PT", 2))
286         return false;
287     uint32_t file_size   = GetDWLE(&header[2]);
288     uint32_t data_offset = GetDWLE(&header[10]);
289     uint32_t header_size = GetDWLE(&header[14]);
290     if (file_size != 14 && file_size != 14 + header_size &&
291         file_size <= data_offset)
292         return false;
293     if (data_offset < header_size + 14)
294         return false;
295     if (header_size != 12 && header_size < 40)
296         return false;
297     return true;
298 }
299
300 static bool IsPcx(stream_t *s)
301 {
302     const uint8_t *header;
303     if (stream_Peek(s, &header, 66) < 66)
304         return false;
305     if (header[0] != 0x0A ||                        /* marker */
306         (header[1] != 0x00 && header[1] != 0x02 &&
307          header[1] != 0x03 && header[1] != 0x05) || /* version */
308         (header[2] != 0 && header[2] != 1) ||       /* encoding */
309         (header[3] != 1 && header[3] != 2 &&
310          header[3] != 4 && header[3] != 8) ||       /* bits per pixel per plane */
311         header[64] != 0 ||                          /* reserved */
312         header[65] == 0 || header[65] > 4)          /* plane count */
313         return false;
314     if (GetWLE(&header[4]) > GetWLE(&header[8]) ||  /* xmin vs xmax */
315         GetWLE(&header[6]) > GetWLE(&header[10]))   /* ymin vs ymax */
316         return false;
317     return true;
318 }
319
320 static bool IsLbm(stream_t *s)
321 {
322     const uint8_t *header;
323     if (stream_Peek(s, &header, 12) < 12)
324         return false;
325     if (memcmp(&header[0], "FORM", 4) ||
326         GetDWBE(&header[4]) <= 4 ||
327         (memcmp(&header[8], "ILBM", 4) && memcmp(&header[8], "PBM ", 4)))
328         return false;
329     return true;
330 }
331 static bool IsPnmBlank(uint8_t v)
332 {
333     return v == ' ' || v == '\t' || v == '\r' || v == '\n';
334 }
335 static bool IsPnm(stream_t *s)
336 {
337     const uint8_t *header;
338     int size = stream_Peek(s, &header, 256);
339     if (size < 3)
340         return false;
341     if (header[0] != 'P' ||
342         header[1] < '1' || header[1] > '6' ||
343         !IsPnmBlank(header[2]))
344         return false;
345
346     int number_count = 0;
347     for (int i = 3, parsing_number = 0; i < size && number_count < 2; i++) {
348         if (IsPnmBlank(header[i])) {
349             if (parsing_number) {
350                 parsing_number = 0;
351                 number_count++;
352             }
353         } else {
354             if (header[i] < '0' || header[i] > '9')
355                 break;
356             parsing_number = 1;
357         }
358     }
359     if (number_count < 2)
360         return false;
361     return true;
362 }
363
364 static uint8_t FindJpegMarker(int *position, const uint8_t *data, int size)
365 {
366     for (int i = *position; i + 1 < size; i++) {
367         if (data[i + 0] != 0xff || data[i + 1] == 0x00)
368             return 0xff;
369         if (data[i + 1] != 0xff) {
370             *position = i + 2;
371             return data[i + 1];
372         }
373     }
374     return 0xff;
375 }
376 static bool IsJfif(stream_t *s)
377 {
378     const uint8_t *header;
379     int size = stream_Peek(s, &header, 256);
380     int position = 0;
381
382     if (FindJpegMarker(&position, header, size) != 0xd8)
383         return false;
384     if (FindJpegMarker(&position, header, size) != 0xe0)
385         return false;
386     position += 2;  /* Skip size */
387     if (position + 5 > size)
388         return false;
389     if (memcmp(&header[position], "JFIF\0", 5))
390         return false;
391     return true;
392 }
393
394 static bool IsSpiff(stream_t *s)
395 {
396     const uint8_t *header;
397     if (stream_Peek(s, &header, 36) < 36) /* SPIFF header size */
398         return false;
399     if (header[0] != 0xff || header[1] != 0xd8 ||
400         header[2] != 0xff || header[3] != 0xe8)
401         return false;
402     if (memcmp(&header[6], "SPIFF\0", 6))
403         return false;
404     return true;
405 }
406
407 static bool IsExif(stream_t *s)
408 {
409     const uint8_t *header;
410     int size = stream_Peek(s, &header, 256);
411     int position = 0;
412
413     if (FindJpegMarker(&position, header, size) != 0xd8)
414         return false;
415     if (FindJpegMarker(&position, header, size) != 0xe1)
416         return false;
417     position += 2;  /* Skip size */
418     if (position + 5 > size)
419         return false;
420     if (memcmp(&header[position], "Exif\0", 5))
421         return false;
422     return true;
423 }
424
425 static bool FindSVGmarker(int *position, const uint8_t *data, const int size, const char *marker)
426 {
427     for( int i = *position; i < size; i++)
428     {
429         if (memcmp(&data[i], marker, strlen(marker)) == 0)
430         {
431             *position = i;
432             return true;
433         }
434     }
435     return false;
436 }
437
438 static bool IsSVG(stream_t *s)
439 {
440     char *ext = strstr(s->psz_path, ".svg");
441     if (!ext) return false;
442
443     const uint8_t *header;
444     int size = stream_Peek(s, &header, 4096);
445     int position = 0;
446
447     const char xml[] = "<?xml version=\"";
448     if (!FindSVGmarker(&position, header, size, xml))
449         return false;
450     if (position != 0)
451         return false;
452
453     const char endxml[] = ">\0";
454     if (!FindSVGmarker(&position, header, size, endxml))
455         return false;
456     if (position <= 15)
457         return false;
458
459     const char svg[] = "<svg";
460     if (!FindSVGmarker(&position, header, size, svg))
461         return false;
462     if (position < 19)
463         return false;
464
465     /* SVG Scalable Vector Graphics image */
466
467     /* NOTE: some SVG images have the mimetype set in a meta data section
468      * and some do not */
469     return true;
470 }
471
472 static bool IsTarga(stream_t *s)
473 {
474     /* The header is not enough to ensure proper detection, we need
475      * to have a look at the footer. But doing so can be slow. So
476      * try to avoid it when possible */
477     const uint8_t *header;
478     if (stream_Peek(s, &header, 18) < 18)   /* Targa fixed header */
479         return false;
480     if (header[1] > 1)                      /* Color Map Type */
481         return false;
482     if ((header[1] != 0 || header[3 + 4] != 0) &&
483         header[3 + 4] != 8  &&
484         header[3 + 4] != 15 && header[3 + 4] != 16 &&
485         header[3 + 4] != 24 && header[3 + 4] != 32)
486         return false;
487     if ((header[2] > 3 && header[2] < 9) || header[2] > 11) /* Image Type */
488         return false;
489     if (GetWLE(&header[8 + 4]) <= 0 ||      /* Width */
490         GetWLE(&header[8 + 6]) <= 0)        /* Height */
491         return false;
492     if (header[8 + 8] != 8  &&
493         header[8 + 8] != 15 && header[8 + 8] != 16 &&
494         header[8 + 8] != 24 && header[8 + 8] != 32)
495         return false;
496     if (header[8 + 9] & 0xc0)               /* Reserved bits */
497         return false;
498
499     const int64_t size = stream_Size(s);
500     if (size <= 18 + 26)
501         return false;
502     bool can_seek;
503     if (stream_Control(s, STREAM_CAN_SEEK, &can_seek) || !can_seek)
504         return false;
505
506     const int64_t position = stream_Tell(s);
507     if (stream_Seek(s, size - 26))
508         return false;
509
510     const uint8_t *footer;
511     bool is_targa = stream_Peek(s, &footer, 26) >= 26 &&
512                     !memcmp(&footer[8], "TRUEVISION-XFILE.\x00", 18);
513     stream_Seek(s, position);
514     return is_targa;
515 }
516
517 typedef struct {
518     vlc_fourcc_t  codec;
519     int           marker_size;
520     const uint8_t marker[14];
521     bool          (*detect)(stream_t *s);
522 } image_format_t;
523
524 #define VLC_CODEC_XCF VLC_FOURCC('X', 'C', 'F', ' ')
525 #define VLC_CODEC_LBM VLC_FOURCC('L', 'B', 'M', ' ')
526 static const image_format_t formats[] = {
527     { .codec = VLC_CODEC_XCF,
528       .marker_size = 9 + 4 + 1,
529       .marker = { 'g', 'i', 'm', 'p', ' ', 'x', 'c', 'f', ' ',
530                   'f', 'i', 'l', 'e', '\0' }
531     },
532     { .codec = VLC_CODEC_XCF,
533       .marker_size = 9 + 4 + 1,
534       .marker = { 'g', 'i', 'm', 'p', ' ', 'x', 'c', 'f', ' ',
535                   'v', '0', '0', '1', '\0' }
536     },
537     { .codec = VLC_CODEC_XCF,
538       .marker_size = 9 + 4 + 1,
539       .marker = { 'g', 'i', 'm', 'p', ' ', 'x', 'c', 'f', ' ',
540                   'v', '0', '0', '2', '\0' }
541     },
542     { .codec = VLC_CODEC_PNG,
543       .marker_size = 8,
544       .marker = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }
545     },
546     { .codec = VLC_CODEC_GIF,
547       .marker_size = 6,
548       .marker = { 'G', 'I', 'F', '8', '7', 'a' }
549     },
550     { .codec = VLC_CODEC_GIF,
551       .marker_size = 6,
552       .marker = { 'G', 'I', 'F', '8', '9', 'a' }
553     },
554     /* XXX TIFF detection may be a bit weak */
555     { .codec = VLC_CODEC_TIFF,
556       .marker_size = 4,
557       .marker = { 'I', 'I', 0x2a, 0x00 },
558     },
559     { .codec = VLC_CODEC_TIFF,
560       .marker_size = 4,
561       .marker = { 'M', 'M', 0x00, 0x2a },
562     },
563     { .codec = VLC_CODEC_BMP,
564       .detect = IsBmp,
565     },
566     { .codec = VLC_CODEC_PCX,
567       .detect = IsPcx,
568     },
569     { .codec = VLC_CODEC_LBM,
570       .detect = IsLbm,
571     },
572     { .codec = VLC_CODEC_PNM,
573       .detect = IsPnm,
574     },
575     { .codec = VLC_CODEC_MXPEG,
576       .detect = IsMxpeg,
577     },
578     { .codec = VLC_CODEC_JPEG,
579       .detect = IsJfif,
580     },
581     { .codec = VLC_CODEC_JPEG,
582       .detect = IsSpiff,
583     },
584     { .codec = VLC_CODEC_JPEG,
585       .detect = IsExif,
586     },
587     { .codec = VLC_CODEC_BPG,
588       .marker_size = 4,
589       .marker = { 'B', 'P', 'G', 0xFB },
590     },
591     { .codec = VLC_CODEC_SVG,
592       .detect = IsSVG,
593     },
594     { .codec = VLC_CODEC_TARGA,
595       .detect = IsTarga,
596     },
597     { .codec = 0 }
598 };
599
600 static int Open(vlc_object_t *object)
601 {
602     demux_t *demux = (demux_t*)object;
603
604     /* Detect the image type */
605     const image_format_t *img;
606
607     const uint8_t *peek;
608     int peek_size = 0;
609     for (int i = 0; ; i++) {
610         img = &formats[i];
611         if (!img->codec)
612             return VLC_EGENERIC;
613
614         if (img->detect) {
615             if (img->detect(demux->s))
616                 break;
617         } else {
618             if (peek_size < img->marker_size)
619                 peek_size = stream_Peek(demux->s, &peek, img->marker_size);
620             if (peek_size >= img->marker_size &&
621                 !memcmp(peek, img->marker, img->marker_size))
622                 break;
623         }
624     }
625     msg_Dbg(demux, "Detected image: %s",
626             vlc_fourcc_GetDescription(VIDEO_ES, img->codec));
627
628     if( img->codec == VLC_CODEC_MXPEG )
629     {
630         return VLC_EGENERIC; //let avformat demux this file
631     }
632
633     /* Load and if selected decode */
634     es_format_t fmt;
635     es_format_Init(&fmt, VIDEO_ES, img->codec);
636     fmt.video.i_chroma = fmt.i_codec;
637
638     block_t *data = Load(demux);
639     if (data && var_InheritBool(demux, "image-decode")) {
640         char *string = var_InheritString(demux, "image-chroma");
641         vlc_fourcc_t chroma = vlc_fourcc_GetCodecFromString(VIDEO_ES, string);
642         free(string);
643
644         data = Decode(demux, &fmt.video, chroma, data);
645         fmt.i_codec = fmt.video.i_chroma;
646     }
647     fmt.i_id    = var_InheritInteger(demux, "image-id");
648     fmt.i_group = var_InheritInteger(demux, "image-group");
649     if (var_InheritURational(demux,
650                              &fmt.video.i_frame_rate,
651                              &fmt.video.i_frame_rate_base,
652                              "image-fps") ||
653         fmt.video.i_frame_rate <= 0 || fmt.video.i_frame_rate_base <= 0) {
654         msg_Err(demux, "Invalid frame rate, using 10/1 instead");
655         fmt.video.i_frame_rate      = 10;
656         fmt.video.i_frame_rate_base = 1;
657     }
658
659     /* If loadind failed, we still continue to avoid mis-detection
660      * by other demuxers. */
661     if (!data)
662         msg_Err(demux, "Failed to load the image");
663
664     /* */
665     demux_sys_t *sys = malloc(sizeof(*sys));
666     if (!sys) {
667         if (data)
668             block_Release(data);
669         es_format_Clean(&fmt);
670         return VLC_ENOMEM;
671     }
672
673     sys->data        = data;
674     sys->es          = es_out_Add(demux->out, &fmt);
675     sys->duration    = CLOCK_FREQ * var_InheritFloat(demux, "image-duration");
676     sys->is_realtime = var_InheritBool(demux, "image-realtime");
677     sys->pts_origin  = sys->is_realtime ? mdate() : 0;
678     sys->pts_next    = VLC_TS_INVALID;
679     date_Init(&sys->pts, fmt.video.i_frame_rate, fmt.video.i_frame_rate_base);
680     date_Set(&sys->pts, 0);
681
682     es_format_Clean(&fmt);
683
684     demux->pf_demux   = Demux;
685     demux->pf_control = Control;
686     demux->p_sys      = sys;
687     return VLC_SUCCESS;
688 }
689
690 static void Close(vlc_object_t *object)
691 {
692     demux_t     *demux = (demux_t*)object;
693     demux_sys_t *sys   = demux->p_sys;
694
695     if (sys->data)
696         block_Release(sys->data);
697     free(sys);
698 }
699