2

Am developing an android app that has the feature to record the user speech. For this I have used the AndroidRecord Audio API.

Currently the pcm file(recorded audio file - recordedAudio.pcm) getting generated successfully in the sd card. But am not able to play that file. I tried in PC also with windows media palyer and some other players. But nothing helps.

Following are my code snippet.

private int minBufSize;
private AudioRecord recorder;
private int sampleRate = 44100;
private int channelConfig = AudioFormat.CHANNEL_IN_MONO;
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
private boolean status;

minBufSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig,
            audioFormat);
status = true;
startStreaming();

public void startStreaming() {
Thread streamThread = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            String filePath = Environment.getExternalStorageDirectory()
                    .getPath() + "/audioRecord.pcm";
            FileOutputStream fileOutputStreamObj = null;
            try {
                fileOutputStreamObj = new FileOutputStream(filePath);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                Log.e(TAG, "Exception" + e.getMessage());
            }

                // short[] sData = new short[minBufSize];
                byte[] buffer = new byte[minBufSize];

                // recorder = findAudioRecord();
                recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
                        sampleRate, channelConfig, audioFormat, minBufSize);
                Log.d(TAG, "Recorder initialized");

                recorder.startRecording();

                while (status) {
                    // reading data from MIC into buffer
                    minBufSize = recorder.read(buffer, 0, buffer.length);
                    try {
                        // writes the data to file from buffer
                        // stores the voice buffer
                        // byte bData[] = short2byte(sData);

                        fileOutputStreamObj.write(buffer, 0, buffer.length);
                    } catch (IOException e) {
                        e.printStackTrace();
                        Log.e(TAG, "Exception" + e.getMessage());
                    }
                    // mConnection.sendBinaryMessage(buffer);
                    System.out.println("MinBufferSize: " + minBufSize);
                }

            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "Exception" + e.getMessage());
            }
        }

    });
    streamThread.start();
}

Please help me on this. Thanks in advance.

M Vignesh
  • 1,586
  • 2
  • 18
  • 55
  • How did you try to play the file? By the looks of it you're saving uncompressed PCM without any headers. So why are you giving the file a .mp3 extension? – Michael Sep 22 '14 at 09:24
  • I have tried by giving .mp3 and some other extensions. Yes I have not added any headers and uncompressed. IS it must to add the headers? – M Vignesh Sep 22 '14 at 09:28
  • 1
    Currently your file doesn't contain any information that tells the player what kind of audio the file contains. So if you want the file to be playable on a PC you'll probably have to put the audio data in some sort of container, like WAV. – Michael Sep 22 '14 at 09:30
  • So can you please give me a code snippet to add the header. Change my code and can you please post the answer. – M Vignesh Sep 22 '14 at 09:32
  • 1
    The WAV format is documented (see e.g. https://ccrma.stanford.edu/courses/422/projects/WaveFormat/), so it should not be that difficult to implement a WAV writer yourself. – Michael Sep 22 '14 at 09:53
  • Thanks for your help. This saves me some more time. – M Vignesh Sep 22 '14 at 09:55
  • 1
    You can add code for playing the recorded audio track. Adding a WAV header is also not required. You can use Android `AudioTrack` to play the raw PCM file. – Saurabh Meshram Sep 22 '14 at 11:47

2 Answers2

3

You don't have to convert it into WAV and Play.
AudioTrack can directly play the recorded Audio.

Following is a Code snippet to Record audio into a file using AudioRecord and playback the same using AudioTrack API.

The operation is controlled from User using Buttons.


Code

private int BufferSize;
byte[] buffer = new byte[BufferSize];

/* AudioRecord and AudioTrack Object */
private AudioRecord record = null;
private AudioTrack track = null;

/* Audio Configuration */
private int sampleRate = 44100;
private int channelConfig = AudioFormat.CHANNEL_IN_MONO;
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;

private boolean isRecording = true;
private Thread recordingThread = null;

The Audio Configuration can change as per device.
Refer to this question.


GUI has three buttons, Record, Stop and Play

protected void onCreate(Bundle savedInstanceState) 
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    setButtonHandlers();

    /* Set Button Visibility */
    enableButton(R.id.btnStartRec,true);
    enableButton(R.id.btnStopRec,false);
    enableButton(R.id.btnStartPlay,false);

    BufferSize = AudioRecord.getMinBufferSize(sampleRate, 
                       channelConfig, audioFormat); 
}

/* Function to Enable/Disable Buttons */
private void enableButton(int id,boolean isEnable){
    ((Button)findViewById(id)).setEnabled(isEnable);
}

/* Assign OnClickListener to Buttons */
private void setButtonHandlers() {
    ((Button)findViewById(R.id.btnStartRec)).setOnClickListener(btnClick);
    ((Button)findViewById(R.id.btnStopRec)).setOnClickListener(btnClick);
    ((Button)findViewById(R.id.btnStartPlay)).setOnClickListener(btnClick);
}

Handling Button click:

private View.OnClickListener btnClick = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        switch(v.getId()){
        case R.id.btnStartRec:{
            Log.d(TAG, "Start Recording");
            enableButton(R.id.btnStartRec,false);
            enableButton(R.id.btnStopRec,true);
            startRecording();
            break;
        }
        case R.id.btnStopRec:{
            Log.d(TAG, "Stop Recording");
            enableButton(R.id.btnStartRec,true);
            enableButton(R.id.btnStopRec,false);
            stopRecording();
            enableButton(R.id.btnStartPlay,true);
            break;
        }
        case R.id.btnStartPlay:{
            Log.d(TAG, "Play Recording");
            enableButton(R.id.btnStartRec,false);
            enableButton(R.id.btnStopRec,false);
            StartPlaying();
            break;
        }
        }
    }
};

Code for Start Recording

private void startRecording()
{
    record = new AudioRecord(AudioSource.DEFAULT, sampleRate, 
                                channelConfig, audioFormat, BufferSize);
    if (AudioRecord.STATE_INITIALIZED == record.getState())
        record.startRecording();

    isRecording = true;

    /* Run a thread for Recording */
    recordingThread = new Thread(new Runnable() {
        @Override
        public void run() {
            writeAudioDataToFile();
        }
    },"AudioRecorder Thread");
    recordingThread.start();
}


private void writeAudioDataToFile()
{
    byte data[] = new byte[BufferSize];

    /* Record audio to following file */
    String filename = "/sdcard/audiofile.pcm";
    FileOutputStream os = null;

    try {
        os = new FileOutputStream(filename);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }

    int read_bytes = 0;

    if(null != os){
        while(isRecording)
        {
            read_bytes = record.read(data, 0, BufferSize);

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

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

Code for Stop Recording

private void stopRecording()
{
    if(null != record)
    {
        isRecording = false;

        if (AudioRecord.STATE_INITIALIZED == record.getState()) 
        {
            record.stop();
            record.release();
            Log.d(TAG, "===== Recording Audio Completed ===== ");
        }

        record = null;
        recordingThread = null;
    }
}   

Code for Playing the Audio file:

public void startPlaying()
{
    enableButton(R.id.btnStartPlay,false);

    int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, 
            AudioFormat.CHANNEL_OUT_MONO, 
            AudioFormat.ENCODING_PCM_16BIT);

    track = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, 
                            AudioFormat.CHANNEL_OUT_MONO, 
                            AudioFormat.ENCODING_PCM_16BIT, minBufferSize, 
                            AudioTrack.MODE_STREAM);

    int i = 0;
    byte[] temp = new byte[minBufferSize];

    try {
        FileInputStream fin = new FileInputStream("/sdcard/audiofile.pcm");
        Log.d(TAG, "===== Opening File for Playing : /sdcard/audiofile.pcm ===== ");

        DataInputStream dis = new DataInputStream(fin); 

        track.play();
        while((i = dis.read(temp, 0, minBufferSize)) > -1)
        {
            track.write(temp, 0, i);
        }

        Log.d(TAG, "===== Playing Audio Completed ===== ");
        track.stop();
        track.release();
        dis.close();
        fin.close();

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    enableButton(R.id.btnStartRec,true);
}

Please include the following in AndroidManifest.xml

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" > </uses-permission>
<uses-permission android:name="android.permission.RECORD_AUDIO" >  </uses-permission>

The activity_main.xml looks like this.
The string.xml looks like this.

The above code is working and tested.

You can also do the same, without a file and using a intermediate buffer.
See: Audio Recording and Streaming in Android

Community
  • 1
  • 1
Saurabh Meshram
  • 8,106
  • 3
  • 23
  • 32
  • Actually I have to play this audio file in the server side not in the mobile. So Windows media players can play only the supported formats not the pcm file directly. So only I have implemented like this. – M Vignesh Sep 23 '14 at 05:40
  • 1
    Yeah in that case you will have to convert it to WAV or MP3. I assumed you are trying to play on the same side and hence my above answer. – Saurabh Meshram Sep 23 '14 at 05:58
1

Yes finally I found the answer with the clue of Michael's Comment above.

Am posting here the working code.

The Client Side Code as Follow's, From the client side am streaming the audio data to the web socket server.

private int minBufSize;
private AudioRecord recorder;
private int sampleRate = 44100;
private int channelConfig = AudioFormat.CHANNEL_IN_MONO;
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;

minBufSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig,
                audioFormat);
startStreaming();

public void startStreaming() {
        Thread streamThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {

                    byte[] buffer = new byte[minBufSize];

                    recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
                            sampleRate, channelConfig, audioFormat, minBufSize);
                    Log.d(TAG, "Recorder initialized");

                    recorder.startRecording();

                    while (status) {

                        // reading data from MIC into buffer
                        minBufSize = recorder.read(buffer, 0, buffer.length);

                        mConnection.sendBinaryMessage(buffer);
                        System.out.println("MinBufferSize: " + minBufSize);
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                    Log.e(TAG, "Exception" + e.getMessage());
                }
            }

        });
        streamThread.start();
    }

The Server Side Code added implementation as follows, First the server will create the .pcm from the streamed data. Then from that pcm file it will create the wave file by adding header.

  @OnMessage
  public void onMessage(byte[] data, boolean arg1)
  {
    if ((!this.currentCommand.equals("stop")) && 
      (this.currentCommand.equals("start")))
      try {
        System.out.println("Starting new recording.");
        FileOutputStream fOut = new FileOutputStream(this.f2, true);
        fOut.write(data);
        fOut.close();

        properWAV(this.f2, 111133.0F);
      }
      catch (Exception e) {
        e.printStackTrace();
      }
  }

private void properWAV(File fileToConvert, float newRecordingID)
  {
    try {
      long mySubChunk1Size = 16L;
      int myBitsPerSample = 16;
      int myFormat = 1;
      long myChannels = 1L;
      long mySampleRate = 44100L;
      long myByteRate = mySampleRate * myChannels * myBitsPerSample / 8L;
      int myBlockAlign = (int)(myChannels * myBitsPerSample / 8L);

      byte[] clipData = getBytesFromFile(fileToConvert);

      long myDataSize = clipData.length;
      long myChunk2Size = myDataSize * myChannels * myBitsPerSample / 8L;
      long myChunkSize = 36L + myChunk2Size;

      OutputStream os = new FileOutputStream(new File("D:/audio/" + newRecordingID + ".wav"));
      BufferedOutputStream bos = new BufferedOutputStream(os);
      DataOutputStream outFile = new DataOutputStream(bos);

      outFile.writeBytes("RIFF");
      outFile.write(intToByteArray((int)myChunkSize), 0, 4);
      outFile.writeBytes("WAVE");
      outFile.writeBytes("fmt ");
      outFile.write(intToByteArray((int)mySubChunk1Size), 0, 4);
      outFile.write(shortToByteArray((short)myFormat), 0, 2);
      outFile.write(shortToByteArray((short)(int)myChannels), 0, 2);
      outFile.write(intToByteArray((int)mySampleRate), 0, 4);
      outFile.write(intToByteArray((int)myByteRate), 0, 4);
      outFile.write(shortToByteArray((short)myBlockAlign), 0, 2);
      outFile.write(shortToByteArray((short)myBitsPerSample), 0, 2);
      outFile.writeBytes("data");
      outFile.write(intToByteArray((int)myDataSize), 0, 4);
      outFile.write(clipData);

      outFile.flush();
      outFile.close();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }

  private static byte[] intToByteArray(int i)
  {
    byte[] b = new byte[4];
    b[0] = (byte)(i & 0xFF);
    b[1] = (byte)(i >> 8 & 0xFF);
    b[2] = (byte)(i >> 16 & 0xFF);
    b[3] = (byte)(i >> 24 & 0xFF);
    return b;
  }

  public static byte[] shortToByteArray(short data)
  {
    return new byte[] { (byte)(data & 0xFF), (byte)(data >>> 8 & 0xFF) };
  }

  public byte[] getBytesFromFile(File file)
    throws IOException
  {
    byte[] buffer = new byte[(int)file.length()];
    InputStream ios = null;
    try {
      ios = new FileInputStream(file);
      if (ios.read(buffer) == -1)
        throw new IOException("EOF reached while trying to read the whole file");
    }
    finally {
      try {
        if (ios != null)
          ios.close();
      }
      catch (IOException localIOException)
      {
      }
    }
    try
    {
      if (ios != null)
        ios.close();
    }
    catch (IOException localIOException1)
    {
    }
    return buffer;
  }

Hope this one saves many of the developer's time.

M Vignesh
  • 1,586
  • 2
  • 18
  • 55