added library for audio-processing
This commit is contained in:
1 parent
7975382705
commit
6eabb15f07
20 files changed
+4174
No files matched your search
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Darth Affe <http://wyrez.org> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This Code is based on the source provided with a tutorial by
|
||||
* http://www.badlogicgames.com (as of 16.05.2013).
|
||||
* The original source can be found here:
|
||||
* http://code.google.com/p/audio-analysis/source/browse/trunk/src/com/badlogic/audio/io/Decoder.java
|
||||
*/
|
||||
package org.wyrez.audio.decoder;
|
||||
|
||||
/**
|
||||
* Interface for audio decoders that return successive amplitude frames.
|
||||
*
|
||||
* @author Darth Affe
|
||||
*/
|
||||
public interface Decoder {
|
||||
|
||||
/**
|
||||
* Reads in samples.length stereo samples from the decoder. Returns the
|
||||
* actual number read in. If this number is smaller than samples.length then
|
||||
* the end of stream has been reached.
|
||||
*
|
||||
* @param samples The samples array to write the samples to
|
||||
* @returns The number of samples actually read
|
||||
*/
|
||||
public int readSamplesStereo(float[] samples);
|
||||
|
||||
/**
|
||||
* Reads in samples.length mono samples from the decoder. If the audio
|
||||
* source isn't mono they channels are merged. Returns the actual number
|
||||
* read in. If this number is smaller than samples.length then the end of
|
||||
* stream has been reached. All Channels are merged by calculating the
|
||||
* average.
|
||||
*
|
||||
* @param samples The samples array to write the samples to
|
||||
* @returns The number of samples actually read
|
||||
*/
|
||||
public int readSamplesMergedMono(float[] samples);
|
||||
|
||||
/**
|
||||
* Returns the number of channels in the audio source.
|
||||
*
|
||||
* @returns The channel count
|
||||
*/
|
||||
public int getChannelCount();
|
||||
|
||||
/**
|
||||
* Returns the sampling rate in Hz of the audio source.
|
||||
*
|
||||
* @returns the sampling rate as float
|
||||
*/
|
||||
public float getSamplingRate();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Darth Affe <http://wyrez.org> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.wyrez.audio.decoder;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Factory used to create Audio-Decoder.
|
||||
*
|
||||
* @author Darth Affe
|
||||
*/
|
||||
public enum DecoderFactory {
|
||||
|
||||
WAVE, MP3;
|
||||
|
||||
/**
|
||||
* Creates a decoder for the selected codec.
|
||||
*
|
||||
* @returns The new decoder
|
||||
* @throws Exception in case the Decoder could not be created
|
||||
*/
|
||||
public Decoder create(InputStream is) throws Exception {
|
||||
switch (this) {
|
||||
case WAVE:
|
||||
return new WaveDecoder(is);
|
||||
case MP3:
|
||||
return new MP3Decoder(is);
|
||||
default: //impossible
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a decoder for the given file, if there isn't one an exception is
|
||||
* thrown.
|
||||
*
|
||||
* @returns The new decoder
|
||||
* @throws Exception in case the Decoder could not be created
|
||||
* @throws IllegalArgumentException in case there is now decoder for the
|
||||
* given file
|
||||
*/
|
||||
public static Decoder create(String file) throws Exception, IllegalArgumentException {
|
||||
int i = file.lastIndexOf('.');
|
||||
if (i == 0) {
|
||||
throw new IllegalArgumentException("Can't detect a filetype");
|
||||
}
|
||||
String extension = file.substring(i + 1);
|
||||
switch (extension.toUpperCase()) {
|
||||
case "MP3":
|
||||
return MP3.create(new FileInputStream(file));
|
||||
case "WAV":
|
||||
case "WAVE":
|
||||
return WAVE.create(new FileInputStream(file));
|
||||
default:
|
||||
throw new IllegalArgumentException("Filetype \"" + extension + "\" is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Darth Affe <http://wyrez.org> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This Code is based on the source provided with a tutorial by
|
||||
* http://www.badlogicgames.com (as of 16.05.2013).
|
||||
* The original source can be found here:
|
||||
* http://code.google.com/p/audio-analysis/source/browse/trunk/src/com/badlogic/audio/io/MP3Decoder.java
|
||||
*/
|
||||
package org.wyrez.audio.decoder;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PushbackInputStream;
|
||||
import java.util.HashMap;
|
||||
import javax.sound.sampled.AudioFileFormat;
|
||||
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.Header;
|
||||
import javazoom.spi.mpeg.sampled.file.MpegAudioFileFormat;
|
||||
import javazoom.spi.mpeg.sampled.file.MpegAudioFormat;
|
||||
import javazoom.spi.mpeg.sampled.file.MpegEncoding;
|
||||
import javazoom.spi.mpeg.sampled.file.MpegFileFormatType;
|
||||
import org.tritonus.share.TDebug;
|
||||
import org.tritonus.share.sampled.FloatSampleBuffer;
|
||||
import org.tritonus.share.sampled.file.TAudioFileReader;
|
||||
|
||||
/**
|
||||
* A decoder for MP3 files with a maximum of 2 channels.
|
||||
*
|
||||
* @author Darth Affe
|
||||
*/
|
||||
public class MP3Decoder implements Decoder {
|
||||
|
||||
private AudioInputStream in;
|
||||
private FloatSampleBuffer buffer;
|
||||
private byte[] bytes;
|
||||
private int channels;
|
||||
private float samplingRate;
|
||||
|
||||
/**
|
||||
* Constructor, sets the input stream to read the MP3 file from. Audio files
|
||||
* with more than 2 channels aren't supported!
|
||||
*
|
||||
* @param stream The input stream.
|
||||
* @throws Exception in case the input stream couldn't be read properly
|
||||
*/
|
||||
public MP3Decoder(InputStream stream) throws Exception {
|
||||
InputStream in = new BufferedInputStream(stream, 1024 * 1024);
|
||||
this.in = new MP3AudioFileReader().getAudioInputStream(in);
|
||||
AudioFormat baseFormat = this.in.getFormat();
|
||||
this.channels = baseFormat.getChannels();
|
||||
if (channels > 2) {
|
||||
throw new IllegalArgumentException("more than 2 channels aren't supported");
|
||||
}
|
||||
this.samplingRate = baseFormat.getSampleRate();
|
||||
AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
|
||||
baseFormat.getSampleRate(), 16,
|
||||
baseFormat.getChannels(),
|
||||
baseFormat.getChannels() * 2,
|
||||
baseFormat.getSampleRate(), false);
|
||||
this.in = AudioSystem.getAudioInputStream(format, this.in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to read in samples.length stereo samples. Returns the number of
|
||||
* samples actually read. Guarantees that samples.length samples are read in
|
||||
* if there was enough data in the stream.
|
||||
*
|
||||
* @param samples The samples array to write the samples to
|
||||
* @return The number of samples actually read
|
||||
*/
|
||||
@Override
|
||||
public int readSamplesStereo(float[] samples) {
|
||||
if (buffer == null || buffer.getSampleCount() < samples.length / 2) {
|
||||
buffer = new FloatSampleBuffer(in.getFormat().getChannels(), samples.length / 2, in.getFormat().getSampleRate());
|
||||
bytes = new byte[buffer.getByteArrayBufferSize(in.getFormat())];
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
int readBytes = 0;
|
||||
try {
|
||||
readBytes = in.read(bytes, read, bytes.length - read);
|
||||
} catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
if (readBytes == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
read += readBytes;
|
||||
while (readBytes != -1 && read != bytes.length) {
|
||||
try {
|
||||
readBytes = in.read(bytes, read, bytes.length - read);
|
||||
} catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
read += readBytes;
|
||||
}
|
||||
|
||||
int frameCount = bytes.length / in.getFormat().getFrameSize();
|
||||
buffer.setSamplesFromBytes(bytes, 0, in.getFormat(), 0, frameCount);
|
||||
|
||||
for (int i = 0, j = 0; i < buffer.getSampleCount(); i++, j += 2) {
|
||||
samples[j] = buffer.getChannel(0)[i];
|
||||
samples[j + 1] = buffer.getChannel(buffer.getChannelCount() == 2 ? 1 : 0)[i];
|
||||
}
|
||||
return buffer.getSampleCount() * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to read in samples.length samples, merging stereo to a mono channel
|
||||
* by averaging. Returns the number of samples actually read. Guarantees
|
||||
* that samples.length samples are read in if there was enough data in the
|
||||
* stream.
|
||||
*
|
||||
* @param samples The samples array to write the samples to
|
||||
* @return The number of samples actually read
|
||||
*/
|
||||
@Override
|
||||
public int readSamplesMergedMono(float[] samples) {
|
||||
if (buffer == null || buffer.getSampleCount() < samples.length) {
|
||||
buffer = new FloatSampleBuffer(in.getFormat().getChannels(), samples.length, in.getFormat().getSampleRate());
|
||||
bytes = new byte[buffer.getByteArrayBufferSize(in.getFormat())];
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
int readBytes = 0;
|
||||
try {
|
||||
readBytes = in.read(bytes, read, bytes.length - read);
|
||||
} catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
if (readBytes == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
read += readBytes;
|
||||
while (readBytes != -1 && read != bytes.length) {
|
||||
try {
|
||||
readBytes = in.read(bytes, read, bytes.length - read);
|
||||
} catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
read += readBytes;
|
||||
}
|
||||
|
||||
int frameCount = bytes.length / in.getFormat().getFrameSize();
|
||||
buffer.setSamplesFromBytes(bytes, 0, in.getFormat(), 0, frameCount);
|
||||
|
||||
for (int i = 0; i < buffer.getSampleCount(); i++) {
|
||||
if (buffer.getChannelCount() == 2) {
|
||||
samples[i] = (buffer.getChannel(0)[i] + buffer.getChannel(1)[i]) / 2;
|
||||
} else {
|
||||
samples[i] = buffer.getChannel(0)[i];
|
||||
}
|
||||
}
|
||||
return buffer.getSampleCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of channels in the audio source. (This value can't be
|
||||
* greater than 2)
|
||||
*
|
||||
* @returns The channel count
|
||||
*/
|
||||
@Override
|
||||
public int getChannelCount() {
|
||||
return channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sampling rate in Hz of the audio source.
|
||||
*
|
||||
* @returns the sampling rate as float
|
||||
*/
|
||||
@Override
|
||||
public float getSamplingRate() {
|
||||
return samplingRate;
|
||||
}
|
||||
|
||||
class MP3AudioFileReader extends TAudioFileReader {
|
||||
|
||||
public static final int INITAL_READ_LENGTH = 128000;
|
||||
private static final int MARK_LIMIT = INITAL_READ_LENGTH + 1;
|
||||
private final AudioFormat.Encoding[][] sm_aEncodings = {
|
||||
{MpegEncoding.MPEG2L1, MpegEncoding.MPEG2L2, MpegEncoding.MPEG2L3},
|
||||
{MpegEncoding.MPEG1L1, MpegEncoding.MPEG1L2, MpegEncoding.MPEG1L3},
|
||||
{MpegEncoding.MPEG2DOT5L1, MpegEncoding.MPEG2DOT5L2,
|
||||
MpegEncoding.MPEG2DOT5L3},};
|
||||
|
||||
protected MP3AudioFileReader() {
|
||||
super(MARK_LIMIT, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AudioFileFormat getAudioFileFormat(InputStream inputStream, long mediaLength)
|
||||
throws UnsupportedAudioFileException, IOException {
|
||||
HashMap aff_properties = new HashMap();
|
||||
HashMap af_properties = new HashMap();
|
||||
int mLength = (int) mediaLength;
|
||||
int size = inputStream.available();
|
||||
PushbackInputStream pis = new PushbackInputStream(inputStream, MARK_LIMIT);
|
||||
byte head[] = new byte[22];
|
||||
pis.read(head);
|
||||
|
||||
// Check for WAV, AU, and AIFF, Ogg Vorbis, Flac, MAC file formats.
|
||||
// Next check for Shoutcast (supported) and OGG (unsupported) streams.
|
||||
if ((head[0] == 'R') && (head[1] == 'I') && (head[2] == 'F')
|
||||
&& (head[3] == 'F') && (head[8] == 'W') && (head[9] == 'A')
|
||||
&& (head[10] == 'V') && (head[11] == 'E')) {
|
||||
int isPCM = ((head[21] << 8) & 0x0000FF00) | ((head[20]) & 0x00000FF);
|
||||
throw new UnsupportedAudioFileException("WAV PCM stream found");
|
||||
|
||||
} else if ((head[0] == '.') && (head[1] == 's') && (head[2] == 'n')
|
||||
&& (head[3] == 'd')) {
|
||||
throw new UnsupportedAudioFileException("AU stream found");
|
||||
} else if ((head[0] == 'F') && (head[1] == 'O') && (head[2] == 'R')
|
||||
&& (head[3] == 'M') && (head[8] == 'A') && (head[9] == 'I')
|
||||
&& (head[10] == 'F') && (head[11] == 'F')) {
|
||||
throw new UnsupportedAudioFileException("AIFF stream found");
|
||||
} else if (((head[0] == 'M') | (head[0] == 'm'))
|
||||
&& ((head[1] == 'A') | (head[1] == 'a'))
|
||||
&& ((head[2] == 'C') | (head[2] == 'c'))) {
|
||||
throw new UnsupportedAudioFileException("APE stream found");
|
||||
} else if (((head[0] == 'F') | (head[0] == 'f'))
|
||||
&& ((head[1] == 'L') | (head[1] == 'l'))
|
||||
&& ((head[2] == 'A') | (head[2] == 'a'))
|
||||
&& ((head[3] == 'C') | (head[3] == 'c'))) {
|
||||
throw new UnsupportedAudioFileException("FLAC stream found");
|
||||
} // Shoutcast stream ?
|
||||
else if (((head[0] == 'I') | (head[0] == 'i'))
|
||||
&& ((head[1] == 'C') | (head[1] == 'c'))
|
||||
&& ((head[2] == 'Y') | (head[2] == 'y'))) {
|
||||
pis.unread(head);
|
||||
// Load shoutcast meta data.
|
||||
} // Ogg stream ?
|
||||
else if (((head[0] == 'O') | (head[0] == 'o'))
|
||||
&& ((head[1] == 'G') | (head[1] == 'g'))
|
||||
&& ((head[2] == 'G') | (head[2] == 'g'))) {
|
||||
throw new UnsupportedAudioFileException("Ogg stream found");
|
||||
} // No, so pushback.
|
||||
else {
|
||||
pis.unread(head);
|
||||
}
|
||||
// MPEG header info.
|
||||
int nVersion = AudioSystem.NOT_SPECIFIED;
|
||||
int nLayer = AudioSystem.NOT_SPECIFIED;
|
||||
// int nSFIndex = AudioSystem.NOT_SPECIFIED;
|
||||
int nMode = AudioSystem.NOT_SPECIFIED;
|
||||
int FrameSize = AudioSystem.NOT_SPECIFIED;
|
||||
// int nFrameSize = AudioSystem.NOT_SPECIFIED;
|
||||
int nFrequency = AudioSystem.NOT_SPECIFIED;
|
||||
int nTotalFrames = AudioSystem.NOT_SPECIFIED;
|
||||
float FrameRate = AudioSystem.NOT_SPECIFIED;
|
||||
int BitRate = AudioSystem.NOT_SPECIFIED;
|
||||
int nChannels = AudioSystem.NOT_SPECIFIED;
|
||||
int nHeader = AudioSystem.NOT_SPECIFIED;
|
||||
int nTotalMS = AudioSystem.NOT_SPECIFIED;
|
||||
boolean nVBR = false;
|
||||
AudioFormat.Encoding encoding = null;
|
||||
try {
|
||||
Bitstream m_bitstream = new Bitstream(pis);
|
||||
aff_properties.put("mp3.header.pos",
|
||||
new Integer(m_bitstream.header_pos()));
|
||||
Header m_header = m_bitstream.readFrame();
|
||||
// nVersion = 0 => MPEG2-LSF (Including MPEG2.5), nVersion = 1 => MPEG1
|
||||
nVersion = m_header.version();
|
||||
if (nVersion == 2) {
|
||||
aff_properties.put("mp3.version.mpeg", Float.toString(2.5f));
|
||||
} else {
|
||||
aff_properties.put("mp3.version.mpeg",
|
||||
Integer.toString(2 - nVersion));
|
||||
}
|
||||
// nLayer = 1,2,3
|
||||
nLayer = m_header.layer();
|
||||
aff_properties.put("mp3.version.layer", Integer.toString(nLayer));
|
||||
// nSFIndex = m_header.sample_frequency();
|
||||
nMode = m_header.mode();
|
||||
aff_properties.put("mp3.mode", new Integer(nMode));
|
||||
nChannels = nMode == 3 ? 1 : 2;
|
||||
aff_properties.put("mp3.channels", new Integer(nChannels));
|
||||
nVBR = m_header.vbr();
|
||||
af_properties.put("vbr", new Boolean(nVBR));
|
||||
aff_properties.put("mp3.vbr", new Boolean(nVBR));
|
||||
aff_properties.put("mp3.vbr.scale", new Integer(m_header.vbr_scale()));
|
||||
FrameSize = m_header.calculate_framesize();
|
||||
aff_properties.put("mp3.framesize.bytes", new Integer(FrameSize));
|
||||
if (FrameSize < 0) {
|
||||
throw new UnsupportedAudioFileException("Invalid FrameSize : " + FrameSize);
|
||||
}
|
||||
nFrequency = m_header.frequency();
|
||||
aff_properties.put("mp3.frequency.hz", new Integer(nFrequency));
|
||||
FrameRate = (float) ((1.0 / (m_header.ms_per_frame())) * 1000.0);
|
||||
aff_properties.put("mp3.framerate.fps", new Float(FrameRate));
|
||||
if (FrameRate < 0) {
|
||||
throw new UnsupportedAudioFileException("Invalid FrameRate : " + FrameRate);
|
||||
}
|
||||
if (mLength != AudioSystem.NOT_SPECIFIED) {
|
||||
aff_properties.put("mp3.length.bytes", new Integer(mLength));
|
||||
nTotalFrames = m_header.max_number_of_frames(mLength);
|
||||
aff_properties.put("mp3.length.frames", new Integer(nTotalFrames));
|
||||
}
|
||||
BitRate = m_header.bitrate();
|
||||
af_properties.put("bitrate", new Integer(BitRate));
|
||||
aff_properties.put("mp3.bitrate.nominal.bps", new Integer(BitRate));
|
||||
nHeader = m_header.getSyncHeader();
|
||||
encoding = sm_aEncodings[nVersion][nLayer - 1];
|
||||
aff_properties.put("mp3.version.encoding", encoding.toString());
|
||||
if (mLength != AudioSystem.NOT_SPECIFIED) {
|
||||
nTotalMS = Math.round(m_header.total_ms(mLength));
|
||||
aff_properties.put("duration", new Long((long) nTotalMS * 1000L));
|
||||
}
|
||||
aff_properties.put("mp3.copyright", new Boolean(m_header.copyright()));
|
||||
aff_properties.put("mp3.original", new Boolean(m_header.original()));
|
||||
aff_properties.put("mp3.crc", new Boolean(m_header.checksums()));
|
||||
aff_properties.put("mp3.padding", new Boolean(m_header.padding()));
|
||||
InputStream id3v2 = m_bitstream.getRawID3v2();
|
||||
if (id3v2 != null) {
|
||||
aff_properties.put("mp3.id3tag.v2", id3v2);
|
||||
}
|
||||
if (TDebug.TraceAudioFileReader) {
|
||||
TDebug.out(m_header.toString());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new UnsupportedAudioFileException("not a MPEG stream:"
|
||||
+ e.getMessage());
|
||||
}
|
||||
// Deeper checks ?
|
||||
int cVersion = (nHeader >> 19) & 0x3;
|
||||
if (cVersion == 1) {
|
||||
throw new UnsupportedAudioFileException(
|
||||
"not a MPEG stream: wrong version");
|
||||
}
|
||||
int cSFIndex = (nHeader >> 10) & 0x3;
|
||||
if (cSFIndex == 3) {
|
||||
|
||||
throw new UnsupportedAudioFileException(
|
||||
"not a MPEG stream: wrong sampling rate");
|
||||
}
|
||||
|
||||
AudioFormat format = new MpegAudioFormat(encoding, (float) nFrequency,
|
||||
AudioSystem.NOT_SPECIFIED, nChannels, -1, FrameRate, true, af_properties);
|
||||
return new MpegAudioFileFormat(MpegFileFormatType.MP3, format,
|
||||
nTotalFrames, mLength, aff_properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Darth Affe <http://wyrez.org> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This Code is based on the source provided with a tutorial by
|
||||
* http://www.badlogicgames.com (as of 16.05.2013).
|
||||
* The original source can be found here:
|
||||
* http://code.google.com/p/audio-analysis/source/browse/trunk/src/com/badlogic/audio/io/WaveDecoder.java
|
||||
*/
|
||||
package org.wyrez.audio.decoder;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.InputStream;
|
||||
import org.wyrez.audio.util.EndianDataInputStream;
|
||||
|
||||
/**
|
||||
* A simple converter that can read in the PCM data from a Wav file, converting
|
||||
* the data to signed 32-bit floats in the range [-1,1]. This only supports
|
||||
* 16-bit Wav files with a maximum of 2 channels.
|
||||
*
|
||||
* @author Darth Affe
|
||||
*/
|
||||
public class WaveDecoder implements Decoder {
|
||||
|
||||
private final float MAX_VALUE = 1.0f / Short.MAX_VALUE;//inverse max short value as float
|
||||
private final EndianDataInputStream in;
|
||||
private final int channels;
|
||||
private final float samplingRate;
|
||||
|
||||
/**
|
||||
* Constructor, sets the input stream to read the Wav file from. Audio files
|
||||
* with more than 2 channels aren't supported!
|
||||
*
|
||||
* @param stream The input stream.
|
||||
* @throws Exception in case the input stream couldn't be read properly
|
||||
*/
|
||||
public WaveDecoder(InputStream stream) throws Exception {
|
||||
if (stream == null) {
|
||||
throw new IllegalArgumentException("Input stream must not be null");
|
||||
}
|
||||
|
||||
in = new EndianDataInputStream(new BufferedInputStream(stream, 1024 * 1024));
|
||||
if (!in.read4ByteString().equals("RIFF")) {
|
||||
throw new IllegalArgumentException("not a wav");
|
||||
}
|
||||
|
||||
in.readIntLittleEndian();
|
||||
|
||||
if (!in.read4ByteString().equals("WAVE")) {
|
||||
throw new IllegalArgumentException("expected WAVE tag");
|
||||
}
|
||||
|
||||
if (!in.read4ByteString().equals("fmt ")) {
|
||||
throw new IllegalArgumentException("expected fmt tag");
|
||||
}
|
||||
|
||||
if (in.readIntLittleEndian() != 16) {
|
||||
throw new IllegalArgumentException("expected wave chunk size to be 16");
|
||||
}
|
||||
|
||||
if (in.readShortLittleEndian() != 1) {
|
||||
throw new IllegalArgumentException("expected format to be 1");
|
||||
}
|
||||
|
||||
channels = in.readShortLittleEndian();
|
||||
if (channels > 2) {
|
||||
throw new IllegalArgumentException("more than 2 channels aren't supported");
|
||||
}
|
||||
samplingRate = in.readIntLittleEndian();
|
||||
|
||||
in.readIntLittleEndian();
|
||||
in.readShortLittleEndian();
|
||||
int fmt = in.readShortLittleEndian();
|
||||
|
||||
if (fmt != 16) {
|
||||
throw new IllegalArgumentException("Only 16-bit signed format supported");
|
||||
}
|
||||
|
||||
if (!in.read4ByteString().equals("data")) {
|
||||
throw new RuntimeException("expected data tag");
|
||||
}
|
||||
|
||||
in.readIntLittleEndian();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to read in samples.length stereo samples, converting non float
|
||||
* formats to float 32-bit. Returns the number of samples actually read.
|
||||
* Guarantees that samples.length samples are read in if there was enough
|
||||
* data in the stream.
|
||||
*
|
||||
* @param samples The samples array to write the samples to
|
||||
* @return The number of samples actually read
|
||||
*/
|
||||
@Override
|
||||
public int readSamplesStereo(float[] samples) {
|
||||
switch (channels) {
|
||||
case 1:
|
||||
return readMonoAsStereo(samples);
|
||||
case 2:
|
||||
return readRealStereo(samples);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private int readRealStereo(float[] samples) {
|
||||
int readSamples = 0;
|
||||
for (int i = 0; i < samples.length - 1; i += 2) {
|
||||
try {
|
||||
samples[i] = in.readShortLittleEndian() * MAX_VALUE;
|
||||
samples[i + 1] = in.readShortLittleEndian() * MAX_VALUE;
|
||||
readSamples += 2;
|
||||
} catch (Exception ex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return readSamples;
|
||||
}
|
||||
|
||||
private int readMonoAsStereo(float[] samples) {
|
||||
int readSamples = 0;
|
||||
for (int i = 0; i < samples.length - 1; i += 2) {
|
||||
try {
|
||||
float sample = in.readShortLittleEndian() * MAX_VALUE;
|
||||
samples[i] = sample;
|
||||
samples[i + 1] = sample;
|
||||
readSamples += 2;
|
||||
} catch (Exception ex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return readSamples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to read in samples.length samples, merging stereo to a mono channel
|
||||
* by averaging and converting non float formats to float 32-bit. Returns
|
||||
* the number of samples actually read. Guarantees that samples.length
|
||||
* samples are read in if there was enough data in the stream.
|
||||
*
|
||||
* @param samples The samples array to write the samples to
|
||||
* @return The number of samples actually read
|
||||
*/
|
||||
@Override
|
||||
public int readSamplesMergedMono(float[] samples) {
|
||||
int readSamples = 0;
|
||||
for (int i = 0; i < samples.length; i++) {
|
||||
float sample = 0;
|
||||
try {
|
||||
for (int j = 0; j < channels; j++) {
|
||||
int shortValue = in.readShortLittleEndian();
|
||||
sample += (shortValue * MAX_VALUE);
|
||||
}
|
||||
sample /= channels;
|
||||
samples[i] = sample;
|
||||
readSamples++;
|
||||
} catch (Exception ex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return readSamples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of channels in the audio source. (This value can't be
|
||||
* greater than 2)
|
||||
*
|
||||
* @returns The channel count
|
||||
*/
|
||||
@Override
|
||||
public int getChannelCount() {
|
||||
return channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sampling rate in Hz of the audio source.
|
||||
*
|
||||
* @returns the sampling rate as floatF
|
||||
*/
|
||||
@Override
|
||||
public float getSamplingRate() {
|
||||
return samplingRate;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user