I am trying to play a audio stream that is returned to me by a server via UDP. The server uses DPCM to encode the audio, thus every byte contains two audio samples. When I play the audio with 8 bits/sample everything works fine, but when I try with 16 doing AudioFormat DPCM = new AudioFormat(8000,16,1,true,false); the clip is shorter and not so clear. What am I doing wrong?
ByteArrayOutputStream sound_buffer = new ByteArrayOutputStream();
clientRequest = new DatagramPacket( sound_request_buffer, sound_request_buffer.length );
server.send(clientRequest);
for(int i=0;i<100;i++){
buffer = new byte[128];
serverResponse = new DatagramPacket( buffer, buffer.length);
client.receive(serverResponse);
sound_buffer.write(buffer);
}
byte[] encoded_sound = sound_buffer.toByteArray();
byte[] decoded_sound = new byte[2*encoded_sound.length];
byte msnibble = (byte)((encoded_sound[0]>>4) & 0x000F);
decoded_sound[0] = (byte)(msnibble - 8);
byte lsnibble = (byte)(encoded_sound[0] & 0x000F );
decoded_sound[1] = (byte) (decoded_sound[0] + lsnibble - 8);
for(int i=1;i<encoded_sound.length;i++){
msnibble = (byte)((encoded_sound[i] >> 4) & 0x000F);
decoded_sound[2*i] = (byte)(decoded_sound[2*i-1] + msnibble - 8);
lsnibble = (byte)(encoded_sound[i] & 0x000F );
decoded_sound[2*i+1] = (byte)(decoded_sound[2*i] + lsnibble - 8);
}
AudioFormat DPCM = new AudioFormat(8000,8,1,true,false);
SourceDataLine lineOut=AudioSystem.getSourceDataLine(DPCM);
lineOut.open(DPCM,decoded_sound.length);
lineOut.start();
lineOut.write(decoded_sound,0,decoded_sound.length);
The problem is that you are giving the SourceDataLine 8-bit audio and telling it to play it as if it were 16-bit audio. This will make it halve the playback time (because it uses twice the number of bits per sample). It also does weird stuff with the actual numbers that are used for the sound, but I'm not exactly sure what (I haven't tested your example.)
The AudioFormat doesn't format the audio, it tells the SourceDataLine how your audio is currently formatted so that it plays it correctly.
I'm not really sure what you want to do, and I guess it would depend on why you want 16-bit audio. You might need to request 16-bit audio from the server instead of 8-bit, or you might not even need the audio to be 16-bit.
Related
I'm currently working on an application that plays back sound. I implemented playback for standard WAV File with the Java Sound API, no problems there, everything working fine. Now I want to add support for MP3 as well, but I'm having a strange problem: the playback gets distorted. I'm trying to figure out what I'm doing wrong, I would appreciate any leads in the right direction.
I'm using the Mp3SPI (http://www.javazoom.net/mp3spi/documents.html) for playing back the Mp3 Files.
I have already tried to take a look at the output and recorded a wav-file with the output I get from the mp3, then I compared the waveforms of the original and the recorded file. As it turns out, in the recorded file there are a lot of samples that are 0, or very close to it. Longer tones get broken up and the waveform returns to 0 all the time, then jumping back to the place the waveform is in the original.
I open the file like this:
private AudioInputStream mp3;
private AudioInputStream rawMp3;
private void openMP3(File file) {
// open the Audio INput Stream
try {
rawMp3 = AudioSystem.getAudioInputStream(file);
AudioFormat baseFormat = rawMp3.getFormat();
AudioFormat decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
baseFormat.getSampleRate(),
16,
baseFormat.getChannels(),
baseFormat.getChannels() * 2,
baseFormat.getSampleRate(),
false);
mp3 = AudioSystem.getAudioInputStream(decodedFormat, rawMp3);
} catch (UnsupportedAudioFileException | IOException ex) {
Logger.getLogger(SoundFile.class.getName()).log(Level.SEVERE, null, ex);
}
}
The part where I read the Mp3 File:
byte[] data = new byte[length];
// read the data into the buffer
int nBytesRead = 0;
while (nBytesRead != - 1 && nBytesRead < length) {
nBytesRead = mp3.read(data, 0, data.length - nBytesRead);
}
Also I convert the byte-array to doubles, perhaps I do something wrong here (I'm fairly new to using bitwise operators, so maybe there is the problem
double[][] frameBuffer = new double[2][1024]; // 2 channel stereo buffer
int nFramesRead = 0;
int byteIndex = 0;
// convert the data into double and write it to frameBuffer
for (int i = 0; i < length; ++i) {
for (int c = 0; c < 2; ++c) {
byte a = data[byteIndex++];
byte b = data[byteIndex++];
int val = a | b << 8; // a is the least significant byte. | functions as a + here. b << 8 moves 8 zeroes to the end of b.
frameBuffer[c][i] = (double) val / (double) Short.MAX_VALUE;
nFramesRead++;
}
}
The double-array is then later used to play back the sound. When playing a wav file, I do the exact same thing to the buffer, so I'm pretty sure it has to be something during the read process, not me sending faulty bytes to the ouput.
I would expect this to work out of the box with Mp3SPI, but somehow something breaks the audio along the way.
I am also open to trying other libraries to play back the MP3, if you have any recommendations. Just a Decoder for the raw MP3 Data would actually be enough.
As it turns out, the AudioFormat from the mp3 (input) and the AudioFormat of the output didnt match, obviously resulting in distortion. So with those matched up, playback is fine!
I've confirmed my program "works" with a variety of signed integer output formats. However, when I attempt to use 32-bit floating point, the output audio metadata indicates it to be 32-bit signed integer, and this results in broken playback.
Here's my audio format:
AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_FLOAT,
48000, // Hz sample rate
32, // bits per sample
2, // channels
8, // bytes per frame
48000, // Hz frame rate
false), // not big-endian
This is sent to a processor function (which I've confirmed "works" using other output formats):
public void mixToFile(AudioFormat format,
String outputPath,
int totalFrames) throws Exception {
ByteBuffer outputBytes = byteBufferOf(mix()); // the big show
AudioInputStream ais = new AudioInputStream(
new ByteArrayInputStream(outputBytes.array()), outputFormat,
totalFrames
);
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File(outputPath));
}
The result is failure; the file has metadata format 32-bit signed int, see:
Playing WAVE '/tmp/output.wav' : Signed 32 bit Little Endian, Rate 48000 Hz, Stereo
I'm looking for the equivalent of How to write wav file with 32-bit float data?
which I've dealt with before, manually setting the wFormat tag in the 'fmt' chunk to WAVE_FORMAT_IEEE_FLOAT (3) when writing a RIFF container.
Is it possible to achieve this using AudioSystem.write, AudioInputStream and AudioFormat?
I am writing an Android application, which sends recorded sound to a server and I need to adapt its format to the one which is required. I was told that the server's audio format is specified by javax.sound.sampled.AudioFormat class constructor with the following parameters: AudioFormat(44100, 8, 1, true, true), which means that the required sound should have 44100 sample rate, 8 bit sample size, mono channel, be signed and encoded with big endian byte order. My question is how can I convert my recorded sound to the one I want? I think that the biggest problem might be Android's 16b restriction as far as the smallest sample size is concerned
You can record 44100 8bit directly by AudioRecord, specifying the format in the constructor
int bufferSize = Math.max(
AudioRecord.getMinBufferSize(44100,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_8BIT),
ENOUGH_SIZE_FOR_BUFFER);
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_8BIT, bufferSize);
then pull data from audioRecord, using read(byte[], int, int) method:
byte[] myBuf = new byte[bufferSize];
audioRecord.startRecording();
while (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
int l = audioRecord.read(myBuf, 0, myBuf.length);
if (l > 0) {
// process data
}
}
in this case the data in the buffer will be as you want: 8 bit, mono, 44100.
But, some devices may not support 8 bit recording. In this case you can record the data in 16 bit format, and obtain it using read(short[], int, int) method. In this case you need to resample data on your own:
short[] recordBuf = new short[bufferSize];
byte[] myBuf = new byte[bufferSize];
...
int l = audioRecord.read(recordBuf, 0, recordBuf.length);
if (l > 0) {
for (int i = 0; i < l; i++) {
myBuf[i] = (byte)(recordBuffer[I] >> 8);
}
// process data
}
Using the same approach, you can resample any PCM format to any another format;
I'm trying to generate sound with Java. In the end, I'm willing to continuously send sound to the sound card, but for now I would be able to send a unique sound wave.
So, I filled an array with 44100 signed integers representing a simple sine wave, and I would like to send it to my sound card, but I just can't get it to work.
int samples = 44100; // 44100 samples/s
int[] data = new int[samples];
// Generate all samples
for ( int i=0; i<samples; ++i )
{
data[i] = (int) (Math.sin((double)i/(double)samples*2*Math.PI)*(Integer.MAX_VALUE/2));
}
And I send it to a sound line using:
AudioFormat format = new AudioFormat(Encoding.PCM_SIGNED, 44100, 16, 1, 1, 44100, false);
Clip clip = AudioSystem.getClip();
AudioInputStream inputStream = new AudioInputStream(ais,format,44100);
clip.open(inputStream);
clip.start();
My problem resides between these to code snippets. I just can't find a way to convert my int[] to an input stream!
Firstly I think you want short samples rather than int:
short[] data = new short[samples];
because your AudioFormat specifies 16-bit samples. short is 16-bits wide but int is 32 bits.
An easy way to convert it to a stream is:
Allocate a ByteBuffer
Populate it using putShort calls
Wrap the resulting byte[] in a ByteArrayInputStream
Create an AudioInputStream from the ByteArrayInputStream and format
Example:
float frameRate = 44100f; // 44100 samples/s
int channels = 2;
double duration = 1.0;
int sampleBytes = Short.SIZE / 8;
int frameBytes = sampleBytes * channels;
AudioFormat format =
new AudioFormat(Encoding.PCM_SIGNED,
frameRate,
Short.SIZE,
channels,
frameBytes,
frameRate,
true);
int nFrames = (int) Math.ceil(frameRate * duration);
int nSamples = nFrames * channels;
int nBytes = nSamples * sampleBytes;
ByteBuffer data = ByteBuffer.allocate(nBytes);
double freq = 440.0;
// Generate all samples
for ( int i=0; i<nFrames; ++i )
{
double value = Math.sin((double)i/(double)frameRate*freq*2*Math.PI)*(Short.MAX_VALUE);
for (int c=0; c<channels; ++ c) {
int index = (i*channels+c)*sampleBytes;
data.putShort(index, (short) value);
}
}
AudioInputStream stream =
new AudioInputStream(new ByteArrayInputStream(data.array()), format, nFrames*2);
Clip clip = AudioSystem.getClip();
clip.open(stream);
clip.start();
clip.drain();
Note: I changed your AudioFormat to stereo, because it threw an exception when I requested a mono line. I also increased the frequency of your waveform to something in the audible range.
Update - the previous modification (writing directly to the data line) was not necessary - using a Clip works fine. I have also introduced some variables to make the calculations clearer.
If you want to play a simple Sound, you should use a SourceDataLine.
Here's an example:
import javax.sound.sampled.*;
public class Sound implements Runnable {
//Specify the Format as
//44100 samples per second (sample rate)
//16-bit samples,
//Mono sound,
//Signed values,
//Big-Endian byte order
final AudioFormat format=new AudioFormat(44100f,16,2,true,true);
//Your output line that sends the audio to the speakers
SourceDataLine line;
public Sound(){
try{
line=AudioSystem.getSourceDataLine(format);
line.open(format);
}catch(LineUnavailableExcecption oops){
oops.printStackTrace();
}
new Thread(this).start();
}
public void run(){
//a buffer to store the audio samples
byte[] buffer=new byte[1000];
int bufferposition=0;
//a counter to generate the samples
long c=0;
//The pitch of your sine wave (440.0 Hz in this case)
double wavelength=44100.0/440.0;
while(true){
//Generate a sample
short sample=(short) (Math.sin(2*Math.PI*c/wavelength)*32000);
//Split the sample into two bytes and store them in the buffer
buffer[bufferposition]=(byte) (sample>>>8);
bufferposition++;
buffer[bufferposition]=(byte) (sample & 0xff);
bufferposition++;
//if the buffer is full, send it to the speakers
if(bufferposition>=buffer.length){
line.write(buffer,0,buffer.length);
line.start();
//Reset the buffer
bufferposition=0;
}
}
//Increment the counter
c++;
}
public static void main(String[] args){
new Sound();
}
}
In this example you're continuosly generating a sine wave, but you can use this code to play sound from any source you want. You just have to make sure that you format the samples right. In this case, I'm using raw, uncompressed 16-bit samples at a sample rate of 44100 Hz. However, if you want to play audio from a file, you can use a Clip object
public void play(File file){
Clip clip=AudioSystem.getClip();
clip.open(AudioSystem.getAudioInputStream(file));
clip.loop(1);
}
First of all i'm working on a little project to see the spectrum from some sounds.
I got this working with a microphone:
alt text http://img25.imageshack.us/img25/4271/spectrumanalyzerfourier.png
The image above is just me talking and shouting through a microphone for a few seconds. This looks good to me.
But when I try to read an MP3 file and make a spectogram image of it it looks a bit different. I tried the Aphex Twin - Windowlicker where you should normally see a face in the spectrogram image or at least some more darker colors. But it doesn't look so good:
alt text http://img10.imageshack.us/img10/3475/aphextwinhmm.png
Here is what I did with the microphone:
byte tempBuffer[] = new byte[10000];
ByteArrayOutputStream out = new ByteArrayOutputStream();
counter = 20;
// Microphone
while (counter != 0) {
int count = line.read(tempBuffer, 0, tempBuffer.length);
if (count > 0) {
out.write(tempBuffer, 0, count);
}
counter--;
}
out.close();
// FFT code below ...
byte audio[] = out.toByteArray();
// ...
And this is how I do it with the MP3:
I used the same code to do the transformation and visualization only the audio capturing part is different (I only adjusted the hight in the drawing method to see if there is a difference but there wasn't one):
byte tempBuffer[] = new byte[10000];
ByteArrayOutputStream out = new ByteArrayOutputStream();
FileInputStream input = null;
File mp3 = new File("Aphex Twin - Widowlicker.mp3");
input = new FileInputStream(mp3);
int len;
while((len = input.read(tempBuffer)) > 0) {
out.write(tempBuffer, 0, len);
}
out.close();
input.close();
// FFT code below ...
byte audio[] = out.toByteArray();
// ...
It would be nice if somebody could point me out what I am doing wrong with the MP3 file.
These are my settings:
Sample rate: 44100
Bit per sample: 8
Channels: 1 (mono)
signed: true
big endian: true (i'm using AudioFormat in Java)
tempBuffer to read audio: 10000 ( byte tempBuffer[] = new byte[10000]; )
and for the FFT I split the audio in chuncks of 4096 (must be a power of 2)
By the way: are these settings ok or should I use 16bps or stereo or is 10000 for the buffer too much or 4096 to small/big ?
Thanks in advance
MP3 is a compressed audio format. You should first decompress the data before you can use it as an audio stream comparable to the data from your microphone. The raw MP3 data has maximum entropy and should look much like white noise, which it does in you spectrogram.