AudioRecord records intermittent sound in Android L Developer Preview
Asked Answered
B

1

9

I'm recording sound with AudioRecord in PCM16LE format, 8000Hz, 1channel. It records ok in Android versions 2.3.3-4.4.4, but records strange intermittent sound in Android L(5.0) Developer Preview (on nexus 5, nexus 7 and emulator).

Here is the sample of recorded sound (the first half - recording, the second half - playback): https://www.dropbox.com/s/3wcgufua5pphwtt/android_l_sound_record_error.m4a?dl=0

I tried to play recorded sound using different sample rate (4000, 16000) and as 8bit but sound keeps to be intermittent. What the problem could be with this sound?

I'm using this AudioRecordTask to record audio with getAudioRecord() for initializing input (no errors returned during operation; receiving audio chunks sized equally to internalBufferSize value):

public final int SAMPLING_RATE = 8000;

private AudioRecord getAudioRecord() {
    int internalBufferSize = AudioRecord.getMinBufferSize(SAMPLING_RATE,
            AudioFormat.CHANNEL_IN_MONO,
            AudioFormat.ENCODING_PCM_16BIT); //returns 640

    internalBufferSize = 8000; //also tried returned value (640) and values 2560, 30000 - no changes 

    final int SOURCE;
    if (Build.VERSION.SDK_INT < 11) {
        SOURCE = MediaRecorder.AudioSource.MIC;
    } else {
        SOURCE = MediaRecorder.AudioSource.VOICE_COMMUNICATION;
    }

    AudioRecord record = new AudioRecord(SOURCE,
            SAMPLING_RATE,
            AudioFormat.CHANNEL_IN_MONO,
            AudioFormat.ENCODING_PCM_16BIT,
            internalBufferSize);

    int state = record.getState();
    if (state != AudioRecord.STATE_INITIALIZED) {
        try {
            record.release();
        } catch (Exception e) {
        }
        return null;
    }

    if (record.getState() == android.media.AudioRecord.STATE_INITIALIZED) {
        record.startRecording();
    } else {
        record.release();
        return null;
    }
    return record;
}

private class AudioRecordTask extends AsyncTask<Void, Void, Void> {
    final int PARTIAL_BUFFER_SIZE = SAMPLING_RATE;
    final int NECESSARY_BUFFER_SIZE = 15 * PARTIAL_BUFFER_SIZE * Short.SIZE / 8;
    final int FULL_BUFFER_SIZE = NECESSARY_BUFFER_SIZE * 2; //XXX: * 2 for the case when system returns more data than needed
    short[] mBuffer;
    int mTotalSize;
    int mTotalSizeInBytes;
    boolean mResult;
    private Object mLock = new Object();

    @Override
    protected void onPreExecute()
    {
        mIsRecording = true;

        mBuffer = new short[FULL_BUFFER_SIZE];
        mTotalSize = 0;
        mTotalSizeInBytes = 0;
        mResult = false;
    }

    @Override
    protected Void doInBackground(Void... arg0) {
        synchronized (mLock) {
            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
            AudioRecord record = getAudioRecord();
            if (record == null) {
                mResult = false;
                return null;
            }

            for (int i = 0; i < 15 * 100; i++) { //XXX: * 100 to record enough data (system can return lesser than needed)
                int datalen = record.read(mBuffer, mTotalSize, PARTIAL_BUFFER_SIZE);
                if (datalen > 0) {
                    mTotalSize += datalen;
                    mTotalSizeInBytes = mTotalSize*2;
                } else {
                    Log.w("", "error " + datalen + " in AudioRecord.read");
                }
                if (isCancelled() || mTotalSizeInBytes > NECESSARY_BUFFER_SIZE) {
                    break;
                }
            }
            if (record.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
                record.stop();
            }

            record.release();
            mResult = true;
            return null;
        }
    }

    @Override
    protected void onPostExecute(Void r) {
        synchronized (mLock) {
            mIsRecording = false;
            fin();
        }
    }

    @Override
    protected void onCancelled() {
        //XXX: on old Androids (e.g. 2.3.3) onCancelled being called while doInBackground is still running
        synchronized (mLock) {
            mIsRecording = false;
            if (mAbort) {
                return;
            }
            fin();
        }
    }

    private void fin() {
        if (mResult && mTotalSizeInBytes > 0) {
            sendRecordedAudioToServer(mBuffer, mTotalSize, mTotalSizeInBytes);
        } else {
            showError(null);
        }
    }
}
Balmoral answered 7/10, 2014 at 12:54 Comment(0)
B
4

It's a bug in Android L Developer Preview: https://code.google.com/p/android-developer-preview/issues/detail?id=1492

AudioRecord.read for short[] buffer argument returns value in bytes instead of value in shorts.

As a workaround use AudioRecord.read with byte[] buffer.

Balmoral answered 8/10, 2014 at 14:24 Comment(4)
This appears to be fixed in released 5.0, however there is a related issue where the offset parameter is erroneously doubled - see code.google.com/p/android/issues/detail?id=80866 when attempting to submit a patch I was told that is also fixed internally, though no idea when it will appear in a release.Raki
I have just crashed into this (I think) in 5.0.2. Does this mean that if the byte[] buffer read is used it all works right, including the offset parameter, or is that still doubled?Musteline
when using byte[] buffer all is ok including offset (it was so on 5.0 and 5.0.1, haven't tested on 5.0.2 yet)Balmoral
The distinct issue with the offset argument exists from 5.0-5.02 but appears to be fixed in 5.1Raki

© 2022 - 2024 — McMap. All rights reserved.