Запись файла WAV каждые 30 секунд - PullRequest
0 голосов
/ 09 июля 2019

Я пытаюсь создать приложение, которое записывает wav-файл каждые 30 секунд, прежде чем сравнивать его с сохраненным wav-файлом, а затем удаляет его.Я хочу, чтобы это происходило непрерывно до тех пор, пока не будет остановлено вручную.

Я отредактировал код, который записывает один wav-файл с реализацией таймера:

Timer t = new Timer();

@Override


protected void onCreate(Bundle savedInstanceState) {
    String name = getIntent().getExtras().getString("Name");
    String email = getIntent().getExtras().getString("email");
    String phone = getIntent().getExtras().getString("phone");

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main7);







    //noinspection ConstantConditions

    findViewById(R.id.btnStart).setOnClickListener(new View.OnClickListener() 
{

        @Override

        public void onClick(View v) {

            if (ContextCompat.checkSelfPermission(Main7Activity.this, Manifest.permission.RECORD_AUDIO)

                    != PackageManager.PERMISSION_GRANTED) {

                // Request permission

                ActivityCompat.requestPermissions(Main7Activity.this,

                        new String[] { Manifest.permission.RECORD_AUDIO },

                        PERMISSION_RECORD_AUDIO);

                return;

            }

            // Permission already available

            launchTask();

        }

    });




    //noinspection ConstantConditions

    findViewById(R.id.btnStop).setOnClickListener(new View.OnClickListener() {

        @Override

        public void onClick(View v) {

            if (!recordTask.isCancelled() && recordTask.getStatus() == AsyncTask.Status.RUNNING) {

                recordTask.cancel(false);

            } else {

                Toast.makeText(Main7Activity.this, "Task not running.", Toast.LENGTH_SHORT).show();

            }

        }

    });


    // Restore the previous task or create a new one if necessary

    recordTask = (RecordWaveTask) getLastCustomNonConfigurationInstance();

    if (recordTask == null) {

        recordTask = new RecordWaveTask(this);

    } else {

        recordTask.setContext(this);

    }




}



@Override

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

    switch (requestCode) {

        case PERMISSION_RECORD_AUDIO:

            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                // Permission granted

                launchTask();

            } else {

                // Permission denied

                Toast.makeText(this, "\uD83D\uDE41", Toast.LENGTH_SHORT).show();

            }

            break;

    }

}




private void launchTask() {

    switch (recordTask.getStatus()) {

        case RUNNING:

            Toast.makeText(this, "Task already running...", Toast.LENGTH_SHORT).show();

            return;



        case FINISHED:

            recordTask = new RecordWaveTask(this);

            break;

        case PENDING:

            if (recordTask.isCancelled()) {

                recordTask = new RecordWaveTask(this);

            }

    }


    wavFile = new File(getFilesDir(), "monitor.wav");
    name = wavFile.getName();
    fileName = System.currentTimeMillis() / 1000 + ".wav";
    fileDir = getFilesDir().toString();

    String path = this.getFilesDir().getAbsolutePath();

    Toast.makeText(this , "Directory : " + path , Toast.LENGTH_LONG);
    Toast.makeText(this, wavFile.getAbsolutePath(), Toast.LENGTH_LONG).show();
    Toast.makeText(this, "name : " + wavFile.getName(), Toast.LENGTH_LONG).show();
    Toast.makeText(this,  "directory : " + fileDir, Toast.LENGTH_LONG).show();
    Toast.makeText(this, "path " + path , Toast.LENGTH_LONG).show();

    t.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            launchTask();

            recordTask.execute(wavFile);
        }
    }, 0, 30000);


}




@Override

public Object onRetainCustomNonConfigurationInstance() {

    recordTask.setContext(null);

    return recordTask;

}



private static class RecordWaveTask extends AsyncTask<File, Void, Object[]> {


    // Configure me!

    private static final int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;

    private static final int SAMPLE_RATE = 44100; // Hz

    private static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;

    private static final int CHANNEL_MASK = AudioFormat.CHANNEL_IN_MONO;

    //


    private static final int BUFFER_SIZE = 2 * AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_MASK, ENCODING);


    private Context ctx;


    private RecordWaveTask(Context ctx) {

        setContext(ctx);

    }


    private void setContext(Context ctx) {

        this.ctx = ctx;

    }


    /**
     * Opens up the given file, writes the header, and keeps filling it with raw PCM bytes from
     * <p>
     * AudioRecord until it reaches 4GB or is stopped by the user. It then goes back and updates
     * <p>
     * the WAV header to include the proper final chunk sizes.
     *
     * @param files Index 0 should be the file to write to
     * @return Either an Exception (error) or two longs, the filesize, elapsed time in ms (success)
     */

    @Override

    protected Object[] doInBackground(File... files) {

        AudioRecord audioRecord = null;

        FileOutputStream wavOut = null;

        long startTime = 0;

        long endTime = 0;


        try {

            // Open our two resources

            audioRecord = new AudioRecord(AUDIO_SOURCE, SAMPLE_RATE, CHANNEL_MASK, ENCODING, BUFFER_SIZE);

            wavOut = new FileOutputStream(files[0]);


            // Write out the wav file header

            writeWavHeader(wavOut, CHANNEL_MASK, SAMPLE_RATE, ENCODING);


            // Avoiding loop allocations

            byte[] buffer = new byte[BUFFER_SIZE];

            boolean run = true;

            int read;

            long total = 0;


            // Let's go

            startTime = SystemClock.elapsedRealtime();

            audioRecord.startRecording();

            while (run && !isCancelled()) {

                read = audioRecord.read(buffer, 0, buffer.length);


                // WAVs cannot be > 4 GB due to the use of 32 bit unsigned integers.

                if (total + read > 4294967295L) {

                    // Write as many bytes as we can before hitting the max size

                    for (int i = 0; i < read && total <= 4294967295L; i++, total++) {

                        wavOut.write(buffer[i]);

                    }

                    run = false;

                } else {

                    // Write out the entire read buffer

                    wavOut.write(buffer, 0, read);

                    total += read;

                }

            }

        } catch (IOException ex) {

            return new Object[]{ex};

        } finally {

            if (audioRecord != null) {

                try {

                    if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {

                        audioRecord.stop();

                        endTime = SystemClock.elapsedRealtime();

                    }

                } catch (IllegalStateException ex) {

                    //

                }

                if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {

                    audioRecord.release();

                }

            }

            if (wavOut != null) {

                try {

                    wavOut.close();

                } catch (IOException ex) {

                    //

                }

            }

        }


        try {

            // This is not put in the try/catch/finally above since it needs to run

            // after we close the FileOutputStream

            updateWavHeader(files[0]);

        } catch (IOException ex) {

            return new Object[]{ex};

        }


        return new Object[]{files[0].length(), endTime - startTime};

    }


    /**
     * Writes the proper 44-byte RIFF/WAVE header to/for the given stream
     * <p>
     * Two size fields are left empty/null since we do not yet know the final stream size
     *
     * @param out         The stream to write the header to
     * @param channelMask An AudioFormat.CHANNEL_* mask
     * @param sampleRate  The sample rate in hertz
     * @param encoding    An AudioFormat.ENCODING_PCM_* value
     * @throws IOException
     */

    private static void writeWavHeader(OutputStream out, int channelMask, int sampleRate, int encoding) throws IOException {

        short channels;

        switch (channelMask) {

            case AudioFormat.CHANNEL_IN_MONO:

                channels = 1;

                break;



            default:

                throw new IllegalArgumentException("Unacceptable channel mask");

        }


        short bitDepth;

        switch (encoding) {

            case AudioFormat.ENCODING_PCM_8BIT:

                bitDepth = 8;

                break;

            case AudioFormat.ENCODING_PCM_16BIT:

                bitDepth = 16;

                break;

            case AudioFormat.ENCODING_PCM_FLOAT:

                bitDepth = 32;

                break;

            default:

                throw new IllegalArgumentException("Unacceptable encoding");

        }


        writeWavHeader(out, channels, sampleRate, bitDepth);

    }


    /**
     * Writes the proper 44-byte RIFF/WAVE header to/for the given stream
     * <p>
     * Two size fields are left empty/null since we do not yet know the final stream size
     *
     * @param out        The stream to write the header to
     * @param channels   The number of channels
     * @param sampleRate The sample rate in hertz
     * @param bitDepth   The bit depth
     * @throws IOException Throws Exception
     */

    private static void writeWavHeader(OutputStream out, short channels, int sampleRate, short bitDepth) throws IOException {

        // Convert the multi-byte integers to raw bytes in little endian format as required by the spec

        byte[] littleBytes = ByteBuffer

                .allocate(14)

                .order(ByteOrder.LITTLE_ENDIAN)

                .putShort(channels)

                .putInt(sampleRate)

                .putInt(sampleRate * channels * (bitDepth / 8))

                .putShort((short) (channels * (bitDepth / 8)))

                .putShort(bitDepth)

                .array();


        // Not necessarily the best, but it's very easy to visualize this way

        out.write(new byte[]{

                // RIFF header

                'R', 'I', 'F', 'F', // ChunkID

                0, 0, 0, 0, // ChunkSize (must be updated later)

                'W', 'A', 'V', 'E', // Format

                // fmt subchunk

                'f', 'm', 't', ' ', // Subchunk1ID

                16, 0, 0, 0, // Subchunk1Size

                1, 0, // AudioFormat

                littleBytes[0], littleBytes[1], // NumChannels

                littleBytes[2], littleBytes[3], littleBytes[4], littleBytes[5], // SampleRate

                littleBytes[6], littleBytes[7], littleBytes[8], littleBytes[9], // ByteRate

                littleBytes[10], littleBytes[11], // BlockAlign

                littleBytes[12], littleBytes[13], // BitsPerSample

                // data subchunk

                'd', 'a', 't', 'a', // Subchunk2ID

                0, 0, 0, 0, // Subchunk2Size (must be updated later)

        });

    }


    /**
     * Updates the given wav file's header to include the final chunk sizes
     *
     * @param wav The wav file to update
     * @throws IOException
     */

    private static void updateWavHeader(File wav) throws IOException {

        byte[] sizes = ByteBuffer

                .allocate(8)

                .order(ByteOrder.LITTLE_ENDIAN)

                // There are probably a bunch of different/better ways to calculate

                // these two given your circumstances. Cast should be safe since if the WAV is

                // > 4 GB we've already made a terrible mistake.

                .putInt((int) (wav.length() - 8)) // ChunkSize

                .putInt((int) (wav.length() - 44)) // Subchunk2Size

                .array();


        RandomAccessFile accessWave = null;

        //noinspection CaughtExceptionImmediatelyRethrown

        try {

            accessWave = new RandomAccessFile(wav, "rw");

            // ChunkSize

            accessWave.seek(4);

            accessWave.write(sizes, 0, 4);


            // Subchunk2Size

            accessWave.seek(40);

            accessWave.write(sizes, 4, 4);

        } catch (IOException ex) {

            // Rethrow but we still close accessWave in our finally

            throw ex;

        } finally {

            if (accessWave != null) {

                try {

                    accessWave.close();

                } catch (IOException ex) {

                    //

                }

            }

        }

    }


    @Override

    protected void onCancelled(Object[] results) {

        // Handling cancellations and successful runs in the same way

        onPostExecute(results);

    }


    @Override

    protected void onPostExecute(Object[] results) {

        Throwable throwable = null;

        if (results[0] instanceof Throwable) {

            // Error

            throwable = (Throwable) results[0];

            Log.e(RecordWaveTask.class.getSimpleName(), throwable.getMessage(), throwable);

        }


        // If we're attached to an activity

        if (ctx != null) {

            if (throwable == null) {

                // Display final recording stats

                double size = (long) results[0] / 1000000.00;

                long time = (long) results[1] / 1000;

                Toast.makeText(ctx, String.format(Locale.getDefault(), "%.2f MB / %d seconds",

                        size, time), Toast.LENGTH_LONG).show();

            } else {

                // Error

                Toast.makeText(ctx, throwable.getLocalizedMessage(), Toast.LENGTH_LONG).show();

            }

        }




    }




}



}

, что каждый раз вызывало сбой приложения.

см. Логат ниже:

  2019-07-09 17:54:29.003 16959-17031/? E/AndroidRuntime: FATAL EXCEPTION: Timer-0
Process: com.example.androidaudiorecorder, PID: 16959
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
    at android.os.Handler.<init>(Handler.java:208)
    at android.os.Handler.<init>(Handler.java:122)
    at android.widget.Toast$TN.<init>(Toast.java:351)
    at android.widget.Toast.<init>(Toast.java:106)
    at android.widget.Toast.makeText(Toast.java:265)
    at com.example.androidaudiorecorder.Main7Activity.launchTask(Main7Activity.java:254)
    at com.example.androidaudiorecorder.Main7Activity.access$000(Main7Activity.java:62)
    at com.example.androidaudiorecorder.Main7Activity$3.run(Main7Activity.java:263)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)

Основной раздел кода, на который ссылается logcat, таков:

 t.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            launchTask();

            recordTask.execute(wavFile);
        }
    }, 0, 30000);

Буду очень признателен за любые советы или отзывы, касающиесякак я должен идти об этом.

...