/*
* Copyright (C) 2010-2016 JPEXS, All rights reserved.
*
* 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 3.0 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.
*/
package com.jpexs.decompiler.flash.types.sound;
import com.jpexs.decompiler.flash.SWFInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javazoom.jl.decoder.Bitstream;
import javazoom.jl.decoder.BitstreamException;
import javazoom.jl.decoder.Decoder;
import javazoom.jl.decoder.DecoderException;
import javazoom.jl.decoder.Header;
import javazoom.jl.decoder.SampleBuffer;
/**
*
* @author JPEXS
*/
public class MP3Decoder extends SoundDecoder {
private final Decoder decoder = new Decoder();
private final MyInputStream inputStream = new MyInputStream();
private final Bitstream bitStream = new Bitstream(inputStream);
class MyInputStream extends InputStream {
byte[] buf = new byte[4096];
int pos = 0;
int remaining = 0;
public void add(byte[] data) {
if (data == null || data.length == 0) {
return;
}
int remaining = this.remaining;
int requiredSize = data.length + remaining;
byte[] oldBuf = this.buf;
byte[] buf = oldBuf;
int pos = this.pos;
if (requiredSize > buf.length) {
int newSize = buf.length;
while (requiredSize > newSize) {
newSize *= 2;
}
buf = new byte[newSize];
this.buf = buf;
}
if (remaining > 0) {
System.arraycopy(oldBuf, pos, buf, 0, remaining);
}
this.pos = 0;
System.arraycopy(data, 0, buf, remaining, data.length);
this.remaining = remaining + data.length;
}
@Override
public int read() throws IOException {
if (remaining > 0) {
int result = buf[pos] & 0xff;
remaining--;
pos++;
return result;
}
return -1;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
len = Math.min(len, remaining);
if (len > 0) {
System.arraycopy(buf, pos, b, off, len);
remaining -= len;
pos += len;
}
return len;
}
}
public MP3Decoder(SoundFormat soundFormat) {
super(soundFormat);
}
@Override
public void decode(SWFInputStream sis, OutputStream os) throws IOException {
byte[] data = sis.readBytesEx(sis.available(), "soundStream");
inputStream.add(data);
SampleBuffer buf;
while ((buf = readFrame(decoder, bitStream)) != null) {
short[] audio = buf.getBuffer();
byte[] d = new byte[buf.getBufferLength() * 2];
for (int i = 0; i < buf.getBufferLength(); i++) {
int s = audio[i];
d[i * 2] = (byte) (s & 0xff);
d[i * 2 + 1] = (byte) ((s >> 8) & 0xff);
}
os.write(d);
}
}
private SampleBuffer readFrame(Decoder decoder, Bitstream bitstream) {
try {
Header h = bitstream.readFrame();
if (h == null) {
return null;
}
soundFormat.samplingRate = getSamplingRate(h);
soundFormat.stereo = h.mode() != Header.SINGLE_CHANNEL;
try {
SampleBuffer ret = (SampleBuffer) decoder.decodeFrame(h, bitstream);
bitstream.closeFrame();
return ret;
} catch (DecoderException ex) {
return null;
}
} catch (BitstreamException ex) {
return null;
}
}
private static int getSamplingRate(Header h) {
switch (h.sample_frequency()) {
case Header.THIRTYTWO:
if (h.version() == Header.MPEG1) {
return 32000;
} else if (h.version() == Header.MPEG2_LSF) {
return 16000;
} else // SZD
{
return 8000;
}
case Header.FOURTYFOUR_POINT_ONE:
if (h.version() == Header.MPEG1) {
return 44100;
} else if (h.version() == Header.MPEG2_LSF) {
return 22050;
} else // SZD
{
return 11025;
}
case Header.FOURTYEIGHT:
if (h.version() == Header.MPEG1) {
return 48000;
} else if (h.version() == Header.MPEG2_LSF) {
return 24000;
} else // SZD
{
return 12000;
}
default:
return 0;
}
}
}