⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 mp3audioheader.java

📁 java+eclipse做的TTPlayer
💻 JAVA
📖 第 1 页 / 共 2 页
字号:
/**
 *  @author : Paul Taylor
 *
 *  Version @version:$Id: MP3AudioHeader.java,v 1.22 2007/11/29 19:32:57 paultaylor Exp $
 *
 *  MusicTag Copyright (C)2003,2004
 *
 *  This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
 *  General Public  License as published by the Free Software Foundation; either version 2.1 of the License,
 *  or (at your option) any later version.
 *
 *  This library 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License along with this library; if not,
 *  you can get a copy from http://www.opensource.org/licenses/lgpl-license.php or write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package com.hadeslee.audiotag.audio.mp3;

import com.hadeslee.audiotag.audio.AudioHeader;
import com.hadeslee.audiotag.audio.exceptions.InvalidAudioFrameException;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Represents the audio header of an MP3 File
 *
 * <p>The audio header consists of a number of
 * audio frames. Because we are not trying to play the audio but only extract some information
 * regarding the audio we only need to read the first  audio frames to ensure that we have correctly
 * identified them as audio frames and extracted the metadata we reuire.

 * <p>Start of Audio id 0xFF (11111111) and then second byte anded with 0xE0(11100000).
 * For example 2nd byte doesnt have to be 0xE0 is just has to have the top 3 signicant
 * bits set. For example 0xFB (11111011) is a common occurence of the second match. The 2nd byte
 * defines flags to indicate various mp3 values.
 *
 * <p>Having found these two values we then read the header which comprises these two bytes plus a further
 * two to ensure this really is a MP3Header, sometimes the first frame is actually a dummy frame with summary information
 * held within about the whole file, typically using a Xing Header or LAme Header. This is most useful when the file
 * is variable bit rate, if the file is variable bit rate but does not use a summary header it will not be correctly
 * identified as a VBR frame and the track length will be incorreclty calculated. Strictly speaking MP3 means an MPEG-1,
 * Layer III file but MP2 (MPEG-1,Layer II), MP1 (MPEG-1,Layer I) and MPEG-2 files are sometimes used and named with
 * the .mp3 suffix so this library attempts to supports all these formats.
*/
public final class MP3AudioHeader implements AudioHeader
{
    private MPEGFrameHeader mp3FrameHeader;
    private XingFrame       mp3XingFrame;


    private long    fileSize;
    private long    startByte;
    private double  timePerFrame;
    private double  trackLength;
    private long    numberOfFrames;
    private long    numberOfFramesEstimate;
    private long    bitrate;
    private String  encoder ="";

    private static final SimpleDateFormat timeInFormat    = new SimpleDateFormat("ss");
    private static final SimpleDateFormat timeOutFormat   = new SimpleDateFormat("mm:ss");
    private static final char             isVbrIdentifier = '~';
    private static final int              CONVERT_TO_KILOBITS = 1000;
    private static final String           TYPE_MP3 = "mp3";
    private static final int              CONVERTS_BYTE_TO_BITS = 8;

     //Logger
     public static Logger logger = Logger.getLogger("com.hadeslee.jaudiotagger.audio.mp3");

    /** After testing the average location of the first MP3Header bit was at 5000 bytes so this is
     *  why chosen as a default.
     */
    private final static int FILE_BUFFER_SIZE = 5000;
    private final static int MIN_BUFFER_REMAINING_REQUIRED =
        MPEGFrameHeader.HEADER_SIZE + XingFrame.MAX_BUFFER_SIZE_NEEDED_TO_READ_XING;

    /**
     * Search for the first MP3Header in the file
     *
     * The search starts from the start of the file, it is usually safer to use the alternative constructor that
     * allows you to provide the length of the tag header as a parameter so the tag can be skipped over.
     *
     * @param seekFile
     * @throws IOException
     * @throws InvalidAudioFrameException
     */
    public MP3AudioHeader(final File seekFile)throws IOException,InvalidAudioFrameException
    {
         if(seek(seekFile, 0)==false)
         {
             throw new InvalidAudioFrameException("No audio header found within"+seekFile.getName());
         }
    }

    /**
     * Search for the first MP3Header in the file
     *
     * Starts searching from location startByte, this is because there is likely to be an ID3TagHeader
     * before the start of the audio. If this tagHeader contains unsynchronized information there is a
     * possibility that it might be inaccurately identified as the start of the Audio data. Various checks
     * are done in this code to prevent this happening but it cannot be guaranteed.
     *
     * Of course if the startByte provided overstates the length og the tag header, this could mean the
     * start of the MP3AudioHeader is missed, further checks are done within the MP3 class to recognize
     * if this has occurred and take appropriate action.
     *
     * @param seekFile
     * @param startByte
     * @throws IOException
     * @throws InvalidAudioFrameException
     */
    public MP3AudioHeader(final File seekFile,long startByte)throws IOException,InvalidAudioFrameException
    {
         if(seek(seekFile, startByte)==false)
         {
             throw new InvalidAudioFrameException("No audio header found within"+seekFile.getName());
         }
    }

    /**
     * Returns true if the first MP3 frame can be found for the MP3 file
     *
     * This is the first byte of  music data and not the ID3 Tag Frame.     *
     *
     * @param seekFile MP3 file to seek
     * @param startByte if there is an ID3v2tag we dont want to start reading from the start of the tag
     * @return true if the first MP3 frame can be found
     * @throws IOException on any I/O error
     * @noinspection NestedTryStatement
     */
    public boolean seek(final File seekFile, long startByte)
        throws IOException
    {
        //This is substantially faster than updating the filechannels position
        long filePointerCount;

        final FileInputStream     fis = new FileInputStream(seekFile);
        final FileChannel fc = fis.getChannel();

        //Read into Byte Buffer in Chunks
        ByteBuffer  bb = ByteBuffer.allocateDirect(FILE_BUFFER_SIZE);

        //Move FileChannel to the starting position (skipping over tag if any)
        fc.position(startByte);

        //Update filePointerCount
        filePointerCount=startByte;

        //Read from here into the byte buffer , doesnt move location of filepointer
        fc.read(bb,startByte);
        bb.flip();

        boolean syncFound  = false;
        try
        {
            do
            {
                if(bb.remaining()<=MIN_BUFFER_REMAINING_REQUIRED)
                {
                    bb.clear();
                    fc.position(filePointerCount);
                    fc.read(bb,fc.position());
                    bb.flip();
                    if(bb.limit()<=MIN_BUFFER_REMAINING_REQUIRED)
                    {
                        //No mp3 exists
                        return false;
                    }
                }
                //MP3File.logger.finest("fc:"+fc.position() + "bb"+bb.position());
                if(MPEGFrameHeader.isMPEGFrame(bb))
                {
                    try
                    {
                        if(MP3AudioHeader.logger.isLoggable(Level.FINEST))
                        {
                            MP3AudioHeader.logger.finest("Found Possible header at:"+filePointerCount);
                        }

                        mp3FrameHeader = MPEGFrameHeader.parseMPEGHeader(bb);
                        syncFound = true;
                         if(XingFrame.isXingFrame(bb,mp3FrameHeader))
                        {
                            if(MP3AudioHeader.logger.isLoggable(Level.FINEST))
                            {
                                MP3AudioHeader.logger.finest("Found Possible XingHeader");
                            }
                            try
                            {
                                //Parses Xing frame without modifying position of main buffer
                                mp3XingFrame = XingFrame.parseXingFrame();
                            }
                            catch (InvalidAudioFrameException ex)
                            {
                                // We Ignore because even if Xing Header is corrupted
                                //doesn't mean file is corrupted
                            }
                            break;
                        }
                        // There is a small but real chance that an unsynchronised ID3 Frame could fool the MPEG
                        // Parser into thinking it was an MPEG Header. If this happens the chances of the next bytes
                        // forming a Xing frame header are very remote. On the basis that  most files these days have
                        // Xing headers we do an additional check for when an apparent frame header has been found
                        // but is not followed by a Xing Header:We check the next header this wont impose a large
                        // overhead because wont apply to most Mpegs anyway ( Most likely to occur if audio
                        // has an  APIC frame which should have been unsynchronised but has not been) , or if the frame
                        // has been encoded with as Unicode LE because these have a BOM of 0xFF 0xFE
                        else
                        {
                            syncFound=isNextFrameValid(seekFile,filePointerCount,bb,fc);
                            if(syncFound==true)
                            {
                                break;
                            }
                        }

                    }
                    catch (InvalidAudioFrameException ex)
                    {
                        // We Ignore because likely to be incorrect sync bits ,
                        // will just continue in loop
                    }
                }
                bb.position(bb.position()+1);
                filePointerCount++;
            }
            while(!syncFound);
        }
        catch (EOFException ex)
        {
            MP3AudioHeader.logger.log(Level.WARNING,"Reached end of file without finding sync match",ex);
            syncFound = false;
        }
        catch (IOException iox)
        {
            MP3AudioHeader.logger.log(Level.SEVERE,"IOException occurred whilst trying to find sync",iox);
            syncFound = false;
            throw iox;
        }
        finally
        {
            if (fc != null)
            {
                fc.close();
            }

            if (fis != null)
            {
                fis.close();
            } 
        }

        //Return to start of audio header
        if(MP3AudioHeader.logger.isLoggable(Level.FINEST))
        {
            MP3AudioHeader.logger.finer("Return found matching mp3 header starting at" + filePointerCount);
        }
        setFileSize(seekFile.length());
        setMp3StartByte(filePointerCount);
        setTimePerFrame();
        setNumberOfFrames();
        setTrackLength();
        setBitRate();
        setEncoder();
        return syncFound;
    }

    /** Called in some circumstances to check the next frame to ensure we have the correct audio header
     *
     * @return  true if frame is valid
     */
    private boolean isNextFrameValid(File seekFile,long filePointerCount,ByteBuffer  bb,FileChannel fc)
    throws IOException
    {
        if(MP3AudioHeader.logger.isLoggable(Level.FINEST))
        {
             MP3AudioHeader.logger.finer("Checking next frame"+seekFile.getName()+ ":fpc:"
                 +filePointerCount+"skipping to:"+ (filePointerCount + mp3FrameHeader.getFrameLength()));
        }
        boolean result=false;

        int currentPosition = bb.position();

        //Our buffer is not large enough to fit in the whole of this frame, something must
        //have gone wrong because frames are not this large, so just return false
        //bad frame header
        if( mp3FrameHeader.getFrameLength()>( FILE_BUFFER_SIZE - MIN_BUFFER_REMAINING_REQUIRED))
        {
            MP3AudioHeader.logger.finer("Frame size is too large to be a frame:"+mp3FrameHeader.getFrameLength());
            return false;
        }

        //Check for end of buffer if not enough room get some more
        if(bb.remaining()<=MIN_BUFFER_REMAINING_REQUIRED + mp3FrameHeader.getFrameLength())
        {
            MP3AudioHeader.logger.finer("Buffer too small, need to reload, buffer size:"+bb.remaining());
            bb.clear();
            fc.position(filePointerCount);
            fc.read(bb,fc.position());
            bb.flip();
            //So now original buffer has been replaced, so set current position to start of buffer
            currentPosition = 0;
            if(bb.limit()<=MIN_BUFFER_REMAINING_REQUIRED)
            {
                //No mp3 exists
                MP3AudioHeader.logger.finer("Nearly at end of file, no header found:");
                return false;
            }
        }

        //Position bb to the start of the alleged next frame
        bb.position(bb.position() + mp3FrameHeader.getFrameLength());
        if(MPEGFrameHeader.isMPEGFrame(bb))
        {
            try
            {
                MPEGFrameHeader.parseMPEGHeader(bb);
                MP3AudioHeader.logger.finer("Check next frame confirms is an audio header ");
                result=true;
            }
            catch (InvalidAudioFrameException ex)
            {
                MP3AudioHeader.logger.finer("Check next frame has identified this is not an audio header");
                result=false;
            }
        }                         
        //Set back to the start of the previous frame
        bb.position(currentPosition);
        return result;
    }

    /**
     * Set the location of where the Audio file begins in the file
     *
     * @param startByte
     */
    private void setMp3StartByte(final long startByte)
    {
        this.startByte = startByte;
    }


⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -