AudioRecord sometimes skips audio data

偶尔善良 提交于 2020-01-02 10:28:28

问题


I use AudioRecord for recording audio in wav files. I perform read() operation in a loop on secondary thread and the data is passed into a queue in another sec. thread for writing in a file.

The issue is the following: When I choose to save the audioData in the external SD card, the audio contains some millisc of skips/distortions (only sometimes). Other users has this issue when saving on internal SD card too

This is how I instantiate the AudioRecord object:

initBufferSize = assureMinBufferSize();
if (minBufferSize == AudioRecord.ERROR_BAD_VALUE) { 
    throw new Exception("" + ErrorCodes.ERROR_CODE_0);
}
else if (minBufferSize == AudioRecord.ERROR) {
    throw new Exception("" + ErrorCodes.ERROR_CODE_0);
}

buffer = new byte[initBufferSize];
aRecorder = new AudioRecord(mSource, sRate, nChannel, AudioFormat.ENCODING_PCM_16BIT, initBufferSize);

and this is how I pool the AudioRecorder object:

private class AudioReaderRunnable implements Runnable {
    private volatile boolean stopped;

    @Override
    public void run() {                         
        while (!stopped) {
            while(mState == State.RECORDING){
                read();
            }
        }           

        Log.i(getClass().getName(), "AudioReade thread stopped!");
    }

    private void read(){

        if(aRecorder == null)
            return;

        // "dirty" patch for some null crash for some users
        if(buffer == null)
            buffer = new byte[initBufferSize];

        //int x = aRecorder.read(buffer, 0, buffer.length);
        int x = readFully(buffer, 0, buffer.length);
        if(x <= 0)
            return;

        payloadSize += x;

        mWavData.arrayCopy(buffer);

        mWavData.setGain(rGain);
        mWavData.setBitsPerSamples(bitsPerSample);
        mWavData.setNrChannels(nChannelsNumber);

        // send audio to another thread for writing
        mAudioWritter.add(audioData);               
    }

    private int readFully(byte[] data, int off, int length) {
        int read = 0;
        int result = 0;
        int requestedSize = length;

        if(aRecorder == null){
            return read;
        }

        try {           
            result = aRecorder.read(data, off, requestedSize);
        } catch (Exception e) {
            Log.e(getClass().getName(), e.getMessage(), e);
            if(aRecorder == null)
                return read;
        }

        read = result;

        while (requestedSize != result && !stopped) {
            if(aRecorder == null)
                return read;

            requestedSize -= result;                
            try {
                result = aRecorder.read(data, result - 1, requestedSize);   
            } catch (Exception e) {
                Log.e(getClass().getName(), e.getMessage(), e);
                if(aRecorder == null)
                    return read;
            }
            read += result;
        }

        return read;
    }

    public void stopThread() {
        stopped = true;
    }
}

回答1:


Try This Code:-

//This Variable are define in class

private int recordingcounter;
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 = 16000;// /44100; //High voice
                                                            // recording result
                                                            // for use sample
                                                            // rate is 44100
private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_STEREO;
private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;

private AudioRecord recorder = null;
private int bufferSize = 0;
private Thread recordingThread = null;
private boolean isRecording = false;


//--------------------- start button click event in add-----------------------------------//

bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE, RECORDER_CHANNELS,RECORDER_AUDIO_ENCODING);

try {

                                    startRecording();

                                } catch (Exception e) {
                                    // TODO: handle exception

                                    // Toast.makeText(RecordingActivity.this,
                                    // e.getMessage(), Toast.LENGTH_SHORT)
                                    // .show();

                                }







//-------------------- stop button click event in add---------------------------//

try {

                                stopRecording();

                            } catch (Exception e) {
                                // TODO: handle exception
                                e.printStackTrace();
                            }






//---------this methods are add in your class-----------------------------//



// --------------------------------------------------------Sound Recording
    // Code----------------------------------------------------//

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

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

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

    @SuppressLint("NewApi")
    private void startRecording() {
        recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
                RECORDER_SAMPLERATE, RECORDER_CHANNELS,
                RECORDER_AUDIO_ENCODING, bufferSize);

        recorder.startRecording();

        // Log.e("Audio session", ""+recorder.getAudioSessionId());

        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) {
            // TODO Auto-generated catch block
            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();
            }
        }
    }

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

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

            // Utility.Log("", "" + recorder.getRecordingState());

            // AppLog.logString(""+recorder.getRecordingState());

            recorder = null;
            recordingThread = null;

        }

        copyWaveFile(getTempFilename(), getFilename());

    }



    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;

            // Utility.Log("", "File size: " + totalDataLen);

            // AppLog.logString("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);
    }

//----------and manifest file this line-------------//

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


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



回答2:


I had a similar problem on Xamarin.Android, this is how I solved it:

The AudioRecord buffer is like a window - it get's rewritten from start when the buffer is filled. So we need to make sure that the buffer is large enough for us to read from it before it gets rewritten.

And here comes the big catch - when garbage collector kicks in, it stops all app threads for about a second - which is enough time for the recorder to write about 100KB of data to the buffer (when using 44100Hz mono Pcm16bit), but we are not reading during this period, because app threads are stopped! So if the buffer is not big enough, the data gets rewritten before we have a chance to read it and write to a file. As a result, the audio file is missing the audio recorded during the garbage collecting.

So, you need to initialize the AudioRecord with bufferSize large enough to outlive the garbage collection event. This will be a different size for different audio configurations. For me (using 44100Hz, mono, PCM16bit), buffer size 512,000 did the trick.

I am not sure how GC works on native Android, but I believe it might be similar.



来源:https://stackoverflow.com/questions/29233388/audiorecord-sometimes-skips-audio-data

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!