Package com.tulskiy.musique.audio.formats.mp3

Source Code of com.tulskiy.musique.audio.formats.mp3.MP3Decoder

/*
* Copyright (c) 2008, 2009, 2010, 2011 Denis Tulskiy
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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
* version 3 along with this work.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.tulskiy.musique.audio.formats.mp3;

import com.tulskiy.musique.audio.IcyInputStream;
import com.tulskiy.musique.playlist.Track;
import com.tulskiy.musique.playlist.TrackData;
import com.tulskiy.musique.util.AudioMath;
import javazoom.jl.decoder.*;

import javax.sound.sampled.AudioFormat;
import java.io.*;
import java.net.URI;
import java.net.URLDecoder;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* @Author: Denis Tulskiy
* @Date: 12.06.2009
*/
public class MP3Decoder implements com.tulskiy.musique.audio.Decoder {
    private static final int DECODE_AFTER_SEEK = 9;
    private LinkedHashMap<File, SeekTable> seekTableCache = new LinkedHashMap<File, SeekTable>(10, 0.7f, true) {
        @Override
        protected boolean removeEldestEntry(Map.Entry<File, SeekTable> eldest) {
            return size() > 10;
        }
    };

    private Bitstream bitstream;
    private javazoom.jl.decoder.Decoder decoder;
    private AudioFormat audioFormat;
    private Header readFrame;
    private Track track;

    private long totalSamples;
    private long streamSize;
    private int samplesPerFrame;
    private int sampleOffset = 0;
    private int encDelay;
    private long currentSample;
    private boolean streaming = false;
    private int oldBitrate;

    private Header skipFrame() throws BitstreamException {
        readFrame = bitstream.readFrame();
        if (readFrame == null) {
            return null;
        }
        bitstream.closeFrame();

        return readFrame;
    }

    private int samplesToMinutes(long samples) {
        return (int) (samples / track.getTrackData().getSampleRate() / 60f);
    }

    @SuppressWarnings({"ResultOfMethodCallIgnored"})
    private boolean createBitstream(long targetSample) {
        if (bitstream != null)
            bitstream.close();
        bitstream = null;
        try {
            File file = track.getTrackData().getFile();
            FileInputStream fis = new FileInputStream(file);

            //so we compute target frame first
            targetSample += encDelay;
            int targetFrame = (int) ((double) targetSample / samplesPerFrame);
            sampleOffset = (int) (targetSample - targetFrame * samplesPerFrame) * audioFormat.getFrameSize();

            //then we get the seek table or create it if needed
            SeekTable seekTable = seekTableCache.get(file);
            if (seekTable == null &&
                    samplesToMinutes(totalSamples) > 10) {
                seekTable = new SeekTable();
                seekTableCache.put(file, seekTable);
            }

            int currentFrame = 0;
            //if we have a point, use it
            if (seekTable != null) {
                SeekTable.SeekPoint seekPoint = seekTable.get(targetFrame - DECODE_AFTER_SEEK);
                fis.skip(seekPoint.offset);
                currentFrame = seekPoint.frame;
            }

            //then we create the bitstream
            bitstream = new Bitstream(fis);
            decoder = new javazoom.jl.decoder.Decoder();

            readFrame = null;
            for (int i = currentFrame; i < targetFrame - DECODE_AFTER_SEEK; i++) {
                skipFrame();
                //store frame's position
                if (seekTable != null && i % 10000 == 0) {
                    seekTable.add(i, streamSize - bitstream.getPosition());
                }
            }

            //decode some frames to warm up the decoder
            int framesToDecode = targetFrame < DECODE_AFTER_SEEK ? targetFrame : DECODE_AFTER_SEEK;
            for (int i = 0; i < framesToDecode; i++) {
                readFrame = bitstream.readFrame();
                if (readFrame != null)
                    decoder.decodeFrame(readFrame, bitstream);
                bitstream.closeFrame();
            }

            return true;
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }

    public boolean open(final Track track) {
        if (track == null)
            return false;
        this.track = track;
        TrackData trackData = track.getTrackData();
        try {
            URI location = trackData.getLocation();
            InputStream fis;
            if (trackData.isFile()) {
                logger.fine("Opening file: " + trackData.getFile());
                streaming = false;
                fis = new FileInputStream(trackData.getFile());
                streamSize = trackData.getFile().length();
            } else {
              trackData.setCodec("MP3 Stream");
                logger.fine("Opening stream: " + URLDecoder.decode(location.toString(), "utf8"));
                streaming = true;
                fis = IcyInputStream.create(track);
                decoder = new Decoder();
            }
            bitstream = new Bitstream(fis);
            Header header = bitstream.readFrame();
            encDelay = header.getEncDelay();
            int encPadding = header.getEncPadding();
            int sampleRate = header.frequency();
            int channels = header.mode() == Header.SINGLE_CHANNEL ? 1 : 2;
            trackData.setSampleRate(sampleRate);
            trackData.setChannels(channels);
            oldBitrate = trackData.getBitrate();
            samplesPerFrame = (int) (header.ms_per_frame() * header.frequency() / 1000);
            audioFormat = new AudioFormat(sampleRate, 16, channels, true, false);

            if (!streaming) {
                totalSamples = samplesPerFrame * (header.max_number_of_frames(streamSize) + header.min_number_of_frames(streamSize)) / 2;
                if (encPadding < totalSamples) {
                    totalSamples -= encPadding;
                }
                totalSamples -= encDelay;
                bitstream.close();
                fis.close();
                createBitstream(0);
            }

            currentSample = 0;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    public AudioFormat getAudioFormat() {
        return audioFormat;
    }

    public void seekSample(long targetSample) {
        currentSample = targetSample;
        createBitstream(targetSample);
    }

    public int decode(byte[] buf) {
        try {
            readFrame = bitstream.readFrame();

            if (readFrame == null) {
                return -1;
            }

            if (readFrame.bitrate_instant() > 0)
                track.getTrackData().setBitrate(readFrame.bitrate_instant() / 1000);

            if (!streaming && currentSample >= totalSamples)
                return -1;
            SampleBuffer output = (SampleBuffer) decoder.decodeFrame(readFrame, bitstream);
            bitstream.closeFrame();
            int dataLen = output.getBufferLength() * 2;
            int len = dataLen - sampleOffset;
            if (dataLen == 0) {
                return 0;
            }

            currentSample += AudioMath.bytesToSamples(len, audioFormat.getFrameSize());

            if (!streaming && currentSample > totalSamples) {
                len -= AudioMath.samplesToBytes(currentSample - totalSamples, audioFormat.getFrameSize());
            }
            toByteArray(output.getBuffer(), sampleOffset / 2, len / 2, buf);
            sampleOffset = 0;
            readFrame = null;
            return len;
        } catch (BitstreamException e) {
            e.printStackTrace();
        } catch (DecoderException e) {
            e.printStackTrace();
        }
        return -1;
    }

    public void close() {
        if (bitstream != null)
            bitstream.close();
        track.getTrackData().setBitrate(oldBitrate);
        readFrame = null;
    }

    private void toByteArray(short[] samples, int offs, int len, byte[] dest) {
        int idx = 0;
        short s;
        while (len-- > 0) {
            s = samples[offs++];
            dest[idx++] = (byte) s;
            dest[idx++] = (byte) (s >>> 8);
        }
    }
}
TOP

Related Classes of com.tulskiy.musique.audio.formats.mp3.MP3Decoder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.