Я пытаюсь построить амплитуду звука при записи звука в android. Я могу записать аудио и сохранить его как аудиофайл.
Я использую библиотеку MPAndroidCharts для динамического построения амплитуды звука во время записи.
У меня есть кнопка переключения, которая при включении запускает запись звука и отображает ее амплитуду с помощью MediaRecorder API. Однако через несколько секунд после включения кнопки переключения на некоторое время появляется график, а затем происходит сбой приложения. Как предотвратить сбой приложения и плавное построение амплитуды звука во время записи?
Мой текущий код выглядит следующим образом:
activity_main. xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/chart1"
android:layout_width="match_parent"
android:layout_height="300dp"
tools:layout_editor_absoluteY="0dp"
tools:layout_editor_absoluteX="8dp"/>
<ToggleButton
android:id="@+id/recordButton"
android:layout_below="@id/chart1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textOff="Stop Recording"
android:textOn="Start Recording"
android:checked="true"
android:layout_centerInParent="true"
/>
</RelativeLayout>
MainActivity. Java
package com.nitie.audioanalyzer;
import android.Manifest;
import android.graphics.Color;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.widget.CompoundButton;
import android.widget.Toast;
import android.widget.ToggleButton;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import java.io.File;
import java.io.IOException;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
MediaRecorder mediaRecorder;
ToggleButton toggleButton;
private LineChart mChart;
private Thread thread;
private boolean plotData = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE},
1);
////
mChart = findViewById(R.id.chart1);
// enable description text
mChart.getDescription().setEnabled(true);
// enable touch gestures
mChart.setTouchEnabled(true);
// enable scaling and dragging
mChart.setDragEnabled(true);
mChart.setScaleEnabled(true);
mChart.setDrawGridBackground(false);
// if disabled, scaling can be done on x- and y-axis separately
mChart.setPinchZoom(true);
// set an alternative background color
mChart.setBackgroundColor(Color.WHITE);
LineData data = new LineData();
data.setValueTextColor(Color.WHITE);
// add empty data
mChart.setData(data);
// get the legend (only possible after setting data)
Legend l = mChart.getLegend();
// modify the legend ...
l.setForm(Legend.LegendForm.LINE);
l.setTextColor(Color.WHITE);
XAxis xl = mChart.getXAxis();
xl.setTextColor(Color.WHITE);
xl.setDrawGridLines(true);
xl.setAvoidFirstLastClipping(true);
xl.setEnabled(true);
YAxis leftAxis = mChart.getAxisLeft();
leftAxis.setTextColor(Color.WHITE);
leftAxis.setDrawGridLines(false);
leftAxis.setAxisMaximum(10f);
leftAxis.setAxisMinimum(0f);
leftAxis.setDrawGridLines(true);
YAxis rightAxis = mChart.getAxisRight();
rightAxis.setEnabled(false);
mChart.getAxisLeft().setDrawGridLines(false);
mChart.getXAxis().setDrawGridLines(false);
mChart.setDrawBorders(false);
mediaRecorder = new MediaRecorder();
toggleButton = findViewById(R.id.recordButton);
toggleButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
stopRecording();
} else {
startRecording();
}
}
});
}
private void feedMultiple() {
if (thread != null) {
thread.interrupt();
}
thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
plotData = true;
while (plotData)
addEntry(mediaRecorder);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
thread.start();
}
private void addEntry(MediaRecorder mediaRecorder) {
LineData data = mChart.getData();
if (data != null) {
ILineDataSet set = data.getDataSetByIndex(0);
// set.addEntry(...); // can be called as well
if (set == null) {
set = createSet();
data.addDataSet(set);
}
// data.addEntry(new Entry(set.getEntryCount(), (float) (Math.random() * 80) + 10f), 0);
data.addEntry(new Entry(set.getEntryCount(), mediaRecorder.getMaxAmplitude()), 0);
data.notifyDataChanged();
// let the chart know it's data has changed
mChart.notifyDataSetChanged();
// limit the number of visible entries
mChart.setVisibleXRangeMaximum(150);
// mChart.setVisibleYRange(30, AxisDependency.LEFT);
// move to the latest entry
mChart.moveViewToX(data.getEntryCount());
}
}
private LineDataSet createSet() {
LineDataSet set = new LineDataSet(null, "Dynamic Data");
set.setAxisDependency(YAxis.AxisDependency.LEFT);
set.setLineWidth(3f);
set.setColor(Color.MAGENTA);
set.setHighlightEnabled(false);
set.setDrawValues(false);
set.setDrawCircles(false);
set.setMode(LineDataSet.Mode.CUBIC_BEZIER);
set.setCubicIntensity(0.2f);
return set;
}
public void startRecording() {
try {
plotData = true;
Toast.makeText(this, "Recording Started", Toast.LENGTH_SHORT).show();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
File storageDirectory = new File(Environment.getExternalStorageDirectory() + File.separator + "Communication Analyzer");
if (!storageDirectory.exists())
storageDirectory.mkdirs();
File audioFile = new File(storageDirectory, File.separator + "RecordedAudio.3gp");
if (Build.VERSION.SDK_INT < 26) {
mediaRecorder.setOutputFile(audioFile.getAbsolutePath());
} else {
mediaRecorder.setOutputFile(audioFile);
}
mediaRecorder.setOutputFile(audioFile.getAbsolutePath());
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mediaRecorder.prepare();
mediaRecorder.start();
feedMultiple();
} catch (IOException e) {
e.printStackTrace();
}
}
public void stopRecording() {
plotData = false;
Toast.makeText(this, "Recording Stopped", Toast.LENGTH_SHORT).show();
mediaRecorder.stop();
mediaRecorder.release();
}
}
Logcat
--------- beginning of crash
2020-03-15 19:47:02.370 13236-13236/com.nitie.audioanalyzer E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.nitie.audioanalyzer, PID: 13236
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.get(ArrayList.java:411)
at com.github.mikephil.charting.renderer.LegendRenderer.renderLegend(LegendRenderer.java:377)
at com.github.mikephil.charting.charts.BarLineChartBase.onDraw(BarLineChartBase.java:281)
at android.view.View.draw(View.java:17083)
at android.view.View.updateDisplayListIfDirty(View.java:16065)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3752)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3732)
at android.view.View.updateDisplayListIfDirty(View.java:16028)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3752)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3732)
at android.view.View.updateDisplayListIfDirty(View.java:16028)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3752)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3732)
at android.view.View.updateDisplayListIfDirty(View.java:16028)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3752)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3732)
at android.view.View.updateDisplayListIfDirty(View.java:16028)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3752)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3732)
at android.view.View.updateDisplayListIfDirty(View.java:16028)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3752)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3732)
at android.view.View.updateDisplayListIfDirty(View.java:16028)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:657)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:663)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:771)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:2808)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2616)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2223)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1258)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6348)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:871)
at android.view.Choreographer.doCallbacks(Choreographer.java:683)
at android.view.Choreographer.doFrame(Choreographer.java:619)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:857)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6123)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)