Mercurial > audlegacy-plugins
comparison src/madplug/plugin.c @ 2688:17da667fb14d
A completely rewritten approach for probing MPEG Audio files, using a
state-machine based validifier with semi-smart buffer management and
re-synchronization.
| author | Matti Hamalainen <ccr@tnsp.org> |
|---|---|
| date | Fri, 06 Jun 2008 13:40:13 +0300 |
| parents | 206f0322d221 |
| children | c56305e38520 |
comparison
equal
deleted
inserted
replaced
| 2687:206f0322d221 | 2688:17da667fb14d |
|---|---|
| 49 | 49 |
| 50 #ifndef NOGUI | 50 #ifndef NOGUI |
| 51 static GtkWidget *error_dialog = 0; | 51 static GtkWidget *error_dialog = 0; |
| 52 #endif | 52 #endif |
| 53 | 53 |
| 54 extern gboolean scan_file(struct mad_info_t *info, gboolean fast); | |
| 55 | |
| 56 static gint mp3_bitrate_table[5][16] = { | 54 static gint mp3_bitrate_table[5][16] = { |
| 57 { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1 }, /* MPEG1 L1 */ | 55 { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1 }, /* MPEG1 L1 */ |
| 58 { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, /* MPEG1 L2 */ | 56 { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, /* MPEG1 L2 */ |
| 59 { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 }, /* MPEG1 L3 */ | 57 { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 }, /* MPEG1 L3 */ |
| 60 { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 }, /* MPEG2(.5) L1 */ | 58 { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 }, /* MPEG2(.5) L1 */ |
| 65 { 11025, 12000, 8000, -1 }, /* MPEG2.5 */ | 63 { 11025, 12000, 8000, -1 }, /* MPEG2.5 */ |
| 66 { -1, -1, -1, -1 }, /* Reserved */ | 64 { -1, -1, -1, -1 }, /* Reserved */ |
| 67 { 22050, 24000, 16000, -1 }, /* MPEG2 */ | 65 { 22050, 24000, 16000, -1 }, /* MPEG2 */ |
| 68 { 44100, 48000, 32000, -1 } /* MPEG1 */ | 66 { 44100, 48000, 32000, -1 } /* MPEG1 */ |
| 69 }; | 67 }; |
| 68 | |
| 69 typedef struct { | |
| 70 gint version, | |
| 71 layer, | |
| 72 bitRate, | |
| 73 sampleRate, | |
| 74 size, | |
| 75 lsf; | |
| 76 gboolean hasCRC; | |
| 77 } mp3_frame_t; | |
| 70 | 78 |
| 71 /* | 79 /* |
| 72 * Function extname (filename) | 80 * Function extname (filename) |
| 73 * | 81 * |
| 74 * Return pointer within filename to its extenstion, or NULL if | 82 * Return pointer within filename to its extenstion, or NULL if |
| 149 g_cond_free(mad_cond); | 157 g_cond_free(mad_cond); |
| 150 g_mutex_free(mad_mutex); | 158 g_mutex_free(mad_mutex); |
| 151 g_mutex_free(pb_mutex); | 159 g_mutex_free(pb_mutex); |
| 152 } | 160 } |
| 153 | 161 |
| 154 static gboolean | 162 /* Validate a MPEG Audio header and extract some information from it. |
| 155 mp3_head_check(guint32 head, gint *frameSize) | 163 * References used: |
| 156 { | 164 * http://www.mp3-tech.org/programmer/frame_header.html |
| 157 gint version, layer, bitIndex, bitRate, sampleIndex, sampleRate, padding; | 165 * http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm |
| 158 | 166 */ |
| 159 /* http://www.mp3-tech.org/programmer/frame_header.html | 167 static gint |
| 160 * Bits 21-31 must be set (frame sync) | 168 mp3_head_validate(guint32 head, mp3_frame_t *frame) |
| 161 */ | 169 { |
| 170 gint bitIndex, sampleIndex, padding; | |
| 171 | |
| 172 /* bits 21-31 must be set (frame sync) */ | |
| 162 if ((head & 0xffe00000) != 0xffe00000) | 173 if ((head & 0xffe00000) != 0xffe00000) |
| 163 return FALSE; | 174 return -1; |
| 164 | 175 |
| 176 /* check for LSF */ | |
| 177 if ((head >> 20) & 1) | |
| 178 frame->lsf = ((head >> 19) & 1) ? 0 : 1; | |
| 179 else | |
| 180 frame->lsf = 1; | |
| 181 | |
| 165 /* check if layer bits (17-18) are good */ | 182 /* check if layer bits (17-18) are good */ |
| 166 layer = (head >> 17) & 0x3; | 183 frame->layer = (head >> 17) & 3; |
| 167 if (!layer) | 184 if (frame->layer == 0) |
| 168 return FALSE; /* 00 = reserved */ | 185 return -2; /* 00 = reserved */ |
| 169 layer = 4 - layer; | 186 frame->layer = 4 - frame->layer; |
| 170 | 187 |
| 188 /* check CRC bit. if set, a 16-bit CRC follows header (not counted in frameSize!) */ | |
| 189 frame->hasCRC = (head >> 16) & 1; | |
| 190 | |
| 171 /* check if bitrate index bits (12-15) are acceptable */ | 191 /* check if bitrate index bits (12-15) are acceptable */ |
| 172 bitIndex = (head >> 12) & 0xf; | 192 bitIndex = (head >> 12) & 0xf; |
| 173 | 193 |
| 174 /* 1111 and 0000 are reserved values for all layers */ | 194 /* 1111 and 0000 are reserved values for all layers */ |
| 175 if (bitIndex == 0xf || bitIndex == 0) | 195 if (bitIndex == 0xf || bitIndex == 0) |
| 176 return FALSE; | 196 return -3; |
| 177 | 197 |
| 178 /* check samplerate index bits (10-11) */ | 198 /* check samplerate index bits (10-11) */ |
| 179 sampleIndex = (head >> 10) & 0x3; | 199 sampleIndex = (head >> 10) & 3; |
| 180 if (sampleIndex == 0x3) | 200 if (sampleIndex == 3) |
| 181 return FALSE; | 201 return -4; |
| 182 | 202 |
| 183 /* check version bits (19-20) and get bitRate */ | 203 /* check version bits (19-20) and get bitRate */ |
| 184 version = (head >> 19) & 0x03; | 204 frame->version = (head >> 19) & 0x03; |
| 185 switch (version) { | 205 switch (frame->version) { |
| 186 case 0: /* 00 = MPEG Version 2.5 */ | 206 case 0: /* 00 = MPEG Version 2.5 */ |
| 187 case 2: /* 10 = MPEG Version 2 */ | 207 case 2: /* 10 = MPEG Version 2 */ |
| 188 if (layer == 1) | 208 if (frame->layer == 1) |
| 189 bitRate = mp3_bitrate_table[3][bitIndex]; | 209 frame->bitRate = mp3_bitrate_table[3][bitIndex]; |
| 190 else | 210 else |
| 191 bitRate = mp3_bitrate_table[4][bitIndex]; | 211 frame->bitRate = mp3_bitrate_table[4][bitIndex]; |
| 192 break; | 212 break; |
| 193 | 213 |
| 194 case 1: /* 01 = reserved */ | 214 case 1: /* 01 = reserved */ |
| 195 return FALSE; | 215 return -5; |
| 196 | 216 |
| 197 case 3: /* 11 = MPEG Version 1 */ | 217 case 3: /* 11 = MPEG Version 1 */ |
| 198 bitRate = mp3_bitrate_table[layer][bitIndex]; | 218 frame->bitRate = mp3_bitrate_table[frame->layer - 1][bitIndex]; |
| 199 break; | 219 break; |
| 200 | 220 |
| 201 default: | 221 default: |
| 202 return FALSE; | 222 return -6; |
| 203 } | 223 } |
| 224 | |
| 225 if (frame->bitRate < 0) | |
| 226 return -7; | |
| 204 | 227 |
| 205 /* check layer II restrictions vs. bitrate */ | 228 /* check layer II restrictions vs. bitrate */ |
| 206 if (layer == 2) { | 229 if (frame->layer == 2) { |
| 207 gint chanMode = (head >> 6) & 0x3; | 230 gint chanMode = (head >> 6) & 0x3; |
| 208 | 231 |
| 209 if (chanMode == 0x3) { | 232 if (chanMode == 0x3) { |
| 210 /* single channel with bitrate > 192 */ | 233 /* single channel with bitrate > 192 */ |
| 211 if (bitRate > 192) | 234 if (frame->bitRate > 192) |
| 212 return FALSE; | 235 return -8; |
| 213 } else { | 236 } else { |
| 214 /* any other mode with bitrates 32-56 and 80. | 237 /* any other mode with bitrates 32-56 and 80. |
| 215 * NOTICE! this check is not entirely correct, but I think | 238 * NOTICE! this check is not entirely correct, but I think |
| 216 * it is sufficient in most cases. | 239 * it is sufficient in most cases. |
| 217 */ | 240 */ |
| 218 if (((bitRate >= 32 && bitRate <= 56) || bitRate == 80)) | 241 if (((frame->bitRate >= 32 && frame->bitRate <= 56) || frame->bitRate == 80)) |
| 219 return FALSE; | 242 return -9; |
| 220 } | 243 } |
| 221 } | 244 } |
| 222 | 245 |
| 223 /* calculate approx. frame size */ | 246 /* calculate approx. frame size */ |
| 224 padding = (head >> 9) & 1; | 247 padding = (head >> 9) & 1; |
| 225 sampleRate = mp3_samplerate_table[version][sampleIndex]; | 248 frame->sampleRate = mp3_samplerate_table[frame->version][sampleIndex]; |
| 226 if (layer == 1) | 249 if (frame->sampleRate < 0) |
| 227 *frameSize = ((12 * bitRate * 1000 / sampleRate) + padding) * 4; | 250 return -10; |
| 228 else | 251 |
| 229 *frameSize = (144 * bitRate * 1000) / (sampleRate + padding); | 252 switch (frame->layer) { |
| 230 | 253 case 1: |
| 231 /* check if bits 16 - 19 are all set (MPEG 1 Layer I, not protected?) */ | 254 frame->size = ((12 * 1000 * frame->bitRate) / frame->sampleRate + padding) * 4; |
| 232 if (((head >> 19) & 1) == 1 && | 255 break; |
| 233 ((head >> 17) & 3) == 3 && ((head >> 16) & 1) == 1) | 256 |
| 234 return FALSE; | 257 case 2: |
| 235 | 258 frame->size = (144 * 1000 * frame->bitRate) / frame->sampleRate + padding; |
| 236 /* not sure why we check this, but ok! */ | 259 break; |
| 237 if ((head & 0xffff0000) == 0xfffe0000) | 260 |
| 238 return FALSE; | 261 case 3: |
| 239 | 262 default: |
| 240 return TRUE; | 263 frame->size = (144 * 1000 * frame->bitRate) / (frame->sampleRate << frame->lsf) + padding; |
| 264 break; | |
| 265 } | |
| 266 | |
| 267 return 0; | |
| 241 } | 268 } |
| 242 | 269 |
| 243 static int | 270 static int |
| 244 mp3_head_convert(const guchar * hbuf) | 271 mp3_head_convert(const guchar * hbuf) |
| 245 { | 272 { |
| 246 return ((unsigned long) hbuf[0] << 24) | | 273 return |
| 247 ((unsigned long) hbuf[1] << 16) | | 274 ((guint32) hbuf[0] << 24) | |
| 248 ((unsigned long) hbuf[2] << 8) | (unsigned long) hbuf[3]; | 275 ((guint32) hbuf[1] << 16) | |
| 249 } | 276 ((guint32) hbuf[2] << 8) | |
| 277 ((guint32) hbuf[3]); | |
| 278 } | |
| 279 | |
| 280 #if 0 | |
| 281 static gchar *mp3_ver_table[4] = { "2.5", "INVALID", "2", "1" }; | |
| 282 #define LULZ(...) do { fprintf(stderr, "madprobe: "); fprintf(stderr, __VA_ARGS__); } while (0) | |
| 283 #define LOL(...) do { fprintf(stderr, __VA_ARGS__); } while (0) | |
| 284 #else | |
| 285 #define LULZ(...) do { } while(0) | |
| 286 #define LOL(...) do { } while(0) | |
| 287 #endif | |
| 250 | 288 |
| 251 // audacious vfs fast version | 289 // audacious vfs fast version |
| 252 static int | 290 static int |
| 253 audmad_is_our_fd(char *filename, VFSFile *fin) | 291 audmad_is_our_fd(gchar *filename, VFSFile *fin) |
| 254 { | 292 { |
| 255 guint32 check; | |
| 256 gchar *ext = extname(filename); | 293 gchar *ext = extname(filename); |
| 257 guchar buf[16]; | 294 guint32 head = 0; |
| 295 guchar chkbuf[2048]; | |
| 296 gint state, | |
| 297 next = -1, | |
| 298 tries = 0, | |
| 299 chksize = 0, | |
| 300 chkpos = 0, | |
| 301 chkcount = 0, | |
| 302 res, resync_max = -1, | |
| 303 skip = 0; | |
| 304 glong streampos = 0; | |
| 305 mp3_frame_t frame, prev; | |
| 306 | |
| 307 enum { | |
| 308 STATE_HEADERS, | |
| 309 STATE_REBUFFER, | |
| 310 STATE_VALIDATE, | |
| 311 STATE_GOTO_NEXT, | |
| 312 STATE_GET_NEXT, | |
| 313 STATE_RESYNC, | |
| 314 STATE_RESYNC_DO, | |
| 315 STATE_FATAL | |
| 316 }; | |
| 258 | 317 |
| 259 info.remote = aud_vfs_is_remote(filename); | 318 info.remote = aud_vfs_is_remote(filename); |
| 260 | 319 |
| 261 /* I've seen some flac files beginning with id3 frames.. | 320 /* I've seen some flac files beginning with id3 frames.. |
| 262 so let's exclude known non-mp3 filename extensions */ | 321 so let's exclude known non-mp3 filename extensions */ |
| 270 if (fin == NULL) { | 329 if (fin == NULL) { |
| 271 g_message("fin = NULL for %s", filename); | 330 g_message("fin = NULL for %s", filename); |
| 272 return 0; | 331 return 0; |
| 273 } | 332 } |
| 274 | 333 |
| 275 if (aud_vfs_fread(buf, 1, sizeof(buf), fin) == 0) { | 334 state = STATE_REBUFFER; |
| 276 g_message("aud_vfs_fread failed @1 %s", filename); | 335 next = STATE_HEADERS; |
| 277 return 0; | 336 |
| 278 } | 337 /* Check stream data for frame header(s). We employ a simple |
| 279 | 338 * state-machine approach here to find number of sequential |
| 280 check = mp3_head_convert(buf); | 339 * valid MPEG frame headers (with similar attributes). |
| 281 | 340 */ |
| 282 if (memcmp(buf, "ID3", 3) == 0) | 341 do { |
| 283 return 1; | 342 switch (state) { |
| 284 else if (memcmp(buf, "OggS", 4) == 0) | 343 case STATE_HEADERS: |
| 285 return 0; | 344 LULZ("check headers\n"); |
| 286 else if (memcmp(buf, "RIFF", 4) == 0) | 345 /* Check read size */ |
| 287 { | 346 if (chksize < 32) { |
| 288 aud_vfs_fseek(fin, 4, SEEK_CUR); | 347 LULZ("headers check failed, not enough data!\n"); |
| 289 if(aud_vfs_fread(buf, 1, 4, fin) == 0) { | 348 state = STATE_FATAL; |
| 290 g_message("aud_vfs_fread failed @2 %s", filename); | 349 } else { |
| 291 return 0; | 350 state = STATE_GET_NEXT; |
| 351 | |
| 352 if (memcmp(&chkbuf[chkpos], "ID3", 3) == 0) { | |
| 353 /* Skip ID3 header */ | |
| 354 guint tagsize = (chkbuf[chkpos+6] & 0x7f); tagsize <<= 7; | |
| 355 tagsize |= (chkbuf[chkpos+7] & 0x7f); tagsize <<= 7; | |
| 356 tagsize |= (chkbuf[chkpos+8] & 0x7f); tagsize <<= 7; | |
| 357 tagsize |= (chkbuf[chkpos+9] & 0x7f); | |
| 358 | |
| 359 LULZ("ID3 size = %08x\n", tagsize); | |
| 360 state = STATE_GOTO_NEXT; | |
| 361 skip = tagsize + 10; | |
| 362 } else | |
| 363 if (memcmp(&chkbuf[chkpos], "OggS", 4) == 0) | |
| 364 return 0; | |
| 365 else | |
| 366 if (memcmp(&chkbuf[chkpos], "RIFF", 4) == 0 && | |
| 367 memcmp(&chkbuf[chkpos+8], "RMP3", 4) == 0) | |
| 368 return 1; | |
| 369 } | |
| 370 break; | |
| 371 | |
| 372 case STATE_REBUFFER: | |
| 373 streampos = aud_vfs_ftell(fin); | |
| 374 if ((chksize = aud_vfs_fread(chkbuf, 1, sizeof(chkbuf), fin)) == 0) | |
| 375 state = STATE_FATAL; | |
| 376 else { | |
| 377 chkpos = 0; | |
| 378 state = next; | |
| 379 } | |
| 380 LULZ("rebuffered = %d bytes\n", chksize); | |
| 381 break; | |
| 382 | |
| 383 case STATE_VALIDATE: | |
| 384 LULZ("validate %08x .. ", head); | |
| 385 /* Check for valid header */ | |
| 386 if ((res = mp3_head_validate(head, &frame)) >= 0) { | |
| 387 LOL("[is MPEG%s/layer %d, %dHz, %dkbps]", | |
| 388 mp3_ver_table[frame.version], frame.layer, frame.sampleRate, frame.bitRate); | |
| 389 state = STATE_GOTO_NEXT; | |
| 390 skip = frame.size; | |
| 391 chkcount++; | |
| 392 if (chkcount > 1) { | |
| 393 if (frame.sampleRate != prev.sampleRate || | |
| 394 frame.layer != prev.layer || | |
| 395 frame.version != prev.version) { | |
| 396 /* Not similar frame... */ | |
| 397 LOL(" .. but does not match (%d)!\n", chkcount); | |
| 398 state = STATE_RESYNC; | |
| 399 } else if (chkcount >= 5) { | |
| 400 /* Okay, accept this stream */ | |
| 401 LOL(" .. accepted as mp3!!!\n"); | |
| 402 return 1; | |
| 403 } else { | |
| 404 LOL(" .. match %d\n", chkcount); | |
| 405 } | |
| 406 } else { | |
| 407 /* First valid frame of sequence */ | |
| 408 memcpy(&prev, &frame, sizeof(mp3_frame_t)); | |
| 409 LOL(" .. first synced\n"); | |
| 410 } | |
| 411 } else { | |
| 412 /* Nope, try (re)synchronizing */ | |
| 413 if (chkcount > 1) { | |
| 414 LOL("no (%d), trying quick resync ..\n", res); | |
| 415 state = STATE_RESYNC_DO; | |
| 416 resync_max = 32; | |
| 417 } else { | |
| 418 LOL("no (%d)\n", res); | |
| 419 state = STATE_RESYNC; | |
| 420 } | |
| 421 } | |
| 422 break; | |
| 423 | |
| 424 case STATE_GOTO_NEXT: | |
| 425 LULZ("goto next (%x :: %x < %x) ? ", chkpos, skip, chksize); | |
| 426 /* Check if we have the next possible header in buffer? */ | |
| 427 gint tmppos = chkpos + skip + sizeof(guint32); | |
| 428 if (tmppos < chksize) { | |
| 429 LOL("[in buffer]\n"); | |
| 430 chkpos += skip; | |
| 431 state = STATE_GET_NEXT; | |
| 432 } else { | |
| 433 /* No, re-fill buffer and try again .. */ | |
| 434 LOL("[rebuffering: %x, %x]\n", skip, chkpos + skip - chksize); | |
| 435 aud_vfs_fseek(fin, chkpos + skip - chksize, SEEK_CUR); | |
| 436 next = STATE_GET_NEXT; | |
| 437 state = STATE_REBUFFER; | |
| 438 } | |
| 439 break; | |
| 440 | |
| 441 case STATE_GET_NEXT: | |
| 442 /* Get a header */ | |
| 443 LULZ("get next @ bufpos=%08x, streampos=%08lx, realpos=%08lx\n", chkpos, streampos, streampos+chkpos); | |
| 444 head = mp3_head_convert(&chkbuf[chkpos]); | |
| 445 //chkpos += sizeof(guint32); | |
| 446 state = STATE_VALIDATE; | |
| 447 break; | |
| 448 | |
| 449 case STATE_RESYNC: | |
| 450 LULZ("resyncing try #%d ..\n", tries); | |
| 451 /* Re-synchronize aka try to find a valid header */ | |
| 452 head = 0; | |
| 453 chkcount = 0; | |
| 454 resync_max = -1; | |
| 455 state = STATE_RESYNC_DO; | |
| 456 tries++; | |
| 457 break; | |
| 458 | |
| 459 case STATE_RESYNC_DO: | |
| 460 /* Scan for valid frame header */ | |
| 461 for (; chkpos < chksize; chkpos++) { | |
| 462 head <<= 8; | |
| 463 head |= chkbuf[chkpos]; | |
| 464 | |
| 465 if (mp3_head_validate(head, &frame) >= 0) { | |
| 466 /* Found, exit resync */ | |
| 467 chkpos -= 3; | |
| 468 LULZ("resync found @ %x\n", chkpos); | |
| 469 state = STATE_VALIDATE; | |
| 470 break; | |
| 471 } | |
| 472 | |
| 473 /* Check for maximum bytes to search */ | |
| 474 if (resync_max > 0) { | |
| 475 resync_max--; | |
| 476 if (resync_max == 0) { | |
| 477 state = STATE_RESYNC; | |
| 478 break; | |
| 479 } | |
| 480 } | |
| 481 } | |
| 482 if (state == STATE_RESYNC_DO) { | |
| 483 /* Not found, refill buffer */ | |
| 484 next = state; | |
| 485 state = STATE_REBUFFER; | |
| 486 } | |
| 487 break; | |
| 292 } | 488 } |
| 293 | 489 } while (state != STATE_FATAL && tries < 16); |
| 294 if (memcmp(buf, "RMP3", 4) == 0) | 490 /* Give up after 16 failed resync attempts or fatal errors */ |
| 295 return 1; | |
| 296 } | |
| 297 | |
| 298 /* Check stream data for frame header(s) | |
| 299 */ | |
| 300 guchar chkbuf[2048]; | |
| 301 gint chkret, i, framesize, cyc, chkcount, chksize = sizeof(chkbuf); | |
| 302 | |
| 303 for (cyc = chkcount = 0; cyc < 32; cyc++) | |
| 304 { | |
| 305 if ((chkret = aud_vfs_fread(chkbuf, 1, chksize, fin)) == 0) | |
| 306 break; | |
| 307 | |
| 308 for (i = 0; i < chkret; i++) | |
| 309 { | |
| 310 check <<= 8; | |
| 311 check |= chkbuf[i]; | |
| 312 | |
| 313 if (mp3_head_check(check, &framesize)) { | |
| 314 /* when the first matching frame header is found, we check for | |
| 315 * another frame by seeking to the approximate start of the | |
| 316 * next header ... also reduce the check size. | |
| 317 */ | |
| 318 if (++chkcount >= 4) return 1; | |
| 319 aud_vfs_fseek(fin, framesize-8, SEEK_CUR); | |
| 320 check = 0; | |
| 321 chksize = 16; | |
| 322 } else { | |
| 323 aud_vfs_fseek(fin, chksize, SEEK_CUR); | |
| 324 } | |
| 325 } | |
| 326 } | |
| 327 | 491 |
| 328 g_message("Rejecting %s (not an MP3 file?)", filename); | 492 g_message("Rejecting %s (not an MP3 file?)", filename); |
| 329 return 0; | 493 return 0; |
| 330 } | 494 } |
| 331 | 495 |
