Search code examples
androidaudiorecordandroid-audiorecord

AudioRecord with new HTC One series Android phones


I have an app on the market that records audio using the AudioRecord class and saves it to the SD card. Users with the new HTC One series of phones are unable to record.

When a recording is started, it saves a 1kb file to the SD card and it stops there. Usually it will at least create a 44KB header so I believe it is stopping somewhere in the prepare() function. I dont have an HTC one to test this on so I am dumbfounded. I have attached the class that I am using to record and I am recording uncompressed audio. In my record activity, I initialize the extAudioRecorder object with the consturctor as follows:

extAudioRecorder = new ExtAudioRecorder(true, 
AudioSource.MIC, 
44100, 
AudioFormat.CHANNEL_IN_MONO, 
AudioFormat.ENCODING_PCM_16BIT);

I feel this should work on a newer phone.

Any help would be appreciated. Here is the full source of the class I am using for recording. It may be also doing the same thing with LG Optimus One phones.

public class ExtAudioRecorder implements Runnable
{
private final static int[] sampleRates = {44100, 22050, 11025, 8000};
private static final String AUDIO_RECORDER_FOLDER = "FOLDER";
String sdPath = Environment.getExternalStorageDirectory().getPath();
File file = new File(sdPath,AUDIO_RECORDER_FOLDER);
String FullFilePath;
String fileName;
static int sampleRate;
private volatile boolean recording = false;
double RecordReadDelayInSeconds = 0;
double RecordDelayInSeconds = 0;


private MediaPlayer mPlayer;
private static String mp3Path = Environment.getExternalStorageDirectory() + "/FOLDER/tmp/tmp.mp3";


 double beatDelayInSeconds = 0;

public ExtAudioRecorder getInstanse(Boolean recordingCompressed)
{
    ExtAudioRecorder result = null;

    if(recordingCompressed)
    {
        result = new ExtAudioRecorder(  false, 
                                        AudioSource.MIC, 
                                        sampleRates[3], 
                                        AudioFormat.CHANNEL_IN_MONO,
                                        AudioFormat.ENCODING_PCM_16BIT);
    }
    else
    {
        int i=0;
        do
         {
            result = new ExtAudioRecorder(  true, 
                                            AudioSource.MIC, 
                                            sampleRates[i], 
                                            AudioFormat.CHANNEL_IN_MONO,
                                            AudioFormat.ENCODING_PCM_16BIT);
            sampleRate = sampleRates[i];

        } while((++i<sampleRates.length) & !(result.getState() == ExtAudioRecorder.State.INITIALIZING));
    }
    return result;
}

/**
* INITIALIZING : recorder is initializing;
* READY : recorder has been initialized, recorder not yet started
* RECORDING : recording
* ERROR : reconstruction needed
* STOPPED: reset needed
*/
public enum State {INITIALIZING, READY, RECORDING, ERROR, STOPPED};

public static final boolean RECORDING_UNCOMPRESSED = true;
public static final boolean RECORDING_COMPRESSED = false;

// The interval in which the recorded samples are output to the file
// Used only in uncompressed mode
private static final int TIMER_INTERVAL = 120;

// Toggles uncompressed recording on/off; RECORDING_UNCOMPRESSED / RECORDING_COMPRESSED
private boolean         rUncompressed;

// Recorder used for uncompressed recording
private AudioRecord     audioRecorder = null;

// Recorder used for compressed recording
private MediaRecorder   mediaRecorder = null;

// Stores current amplitude (only in uncompressed mode)
private int             cAmplitude= 0;

// Output file path
private String          filePath = null;

// Recorder state; see State
private State           state;

// File writer (only in uncompressed mode)
private RandomAccessFile randomAccessWriter;

// Number of channels, sample rate, sample size(size in bits), buffer size, audio source, sample size(see AudioFormat)
private short                    nChannels;
private int                      sRate;
private short                    bSamples;
private int                      bufferSize;
private int                      aSource;
private int                      aFormat;

// Number of frames written to file on each output(only in uncompressed mode)
private int                      framePeriod;

// Buffer for output(only in uncompressed mode)
private byte[]                   buffer;

// Number of bytes written to file after header(only in uncompressed mode)
// after stop() is called, this size is written to the header/data chunk in the wave file
private int                      payloadSize;

/**
*
* Returns the state of the recorder in a RehearsalAudioRecord.State typed object.
* Useful, as no exceptions are thrown.
*
* @return recorder state
*/
public State getState()
{
    return state;
}

/*
*
* Method used for recording.
*
*/
private AudioRecord.OnRecordPositionUpdateListener updateListener = new AudioRecord.OnRecordPositionUpdateListener()
{
    @Override
    public void onPeriodicNotification(AudioRecord recorder)
    {
        if (state != State.STOPPED)
        {
        audioRecorder.read(buffer, 0, buffer.length); // Fill buffer
        try
        { 
            randomAccessWriter.write(buffer); // Write buffer to file
            payloadSize += buffer.length;
            if (bSamples == 16)
            {
                for (int i=0; i<buffer.length/2; i++)
                { // 16bit sample size
                    short curSample = getShort(buffer[i*2], buffer[i*2+1]);
                    if (curSample > cAmplitude)
                    { // Check amplitude
                        cAmplitude = curSample;
                    }
                }
            }
            else    
            { // 8bit sample size
                for (int i=0; i<buffer.length; i++)
                {
                    if (buffer[i] > cAmplitude)
                    { // Check amplitude
                        cAmplitude = buffer[i];
                    }
                }
            }
        }
        catch (IOException e)
        {
            Log.e(ExtAudioRecorder.class.getName(), "Error occured in updateListener, recording is aborted");
            stop();
        }
        }
    }

    @Override
    public void onMarkerReached(AudioRecord recorder)
    {
        // NOT USED
    }
};
/** 
 * 
 * 
 * Default constructor
 * 
 * Instantiates a new recorder, in case of compressed recording the parameters can be left as 0.
 * In case of errors, no exception is thrown, but the state is set to ERROR
 * 
 */ 
public ExtAudioRecorder(boolean uncompressed, int audioSource, int sampleRate, int channelConfig, int audioFormat)
{
    try
    {
        rUncompressed = uncompressed;
        if (rUncompressed)
        { // RECORDING_UNCOMPRESSED
            if (audioFormat == AudioFormat.ENCODING_PCM_16BIT)
            {
                bSamples = 16;
            }
            else
            {
                bSamples = 8;
            }

            if (channelConfig == AudioFormat.CHANNEL_IN_MONO)
            {
                nChannels = 1;
            }
            else
            {
                nChannels = 2;
            }

            aSource = audioSource;
            sRate   = sampleRate;
            aFormat = audioFormat;

            framePeriod = sampleRate * TIMER_INTERVAL / 1000;
            bufferSize = framePeriod * 2 * bSamples * nChannels / 8;
            if (bufferSize < AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat))
            { // Check to make sure buffer size is not smaller than the smallest allowed one 
                bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
                // Set frame period and timer interval accordingly
                framePeriod = bufferSize / ( 2 * bSamples * nChannels / 8 );
                Log.w(ExtAudioRecorder.class.getName(), "Increasing buffer size to " + Integer.toString(bufferSize));
            }

            audioRecorder = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat, bufferSize);

            if (audioRecorder.getState() != AudioRecord.STATE_INITIALIZED)
                throw new Exception("AudioRecord initialization failed");
            audioRecorder.setRecordPositionUpdateListener(updateListener);
            audioRecorder.setPositionNotificationPeriod(framePeriod);
        } else
        { // RECORDING_COMPRESSED
            mediaRecorder = new MediaRecorder();
            mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
            mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);               
        }
        cAmplitude = 0;
        filePath = null;
        state = State.INITIALIZING;
    } catch (Exception e)
    {
        if (e.getMessage() != null)
        {
            Log.e(ExtAudioRecorder.class.getName(), e.getMessage());
        }
        else
        {
            Log.e(ExtAudioRecorder.class.getName(), "Unknown error occured while initializing recording");
        }
        state = State.ERROR;
    }
}

/**
 * Sets output file path, call directly after construction/reset.
 *  
 * @param output file path
 * 
 */
public void setOutputFile(String argPath)
{

    try
    {
        if (state == State.INITIALIZING)
        {
            filePath = argPath;
            if (!rUncompressed)
            {
                mediaRecorder.setOutputFile(sdPath + "/FOLDER/" + filePath);                    
            }
        }
    }
    catch (Exception e)
    {
        if (e.getMessage() != null)
        {
            Log.e(ExtAudioRecorder.class.getName(), e.getMessage());
        }
        else
        {
            Log.e(ExtAudioRecorder.class.getName(), "Unknown error occured while setting output path");
        }
        state = State.ERROR;
    }
}

/**
 * 
 * Returns the largest amplitude sampled since the last call to this method.
 * 
 * @return returns the largest amplitude since the last call, or 0 when not in recording state. 
 * 
 */
public int getMaxAmplitude()
{
    if (state == State.RECORDING)
    {
        if (rUncompressed)
        {
            int result = cAmplitude;
            cAmplitude = 0;
            return result;
        }
        else
        {
            try
            {
                return mediaRecorder.getMaxAmplitude();
            }
            catch (IllegalStateException e)
            {
                return 0;
            }
        }
    }
    else
    {
        return 0;
    }
}


/**
 * 
* Prepares the recorder for recording, in case the recorder is not in the INITIALIZING state and the file path was not set
* the recorder is set to the ERROR state, which makes a reconstruction necessary.
* In case uncompressed recording is toggled, the header of the wave file is written.
* In case of an exception, the state is changed to ERROR
*    
*/
public void prepare()
{
    try
    {
        if (state == State.INITIALIZING)
        {
            if (rUncompressed)
            {
                if ((audioRecorder.getState() == AudioRecord.STATE_INITIALIZED) & (filePath != null))
                {
                    // write file header

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

                    fileName = filePath;
                    FullFilePath = file.getAbsoluteFile() + "/" +  fileName;
                    randomAccessWriter = new RandomAccessFile(FullFilePath, "rw");

                    randomAccessWriter.setLength(0); // Set file length to 0, to prevent unexpected behavior in case the file already existed
                    randomAccessWriter.writeBytes("RIFF");
                    randomAccessWriter.writeInt(0); // Final file size not known yet, write 0 
                    randomAccessWriter.writeBytes("WAVE");
                    randomAccessWriter.writeBytes("fmt ");
                    randomAccessWriter.writeInt(Integer.reverseBytes(16)); // Sub-chunk size, 16 for PCM
                    randomAccessWriter.writeShort(Short.reverseBytes((short) 1)); // AudioFormat, 1 for PCM
                    randomAccessWriter.writeShort(Short.reverseBytes(nChannels));// Number of channels, 1 for mono, 2 for stereo
                    randomAccessWriter.writeInt(Integer.reverseBytes(sRate)); // Sample rate
                    randomAccessWriter.writeInt(Integer.reverseBytes(sRate*bSamples*nChannels/8)); // Byte rate, SampleRate*NumberOfChannels*BitsPerSample/8
                    randomAccessWriter.writeShort(Short.reverseBytes((short)(nChannels*bSamples/8))); // Block align, NumberOfChannels*BitsPerSample/8
                    randomAccessWriter.writeShort(Short.reverseBytes(bSamples)); // Bits per sample
                    randomAccessWriter.writeBytes("data");
                    randomAccessWriter.writeInt(0); // Data chunk size not known yet, write 0

                    buffer = new byte[framePeriod*bSamples/8*nChannels];
                    state = State.READY;
                }
                else
                {
                    Log.e(ExtAudioRecorder.class.getName(), "prepare() method called on uninitialized recorder");
                    state = State.ERROR;
                }
            }
            else
            {
                mediaRecorder.prepare();
                state = State.READY;
            }
        }
        else
        {
            Log.e(ExtAudioRecorder.class.getName(), "prepare() method called on illegal state");
            release();
            state = State.ERROR;
        }
    }
    catch(Exception e)
    {
        if (e.getMessage() != null)
        {
            Log.e(ExtAudioRecorder.class.getName(), e.getMessage());
        }
        else
        {
            Log.e(ExtAudioRecorder.class.getName(), "Unknown error occured in prepare()");
        }
        state = State.ERROR;
    }
}

/**
 * 
 * 
 *  Releases the resources associated with this class, and removes the unnecessary files, when necessary
 *  
 */
public void release()
{
    if (state == State.RECORDING)
    {
        stop();
    }
    else
    {
        if ((state == State.READY) & (rUncompressed))
        {
            try
            {
                randomAccessWriter.close(); // Remove prepared file
            }
            catch (IOException e)
            {
                Log.e(ExtAudioRecorder.class.getName(), "I/O exception occured while closing output file");
            }
            //delete file
            //(new File(filePath)).delete();
        }
    }

    if (rUncompressed)
    {
        if (audioRecorder != null)
        {
            audioRecorder.release();
        }
    }
    else
    {
        if (mediaRecorder != null)
        {
            mediaRecorder.release();
        }
    }
}

/**
 * 
 * 
 * Resets the recorder to the INITIALIZING state, as if it was just created.
 * In case the class was in RECORDING state, the recording is stopped.
 * In case of exceptions the class is set to the ERROR state.
 * 
 */
public void reset()
{
    try
    {
        if (state != State.ERROR)
        {
            release();
            filePath = null; // Reset file path
            cAmplitude = 0; // Reset amplitude
            if (rUncompressed)
            {
                audioRecorder = new AudioRecord(aSource, sRate, nChannels+1, aFormat, bufferSize);
            }
            else
            {
                mediaRecorder = new MediaRecorder();
                mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
                mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
            }
            state = State.INITIALIZING;
        }
    }
    catch (Exception e)
    {
        Log.e(ExtAudioRecorder.class.getName(), e.getMessage());
        state = State.ERROR;
    }
}

/**
 * 
 * 
 * Starts the recording, and sets the state to RECORDING.
 * Call after prepare().
 * 
 */
public void start()
{
    if (state == State.READY)
    {
        if (rUncompressed)
        {
            payloadSize = 0;
            RecordReadDelayInSeconds = 0;
            RecordDelayInSeconds = 0;

            mPlayer = new MediaPlayer();
            try {
                mPlayer.setDataSource(mp3Path);
                mPlayer.prepare();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalStateException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            long recordstarted = System.nanoTime();
            audioRecorder.startRecording();
            long recordstopped = System.nanoTime();
            long recordDelay = recordstopped - recordstarted;

            double RecordDelayInSeconds = recordDelay / 1000000.0;

            Log.i("StartRecording() Delay in seconds", String.valueOf(RecordDelayInSeconds));

            long recordreadstarted = System.nanoTime();
            audioRecorder.read(buffer, 0, buffer.length);
            long recordreadstopped = System.nanoTime();
            long recordreadDelay = recordreadstopped - recordreadstarted;
            RecordReadDelayInSeconds = recordreadDelay / 1000000.0;
            Log.i("Record read() Delay in seconds", String.valueOf(RecordReadDelayInSeconds));


            long mediastarted = System.nanoTime();
            mPlayer.start();
            long  mediastopped = System.nanoTime();
            long  beatDelay = mediastopped - mediastarted;

            beatDelayInSeconds = 0;
            beatDelayInSeconds = (beatDelay) / 1000000000.0;
            Log.i("Beat Delay in seconds", String.valueOf(beatDelayInSeconds));




        }
        else
        {
            mediaRecorder.start();
        }
        state = State.RECORDING;
    }
    else
    {
        Log.e(ExtAudioRecorder.class.getName(), "start() called on illegal state");
        state = State.ERROR;
    }
}

/**
 * 
 * 
 *  Stops the recording, and sets the state to STOPPED.
 * In case of further usage, a reset is needed.
 * Also finalizes the wave file in case of uncompressed recording.
 * 
 */
public void stop()
{
    if (state == State.RECORDING)
    {
        if (rUncompressed)
        {
            audioRecorder.stop();

            try
            {
                randomAccessWriter.seek(4); // Write size to RIFF header
                randomAccessWriter.writeInt(Integer.reverseBytes(36+payloadSize));

                randomAccessWriter.seek(40); // Write size to Subchunk2Size field
                randomAccessWriter.writeInt(Integer.reverseBytes(payloadSize));

                randomAccessWriter.close();

                //mPlayer.stop();
            }
            catch(IOException e)
            {
                Log.e(ExtAudioRecorder.class.getName(), "I/O exception occured while closing output file");
                state = State.ERROR;
            }
        }
        else
        {
            mediaRecorder.stop();
        }
        state = State.STOPPED;
    }
    else
    {
        Log.e(ExtAudioRecorder.class.getName(), "stop() called on illegal state");
        state = State.ERROR;
    }
}

/* 
 * 
 * Converts a byte[2] to a short, in LITTLE_ENDIAN format
 * 
 */
private short getShort(byte argB1, byte argB2)
{
    return (short)(argB1 | (argB2 << 8));
}

public String[] GetFileProperties()
{
    String[] fileProperties = new String[3];
    fileProperties[0] = FullFilePath;
    fileProperties[1] = fileName;
    fileProperties[2] = Integer.toString(sampleRate);

    return fileProperties;
}

@Override
public void run() {
    android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
    start();

}

public void StopMediaPlayer()
{

    try {
        mPlayer.stop();
    } catch (IllegalStateException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

public boolean isRecording() {
    return recording;
}

public int GetSampleRate()
{
    return sampleRate;
}

public double GetBeatDelay()
{
    return beatDelayInSeconds;
}

public int GetRecordDelay()
{
    return (int)(RecordReadDelayInSeconds +RecordDelayInSeconds);

}

}


Solution

  • I was able to get those values working:
    AudioSource.MIC
    44100
    AudioFormat.CHANNEL_IN_MONO
    AudioFormat.ENCODING_PCM_16BIT

    Using this code to try all values:
    https://github.com/lnanek/Misc/tree/master/TestOneXAudioRecord

    It outputs this log entry on One X (AT&T), One X (Europe), and One S (T-mobile):
    D/TestOneXAudioRecordActivity(10500): Attempting rate 44100Hz, bits: 2, channel: 16
    D/TestOneXAudioRecordActivity(10500): AudioRecord.getState = AudioRecord.STATE_INITIALIZED
    I/TestOneXAudioRecordActivity(10500): Initialized an AudioRecord = android.media.AudioRecord@40ddbcf0

    At one point the program couldn't initialize an AudioRecord, but I rebooted the device and it started working. I think another program was using the mic or one of my previous attempts hadn't released an AudioRecord, meaning the hardware was busy. So make sure you try restarting the phone and not having any other recording apps going.