Wes Summers
Wes Summers

Reputation: 87

why does the byte array form a wav file not look like an audio wave

The code below records, plays, and "decodes" a wav file. When I graph the byte array from the file, it looks like this:

The recording is of me saying "test", which should look like this:

enter image description here

Does anyone know why the graph of the byte array from the wav file does not look like real audiodata?

Here is the code for the entire activity:

package com.example.wesle.noisemachine;

import android.content.Intent;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.os.Environment;
import android.widget.Toast;

import com.jjoe64.graphview.GraphView;
import com.jjoe64.graphview.series.DataPoint;
import com.jjoe64.graphview.series.LineGraphSeries;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.*;

public class ReceiveScreen extends AppCompatActivity {

    private Button buttonStart, buttonStop, buttonDecode, buttonPlay;
    private String filePath;

    private static final int RECORDER_BPP = 16;
    private static final String AUDIO_RECORDER_FILE_EXT_WAV = ".wav";
    private static final String AUDIO_RECORDER_FOLDER = "AudioRecorder";
    private static final String AUDIO_RECORDER_TEMP_FILE = "record_temp.raw";
    private static final int RECORDER_SAMPLERATE = 44100;
    private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_STEREO;
    private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
    short[] audioData;

    private AudioRecord recorder = null;
    private int bufferSize = 0;
    private Thread recordingThread = null;
    private boolean isRecording = false;
    Complex[] fftTempArray;
    Complex[] fftArray;
    int[] bufferData;
    int bytesRecorded;
    LineGraphSeries<DataPoint> series;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_receive_screen);

        filePath = getFilename();
        final File wavfile = new File(filePath);

        buttonStart = (Button) findViewById(R.id.buttonStart);
        buttonStop = (Button) findViewById(R.id.buttonStop);
        buttonPlay = (Button) findViewById(R.id.buttonPlay);
        buttonDecode = (Button) findViewById(R.id.buttonDecode);
        buttonStop.setEnabled(false);
        buttonDecode.setEnabled(false);
        buttonPlay.setEnabled(false);

        bufferSize = AudioRecord.getMinBufferSize
                (RECORDER_SAMPLERATE,RECORDER_CHANNELS,RECORDER_AUDIO_ENCODING)*3;
        audioData = new short [bufferSize];

        buttonStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                buttonStart.setEnabled(false);
                buttonDecode.setEnabled(false);
                buttonPlay.setEnabled(false);
                recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
                        RECORDER_SAMPLERATE,
                        RECORDER_CHANNELS,
                        RECORDER_AUDIO_ENCODING,
                        bufferSize);
                int i = recorder.getState();
                if (i==1)
                    recorder.startRecording();

                isRecording = true;

                recordingThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        writeAudioDataToFile();
                    }
                }, "AudioRecorder Thread");

                recordingThread.start();

                buttonStop.setEnabled(true);

                Toast.makeText(getApplicationContext(), "Recording started", Toast.LENGTH_LONG).show();

            }
        });

        buttonStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                buttonStop.setEnabled(false);
                if (null != recorder){
                    isRecording = false;

                    int i = recorder.getState();
                    if (i==1)
                        recorder.stop();
                    recorder.release();

                    recorder = null;
                    recordingThread = null;
                }
                copyWaveFile(getTempFilename(),filePath);
                deleteTempFile();

                Toast.makeText(getApplicationContext(), "Recording Completed", Toast.LENGTH_LONG).show();
                buttonStart.setEnabled(true);
                buttonPlay.setEnabled(true);
                buttonDecode.setEnabled(true);
            }
        });

        buttonPlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                buttonStart.setEnabled(false);
                buttonDecode.setEnabled(false);
                buttonPlay.setEnabled(false);
                Toast.makeText(getApplicationContext(), "Recording Playing", Toast.LENGTH_LONG).show();

                Uri myUri1 = Uri.fromFile(wavfile);
                final MediaPlayer mPlayer = new MediaPlayer();
                mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);

                try {
                    mPlayer.setDataSource(getApplicationContext(), myUri1);
                } catch (IllegalArgumentException e) {
                    Toast.makeText(getApplicationContext(), "You might not set the URI correctly!", Toast.LENGTH_LONG).show();
                } catch (SecurityException e) {
                    Toast.makeText(getApplicationContext(), "You might not set the URI correctly!", Toast.LENGTH_LONG).show();
                } catch (IllegalStateException e) {
                    Toast.makeText(getApplicationContext(), "You might not set the URI correctly!", Toast.LENGTH_LONG).show();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                try {
                    mPlayer.prepare();
                } catch (IllegalStateException e) {
                    Toast.makeText(getApplicationContext(), "You might not set the URI correctly!", Toast.LENGTH_LONG).show();
                } catch (IOException e) {
                    Toast.makeText(getApplicationContext(), "You might not set the URI correctly!", Toast.LENGTH_LONG).show();
                }

                mPlayer.start();

                buttonStart.setEnabled(true);
                buttonDecode.setEnabled(true);
                buttonPlay.setEnabled(true);
            }
        });

        buttonDecode.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                buttonStart.setEnabled(false);
                buttonDecode.setEnabled(false);
                buttonPlay.setEnabled(false);

                GraphView thegraph = (GraphView) findViewById(R.id.thegraph);
                series = new LineGraphSeries<DataPoint>();
                double x,y;
                x = 0;

                try {
                    ByteArrayOutputStream outt = new ByteArrayOutputStream();
                    BufferedInputStream in = new BufferedInputStream(new FileInputStream(wavfile));

                    int read;
                    byte[] buff = new byte[1024];
                    while ((read = in.read(buff)) > 0)
                    {
                        outt.write(buff, 0, read);
                    }
                    outt.flush();
                    byte[] audioBytes = outt.toByteArray();

                    //int[][] graphData = getUnscaledAmplitude(audioBytes, 1);

                    for(int i = 0; i < audioBytes.length;i++){


                        //System.out.println(audioBytes[i]);
                        byte curByte = audioBytes[i];
                        //int curByte = graphData[0][i];
                        y = (curByte);
                        series.appendData(new DataPoint(x,y), true, audioBytes.length);
                        x = x + 1;
                        //x = x + (1 / RECORDER_SAMPLERATE);
                    }
                    thegraph.addSeries(series);

                } catch (IOException e) {
                    e.printStackTrace();
                }

                buttonStart.setEnabled(true);
                buttonDecode.setEnabled(true);
                buttonPlay.setEnabled(true);

            }
        });

        //Code for the back button
        Button backbuttonR = (Button) findViewById(R.id.backbuttonR);
        backbuttonR.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivity(new Intent(ReceiveScreen.this, MainActivity.class));
            }
        });

    }

    private String getFilename(){
        String filepath = Environment.getExternalStorageDirectory().getPath();
        File file = new File(filepath,AUDIO_RECORDER_FOLDER);
        System.out.println(file.getAbsolutePath() + "/" + System.currentTimeMillis() + AUDIO_RECORDER_FILE_EXT_WAV);
        if (!file.exists()) {
            file.mkdirs();
        }

        return (file.getAbsolutePath() + "/" + System.currentTimeMillis() + AUDIO_RECORDER_FILE_EXT_WAV);
    }

    private String getTempFilename() {
        String filepath = Environment.getExternalStorageDirectory().getPath();
        File file = new File(filepath,AUDIO_RECORDER_FOLDER);

        if (!file.exists()) {
            file.mkdirs();
        }

        File tempFile = new File(filepath,AUDIO_RECORDER_TEMP_FILE);

        if (tempFile.exists())
            tempFile.delete();

        return (file.getAbsolutePath() + "/" + AUDIO_RECORDER_TEMP_FILE);
    }

    private void writeAudioDataToFile() {
        byte data[] = new byte[bufferSize];
        String filename = getTempFilename();
        FileOutputStream os = null;

        try {
            os = new FileOutputStream(filename);
        } catch (FileNotFoundException e) {
            //TODO Auto-generated catch block
            e.printStackTrace();
        }

        int read = 0;
        if (null != os) {
            while(isRecording) {
                read = recorder.read(data, 0, bufferSize);
                if (read > 0){
                }

                if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                    try {
                        os.write(data);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void deleteTempFile() {
        File file = new File(getTempFilename());
        file.delete();
    }

    private void copyWaveFile(String inFilename,String outFilename){
        FileInputStream in = null;
        FileOutputStream out = null;
        long totalAudioLen = 0;
        long totalDataLen = totalAudioLen + 36;
        long longSampleRate = RECORDER_SAMPLERATE;
        int channels = 2;
        long byteRate = RECORDER_BPP * RECORDER_SAMPLERATE * channels/8;

        byte[] data = new byte[bufferSize];

        try {
            in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;

            System.out.println("File size: " + totalDataLen);

            WriteWaveFileHeader(out, totalAudioLen, totalDataLen,
                    longSampleRate, channels, byteRate);

            while(in.read(data) != -1) {
                out.write(data);
            }

            in.close();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void WriteWaveFileHeader(
            FileOutputStream out, long totalAudioLen,
            long totalDataLen, long longSampleRate, int channels,
            long byteRate) throws IOException
    {
        byte[] header = new byte[44];

        header[0] = 'R';  // RIFF/WAVE header
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        header[12] = 'f';  // 'fmt ' chunk
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        header[16] = 16;  // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        header[20] = 1;  // format = 1
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        header[32] = (byte) (2 * 16 / 8);  // block align
        header[33] = 0;
        header[34] = RECORDER_BPP;  // bits per sample
        header[35] = 0;
        header[36] = 'd';
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);

        out.write(header, 0, 44);
    }

    public static final class Complex {
        // The number stored is x+I*y.
        final private double x, y;
        // I don't want to allow anyone to access these numbers so I've labeled
        // them private.

        /** Construct a point from real and imaginary parts. */
        public Complex(double real_part, double imaginary_part) {
            x=real_part;
            y=imaginary_part;
        }

        /** Construct a real number. */
        public Complex(double real_part) {
            x=real_part;
            y=0;
        }

        // A static constructor.

        /** Construct a complex number from the given polar coordinates. */
        public static Complex fromPolar(double r, double theta) {
            return new Complex(r*Math.cos(theta), r*Math.sin(theta));
        }

        // Basic operations on Complex numbers.

        /** Return the real part. */
        public double re(){
            return x;
        }

        /** Return the imaginary part. */
        public double im(){
            return y;
        }

        /** Return the complex conjugate */
        public Complex conj() {
            return new Complex(x,-y);
        }

        /** Return the square of the absolute value. */
        public double absSquared() {
            return x*x+y*y;
        }

        /** Return the absolute value. */
        public double abs() {
            // The java.lang.Math package contains many useful mathematical functions,
            // including the square root function.
            return Math.sqrt(absSquared());
        }

        // ARITHMETIC

        /** Add a complex number to this one.
         *
         * @param z The complex number to be added.
         * @return A new complex number which is the sum.
         */
        public Complex add(Complex z) {
            return new Complex(x+z.x, y+z.y);
        }

        /** Subtract a complex number from this one.
         *
         * @param z The complex number to be subtracted.
         * @return A new complex number which is the sum.
         */
        public Complex minus(Complex z) {
            return new Complex(x-z.x, y-z.y);
        }

        /** Negate this complex number.
         *
         * @return The negation.
         */
        public Complex neg() {
            return new Complex(-x, -y);
        }

        /** Compute the product of two complex numbers
         *
         * @param z The complex number to be multiplied.
         * @return A new complex number which is the product.
         */
        public Complex mult(Complex z) {
            return new Complex(x*z.x-y*z.y, x*z.y+z.x*y);
        }

        /** Divide this complex number by a real number.
         *
         * @param q The number to divide by.
         * @return A new complex number representing the quotient.
         */
        public Complex div(double q) {
            return new Complex(x/q,y/q);
        }

        /** Return the multiplicative inverse. */
        public Complex inv() {
            // find the square of the absolute value of this complex number.
            double abs_squared=absSquared();
            return new Complex(x/abs_squared, -y/abs_squared);
        }

        /** Compute the quotient of two complex numbers.
         *
         * @param z The complex number to divide this one by.
         * @return A new complex number which is the quotient.
         */
        public Complex div(Complex z) {
            return mult(z.inv());
        }

        /** Return the complex exponential of this complex number. */
        public Complex exp() {
            return new Complex(Math.exp(x)*Math.cos(y),Math.exp(x)*Math.sin(y));
        }


        // FUNCTIONS WHICH KEEP JAVA HAPPY:

        /** Returns this point as a string.
         * The main purpose of this function is for printing the string out,
         * so we return a string in a (fairly) human readable format.
         */
        // The _optional_ override directive "@Override" below just says we are
        // overriding a function defined in a parent class. In this case, the
        // parent is java.lang.Object. All classes in Java have the Object class
        // as a superclass.
        @Override
        public String toString() {
            // Comments:
            // 1) "" represents the empty string.
            // 2) If you add something to a string, it converts the thing you
            // are adding to a string, and then concatentates it with the string.

            // We do some voodoo to make sure the number is displayed reasonably.
            if (y==0) {
                return ""+x;
            }
            if (y>0) {
                return ""+x+"+"+y+"*I";
            }
            // otherwise y<0.
            return ""+x+"-"+(-y)+"*I";
        }

        /** Return true if the object is a complex number which is equal to this complex number. */
        @Override
        public boolean equals(Object obj) {
            // Return false if the object is null
            if (obj == null) {
                return false;
            }
            // Return false if the object is not a Complex number
            if (!(obj instanceof Complex)) {
                return false;
            }

            // Now the object must be a Complex number, so we can convert it to a
            // Complex number.
            Complex other = (Complex) obj;

            // If the x-coordinates are not equal, then return false.
            if (x != other.x) {
                return false;
            }
            // If the y-coordinates are not equal, then return false.
            if (y != other.y) {
                return false;
            }
            // Both parts are equal, so return true.
            return true;
        }
        @Override
        public int hashCode() {
            int hash = 3;
            hash = 83 * hash + (int) (Double.doubleToLongBits(this.x) ^ (Double.doubleToLongBits(this.x) >>> 32));
            hash = 83 * hash + (int) (Double.doubleToLongBits(this.y) ^ (Double.doubleToLongBits(this.y) >>> 32));
            return hash;
        }
    }

    public int[][] getUnscaledAmplitude(byte[] eightBitByteArray, int nbChannels)
    {
        int[][] toReturn = new int[nbChannels][eightBitByteArray.length / (2 * nbChannels)];
        int index = 0;

        for (int audioByte = 0; audioByte < eightBitByteArray.length;)
        {
            for (int channel = 0; channel < nbChannels; channel++)
            {
                // Do the byte to sample conversion.
                int low = (int) eightBitByteArray[audioByte];
                audioByte++;
                int high = (int) eightBitByteArray[audioByte];
                audioByte++;
                int sample = (high << 8) + (low & 0x00ff);

                toReturn[channel][index] = sample;

                if (audioByte == 0) {
                    System.out.println("CHANNEL COUNT");
                }
            }
            index++;
        }

        return toReturn;
    }



}

Upvotes: 1

Views: 1286

Answers (2)

Brian Risk
Brian Risk

Reputation: 1449

If this is 16-bit audio then every other byte will be the least-significant data. Plotting these bytes will make it look very noisy (as you have above).

Think of it like this, in a phone book, people's names are ordered by LastName, FirstName. This is similar to how the two bytes are ordered for 16-bit audio. Let's say the names are:

  • Anders, Zoey
  • Anderson, Sally
  • Andrews, Craig

If you were to "plot" only the last names, they would have a very smooth progression from Anders to Anderson to Andrews. However, if you also included the first names in your plot, it would jump all around from "Anders" to "Zoey" then back down to "Anderson" then up to "Sally", and so on.

Upvotes: 2

Andrii Omelchenko
Andrii Omelchenko

Reputation: 13343

Because, seems, You read it from beginning and does not take into account .wav file header. You should read header, then read data according to header specifications, like in this or that or many other examples.

Upvotes: 0

Related Questions