/* * downmix.c * Copyright (C) 2000-2003 Michel Lespinasse * Copyright (C) 1999-2000 Aaron Holtzman * * This file is part of a52dec, a free ATSC A-52 stream decoder. * See http://liba52.sourceforge.net/ for updates. * * a52dec is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * a52dec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config-a52.h" #include #include #include "a52.h" #include "a52_internal.h" #define CONVERT(acmod,output) (((output) << 3) + (acmod)) int a52_downmix_init (int input, int flags, level_t * level, level_t clev, level_t slev) { static uint8_t table[11][8] = { {A52_CHANNEL, A52_DOLBY, A52_STEREO, A52_STEREO, A52_STEREO, A52_STEREO, A52_STEREO, A52_STEREO}, {A52_MONO, A52_MONO, A52_MONO, A52_MONO, A52_MONO, A52_MONO, A52_MONO, A52_MONO}, {A52_CHANNEL, A52_DOLBY, A52_STEREO, A52_STEREO, A52_STEREO, A52_STEREO, A52_STEREO, A52_STEREO}, {A52_CHANNEL, A52_DOLBY, A52_STEREO, A52_3F, A52_STEREO, A52_3F, A52_STEREO, A52_3F}, {A52_CHANNEL, A52_DOLBY, A52_STEREO, A52_STEREO, A52_2F1R, A52_2F1R, A52_2F1R, A52_2F1R}, {A52_CHANNEL, A52_DOLBY, A52_STEREO, A52_STEREO, A52_2F1R, A52_3F1R, A52_2F1R, A52_3F1R}, {A52_CHANNEL, A52_DOLBY, A52_STEREO, A52_3F, A52_2F2R, A52_2F2R, A52_2F2R, A52_2F2R}, {A52_CHANNEL, A52_DOLBY, A52_STEREO, A52_3F, A52_2F2R, A52_3F2R, A52_2F2R, A52_3F2R}, {A52_CHANNEL1, A52_MONO, A52_MONO, A52_MONO, A52_MONO, A52_MONO, A52_MONO, A52_MONO}, {A52_CHANNEL2, A52_MONO, A52_MONO, A52_MONO, A52_MONO, A52_MONO, A52_MONO, A52_MONO}, {A52_CHANNEL, A52_DOLBY, A52_STEREO, A52_DOLBY, A52_DOLBY, A52_DOLBY, A52_DOLBY, A52_DOLBY} }; int output; output = flags & A52_CHANNEL_MASK; if (output > A52_DOLBY) return -1; output = table[output][input & 7]; if (output == A52_STEREO && (input == A52_DOLBY || (input == A52_3F && clev == LEVEL (LEVEL_3DB)))) output = A52_DOLBY; if (flags & A52_ADJUST_LEVEL) { level_t adjust; switch (CONVERT (input & 7, output)) { case CONVERT (A52_3F, A52_MONO): adjust = DIV (LEVEL_3DB, LEVEL (1) + clev); break; case CONVERT (A52_STEREO, A52_MONO): case CONVERT (A52_2F2R, A52_2F1R): case CONVERT (A52_3F2R, A52_3F1R): level_3db: adjust = LEVEL (LEVEL_3DB); break; case CONVERT (A52_3F2R, A52_2F1R): if (clev < LEVEL (LEVEL_PLUS3DB - 1)) goto level_3db; /* break thru */ case CONVERT (A52_3F, A52_STEREO): case CONVERT (A52_3F1R, A52_2F1R): case CONVERT (A52_3F1R, A52_2F2R): case CONVERT (A52_3F2R, A52_2F2R): adjust = DIV (1, LEVEL (1) + clev); break; case CONVERT (A52_2F1R, A52_MONO): adjust = DIV (LEVEL_PLUS3DB, LEVEL (2) + slev); break; case CONVERT (A52_2F1R, A52_STEREO): case CONVERT (A52_3F1R, A52_3F): adjust = DIV (1, LEVEL (1) + MUL_C (slev, LEVEL_3DB)); break; case CONVERT (A52_3F1R, A52_MONO): adjust = DIV (LEVEL_3DB, LEVEL (1) + clev + MUL_C (slev, 0.5)); break; case CONVERT (A52_3F1R, A52_STEREO): adjust = DIV (1, LEVEL (1) + clev + MUL_C (slev, LEVEL_3DB)); break; case CONVERT (A52_2F2R, A52_MONO): adjust = DIV (LEVEL_3DB, LEVEL (1) + slev); break; case CONVERT (A52_2F2R, A52_STEREO): case CONVERT (A52_3F2R, A52_3F): adjust = DIV (1, LEVEL (1) + slev); break; case CONVERT (A52_3F2R, A52_MONO): adjust = DIV (LEVEL_3DB, LEVEL (1) + clev + slev); break; case CONVERT (A52_3F2R, A52_STEREO): adjust = DIV (1, LEVEL (1) + clev + slev); break; case CONVERT (A52_MONO, A52_DOLBY): adjust = LEVEL (LEVEL_PLUS3DB); break; case CONVERT (A52_3F, A52_DOLBY): case CONVERT (A52_2F1R, A52_DOLBY): adjust = LEVEL (1 / (1 + LEVEL_3DB)); break; case CONVERT (A52_3F1R, A52_DOLBY): case CONVERT (A52_2F2R, A52_DOLBY): adjust = LEVEL (1 / (1 + 2 * LEVEL_3DB)); break; case CONVERT (A52_3F2R, A52_DOLBY): adjust = LEVEL (1 / (1 + 3 * LEVEL_3DB)); break; default: return output; } *level = MUL_L (*level, adjust); } return output; } int a52_downmix_coeff (level_t * coeff, int acmod, int output, level_t level, level_t clev, level_t slev) { level_t level_3db; level_3db = MUL_C (level, LEVEL_3DB); switch (CONVERT (acmod, output & A52_CHANNEL_MASK)) { case CONVERT (A52_CHANNEL, A52_CHANNEL): case CONVERT (A52_MONO, A52_MONO): case CONVERT (A52_STEREO, A52_STEREO): case CONVERT (A52_3F, A52_3F): case CONVERT (A52_2F1R, A52_2F1R): case CONVERT (A52_3F1R, A52_3F1R): case CONVERT (A52_2F2R, A52_2F2R): case CONVERT (A52_3F2R, A52_3F2R): case CONVERT (A52_STEREO, A52_DOLBY): coeff[0] = coeff[1] = coeff[2] = coeff[3] = coeff[4] = level; return 0; case CONVERT (A52_CHANNEL, A52_MONO): coeff[0] = coeff[1] = MUL_C (level, LEVEL_6DB); return 3; case CONVERT (A52_STEREO, A52_MONO): coeff[0] = coeff[1] = level_3db; return 3; case CONVERT (A52_3F, A52_MONO): coeff[0] = coeff[2] = level_3db; coeff[1] = MUL_C (MUL_L (level_3db, clev), LEVEL_PLUS6DB); return 7; case CONVERT (A52_2F1R, A52_MONO): coeff[0] = coeff[1] = level_3db; coeff[2] = MUL_L (level_3db, slev); return 7; case CONVERT (A52_2F2R, A52_MONO): coeff[0] = coeff[1] = level_3db; coeff[2] = coeff[3] = MUL_L (level_3db, slev); return 15; case CONVERT (A52_3F1R, A52_MONO): coeff[0] = coeff[2] = level_3db; coeff[1] = MUL_C (MUL_L (level_3db, clev), LEVEL_PLUS6DB); coeff[3] = MUL_L (level_3db, slev); return 15; case CONVERT (A52_3F2R, A52_MONO): coeff[0] = coeff[2] = level_3db; coeff[1] = MUL_C (MUL_L (level_3db, clev), LEVEL_PLUS6DB); coeff[3] = coeff[4] = MUL_L (level_3db, slev); return 31; case CONVERT (A52_MONO, A52_DOLBY): coeff[0] = level_3db; return 0; case CONVERT (A52_3F, A52_DOLBY): coeff[0] = coeff[2] = coeff[3] = coeff[4] = level; coeff[1] = level_3db; return 7; case CONVERT (A52_3F, A52_STEREO): case CONVERT (A52_3F1R, A52_2F1R): case CONVERT (A52_3F2R, A52_2F2R): coeff[0] = coeff[2] = coeff[3] = coeff[4] = level; coeff[1] = MUL_L (level, clev); return 7; case CONVERT (A52_2F1R, A52_DOLBY): coeff[0] = coeff[1] = level; coeff[2] = level_3db; return 7; case CONVERT (A52_2F1R, A52_STEREO): coeff[0] = coeff[1] = level; coeff[2] = MUL_L (level_3db, slev); return 7; case CONVERT (A52_3F1R, A52_DOLBY): coeff[0] = coeff[2] = level; coeff[1] = coeff[3] = level_3db; return 15; case CONVERT (A52_3F1R, A52_STEREO): coeff[0] = coeff[2] = level; coeff[1] = MUL_L (level, clev); coeff[3] = MUL_L (level_3db, slev); return 15; case CONVERT (A52_2F2R, A52_DOLBY): coeff[0] = coeff[1] = level; coeff[2] = coeff[3] = level_3db; return 15; case CONVERT (A52_2F2R, A52_STEREO): coeff[0] = coeff[1] = level; coeff[2] = coeff[3] = MUL_L (level, slev); return 15; case CONVERT (A52_3F2R, A52_DOLBY): coeff[0] = coeff[2] = level; coeff[1] = coeff[3] = coeff[4] = level_3db; return 31; case CONVERT (A52_3F2R, A52_2F1R): coeff[0] = coeff[2] = level; coeff[1] = MUL_L (level, clev); coeff[3] = coeff[4] = level_3db; return 31; case CONVERT (A52_3F2R, A52_STEREO): coeff[0] = coeff[2] = level; coeff[1] = MUL_L (level, clev); coeff[3] = coeff[4] = MUL_L (level, slev); return 31; case CONVERT (A52_3F1R, A52_3F): coeff[0] = coeff[1] = coeff[2] = level; coeff[3] = MUL_L (level_3db, slev); return 13; case CONVERT (A52_3F2R, A52_3F): coeff[0] = coeff[1] = coeff[2] = level; coeff[3] = coeff[4] = MUL_L (level, slev); return 29; case CONVERT (A52_2F2R, A52_2F1R): coeff[0] = coeff[1] = level; coeff[2] = coeff[3] = level_3db; return 12; case CONVERT (A52_3F2R, A52_3F1R): coeff[0] = coeff[1] = coeff[2] = level; coeff[3] = coeff[4] = level_3db; return 24; case CONVERT (A52_2F1R, A52_2F2R): coeff[0] = coeff[1] = level; coeff[2] = level_3db; return 0; case CONVERT (A52_3F1R, A52_2F2R): coeff[0] = coeff[2] = level; coeff[1] = MUL_L (level, clev); coeff[3] = level_3db; return 7; case CONVERT (A52_3F1R, A52_3F2R): coeff[0] = coeff[1] = coeff[2] = level; coeff[3] = level_3db; return 0; case CONVERT (A52_CHANNEL, A52_CHANNEL1): coeff[0] = level; coeff[1] = 0; return 0; case CONVERT (A52_CHANNEL, A52_CHANNEL2): coeff[0] = 0; coeff[1] = level; return 0; } return -1; /* NOTREACHED */ } static void mix2to1 (sample_t * dest, sample_t * src, sample_t bias) { int i; for (i = 0; i < 256; i++) dest[i] += BIAS (src[i]); } static void mix3to1 (sample_t * samples, sample_t bias) { int i; for (i = 0; i < 256; i++) samples[i] += BIAS (samples[i + 256] + samples[i + 512]); } static void mix4to1 (sample_t * samples, sample_t bias) { int i; for (i = 0; i < 256; i++) samples[i] += BIAS (samples[i + 256] + samples[i + 512] + samples[i + 768]); } static void mix5to1 (sample_t * samples, sample_t bias) { int i; for (i = 0; i < 256; i++) samples[i] += BIAS (samples[i + 256] + samples[i + 512] + samples[i + 768] + samples[i + 1024]); } static void mix3to2 (sample_t * samples, sample_t bias) { int i; sample_t common; for (i = 0; i < 256; i++) { common = BIAS (samples[i + 256]); samples[i] += common; samples[i + 256] = samples[i + 512] + common; } } static void mix21to2 (sample_t * left, sample_t * right, sample_t bias) { int i; sample_t common; for (i = 0; i < 256; i++) { common = BIAS (right[i + 256]); left[i] += common; right[i] += common; } } static void mix21toS (sample_t * samples, sample_t bias) { int i; sample_t surround; for (i = 0; i < 256; i++) { surround = samples[i + 512]; samples[i] += BIAS (-surround); samples[i + 256] += BIAS (surround); } } static void mix31to2 (sample_t * samples, sample_t bias) { int i; sample_t common; for (i = 0; i < 256; i++) { common = BIAS (samples[i + 256] + samples[i + 768]); samples[i] += common; samples[i + 256] = samples[i + 512] + common; } } static void mix31toS (sample_t * samples, sample_t bias) { int i; sample_t common, surround; for (i = 0; i < 256; i++) { common = BIAS (samples[i + 256]); surround = samples[i + 768]; samples[i] += common - surround; samples[i + 256] = samples[i + 512] + common + surround; } } static void mix22toS (sample_t * samples, sample_t bias) { int i; sample_t surround; for (i = 0; i < 256; i++) { surround = samples[i + 512] + samples[i + 768]; samples[i] += BIAS (-surround); samples[i + 256] += BIAS (surround); } } static void mix32to2 (sample_t * samples, sample_t bias) { int i; sample_t common; for (i = 0; i < 256; i++) { common = BIAS (samples[i + 256]); samples[i] += common + samples[i + 768]; samples[i + 256] = common + samples[i + 512] + samples[i + 1024]; } } static void mix32toS (sample_t * samples, sample_t bias) { int i; sample_t common, surround; for (i = 0; i < 256; i++) { common = BIAS (samples[i + 256]); surround = samples[i + 768] + samples[i + 1024]; samples[i] += common - surround; samples[i + 256] = samples[i + 512] + common + surround; } } static void move2to1 (sample_t * src, sample_t * dest, sample_t bias) { int i; for (i = 0; i < 256; i++) dest[i] = BIAS (src[i] + src[i + 256]); } static void zero (sample_t * samples) { int i; for (i = 0; i < 256; i++) samples[i] = 0; } void a52_downmix (sample_t * samples, int acmod, int output, sample_t bias, level_t clev, level_t slev) { /* avoid compiler warning */ (void)clev; switch (CONVERT (acmod, output & A52_CHANNEL_MASK)) { case CONVERT (A52_CHANNEL, A52_CHANNEL2): memcpy (samples, samples + 256, 256 * sizeof (sample_t)); break; case CONVERT (A52_CHANNEL, A52_MONO): case CONVERT (A52_STEREO, A52_MONO): mix_2to1: mix2to1 (samples, samples + 256, bias); break; case CONVERT (A52_2F1R, A52_MONO): if (slev == 0) goto mix_2to1; case CONVERT (A52_3F, A52_MONO): mix_3to1: mix3to1 (samples, bias); break; case CONVERT (A52_3F1R, A52_MONO): if (slev == 0) goto mix_3to1; case CONVERT (A52_2F2R, A52_MONO): if (slev == 0) goto mix_2to1; mix4to1 (samples, bias); break; case CONVERT (A52_3F2R, A52_MONO): if (slev == 0) goto mix_3to1; mix5to1 (samples, bias); break; case CONVERT (A52_MONO, A52_DOLBY): memcpy (samples + 256, samples, 256 * sizeof (sample_t)); break; case CONVERT (A52_3F, A52_STEREO): case CONVERT (A52_3F, A52_DOLBY): mix_3to2: mix3to2 (samples, bias); break; case CONVERT (A52_2F1R, A52_STEREO): if (slev == 0) break; mix21to2 (samples, samples + 256, bias); break; case CONVERT (A52_2F1R, A52_DOLBY): mix21toS (samples, bias); break; case CONVERT (A52_3F1R, A52_STEREO): if (slev == 0) goto mix_3to2; mix31to2 (samples, bias); break; case CONVERT (A52_3F1R, A52_DOLBY): mix31toS (samples, bias); break; case CONVERT (A52_2F2R, A52_STEREO): if (slev == 0) break; mix2to1 (samples, samples + 512, bias); mix2to1 (samples + 256, samples + 768, bias); break; case CONVERT (A52_2F2R, A52_DOLBY): mix22toS (samples, bias); break; case CONVERT (A52_3F2R, A52_STEREO): if (slev == 0) goto mix_3to2; mix32to2 (samples, bias); break; case CONVERT (A52_3F2R, A52_DOLBY): mix32toS (samples, bias); break; case CONVERT (A52_3F1R, A52_3F): if (slev == 0) break; mix21to2 (samples, samples + 512, bias); break; case CONVERT (A52_3F2R, A52_3F): if (slev == 0) break; mix2to1 (samples, samples + 768, bias); mix2to1 (samples + 512, samples + 1024, bias); break; case CONVERT (A52_3F1R, A52_2F1R): mix3to2 (samples, bias); memcpy (samples + 512, samples + 768, 256 * sizeof (sample_t)); break; case CONVERT (A52_2F2R, A52_2F1R): mix2to1 (samples + 512, samples + 768, bias); break; case CONVERT (A52_3F2R, A52_2F1R): mix3to2 (samples, bias); move2to1 (samples + 768, samples + 512, bias); break; case CONVERT (A52_3F2R, A52_3F1R): mix2to1 (samples + 768, samples + 1024, bias); break; case CONVERT (A52_2F1R, A52_2F2R): memcpy (samples + 768, samples + 512, 256 * sizeof (sample_t)); break; case CONVERT (A52_3F1R, A52_2F2R): mix3to2 (samples, bias); memcpy (samples + 512, samples + 768, 256 * sizeof (sample_t)); break; case CONVERT (A52_3F2R, A52_2F2R): mix3to2 (samples, bias); memcpy (samples + 512, samples + 768, 256 * sizeof (sample_t)); memcpy (samples + 768, samples + 1024, 256 * sizeof (sample_t)); break; case CONVERT (A52_3F1R, A52_3F2R): memcpy (samples + 1024, samples + 768, 256 * sizeof (sample_t)); break; } } void a52_upmix (sample_t * samples, int acmod, int output) { switch (CONVERT (acmod, output & A52_CHANNEL_MASK)) { case CONVERT (A52_CHANNEL, A52_CHANNEL2): memcpy (samples + 256, samples, 256 * sizeof (sample_t)); break; case CONVERT (A52_3F2R, A52_MONO): zero (samples + 1024); case CONVERT (A52_3F1R, A52_MONO): case CONVERT (A52_2F2R, A52_MONO): zero (samples + 768); case CONVERT (A52_3F, A52_MONO): case CONVERT (A52_2F1R, A52_MONO): zero (samples + 512); case CONVERT (A52_CHANNEL, A52_MONO): case CONVERT (A52_STEREO, A52_MONO): zero (samples + 256); break; case CONVERT (A52_3F2R, A52_STEREO): case CONVERT (A52_3F2R, A52_DOLBY): zero (samples + 1024); case CONVERT (A52_3F1R, A52_STEREO): case CONVERT (A52_3F1R, A52_DOLBY): zero (samples + 768); case CONVERT (A52_3F, A52_STEREO): case CONVERT (A52_3F, A52_DOLBY): mix_3to2: memcpy (samples + 512, samples + 256, 256 * sizeof (sample_t)); zero (samples + 256); break; case CONVERT (A52_2F2R, A52_STEREO): case CONVERT (A52_2F2R, A52_DOLBY): zero (samples + 768); case CONVERT (A52_2F1R, A52_STEREO): case CONVERT (A52_2F1R, A52_DOLBY): zero (samples + 512); break; case CONVERT (A52_3F2R, A52_3F): zero (samples + 1024); case CONVERT (A52_3F1R, A52_3F): case CONVERT (A52_2F2R, A52_2F1R): zero (samples + 768); break; case CONVERT (A52_3F2R, A52_3F1R): zero (samples + 1024); break; case CONVERT (A52_3F2R, A52_2F1R): zero (samples + 1024); case CONVERT (A52_3F1R, A52_2F1R): mix_31to21: memcpy (samples + 768, samples + 512, 256 * sizeof (sample_t)); goto mix_3to2; case CONVERT (A52_3F2R, A52_2F2R): memcpy (samples + 1024, samples + 768, 256 * sizeof (sample_t)); goto mix_31to21; } } >409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
/*
 * iFish -- An iRiver iHP jukebox database creation tool
 *
 * Copyright (c) 2004 Richard "Shred" Körber
 *   http://www.shredzone.net/go/ifish
 *
 *-----------------------------------------------------------------------
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is IFISH.
 *
 * The Initial Developer of the Original Code is
 * Richard "Shred" Körber.
 * Portions created by the Initial Developer are Copyright (C) 2004
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK *****
 */

package net.shredzone.ifish.ltr;

import java.io.*;
import java.util.regex.*;

/**
 * Decodes an MP3 file with ID3v2 tag. The file is compliant to the
 * specifications found at <a href="http://www.id3.org">www.id3.org</a>,
 * V2.4.0 and V2.3.0. ID3 V2.2.0 is handled in a separate tag handler.
 * Anyhow it has certain limitations regarding tag frames which could
 * not be used or entirely used to iFish, e.g. multiple genres.
 *
 * @author    Richard Körber &lt;dev@shredzone.de&gt;
 * @version   $Id$
 */
public class TagMp3v2 extends LTRmp3 {
  private int     globalSize;
  private Pattern patGenre;
  private boolean v230 = false;     // V2.3.0 detected

  private String  artist  = "";
  private String  comment = "";
  private String  title   = "";
  private String  album   = "";
  private String  year    = "";
  private String  track   = "";
  private String  genre   = "";

  /**
   * Create a new TagMp3v2 instance.
   *
   * @param   in      File to be read
   * @throws  FormatDecodeException     Couldn't decode this file
   */
  public TagMp3v2( RandomAccessFile in )
  throws FormatDecodeException {
    super( in );

    patGenre = Pattern.compile( "\\((\\d{1,3})\\).*" );

    try {
      //--- Decode header ---
      if( !readStringLen( 3 ).equals( "ID3" ) ) {
        throw new FormatDecodeException( "not an id3v2 tag" );
      }

      byte version  = in.readByte();
      byte revision = in.readByte();

      if( version==0xFF || revision==0xFF ) {
        throw new FormatDecodeException( "not an id3v2 tag" );
      }

      if( version<=0x02 || version>0x04 ) {
        throw new FormatDecodeException( "unable to decode ID3v2."+version+"."+revision );
      }

      byte flags  = in.readByte();
      v230 = (version==0x03);
      globalSize = readSyncsafeInt() + 10;

      //--- Skip extended header ---
      if( ( flags & 0x40 ) != 0 ) {
        int ehsize  = readAutoInt();
        if( ehsize < 6 ) {
          throw new FormatDecodeException( "extended header too small" );
        }
        in.skipBytes( ehsize - 4 );
      }

      //--- Read all frames ---
      Frame frm;
      while( ( frm = readFrame() ) != null ) {
        String type  = frm.getType();
        String[] answer;

        if( type.equals( "TOPE" ) || type.equals( "TPE1" ) ) {

          answer = frm.getAsStringEnc();
          artist = (answer.length>0 ? answer[0].trim() : "");

        } else if( type.equals( "COMM" ) ) {

          answer = frm.getAsStringEnc();
          comment = (answer.length>1 ? answer[1].trim() : "");

        } else if( type.equals( "TIT2" ) ) {

          answer = frm.getAsStringEnc();
          title =(answer.length>0 ? answer[0].trim() : "");

        } else if( type.equals( "TALB" ) ) {

          answer = frm.getAsStringEnc();
          album = (answer.length>0 ? answer[0].trim() : "");

        } else if( type.equals( "TYER" ) ) {

          answer = frm.getAsStringEnc();
          year = (answer.length>0 ? answer[0].trim() : "");

        } else if( type.equals( "TRCK" ) ) {

          answer = frm.getAsStringEnc();
          track = (answer.length>0 ? answer[0].trim() : "");

        } else if( type.equals( "TCON" ) ) {

          answer = frm.getAsStringEnc();
          if( answer.length>0 ) {
            genre = answer[0].trim();
            Matcher mat  = patGenre.matcher( genre );
            if( mat.matches() ) {
              genre = decodeGenre( Integer.parseInt( mat.group( 1 ) ) );
              if( genre==null ) genre="";
            }
          }
        }
      }

      //--- Footer frame? ---
      if( ( flags & 0x10 ) != 0 ) {
        in.skipBytes( 10 );             // Then skip it
      }
      // Position is now the start of the MP3 data

    } catch( IOException e ) {
      throw new FormatDecodeException( "could not decode file: " + e.toString() );
    }catch( RuntimeException e ) {
      throw new FormatDecodeException( "error decoding file: " + e.toString() );
    }
  }

  /**
   * Get the type of this file. This is usually the compression format
   * itself (e.g. "OGG" or "MP3"). If there are different taggings for
   * this format, the used tag format is appended after a slash (e.g.
   * "MP3/id3v2").
   *
   * @return   The type
   */
  public String getType() {
    return ( v230 ? "MP3/id3v2.3.0" : "MP3/id3v2.4.0" );
  }

  /**
   * Get the artist.
   *
   * @return   The artist (never null)
   */
  public String getArtist() {
    return artist;
  }

  /**
   * Get the album.
   *
   * @return   The album (never null)
   */
  public String getAlbum() {
    return album;
  }

  /**
   * Get the title.
   *
   * @return   The title (never null)
   */
  public String getTitle() {
    return title;
  }

  /**
   * Get the genre.
   *
   * @return   The genre (never null)
   */
  public String getGenre() {
    return genre;
  }

  /**
   * Get the year.
   *
   * @return   The year (never null)
   */
  public String getYear() {
    return year;
  }

  /**
   * Get the comment.
   *
   * @return   The comment (never null)
   */
  public String getComment() {
    return comment;
  }

  /**
   * Get the track.
   *
   * @return    The track (never null)
   */
  public String getTrack() {
    return track;
  }

  /**
   * Read an ID3v2 integer. This is a 4 byte big endian value, which is
   * only syncsafe for ID3v2.4 or higher, but not for ID3v2.3.
   *
   * @return    The integer read.
   * @throws    IOException  If there were not enough bytes in the file.
   */
  protected int readAutoInt()
  throws IOException {
    return( v230 ? readInt() : readSyncsafeInt() );
  }
  
  /**
   * Read a tag frame. A Frame object will be returned, or null if no
   * more frames were available.
   *
   * @return    The next frame, or null
   * @throws    IOException  If premature EOF was reached.
   */
  protected Frame readFrame()
  throws IOException {
    if( in.getFilePointer() >= globalSize ) {
      return null;
    }

    //--- Get the type ---
    String type  = readStringLen( 4 );
    if( type.charAt(0)==0 ) {   // Optional padding frame
      in.skipBytes( (int) ( globalSize - in.getFilePointer() ) );// Skip it...
      return null;                      // Return null
    }

    //--- Read the frame ---
    int size     = readAutoInt();
    byte flag1   = in.readByte();
    byte flag2   = in.readByte();
    
    //--- Read the content ---
    // Stay within reasonable boundaries. If the data part is bigger than
    // 16K, it's not really useful for us, so we will keep the frame empty.
    byte[] data = null;
    if( size<=16384 ) {
      data  = new byte[size];
      int rlen     = in.read( data );
      if( rlen != size ) {
        throw new IOException( "unexpected EOF" );
      }
    }else {
      in.skipBytes( size );
    }

    //--- Return the frame ---
    return new Frame( type, size, flag1, flag2, data );
  }

/*--------------------------------------------------------------------*/
  
  /**
   * This class contains a ID3v2 frame.
   */
  private static class Frame {
    private final String  charset;
    private final String  type;
//    private final int     size;
//    private final byte    flag1;
    private final byte    flag2;
    private       byte[]  data;
    private       boolean decoded = false;

    /**
     * Constructor for the Frame object
     *
     * @param type   Frame type
     * @param size   Frame size
     * @param flag1  Flag 1
     * @param flag2  Flag 2
     * @param data   Frame content, may be null
     */
    public Frame( String type, int size, byte flag1, byte flag2, byte[] data ) {
      charset = "ISO-8859-1";
      this.type  = type;
// Currently unused...
//      this.size  = size;
//      this.flag1 = flag1;
      this.flag2 = flag2;
      this.data  = data;
    }

    /**
     * Get the type.
     *
     * @return   The type of this Frame
     */
    public String getType() {
      return type;