Mercurial > mplayer.hg
comparison libvo/vo_mng.c @ 33389:2672587086ad
Add MNG output support.
Patch by Stefan Schuermans, stefan blinkenarea dot org.
author | cboesch |
---|---|
date | Sat, 21 May 2011 15:04:50 +0000 |
parents | |
children | cc3071a7c057 |
comparison
equal
deleted
inserted
replaced
33388:25880d57740d | 33389:2672587086ad |
---|---|
1 /* | |
2 * MPlayer output to MNG file | |
3 * | |
4 * Copyright (C) 2011 Stefan Schuermans <stefan blinkenarea org> | |
5 * | |
6 * This file is part of MPlayer. | |
7 * | |
8 * MPlayer is free software; you can redistribute it and/or modify | |
9 * it under the terms of the GNU General Public License as published by | |
10 * the Free Software Foundation; either version 2 of the License, or | |
11 * (at your option) any later version. | |
12 * | |
13 * MPlayer is distributed in the hope that it will be useful, | |
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 * GNU General Public License for more details. | |
17 * | |
18 * You should have received a copy of the GNU General Public License along | |
19 * with MPlayer; if not, write to the Free Software Foundation, Inc., | |
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
21 */ | |
22 | |
23 #include <stdlib.h> | |
24 #include <string.h> | |
25 #include <unistd.h> | |
26 #include <fcntl.h> | |
27 #include <errno.h> | |
28 #include <sys/stat.h> | |
29 #include <sys/types.h> | |
30 | |
31 #include <zlib.h> | |
32 | |
33 #define MNG_INCLUDE_WRITE_PROCS | |
34 #define MNG_ACCESS_CHUNKS | |
35 #define MNG_SUPPORT_READ | |
36 #define MNG_SUPPORT_DISPLAY | |
37 #define MNG_SUPPORT_WRITE | |
38 #include <libmng.h> | |
39 | |
40 #include "video_out.h" | |
41 #include "video_out_internal.h" | |
42 #include "mp_msg.h" | |
43 | |
44 #define VOMNG_DEFAULT_DELAY_MS (100) /* default delay of a frame */ | |
45 | |
46 static vo_info_t info = { | |
47 .name = "MNG file", | |
48 .short_name = "mng", | |
49 .author = "Stefan Schuermans <stefan blinkenarea org>" | |
50 }; | |
51 | |
52 LIBVO_EXTERN(mng) | |
53 | |
54 /* a frame to be written to the MNG file */ | |
55 struct vomng_frame { | |
56 mng_ptr data; /**< deflate compressed data, malloc-ed */ | |
57 mng_uint32 len; /**< length of compressed data */ | |
58 unsigned int time_ms; /**< timestamp of frame (in ms) */ | |
59 struct vomng_frame *next; /**< next frame */ | |
60 }; | |
61 | |
62 /* properties of MNG output */ | |
63 struct vomng_properties { | |
64 char *out_file_name; /**< name of output file, malloc-ed */ | |
65 unsigned int width, height; /**< dimensions */ | |
66 unsigned char *canvas; /**< canvas for frame, | |
67 canvas := row ... row, | |
68 row := filter_id pix ... pix, | |
69 pix := red green blue */ | |
70 struct vomng_frame *frame_first; /**< list of frames */ | |
71 struct vomng_frame *frame_last; | |
72 int is_init; /**< if initialized */ | |
73 }; | |
74 | |
75 /* private data of MNG vo module */ | |
76 static struct vomng_properties vomng; | |
77 | |
78 /** | |
79 * @brief libmng callback: allocate memory | |
80 * @param[in] size size of requested memory block | |
81 * @return pointer to memory block, which is initialized to zero | |
82 */ | |
83 static mng_ptr vomng_alloc(mng_size_t size) | |
84 { | |
85 return calloc(1, size); | |
86 } | |
87 | |
88 /** | |
89 * @brief libmng callback: free memory | |
90 * @param[in] pointer to memory block | |
91 * @param[in] size size of requested memory block | |
92 */ | |
93 static void vomng_free(mng_ptr ptr, mng_size_t size) | |
94 { | |
95 free(ptr); | |
96 } | |
97 | |
98 /** | |
99 * @brief libmng callback: open stream | |
100 * @param[in] mng libmng handle | |
101 * @return if stream could be opened | |
102 */ | |
103 static mng_bool vomng_openstream(mng_handle mng) | |
104 { | |
105 return MNG_TRUE; /* stream is always open wen we get here, | |
106 tell libmng that everything is okay */ | |
107 } | |
108 | |
109 /** | |
110 * @brief libmng callback: stream should be closed | |
111 * @param[in] mng libmng handle | |
112 * @return if stream could be closed | |
113 */ | |
114 static mng_bool vomng_closestream(mng_handle mng) | |
115 { | |
116 return MNG_TRUE; /* stream will be closed later, | |
117 tell libmng that everything is okay */ | |
118 } | |
119 | |
120 /** | |
121 * @brief libmng callback: write libmng data to the open stream | |
122 * @param[in] mng libmng handle | |
123 * @param[in] *buf pointer to data to write | |
124 * @param[in] size size of data to write | |
125 * @param[out] *written size of data written | |
126 * @return if data was written successfully | |
127 */ | |
128 static mng_bool vomng_writedata(mng_handle mng, mng_ptr buf, | |
129 mng_uint32 size, mng_uint32 *written) | |
130 { | |
131 FILE *file = mng_get_userdata(mng); | |
132 *written = fwrite(buf, 1, size, file); | |
133 /* according to the example in libmng documentation, true is always | |
134 returned here, short writes can be detected by libmng via *written */ | |
135 return MNG_TRUE; | |
136 } | |
137 | |
138 /** | |
139 * @brief compress frame data | |
140 * @param[in] width width of canvas | |
141 * @param[in] height height of canvas | |
142 * @param[in] *canvas data on canvas (including MNG filter IDs) | |
143 * @param[out] *out_ptr pointer to compressed data, malloc-ed | |
144 * @param[out] *out_len length of compressed data | |
145 */ | |
146 static void vomng_canvas_to_compressed(unsigned int width, unsigned int height, | |
147 const unsigned char *canvas, | |
148 mng_ptr *out_ptr, mng_uint32 *out_len) | |
149 { | |
150 mng_uint32 raw_len; | |
151 unsigned char *ptr; | |
152 unsigned long len; | |
153 | |
154 /* default: no output */ | |
155 *out_ptr = NULL; | |
156 *out_len = 0; | |
157 | |
158 /* raw_len := length of input data | |
159 - it will be significantly shorter than 32 bit | |
160 - the "1 +" is needed because each row starts with the filter ID */ | |
161 raw_len = height * (1 + width * 3); | |
162 | |
163 /* compress data | |
164 - compress2 output size will be smaller than raw_len * 1.001 + 12 (see | |
165 man page), so calculate the next larger integer value in len and | |
166 allocate abuffer of this size | |
167 - len will still contain a value shorter than 32 bit */ | |
168 len = raw_len + (raw_len + 999) / 1000 + 12; | |
169 ptr = malloc(len); | |
170 if (!ptr) | |
171 return; | |
172 compress2(ptr, &len, canvas, raw_len, Z_BEST_COMPRESSION); | |
173 | |
174 /* return allocated compressed data | |
175 - we have to convert the output length to a shorter data type as | |
176 libmng does not accept an unsigned long as length | |
177 - convert here, because we can see here that the conversion is safe | |
178 - see comments about raw_len and len above | |
179 - compress2 never increases value of len */ | |
180 *out_ptr = ptr; | |
181 *out_len = len; | |
182 } | |
183 | |
184 /** | |
185 * @brief write frame to MNG file | |
186 * @param[in] *frame the frame to write to MNG file | |
187 * @param[in] mng libmng handle | |
188 * @param[in] width width of canvas | |
189 * @param[in] height height of canvas | |
190 * @param[in] first_frame if the frame is the first one in the file | |
191 * @return 0 on success, 1 on error | |
192 */ | |
193 static int vomng_write_frame(struct vomng_frame *frame, mng_handle mng, | |
194 unsigned int width, unsigned int height, | |
195 int first_frame) | |
196 { | |
197 unsigned int delay_ms; | |
198 | |
199 /* determine delay */ | |
200 if (frame->next) | |
201 delay_ms = frame->next->time_ms - frame->time_ms; | |
202 else | |
203 delay_ms = VOMNG_DEFAULT_DELAY_MS; /* default delay for last frame */ | |
204 | |
205 /* write frame headers to MNG file */ | |
206 if (mng_putchunk_seek(mng, 0, MNG_NULL)) { | |
207 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing SEEK chunk failed\n"); | |
208 return 1; | |
209 } | |
210 if (mng_putchunk_fram(mng, MNG_FALSE, | |
211 /* keep canvas if not 1st frame */ | |
212 first_frame ? MNG_FRAMINGMODE_1 | |
213 : MNG_FRAMINGMODE_NOCHANGE, | |
214 0, MNG_NULL, /* no frame name */ | |
215 MNG_CHANGEDELAY_DEFAULT, /* change only delay */ | |
216 MNG_CHANGETIMOUT_NO, | |
217 MNG_CHANGECLIPPING_NO, | |
218 MNG_CHANGESYNCID_NO, | |
219 delay_ms, /* new delay */ | |
220 0, /* no new timeout */ | |
221 0, 0, 0, 0, 0, /* no new boundary */ | |
222 0, 0)) { /* no count, no IDs */ | |
223 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing FRAM chunk failed\n"); | |
224 return 1; | |
225 } | |
226 if (mng_putchunk_defi(mng, 0, /* no ID */ | |
227 MNG_DONOTSHOW_VISIBLE, | |
228 MNG_ABSTRACT, | |
229 MNG_TRUE, 0, 0, /* top left location */ | |
230 MNG_FALSE, 0, 0, 0, 0)) { /* no clipping */ | |
231 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing DEFI chunk failed\n"); | |
232 return 1; | |
233 } | |
234 if (mng_putchunk_ihdr(mng, width, height, /* dimensions */ | |
235 8, MNG_COLORTYPE_RGB, /* RBG */ | |
236 MNG_COMPRESSION_DEFLATE, | |
237 MNG_FILTER_ADAPTIVE, | |
238 MNG_INTERLACE_NONE)) { | |
239 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing IHDR chunk failed\n"); | |
240 return 1; | |
241 } | |
242 | |
243 /* write frame data */ | |
244 if (mng_putchunk_idat(mng, frame->len, frame->data)) { | |
245 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing IDAT chunk failed\n"); | |
246 return 1; | |
247 } | |
248 | |
249 /* write frame footers to MNG file */ | |
250 if (mng_putchunk_iend(mng)) { | |
251 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing IEND chunk failed\n"); | |
252 return 1; | |
253 } | |
254 | |
255 return 0; | |
256 } | |
257 | |
258 /** | |
259 * @brief write buffered frames to MNG file | |
260 * @return 0 on success, 1 on error | |
261 */ | |
262 static int vomng_write_file(void) | |
263 { | |
264 FILE *file; | |
265 mng_handle mng; | |
266 struct vomng_frame *frame; | |
267 unsigned int frames, duration_ms; | |
268 int first; | |
269 | |
270 /* refuse to create empty MNG file */ | |
271 if (!vomng.frame_first || !vomng.frame_last) { | |
272 mp_msg(MSGT_VO, MSGL_ERR, "vomng: not creating empty file\n"); | |
273 return 1; | |
274 } | |
275 | |
276 /* create output file */ | |
277 file = fopen(vomng.out_file_name, "wb"); | |
278 if (!file) { | |
279 mp_msg(MSGT_VO, MSGL_ERR, | |
280 "vomng: could not open output file \"%s\": %s\n", | |
281 vomng.out_file_name, strerror(errno)); | |
282 return 1; | |
283 } | |
284 | |
285 /* inititalize MNG library */ | |
286 mng = mng_initialize(file, vomng_alloc, vomng_free, MNG_NULL); | |
287 if (!mng) { | |
288 mp_msg(MSGT_VO, MSGL_ERR, "vomng: could not initialize libmng\n"); | |
289 fclose(file); | |
290 return 1; | |
291 } | |
292 if (mng_setcb_openstream (mng, vomng_openstream ) || | |
293 mng_setcb_closestream(mng, vomng_closestream) || | |
294 mng_setcb_writedata (mng, vomng_writedata )) { | |
295 mp_msg(MSGT_VO, MSGL_ERR, "vomng: cannot set callbacks for libmng\n"); | |
296 mng_cleanup(&mng); | |
297 fclose(file); | |
298 return 1; | |
299 } | |
300 | |
301 /* create new MNG image in memory */ | |
302 if (mng_create(mng)) { | |
303 mp_msg(MSGT_VO, MSGL_ERR, "vomng: cannot create MNG image in memory\n"); | |
304 mng_cleanup(&mng); | |
305 fclose(file); | |
306 return 1; | |
307 } | |
308 | |
309 /* determine number of frames and total duration */ | |
310 frames = 0; | |
311 for (frame = vomng.frame_first; frame; frame = frame->next) | |
312 frames++; | |
313 duration_ms = vomng.frame_last->time_ms - vomng.frame_first->time_ms; | |
314 | |
315 /* write MNG header chunks */ | |
316 if (mng_putchunk_mhdr(mng, | |
317 vomng.width, /* dimensions */ | |
318 vomng.height, | |
319 1000, 0, /* ticks per second, layer */ | |
320 frames, /* number of frames */ | |
321 duration_ms, /* total duration */ | |
322 MNG_SIMPLICITY_VALID | | |
323 MNG_SIMPLICITY_SIMPLEFEATURES | | |
324 MNG_SIMPLICITY_COMPLEXFEATURES) || | |
325 mng_putchunk_save(mng, | |
326 MNG_TRUE, 0, 0) || /* empty save chunk */ | |
327 mng_putchunk_term(mng, | |
328 MNG_TERMACTION_CLEAR, /* show last frame forever */ | |
329 MNG_ITERACTION_CLEAR, | |
330 0, 0)) { | |
331 mp_msg(MSGT_VO, MSGL_ERR, | |
332 "vomng: writing MHDR/SAVE/TERM chunks failed\n"); | |
333 mng_write(mng); /* write out buffered chunks before cleanup */ | |
334 mng_cleanup(&mng); | |
335 fclose(file); | |
336 return 1; | |
337 } | |
338 | |
339 /* write frames */ | |
340 first = 1; | |
341 for (frame = vomng.frame_first; frame; frame = frame->next) { | |
342 if (vomng_write_frame(frame, mng, vomng.width, vomng.height, first)) | |
343 break; | |
344 first = 0; | |
345 } | |
346 if (frame) { | |
347 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing frames failed\n"); | |
348 mng_write(mng); /* write out buffered chunks before cleanup */ | |
349 mng_cleanup(&mng); | |
350 fclose(file); | |
351 return 1; | |
352 } | |
353 | |
354 /* write MNG end chunk */ | |
355 if (mng_putchunk_mend(mng)) { | |
356 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing end chunk failed\n"); | |
357 mng_write(mng); /* write out buffered chunks before cleanup */ | |
358 mng_cleanup(&mng); | |
359 fclose(file); | |
360 return 1; | |
361 } | |
362 | |
363 /* finish and cleanup */ | |
364 mng_write(mng); /* write out buffered chunks before cleanup */ | |
365 mng_cleanup(&mng); | |
366 fclose(file); | |
367 | |
368 return 0; | |
369 } | |
370 | |
371 /** | |
372 * @brief close all files and free all memory of MNG vo module | |
373 */ | |
374 static void vomng_prop_reset(void) | |
375 { | |
376 struct vomng_frame *frame, *next; | |
377 | |
378 /* we are initialized properly */ | |
379 if (vomng.is_init) { | |
380 /* write buffered frames to MNG file */ | |
381 if (vomng_write_file()) | |
382 mp_msg(MSGT_VO, MSGL_ERR, | |
383 "vomng: writing output file failed\n"); | |
384 } | |
385 | |
386 /* reset state */ | |
387 vomng.is_init = 0; | |
388 if (vomng.frame_first) { | |
389 frame = vomng.frame_first; | |
390 while (frame) { | |
391 next = frame->next; | |
392 free(frame->data); | |
393 free(frame); | |
394 frame = next; | |
395 } | |
396 vomng.frame_first = NULL; | |
397 vomng.frame_last = NULL; | |
398 } | |
399 free(vomng.canvas); | |
400 vomng.canvas = NULL; | |
401 vomng.width = 0; | |
402 vomng.height = 0; | |
403 } | |
404 | |
405 /** | |
406 * @brief close files, free memory and delete private data of MNG von module | |
407 */ | |
408 static void vomng_prop_cleanup(void) | |
409 { | |
410 vomng_prop_reset(); | |
411 free(vomng.out_file_name); | |
412 } | |
413 | |
414 /** | |
415 * @brief configure MNG vo module | |
416 * @param[in] width video width | |
417 * @param[in] height video height | |
418 * @param[in] d_width (unused) | |
419 * @param[in] d_height (unused) | |
420 * @param[in] flags (unused) | |
421 * @param[in] title (unused) | |
422 * @param[in] format video frame format | |
423 * @return 0 on success, 1 on error | |
424 */ | |
425 static int config(uint32_t width, uint32_t height, | |
426 uint32_t d_width, uint32_t d_height, | |
427 uint32_t flags, char *title, uint32_t format) | |
428 { | |
429 uint32_t row_stride, y; | |
430 | |
431 /* reset state */ | |
432 vomng_prop_reset(); | |
433 | |
434 /* check format */ | |
435 if (format != IMGFMT_RGB24) { | |
436 mp_msg(MSGT_VO, MSGL_ERR, | |
437 "vomng: config with invalid format (!= IMGFMT_RGB24)\n"); | |
438 return 1; | |
439 } | |
440 | |
441 /* allocate canvas */ | |
442 vomng.width = width; | |
443 vomng.height = height; | |
444 row_stride = 1 + width * 3; /* rows contain filter IDs */ | |
445 vomng.canvas = calloc(height * row_stride, 1); | |
446 if (!vomng.canvas) { | |
447 mp_msg(MSGT_VO, MSGL_ERR, "vomng: out of memory\n"); | |
448 return 1; | |
449 } | |
450 /* fill in filter IDs for rows */ | |
451 for (y = 0; y < height; y++) | |
452 *(vomng.canvas + row_stride * y) = MNG_FILTER_NONE; | |
453 | |
454 /* we are initialized */ | |
455 vomng.is_init = 1; | |
456 | |
457 return 0; | |
458 } | |
459 | |
460 /** | |
461 * @brief draw on screen display (unsupported for MNG vo module) | |
462 */ | |
463 static void draw_osd(void) | |
464 { | |
465 } | |
466 | |
467 /** | |
468 * @brief display data currently on canvas | |
469 */ | |
470 static void flip_page(void) | |
471 { | |
472 unsigned int last_ms; | |
473 struct vomng_frame *frame; | |
474 | |
475 /* get time of last frame in ms | |
476 (intensive testing showed that the time obtained from vo_pts | |
477 is the time of the previous frame) */ | |
478 last_ms = (unsigned int)(vo_pts / 90.0 + 0.5); | |
479 | |
480 /* set time of last frame */ | |
481 if (vomng.frame_last) | |
482 vomng.frame_last->time_ms = last_ms; | |
483 | |
484 /* create new frame */ | |
485 frame = calloc(1, sizeof(*frame)); | |
486 if (!frame) { | |
487 mp_msg(MSGT_VO, MSGL_ERR, "vomng: out of memory\n"); | |
488 return; | |
489 } | |
490 /* time of frame is not yet known (see comment about vo_pts about 20 | |
491 lines above), approximate time using time of last frame and the | |
492 default frame delay */ | |
493 frame->time_ms = last_ms + VOMNG_DEFAULT_DELAY_MS; | |
494 frame->next = NULL; | |
495 | |
496 /* compress canvas data */ | |
497 vomng_canvas_to_compressed(vomng.width, vomng.height, vomng.canvas, | |
498 &frame->data, &frame->len); | |
499 if (!frame->data) { | |
500 mp_msg(MSGT_VO, MSGL_ERR, "vomng: compressing frame failed\n"); | |
501 free(frame); | |
502 return; | |
503 } | |
504 | |
505 /* add frame to list */ | |
506 if (!vomng.frame_first || !vomng.frame_last) { | |
507 vomng.frame_first = frame; | |
508 vomng.frame_last = frame; | |
509 } else { | |
510 vomng.frame_last->next = frame; | |
511 vomng.frame_last = frame; | |
512 } | |
513 } | |
514 | |
515 /** | |
516 * @brief put frame data onto canvas (not supported) | |
517 * @return always 1 to indicate error | |
518 */ | |
519 static int draw_frame(uint8_t *src[]) | |
520 { | |
521 /* draw_frame() not supported | |
522 * VFCAP_ACCEPT_STRIDE is set for format | |
523 * so draw_slice() will be called instead of this function */ | |
524 return 1; | |
525 } | |
526 | |
527 /** | |
528 * @brief deinitialize MNG vo module | |
529 */ | |
530 static void uninit(void) | |
531 { | |
532 vomng_prop_cleanup(); | |
533 } | |
534 | |
535 /** | |
536 * @brief deal with events (not supported) | |
537 */ | |
538 static void check_events(void) | |
539 { | |
540 } | |
541 | |
542 /** | |
543 * @brief put a slice of frame data onto canvas | |
544 * @param[in] srcimg pointer to data | |
545 * @param[in] stride line stride in data | |
546 * @param[in] wf frame slice width | |
547 * @param[in] hf frame slice height | |
548 * @param[in] xf leftmost x coordinate of frame slice | |
549 * @param[in] yf topmost y coordinate of frame slice | |
550 * @return always 0 to indicate success | |
551 */ | |
552 static int draw_slice(uint8_t *srcimg[], int stride[], | |
553 int wf, int hf, int xf, int yf) | |
554 { | |
555 uint8_t *line_ptr; | |
556 int line_len, row_stride, y; | |
557 | |
558 /* put pixel data from slice to canvas */ | |
559 line_ptr = srcimg[0]; | |
560 line_len = stride[0]; | |
561 row_stride = 1 + vomng.width * 3; /* rows contain filter IDs */ | |
562 for (y = 0; y < hf; y++) | |
563 memcpy(vomng.canvas + (yf + y) * row_stride + 1 + xf * 3, | |
564 line_ptr + y * line_len, wf * 3); | |
565 | |
566 return 0; | |
567 } | |
568 | |
569 /** | |
570 * @brief pre-initialize MNG vo module | |
571 * @param[in] *arg arguments passed to MNG vo module (output file name) | |
572 * @return 0 on success, 1 on error | |
573 */ | |
574 static int preinit(const char *arg) | |
575 { | |
576 /* get name of output file */ | |
577 if (!arg || !*arg) { | |
578 mp_msg(MSGT_VO, MSGL_ERR, "vomng: MNG output file must be given," | |
579 " example: -vo mng:output.mng\n"); | |
580 vomng_prop_cleanup(); | |
581 return 1; | |
582 } | |
583 vomng.out_file_name = strdup(arg); | |
584 if (!vomng.out_file_name) { | |
585 mp_msg(MSGT_VO, MSGL_ERR, "vomng: out of memory\n"); | |
586 vomng_prop_cleanup(); | |
587 return 1; | |
588 } | |
589 | |
590 return 0; | |
591 } | |
592 | |
593 /** | |
594 * @brief get supported formats | |
595 * @param[in] format format to check support for | |
596 * @return acceptance flags | |
597 */ | |
598 static int query_format(uint32_t format) | |
599 { | |
600 if (format == IMGFMT_RGB24) | |
601 return VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | | |
602 VFCAP_ACCEPT_STRIDE; | |
603 return 0; | |
604 } | |
605 | |
606 /** | |
607 * @brief handle control stuff | |
608 * @param[in] request control request | |
609 * @param[in] *data data (dependent on control request) | |
610 * @return response to control request | |
611 */ | |
612 static int control(uint32_t request, void *data) | |
613 { | |
614 switch (request) { | |
615 case VOCTRL_QUERY_FORMAT: | |
616 return query_format(*((uint32_t *)data)); | |
617 } | |
618 return VO_NOTIMPL; | |
619 } | |
620 |