我正在尝试创建一个应用程序,该应用程序每30秒记录一次WAV文件,然后将其与已保存的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);
对于我应该如何做的任何建议或反馈,我将不胜感激。