AudioRecord with Gain Adjustment not working on Samsung Device
Asked Answered
F

2

5

I have written code for recording audio file using AudioRecord and while writing file on SD card i am making two version.

Version 1 Recorded file is saved on SD Card as it is.

Version 2 I am applying Gain feature on recorded file and saving on SD card.

This works awesome on Sony Ericson mobiles.Also audio volume is boost to great extent.

But i am struggling to make it work on Samsung Devices.

when i play recorded file it sound like Talking Tom :P

Initially i thought Samusung device did not like the combinations i have used to create AudioRecorder.

So i used following approach in which i loop into available configuration and use best configuration to initialize AudioRecord.

public AudioRecord findAudioRecord() {
    for (int rate: mSampleRates) {
        for (short audioFormat: new short[] {
            AudioFormat.ENCODING_PCM_8BIT, AudioFormat.ENCODING_PCM_16BIT
        }) {
            for (short channelConfig: new short[] {
                AudioFormat.CHANNEL_IN_MONO, AudioFormat.CHANNEL_IN_STEREO
            }) {
                try {
                    Log.i("vipul", "Attempting rate " + rate + "Hz, bits: " + audioFormat + ", channel: " + channelConfig);
                    int bufferSize = AudioRecord.getMinBufferSize(rate, channelConfig, audioFormat);

                    if (bufferSize != AudioRecord.ERROR_BAD_VALUE) {
                        // check if we can instantiate and have a success
                        AudioRecord recorder = new AudioRecord(
                        AudioSource.DEFAULT, rate, channelConfig, audioFormat, bufferSize);

                        if (recorder.getState() == AudioRecord.STATE_INITIALIZED) return recorder;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    return null;
}

Below is Code that is working good on Sony mobiles.But Struggling to work on Samsung Devices.

public class EnvironmentRecorder extends Activity implements OnClickListener {

    private static final int RECORDER_BPP = 16;
    private static final String AUDIO_RECORDER_FILE_EXT_WAV = ".wav";
    private static final String AUDIO_RECORDER_FOLDER = "MyRecorder";
    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;
    private Button start, stop;
    private AudioRecord recorder = null;
    private int bufferSize = 0;
    private Thread recordingThread = null;
    private boolean isRecording = false;
    private static int[] mSampleRates = new int[] {
        8000, 11025, 22050, 44100
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        start = (Button) findViewById(R.id.start);
        stop = (Button) findViewById(R.id.stop);
        start.setOnClickListener(this);
        stop.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
        case R.id.start:
            startRecord();
            break;
        case R.id.stop:
            stopRecording();
            break;
        }
    }

    public EnvironmentRecorder() {

        try {
            bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE, RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

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

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

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

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

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

        return (file.getAbsolutePath() + "/" + "Gained" + 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);
    }

    public AudioRecord findAudioRecord() {
        for (int rate: mSampleRates) {
            for (short audioFormat: new short[] {
                AudioFormat.ENCODING_PCM_8BIT, AudioFormat.ENCODING_PCM_16BIT
            }) {
                for (short channelConfig: new short[] {
                    AudioFormat.CHANNEL_IN_MONO, AudioFormat.CHANNEL_IN_STEREO
                }) {
                    try {
                        Log.v("vipul", "Attempting rate " + rate + "Hz, bits: " + audioFormat + ", channel: " + channelConfig);
                        int bufferSize = AudioRecord.getMinBufferSize(rate, channelConfig, audioFormat);

                        if (bufferSize != AudioRecord.ERROR_BAD_VALUE) {
                            // check if we can instantiate and have a success
                            AudioRecord recorder = new AudioRecord(
                            AudioSource.DEFAULT, rate, channelConfig, audioFormat, bufferSize);

                            if (recorder.getState() == AudioRecord.STATE_INITIALIZED) return recorder;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return null;
    }

    public void startRecord() {
        /*
         * recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
         * RECORDER_SAMPLERATE, RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING,
         * bufferSize);
         */

        recorder = findAudioRecord();

        recorder.startRecording();

        isRecording = true;

        recordingThread = new Thread(new Runnable() {

            @Override
            public void run() {
                writeAudioDataToFile();
            }
        }, "AudioRecorder Thread");

        recordingThread.start();
    }

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

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

        int read = 0;

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

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

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

    public void stopRecording() {
        if (null != recorder) {
            isRecording = false;

            recorder.stop();
            recorder.release();

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

    }

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

        file.delete();
    }

    private void copyWaveFile(String inFilename, String outFileName1, String outFileName2) {
        FileInputStream in = null;
        FileOutputStream out1 = null, out2 = 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);
            out1 = new FileOutputStream(outFileName1);
            out2 = new FileOutputStream(outFileName2);
            totalAudioLen = in .getChannel().size();
            totalDataLen = totalAudioLen + 36;

            WriteWaveFileHeader(out1, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
            WriteWaveFileHeader(out2, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);

            while ( in .read(data) != -1) {

                out1.write(data); // Writing Non-Gained Data

                float rGain = 2.5f;
                for (int i = 0; i < data.length / 2; i++) {

                    short curSample = getShort(data[i * 2], data[i * 2 + 1]);
                    if (rGain != 1) {
                        // apply gain
                        curSample *= rGain;
                        // convert back from short sample that was "gained" to
                        // byte data
                        byte[] a = getByteFromShort(curSample);
                        // modify buffer to contain the gained sample
                        data[i * 2] = a[0];
                        data[i * 2 + 1] = a[1];
                    }

                }

                out2.write(data); // Writing Gained Data
            }
            out1.close();
            out2.close(); in .close();

            Toast.makeText(this, "Done!!", Toast.LENGTH_LONG).show();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private short getShort(byte argB1, byte argB2) {
        return (short)((argB1 & 0xff) | (argB2 << 8));

    }

    private byte[] getByteFromShort(short x) {
        // variant 1 - noise
        byte[] a = new byte[2];
        a[0] = (byte)(x & 0xff);
        a[1] = (byte)((x >> 8) & 0xff);

        // variant 2 - noise and almost broke my ears - very loud
        // ByteBuffer buffer = ByteBuffer.allocate(2);
        // buffer.putShort(x);
        // buffer.flip();

        return a;
    }

    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';
        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';
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        header[16] = 16;
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        header[20] = 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);
        header[33] = 0;
        header[34] = RECORDER_BPP;
        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);
    }

}

I would like to know if i need to add any extra loc to make my AudioRecord comfortable with Samsung Devices.

Fogg answered 2/7, 2012 at 10:33 Comment(1)
Hi Vipul, did you found the solution? We have the same problem on Galaxy S3 and we believe that is like yours.Brandling
T
6

We're also struggling with audio recording on some Samsung Android Devices. Unfortunately it seems to be very broken, as even different revisions of the same phone model are behaving differently with the same codebase.

Here are my current findings, hoping you find something useful:

1. Broken Initialization:

Unfortunately, the strategy you are using to query for valid recording configurations will fail at least on Samsung Galaxy Young and Ace models running Android 2.3 The problem is that some invalid AudioRecord configurations instead of simply failing, will completely brick the audio capture subsystem if tried. You'll need to reset the phone to recover from this state.

2. Inconsistent Sampling-Rate support along revisions of same phone model

On an older Galaxy Ace Phone, recording @ 11025Hz, 16-bit mono will succeed. On newer Ace revisions, this AudioRecord configuration will be accepted as valid, but the resulting recording will be distorted, with a "chipmunk" effect. A very popular guitar tuner app that has hardcoded this sampling rate is failing to give proper tuning readings on these phones precisely because of this problem!

3. Extremely low volume audio capture on some configurations.

In Galaxy Young and Galaxy Ace, recording from the mic or default audio source @ 44,100Hz (the supposedly canonical rate where everything should work fine) produces an undistorted, but extremely low-volume recording. I haven't found yet a way to fix this other than software amplification (which is the equivalent of magnifying a very low res image, with the consecuent "jageddnes" of the result).

4. Failure to support the canonical 44,100Hz sampling rate on every audio capture source.

In Galaxy Young and Galaxy Ace, recording from the Camcorder source fails @ 44,100Hz. (again, the configuration will be accepted as valid) producing complete garbage. However, recording @ 8,000Hz, 16,000Hz and 48,000Hz works fine and produces a recording with very acceptable volume levels. What is frustrating is that according to the Android documentation, 44,100Hz is a sampling rate all devices SHOULD support.

5. OpenSL does not fix any of the problems reported.

Working with the NDK and OpenSL produces the same described results. It seems that the AudioRecorder class is simply wrapping calls to OpenSL, and the problem is either hardware based, or buried at a lower-level tier in the kernel code.

This situation is very unfortunately indeed, as these models are becoming very popular - at least in Mexico.

Good luck - and please report if you had better luck working with these phones. =)

Tiberius answered 26/8, 2012 at 4:38 Comment(8)
It seems that you know quite a bit about this problem. I am facing the same problem right now. Do you maybe know if there was a fix yet by Samsung ? I am asking because i dont have the device, i need 44100Hz and i have no idea what to do now.Bedfast
...sorry forgot to say: That the users ofc have the problem now that my app doesnt work. The only solution I see now is, that i exclude those devices.Bedfast
Hi @stefple, no, I don't know if a fix is there yet. Currently we are handling 44,100 on Ace and Young models by using the MIC audio source, and applying software amplification (multiplying the samples by a constant and clipping the value if it overflows) to compensate for the low levels. Quality suffers, but it is good enough four our DSP purposes. Recording using CAMCORDER works @ 48000Hz with very good volume levels. If you can afford adding a sample rate conversion routine you could go from 48000Hz to 44100Hz with little quality loss.Tiberius
Here's a stackoverflow link that refers to a java library that could handle sample rate conversion for you:Tiberius
thanks a lot, didnt check it out yet, but if it is fast enough, i definatly use the libraryBedfast
Just to give the next one a good hint: I had no clue how to use the above mentioned library, but i found this: musigc - (Lightweight Java API for audio analysing, Android compatible) at code.google.com/p/musicg -- it converts wav files from 48000 to 44100 in about 10 lines of code.Bedfast
Brilliant! Thanks for the info. How is it performance-wise. Does the conversion add little overhead?Tiberius
Seems pretty fast, I will measure the times next time I am at the ide. Should I add a code snippet as an answer?Bedfast
D
1

Audio gain conrol To increase the amplitude of the audio u need to calculate the gain factor and multiply calculated gain factor with every sample captured. The following code does that. P.S. Ignore the unrelated code

public class MainActivity extends Activity {

public static final int SAMPLE_RATE = 16000;

private AudioRecord mRecorder;
private File mRecording;
private short[] mBuffer;
private final String startRecordingLabel = "Start recording";
private final String stopRecordingLabel = "Stop recording";
private boolean mIsRecording = false;
private ProgressBar mProgressBar;
float iGain = 1.0f;
CheckBox gain;

protected int bitsPerSamples = 16;

@Override
public void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.fragment_main);

    initRecorder();

    Button bluetooth = (Button)findViewById(R.id.blue);
    gain = (CheckBox) findViewById(R.id.checkBox1);
    mProgressBar = (ProgressBar) findViewById(R.id.progressBar);

    final Button button = (Button) findViewById(R.id.start);
    button.setText(startRecordingLabel);

    bluetooth.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            Intent i = new Intent("");
        }
    });
    gain.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(CompoundButton buttonView,
                boolean isChecked) {

            if (gain.isChecked()) {
                iGain = 5.0f;

            } else {
                iGain = 2.0f;
            }
        }
    });

    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(final View v) {
            if (!mIsRecording) {
                button.setText(stopRecordingLabel);
                mIsRecording = true;
                mRecorder.startRecording();
                mRecording = getFile("raw");
                startBufferedWrite(mRecording);
            } else {
                button.setText(startRecordingLabel);
                mIsRecording = false;
                mRecorder.stop();
                File waveFile = getFile("wav");
                try {
                    rawToWave(mRecording, waveFile);
                } catch (IOException e) {
                    Toast.makeText(MainActivity.this, e.getMessage(),
                            Toast.LENGTH_SHORT).show();
                }
                Toast.makeText(MainActivity.this,
                        "Recorded to " + waveFile.getName(),
                        Toast.LENGTH_SHORT).show();
            }
        }
    });
}

@Override
public void onDestroy() {
    mRecorder.release();
    super.onDestroy();
}

private void initRecorder() {
    int bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE,
            AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
    mBuffer = new short[bufferSize];
    mRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE,
            AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT,
            bufferSize);
}

private void startBufferedWrite(final File file) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            DataOutputStream output = null;
            try {
                output = new DataOutputStream(new BufferedOutputStream(
                        new FileOutputStream(file)));
                while (mIsRecording) {
                    double sum = 0;

                    int readSize = mRecorder.read(mBuffer, 0,
                            mBuffer.length);

                    final int bytesPerSample = bitsPerSamples / 8;
                    final int emptySpace = 64 - bitsPerSamples;
                    int byteIndex = 0;
                    int byteIndex2 = 0;
                    int temp = 0;
                    int mLeftTemp = 0;
                    int mRightTemp = 0;
                    int a = 0;
                    int x = 0;

                    for (int frameIndex = 0; frameIndex < readSize; frameIndex++) {

                        for (int c = 0; c < 1; c++) {

                            if (iGain != 1) {

                                long accumulator = 0;
                                for (int b = 0; b < bytesPerSample; b++) {

                                    accumulator += ((long) (mBuffer[byteIndex++] & 0xFF)) << (b * 8 + emptySpace);
                                }

                                double sample = ((double) accumulator / (double) Long.MAX_VALUE);
                                sample *= iGain;
                                int intValue = (int) ((double) sample * (double) Integer.MAX_VALUE);

                                for (int i = 0; i < bytesPerSample; i++) {
                                    mBuffer[i + byteIndex2] = (byte) (intValue >>> ((i + 2) * 8) & 0xff);
                                }
                                byteIndex2 += bytesPerSample;

                            }
                        }// end for(channel)

                        // mBuffer[frameIndex] *=iGain;
                        if (mBuffer[frameIndex] > 32765) {
                            mBuffer[frameIndex] = 32767;

                        } else if (mBuffer[frameIndex] < -32767) {
                            mBuffer[frameIndex] = -32767;
                        }
                        output.writeShort(mBuffer[frameIndex]);
                        sum += mBuffer[frameIndex] * mBuffer[frameIndex];

                    }

                    if (readSize > 0) {
                        final double amplitude = sum / readSize;
                        mProgressBar.setProgress((int) Math.sqrt(amplitude));
                    }
                }
            } catch (IOException e) {
                Toast.makeText(MainActivity.this, e.getMessage(),
                        Toast.LENGTH_SHORT).show();
            } finally {
                mProgressBar.setProgress(0);
                if (output != null) {
                    try {
                        output.flush();
                    } catch (IOException e) {
                        Toast.makeText(MainActivity.this, e.getMessage(),
                                Toast.LENGTH_SHORT).show();
                    } finally {
                        try {
                            output.close();
                        } catch (IOException e) {
                            Toast.makeText(MainActivity.this, e.getMessage(),
                                    Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            }
        }
    }).start();
}

private void rawToWave(final File rawFile, final File waveFile)
        throws IOException {

    byte[] rawData = new byte[(int) rawFile.length()];
    DataInputStream input = null;
    try {

        input = new DataInputStream(new FileInputStream(rawFile));
        input.read(rawData);
    } finally {
        if (input != null) {
            input.close();
        }
    }

    DataOutputStream output = null;
    try {
        output = new DataOutputStream(new FileOutputStream(waveFile));
        // WAVE header
        // see http://ccrma.stanford.edu/courses/422/projects/WaveFormat/
        writeString(output, "RIFF"); // chunk id
        writeInt(output, 36 + rawData.length); // chunk size
        writeString(output, "WAVE"); // format
        writeString(output, "fmt "); // subchunk 1 id
        writeInt(output, 16); // subchunk 1 size
        writeShort(output, (short) 1); // audio format (1 = PCM)
        writeShort(output, (short) 1); // number of channels
        writeInt(output, SAMPLE_RATE); // sample rate
        writeInt(output, SAMPLE_RATE * 2); // byte rate
        writeShort(output, (short) 2); // block align
        writeShort(output, (short) 16); // bits per sample
        writeString(output, "data"); // subchunk 2 id
        writeInt(output, rawData.length); // subchunk 2 size
        // Audio data (conversion big endian -> little endian)
        short[] shorts = new short[rawData.length / 2];
        ByteBuffer.wrap(rawData).order(ByteOrder.LITTLE_ENDIAN)
                .asShortBuffer().get(shorts);
        ByteBuffer bytes = ByteBuffer.allocate(shorts.length * 2);

        for (short s : shorts) {

            // Apply Gain
            /*
             * s *= iGain; if(s>32767) { s=32767; } else if(s<-32768) {
             * s=-32768; }
             */
            bytes.putShort(s);
        }
        output.write(bytes.array());
    } finally {
        if (output != null) {
            output.close();
        }
    }
}

private File getFile(final String suffix) {
    Time time = new Time();
    time.setToNow();
    return new File(Environment.getExternalStorageDirectory(),
            time.format("%Y%m%d%H%M%S") + "." + suffix);
}





private void writeInt(final DataOutputStream output, final int value)
        throws IOException {
    output.write(value >> 0);
    output.write(value >> 8);
    output.write(value >> 16);
    output.write(value >> 24);
}

private void writeShort(final DataOutputStream output, final short value)
        throws IOException {
    output.write(value >> 0);
    output.write(value >> 8);
}

private void writeString(final DataOutputStream output, final String value)
        throws IOException {
    for (int i = 0; i < value.length(); i++) {
        output.write(value.charAt(i));
    }
}
}
Damick answered 28/8, 2014 at 6:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.