Mercurial > libavformat.hg
annotate swf.c @ 357:f4f573c7dc56 libavformat
Patch for MPEG-2 VOB headers by (Jimmy Blair <blueskyjb at verizon dot net>)
author | michael |
---|---|
date | Sun, 01 Feb 2004 13:06:46 +0000 |
parents | e1d4300bf783 |
children | e9232aa21976 |
rev | line source |
---|---|
0 | 1 /* |
2 * Flash Compatible Streaming Format | |
3 * Copyright (c) 2000 Fabrice Bellard. | |
4 * | |
5 * This library is free software; you can redistribute it and/or | |
6 * modify it under the terms of the GNU Lesser General Public | |
7 * License as published by the Free Software Foundation; either | |
8 * version 2 of the License, or (at your option) any later version. | |
9 * | |
10 * This library is distributed in the hope that it will be useful, | |
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 * Lesser General Public License for more details. | |
14 * | |
15 * You should have received a copy of the GNU Lesser General Public | |
16 * License along with this library; if not, write to the Free Software | |
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
18 */ | |
19 #include "avformat.h" | |
20 | |
21 /* should have a generic way to indicate probable size */ | |
22 #define DUMMY_FILE_SIZE (100 * 1024 * 1024) | |
23 #define DUMMY_DURATION 600 /* in seconds */ | |
24 | |
25 #define TAG_END 0 | |
26 #define TAG_SHOWFRAME 1 | |
27 #define TAG_DEFINESHAPE 2 | |
28 #define TAG_FREECHARACTER 3 | |
29 #define TAG_PLACEOBJECT 4 | |
30 #define TAG_REMOVEOBJECT 5 | |
31 #define TAG_STREAMHEAD 18 | |
32 #define TAG_STREAMBLOCK 19 | |
33 #define TAG_JPEG2 21 | |
34 | |
35 #define TAG_LONG 0x100 | |
36 | |
37 /* flags for shape definition */ | |
38 #define FLAG_MOVETO 0x01 | |
39 #define FLAG_SETFILL0 0x02 | |
40 #define FLAG_SETFILL1 0x04 | |
41 | |
42 /* character id used */ | |
43 #define BITMAP_ID 0 | |
44 #define SHAPE_ID 1 | |
45 | |
46 typedef struct { | |
47 offset_t duration_pos; | |
48 offset_t tag_pos; | |
49 int tag; | |
50 } SWFContext; | |
51 | |
277
a313e1080322
disable encoders where appropriate (patch courtesy of BERO
melanson
parents:
276
diff
changeset
|
52 #ifdef CONFIG_ENCODERS |
0 | 53 static void put_swf_tag(AVFormatContext *s, int tag) |
54 { | |
55 SWFContext *swf = s->priv_data; | |
56 ByteIOContext *pb = &s->pb; | |
57 | |
58 swf->tag_pos = url_ftell(pb); | |
59 swf->tag = tag; | |
60 /* reserve some room for the tag */ | |
61 if (tag & TAG_LONG) { | |
62 put_le16(pb, 0); | |
63 put_le32(pb, 0); | |
64 } else { | |
65 put_le16(pb, 0); | |
66 } | |
67 } | |
68 | |
69 static void put_swf_end_tag(AVFormatContext *s) | |
70 { | |
71 SWFContext *swf = s->priv_data; | |
72 ByteIOContext *pb = &s->pb; | |
73 offset_t pos; | |
74 int tag_len, tag; | |
75 | |
76 pos = url_ftell(pb); | |
77 tag_len = pos - swf->tag_pos - 2; | |
78 tag = swf->tag; | |
79 url_fseek(pb, swf->tag_pos, SEEK_SET); | |
80 if (tag & TAG_LONG) { | |
81 tag &= ~TAG_LONG; | |
82 put_le16(pb, (tag << 6) | 0x3f); | |
83 put_le32(pb, tag_len - 4); | |
84 } else { | |
85 assert(tag_len < 0x3f); | |
86 put_le16(pb, (tag << 6) | tag_len); | |
87 } | |
88 url_fseek(pb, pos, SEEK_SET); | |
89 } | |
90 | |
91 static inline void max_nbits(int *nbits_ptr, int val) | |
92 { | |
93 int n; | |
94 | |
95 if (val == 0) | |
96 return; | |
97 val = abs(val); | |
98 n = 1; | |
99 while (val != 0) { | |
100 n++; | |
101 val >>= 1; | |
102 } | |
103 if (n > *nbits_ptr) | |
104 *nbits_ptr = n; | |
105 } | |
106 | |
107 static void put_swf_rect(ByteIOContext *pb, | |
108 int xmin, int xmax, int ymin, int ymax) | |
109 { | |
110 PutBitContext p; | |
65 | 111 uint8_t buf[256]; |
0 | 112 int nbits, mask; |
113 | |
276 | 114 init_put_bits(&p, buf, sizeof(buf)); |
0 | 115 |
116 nbits = 0; | |
117 max_nbits(&nbits, xmin); | |
118 max_nbits(&nbits, xmax); | |
119 max_nbits(&nbits, ymin); | |
120 max_nbits(&nbits, ymax); | |
121 mask = (1 << nbits) - 1; | |
122 | |
123 /* rectangle info */ | |
124 put_bits(&p, 5, nbits); | |
125 put_bits(&p, nbits, xmin & mask); | |
126 put_bits(&p, nbits, xmax & mask); | |
127 put_bits(&p, nbits, ymin & mask); | |
128 put_bits(&p, nbits, ymax & mask); | |
129 | |
130 flush_put_bits(&p); | |
131 put_buffer(pb, buf, pbBufPtr(&p) - p.buf); | |
132 } | |
133 | |
134 static void put_swf_line_edge(PutBitContext *pb, int dx, int dy) | |
135 { | |
136 int nbits, mask; | |
137 | |
138 put_bits(pb, 1, 1); /* edge */ | |
139 put_bits(pb, 1, 1); /* line select */ | |
140 nbits = 2; | |
141 max_nbits(&nbits, dx); | |
142 max_nbits(&nbits, dy); | |
143 | |
144 mask = (1 << nbits) - 1; | |
145 put_bits(pb, 4, nbits - 2); /* 16 bits precision */ | |
146 if (dx == 0) { | |
147 put_bits(pb, 1, 0); | |
148 put_bits(pb, 1, 1); | |
149 put_bits(pb, nbits, dy & mask); | |
150 } else if (dy == 0) { | |
151 put_bits(pb, 1, 0); | |
152 put_bits(pb, 1, 0); | |
153 put_bits(pb, nbits, dx & mask); | |
154 } else { | |
155 put_bits(pb, 1, 1); | |
156 put_bits(pb, nbits, dx & mask); | |
157 put_bits(pb, nbits, dy & mask); | |
158 } | |
159 } | |
160 | |
161 #define FRAC_BITS 16 | |
162 | |
163 /* put matrix (not size optimized */ | |
164 static void put_swf_matrix(ByteIOContext *pb, | |
165 int a, int b, int c, int d, int tx, int ty) | |
166 { | |
167 PutBitContext p; | |
65 | 168 uint8_t buf[256]; |
0 | 169 |
276 | 170 init_put_bits(&p, buf, sizeof(buf)); |
0 | 171 |
172 put_bits(&p, 1, 1); /* a, d present */ | |
173 put_bits(&p, 5, 20); /* nb bits */ | |
174 put_bits(&p, 20, a); | |
175 put_bits(&p, 20, d); | |
176 | |
177 put_bits(&p, 1, 1); /* b, c present */ | |
178 put_bits(&p, 5, 20); /* nb bits */ | |
179 put_bits(&p, 20, c); | |
180 put_bits(&p, 20, b); | |
181 | |
182 put_bits(&p, 5, 20); /* nb bits */ | |
183 put_bits(&p, 20, tx); | |
184 put_bits(&p, 20, ty); | |
185 | |
186 flush_put_bits(&p); | |
187 put_buffer(pb, buf, pbBufPtr(&p) - p.buf); | |
188 } | |
189 | |
190 /* XXX: handle audio only */ | |
191 static int swf_write_header(AVFormatContext *s) | |
192 { | |
193 SWFContext *swf; | |
194 ByteIOContext *pb = &s->pb; | |
195 AVCodecContext *enc, *audio_enc, *video_enc; | |
196 PutBitContext p; | |
65 | 197 uint8_t buf1[256]; |
85
25062c9b1f86
per context frame_rate_base, this should finally fix frame_rate related av sync issues
michaelni
parents:
65
diff
changeset
|
198 int i, width, height, rate, rate_base; |
0 | 199 |
200 swf = av_malloc(sizeof(SWFContext)); | |
201 if (!swf) | |
202 return -1; | |
203 s->priv_data = swf; | |
204 | |
205 video_enc = NULL; | |
206 audio_enc = NULL; | |
207 for(i=0;i<s->nb_streams;i++) { | |
208 enc = &s->streams[i]->codec; | |
209 if (enc->codec_type == CODEC_TYPE_AUDIO) | |
210 audio_enc = enc; | |
211 else | |
212 video_enc = enc; | |
213 } | |
214 | |
215 if (!video_enc) { | |
216 /* currenty, cannot work correctly if audio only */ | |
217 width = 320; | |
218 height = 200; | |
85
25062c9b1f86
per context frame_rate_base, this should finally fix frame_rate related av sync issues
michaelni
parents:
65
diff
changeset
|
219 rate = 10; |
25062c9b1f86
per context frame_rate_base, this should finally fix frame_rate related av sync issues
michaelni
parents:
65
diff
changeset
|
220 rate_base= 1; |
0 | 221 } else { |
222 width = video_enc->width; | |
223 height = video_enc->height; | |
224 rate = video_enc->frame_rate; | |
85
25062c9b1f86
per context frame_rate_base, this should finally fix frame_rate related av sync issues
michaelni
parents:
65
diff
changeset
|
225 rate_base = video_enc->frame_rate_base; |
0 | 226 } |
227 | |
228 put_tag(pb, "FWS"); | |
229 put_byte(pb, 4); /* version (should use 4 for mpeg audio support) */ | |
230 put_le32(pb, DUMMY_FILE_SIZE); /* dummy size | |
231 (will be patched if not streamed) */ | |
232 | |
233 put_swf_rect(pb, 0, width, 0, height); | |
85
25062c9b1f86
per context frame_rate_base, this should finally fix frame_rate related av sync issues
michaelni
parents:
65
diff
changeset
|
234 put_le16(pb, (rate * 256) / rate_base); /* frame rate */ |
0 | 235 swf->duration_pos = url_ftell(pb); |
85
25062c9b1f86
per context frame_rate_base, this should finally fix frame_rate related av sync issues
michaelni
parents:
65
diff
changeset
|
236 put_le16(pb, (uint16_t)(DUMMY_DURATION * (int64_t)rate / rate_base)); /* frame count */ |
0 | 237 |
238 /* define a shape with the jpeg inside */ | |
239 | |
240 put_swf_tag(s, TAG_DEFINESHAPE); | |
241 | |
242 put_le16(pb, SHAPE_ID); /* ID of shape */ | |
243 /* bounding rectangle */ | |
244 put_swf_rect(pb, 0, width, 0, height); | |
245 /* style info */ | |
246 put_byte(pb, 1); /* one fill style */ | |
247 put_byte(pb, 0x41); /* clipped bitmap fill */ | |
248 put_le16(pb, BITMAP_ID); /* bitmap ID */ | |
249 /* position of the bitmap */ | |
250 put_swf_matrix(pb, (int)(1.0 * (1 << FRAC_BITS)), 0, | |
251 0, (int)(1.0 * (1 << FRAC_BITS)), 0, 0); | |
252 put_byte(pb, 0); /* no line style */ | |
253 | |
254 /* shape drawing */ | |
276 | 255 init_put_bits(&p, buf1, sizeof(buf1)); |
0 | 256 put_bits(&p, 4, 1); /* one fill bit */ |
257 put_bits(&p, 4, 0); /* zero line bit */ | |
258 | |
259 put_bits(&p, 1, 0); /* not an edge */ | |
260 put_bits(&p, 5, FLAG_MOVETO | FLAG_SETFILL0); | |
261 put_bits(&p, 5, 1); /* nbits */ | |
262 put_bits(&p, 1, 0); /* X */ | |
263 put_bits(&p, 1, 0); /* Y */ | |
264 put_bits(&p, 1, 1); /* set fill style 1 */ | |
265 | |
266 /* draw the rectangle ! */ | |
267 put_swf_line_edge(&p, width, 0); | |
268 put_swf_line_edge(&p, 0, height); | |
269 put_swf_line_edge(&p, -width, 0); | |
270 put_swf_line_edge(&p, 0, -height); | |
271 | |
272 /* end of shape */ | |
273 put_bits(&p, 1, 0); /* not an edge */ | |
274 put_bits(&p, 5, 0); | |
275 | |
276 flush_put_bits(&p); | |
277 put_buffer(pb, buf1, pbBufPtr(&p) - p.buf); | |
278 | |
279 put_swf_end_tag(s); | |
280 | |
281 | |
282 if (audio_enc) { | |
283 int v; | |
284 | |
285 /* start sound */ | |
286 | |
287 v = 0; | |
288 switch(audio_enc->sample_rate) { | |
289 case 11025: | |
290 v |= 1 << 2; | |
291 break; | |
292 case 22050: | |
293 v |= 2 << 2; | |
294 break; | |
295 case 44100: | |
296 v |= 3 << 2; | |
297 break; | |
298 default: | |
299 /* not supported */ | |
300 av_free(swf); | |
301 return -1; | |
302 } | |
303 if (audio_enc->channels == 2) | |
304 v |= 1; | |
305 v |= 0x20; /* mp3 compressed */ | |
306 v |= 0x02; /* 16 bits */ | |
307 | |
308 put_swf_tag(s, TAG_STREAMHEAD); | |
309 put_byte(&s->pb, 0); | |
310 put_byte(&s->pb, v); | |
85
25062c9b1f86
per context frame_rate_base, this should finally fix frame_rate related av sync issues
michaelni
parents:
65
diff
changeset
|
311 put_le16(&s->pb, (audio_enc->sample_rate * rate_base) / rate); /* avg samples per frame */ |
0 | 312 |
313 | |
314 put_swf_end_tag(s); | |
315 } | |
316 | |
317 put_flush_packet(&s->pb); | |
318 return 0; | |
319 } | |
320 | |
321 static int swf_write_video(AVFormatContext *s, | |
241 | 322 AVCodecContext *enc, const uint8_t *buf, int size) |
0 | 323 { |
324 ByteIOContext *pb = &s->pb; | |
325 static int tag_id = 0; | |
326 | |
327 if (enc->frame_number > 1) { | |
328 /* remove the shape */ | |
329 put_swf_tag(s, TAG_REMOVEOBJECT); | |
330 put_le16(pb, SHAPE_ID); /* shape ID */ | |
331 put_le16(pb, 1); /* depth */ | |
332 put_swf_end_tag(s); | |
333 | |
334 /* free the bitmap */ | |
335 put_swf_tag(s, TAG_FREECHARACTER); | |
336 put_le16(pb, BITMAP_ID); | |
337 put_swf_end_tag(s); | |
338 } | |
339 | |
340 put_swf_tag(s, TAG_JPEG2 | TAG_LONG); | |
341 | |
342 put_le16(pb, tag_id); /* ID of the image */ | |
343 | |
344 /* a dummy jpeg header seems to be required */ | |
345 put_byte(pb, 0xff); | |
346 put_byte(pb, 0xd8); | |
347 put_byte(pb, 0xff); | |
348 put_byte(pb, 0xd9); | |
349 /* write the jpeg image */ | |
350 put_buffer(pb, buf, size); | |
351 | |
352 put_swf_end_tag(s); | |
353 | |
354 /* draw the shape */ | |
355 | |
356 put_swf_tag(s, TAG_PLACEOBJECT); | |
357 put_le16(pb, SHAPE_ID); /* shape ID */ | |
358 put_le16(pb, 1); /* depth */ | |
359 put_swf_matrix(pb, 1 << FRAC_BITS, 0, 0, 1 << FRAC_BITS, 0, 0); | |
360 put_swf_end_tag(s); | |
361 | |
362 /* output the frame */ | |
363 put_swf_tag(s, TAG_SHOWFRAME); | |
364 put_swf_end_tag(s); | |
365 | |
366 put_flush_packet(&s->pb); | |
367 return 0; | |
368 } | |
369 | |
241 | 370 static int swf_write_audio(AVFormatContext *s, const uint8_t *buf, int size) |
0 | 371 { |
372 ByteIOContext *pb = &s->pb; | |
373 | |
374 put_swf_tag(s, TAG_STREAMBLOCK | TAG_LONG); | |
375 | |
376 put_buffer(pb, buf, size); | |
377 | |
378 put_swf_end_tag(s); | |
379 put_flush_packet(&s->pb); | |
380 return 0; | |
381 } | |
382 | |
383 static int swf_write_packet(AVFormatContext *s, int stream_index, | |
241 | 384 const uint8_t *buf, int size, int64_t pts) |
0 | 385 { |
386 AVCodecContext *codec = &s->streams[stream_index]->codec; | |
387 if (codec->codec_type == CODEC_TYPE_AUDIO) | |
388 return swf_write_audio(s, buf, size); | |
389 else | |
390 return swf_write_video(s, codec, buf, size); | |
391 } | |
392 | |
393 static int swf_write_trailer(AVFormatContext *s) | |
394 { | |
395 SWFContext *swf = s->priv_data; | |
396 ByteIOContext *pb = &s->pb; | |
397 AVCodecContext *enc, *video_enc; | |
398 int file_size, i; | |
399 | |
400 video_enc = NULL; | |
401 for(i=0;i<s->nb_streams;i++) { | |
402 enc = &s->streams[i]->codec; | |
403 if (enc->codec_type == CODEC_TYPE_VIDEO) | |
404 video_enc = enc; | |
405 } | |
406 | |
407 put_swf_tag(s, TAG_END); | |
408 put_swf_end_tag(s); | |
409 | |
410 put_flush_packet(&s->pb); | |
411 | |
412 /* patch file size and number of frames if not streamed */ | |
413 if (!url_is_streamed(&s->pb) && video_enc) { | |
414 file_size = url_ftell(pb); | |
415 url_fseek(pb, 4, SEEK_SET); | |
416 put_le32(pb, file_size); | |
417 url_fseek(pb, swf->duration_pos, SEEK_SET); | |
418 put_le16(pb, video_enc->frame_number); | |
419 } | |
420 return 0; | |
421 } | |
277
a313e1080322
disable encoders where appropriate (patch courtesy of BERO
melanson
parents:
276
diff
changeset
|
422 #endif //CONFIG_ENCODERS |
0 | 423 |
424 /***********************************/ | |
425 /* just to extract MP3 from swf */ | |
426 | |
427 static int get_swf_tag(ByteIOContext *pb, int *len_ptr) | |
428 { | |
429 int tag, len; | |
430 | |
431 if (url_feof(pb)) | |
432 return -1; | |
433 | |
434 tag = get_le16(pb); | |
435 len = tag & 0x3f; | |
436 tag = tag >> 6; | |
437 if (len == 0x3f) { | |
438 len = get_le32(pb); | |
439 } | |
440 *len_ptr = len; | |
441 return tag; | |
442 } | |
443 | |
444 | |
445 static int swf_probe(AVProbeData *p) | |
446 { | |
447 /* check file header */ | |
448 if (p->buf_size <= 16) | |
449 return 0; | |
450 if (p->buf[0] == 'F' && p->buf[1] == 'W' && | |
451 p->buf[2] == 'S') | |
452 return AVPROBE_SCORE_MAX; | |
453 else | |
454 return 0; | |
455 } | |
456 | |
457 static int swf_read_header(AVFormatContext *s, AVFormatParameters *ap) | |
458 { | |
459 ByteIOContext *pb = &s->pb; | |
460 int nbits, len, frame_rate, tag, v; | |
461 AVStream *st; | |
462 | |
463 if ((get_be32(pb) & 0xffffff00) != MKBETAG('F', 'W', 'S', 0)) | |
464 return -EIO; | |
465 get_le32(pb); | |
466 /* skip rectangle size */ | |
467 nbits = get_byte(pb) >> 3; | |
468 len = (4 * nbits - 3 + 7) / 8; | |
469 url_fskip(pb, len); | |
470 frame_rate = get_le16(pb); | |
471 get_le16(pb); /* frame count */ | |
472 | |
473 for(;;) { | |
474 tag = get_swf_tag(pb, &len); | |
475 if (tag < 0) { | |
476 fprintf(stderr, "No streaming found in SWF\n"); | |
477 return -EIO; | |
478 } | |
479 if (tag == TAG_STREAMHEAD) { | |
480 /* streaming found */ | |
481 get_byte(pb); | |
482 v = get_byte(pb); | |
483 get_le16(pb); | |
325
e1d4300bf783
SWF sanity check patch by (Glyn Kennington <glyn dot kennington at ox dot compsoc dot net>)
michael
parents:
277
diff
changeset
|
484 if (len!=4) |
e1d4300bf783
SWF sanity check patch by (Glyn Kennington <glyn dot kennington at ox dot compsoc dot net>)
michael
parents:
277
diff
changeset
|
485 url_fskip(pb,len-4); |
0 | 486 /* if mp3 streaming found, OK */ |
487 if ((v & 0x20) != 0) { | |
187 | 488 st = av_new_stream(s, 0); |
0 | 489 if (!st) |
490 return -ENOMEM; | |
5 | 491 |
0 | 492 if (v & 0x01) |
493 st->codec.channels = 2; | |
494 else | |
495 st->codec.channels = 1; | |
496 | |
497 switch((v>> 2) & 0x03) { | |
498 case 1: | |
499 st->codec.sample_rate = 11025; | |
500 break; | |
501 case 2: | |
502 st->codec.sample_rate = 22050; | |
503 break; | |
504 case 3: | |
505 st->codec.sample_rate = 44100; | |
506 break; | |
507 default: | |
508 av_free(st); | |
509 return -EIO; | |
510 } | |
511 st->codec.codec_type = CODEC_TYPE_AUDIO; | |
512 st->codec.codec_id = CODEC_ID_MP2; | |
513 break; | |
514 } | |
515 } else { | |
516 url_fskip(pb, len); | |
517 } | |
518 } | |
519 | |
520 return 0; | |
521 } | |
522 | |
523 static int swf_read_packet(AVFormatContext *s, AVPacket *pkt) | |
524 { | |
525 ByteIOContext *pb = &s->pb; | |
526 int tag, len; | |
527 | |
528 for(;;) { | |
529 tag = get_swf_tag(pb, &len); | |
530 if (tag < 0) | |
531 return -EIO; | |
532 if (tag == TAG_STREAMBLOCK) { | |
533 av_new_packet(pkt, len); | |
534 get_buffer(pb, pkt->data, pkt->size); | |
535 break; | |
536 } else { | |
537 url_fskip(pb, len); | |
538 } | |
539 } | |
540 return 0; | |
541 } | |
542 | |
543 static int swf_read_close(AVFormatContext *s) | |
544 { | |
545 return 0; | |
546 } | |
547 | |
548 static AVInputFormat swf_iformat = { | |
549 "swf", | |
550 "Flash format", | |
551 0, | |
552 swf_probe, | |
553 swf_read_header, | |
554 swf_read_packet, | |
555 swf_read_close, | |
556 }; | |
557 | |
277
a313e1080322
disable encoders where appropriate (patch courtesy of BERO
melanson
parents:
276
diff
changeset
|
558 #ifdef CONFIG_ENCODERS |
0 | 559 static AVOutputFormat swf_oformat = { |
560 "swf", | |
561 "Flash format", | |
562 "application/x-shockwave-flash", | |
563 "swf", | |
564 sizeof(SWFContext), | |
565 CODEC_ID_MP2, | |
566 CODEC_ID_MJPEG, | |
567 swf_write_header, | |
568 swf_write_packet, | |
569 swf_write_trailer, | |
570 }; | |
277
a313e1080322
disable encoders where appropriate (patch courtesy of BERO
melanson
parents:
276
diff
changeset
|
571 #endif //CONFIG_ENCODERS |
0 | 572 |
573 int swf_init(void) | |
574 { | |
575 av_register_input_format(&swf_iformat); | |
277
a313e1080322
disable encoders where appropriate (patch courtesy of BERO
melanson
parents:
276
diff
changeset
|
576 #ifdef CONFIG_ENCODERS |
0 | 577 av_register_output_format(&swf_oformat); |
277
a313e1080322
disable encoders where appropriate (patch courtesy of BERO
melanson
parents:
276
diff
changeset
|
578 #endif //CONFIG_ENCODERS |
0 | 579 return 0; |
580 } |