📄 mp3audioheader.java
字号:
/**
* @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 + -