package ch.retorte.intervalmusiccompositor.decoder; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.UnsupportedAudioFileException; import javazoom.jl.decoder.Bitstream; import javazoom.jl.decoder.BitstreamException; import javazoom.spi.mpeg.sampled.convert.DecodedMpegAudioInputStream; import javazoom.spi.mpeg.sampled.file.MpegAudioFileReader; import ch.retorte.intervalmusiccompositor.spi.audio.AudioStandardizer; import ch.retorte.intervalmusiccompositor.spi.decoder.AudioFileDecoder; import static com.google.common.collect.Lists.newArrayList; /** * @author nw */ public class Mp3AudioFileDecoder implements AudioFileDecoder { private Mp3FileProperties mp3File = new Mp3FileProperties(); private AudioStandardizer audioStandardizer; public Mp3AudioFileDecoder(AudioStandardizer audioStandardizer) { this.audioStandardizer = audioStandardizer; } @Override public AudioInputStream decode(File inputFile) throws UnsupportedAudioFileException, IOException { MpegAudioFileReader mafr = new MpegAudioFileReader(); AudioInputStream mp3Ais; // AudioInputStream mp3Dmais = null; AudioInputStream result; try { mp3Ais = mafr.getAudioInputStream(inputFile); } catch (Exception e) { /* * This is now a work around (aka hack) for files with stored images in them. The images have to be removed. We load the file as FileInputStream, check * the length of the header and skip it. */ FileInputStream f_in = new FileInputStream(inputFile); Bitstream m = new Bitstream(f_in); long start = m.header_pos(); try { m.close(); } catch (BitstreamException be) { // nop } f_in = new FileInputStream(inputFile); // Skip the header f_in.skip(start); // Now try again with the 'truncated' sound stream mp3Ais = mafr.getAudioInputStream(f_in); } AudioFormat decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, mp3Ais.getFormat().getSampleRate(), 16, mp3Ais.getFormat().getChannels(), mp3Ais.getFormat().getChannels() * 2, mp3Ais.getFormat().getSampleRate(), false); result = new DecodedMpegAudioInputStream(decodedFormat, mp3Ais); // result = new PCMtoPCMCodec().getAudioInputStream(TARGET_ENCODING, mp3Dmais); // result = new MpegFormatConversionProvider().getAudioInputStream(TARGET_ENCODING, mp3Dmais); // result = AudioSystem.getAudioInputStream(TARGET_ENCODING, mp3Dmais); return audioStandardizer.standardize(tidyStream(result)); } @Override public boolean isAbleToDecode(File file) { return mp3File.isOfThisType(file); } @Override public Collection<String> getExtensions() { return newArrayList(mp3File.getFileExtensions()); } /** * Creates a new, fresh {@link AudioInputStream} from an existing one. This showed to cure some MP3-related problems. */ private AudioInputStream tidyStream(AudioInputStream ais) { AudioInputStream result = null; try { // First read stream into byte array byte[] stream = getByteArrayOfUndefStream(ais); // Then create new AudioInputStream from byte array result = new AudioInputStream(new ByteArrayInputStream(stream), ais.getFormat(), AudioSystem.NOT_SPECIFIED); } catch (IOException e) { // nop } return result; } private byte[] getByteArrayOfUndefStream(AudioInputStream inputStream) throws IOException { ArrayList<byte[]> totalBuffer = new ArrayList<>(); int size = 10240000; byte[] metaBuffer = new byte[size]; totalBuffer.add(metaBuffer); // Choose a buffer of 100 KB byte[] buffer = new byte[102400]; int len; int writtenBytes = 0; while ((len = inputStream.read(buffer)) != -1) { if ((writtenBytes + len) <= size) { System.arraycopy(buffer, 0, metaBuffer, writtenBytes, len); writtenBytes += len; } else { int remainingBytes = size - writtenBytes; int outstandingBytes = len - remainingBytes; // Copy remaining bytes to metaBuffer System.arraycopy(buffer, 0, metaBuffer, writtenBytes, remainingBytes); // Change metaBuffer metaBuffer = new byte[size]; totalBuffer.add(metaBuffer); // Copy rest bytes to new buffer System.arraycopy(buffer, remainingBytes, metaBuffer, 0, outstandingBytes); writtenBytes = outstandingBytes; } } // Assemble to result array byte[] result = new byte[((totalBuffer.size() - 1) * size) + writtenBytes]; for (int i = 0; i < totalBuffer.size(); i++) { int bytesToRead = size; if (i == totalBuffer.size() - 1) { bytesToRead = writtenBytes; } System.arraycopy(totalBuffer.get(i), 0, result, size * i, bytesToRead); } return result; } }