AudioRecord понякога пропуска аудио данни

Използвам AudioRecord за запис на аудио в wav файлове. Извършвам операция read() в цикъл на вторична нишка и данните се предават в опашка за друга секунда. нишка за запис във файл.

Проблемът е следният: Когато избера да запазя аудиоданните във външната SD карта, аудиото съдържа някои милисици пропуски/изкривявания (само понякога). Други потребители също имат този проблем, когато записват на вътрешна SD карта

Ето как инстанцирам обекта AudioRecord:

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);

и ето как обединявам обекта AudioRecorder:

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;
    }
}

person Alexandru Circus    schedule 24.03.2015    source източник
comment
Записът е перфектна работа във файл във формат WAV?   -  person cyberlobe    schedule 24.03.2015
comment
@cyberlobe - да, форматът е WAV   -  person Alexandru Circus    schedule 24.03.2015
comment
Имам работещ код със себе си, но кодът е различен. Искаш ли да опиташ?   -  person cyberlobe    schedule 24.03.2015
comment
Да разбира се. Благодаря.   -  person Alexandru Circus    schedule 24.03.2015
comment
@cyberlobe - Тествахте ли кода на външна SD карта? Имали ли сте докладвани пропуски/изкривявания на някои телефони?   -  person Alexandru Circus    schedule 24.03.2015
comment
всички телефони в моя код работят   -  person cyberlobe    schedule 24.03.2015
comment
този файл със звукозапис се запазва в sdCard   -  person cyberlobe    schedule 24.03.2015


Отговори (2)


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);
    }

//----------и манифестен файл на този ред-------------//

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


<uses-permission android:name="android.permission.RECORD_AUDIO" />
person cyberlobe    schedule 24.03.2015
comment
Благодаря, че публикувахте това. Ще създам друго приложение (само за тест) и ще интегрирам вашия код. Бихте ли обяснили защо съхранявате audioData във временен файл и го копирате в друг, когато записът приключи? - person Alexandru Circus; 24.03.2015

Имах подобен проблем на Xamarin.Android, ето как го реших:

Буферът AudioRecord е като прозорец - той се пренаписва от самото начало, когато буферът се запълни. Така че трябва да сме сигурни, че буферът е достатъчно голям, за да можем да четем от него, преди да бъде пренаписан.

И тук идва голямата уловка - когато колекторът за боклук се включи, той спира всички нишки на приложения за около секунда - което е достатъчно време, за да може записващото устройство да запише около 100 KB данни в буфера (при използване на 44100Hz моно Pcm16bit), но ние не четем през този период, защото нишките на приложението са спрени! Така че, ако буферът не е достатъчно голям, данните се пренаписват, преди да имаме възможност да ги прочетем и запишем във файл. В резултат на това в аудиофайла липсва звукът, записан по време на събирането на боклука.

Така че трябва да инициализирате AudioRecord с bufferSize, достатъчно голям, за да надживее събитието за събиране на боклук. Това ще бъде различен размер за различни аудио конфигурации. За мен (използвайки 44100Hz, моно, PCM16bit), размерът на буфера 512 000 свърши работа.

Не съм сигурен как GC работи на родния Android, но вярвам, че може да е подобно.

person David Riha    schedule 04.02.2019