录音工具类
java
复制代码
public class AudioRecorder {
private final String TAG = getClass().getSimpleName();
private final Context mContext;
private AudioRecord mAudioRecorder; //录音器
private final Executor mExecutor = Executors.newSingleThreadExecutor();
private final RecorderCallback callback;
public AudioRecorder(Context context, RecorderCallback callback){
this.mContext = context;
this.callback = callback;
}
@SuppressLint("MissingPermission")
public void start(){
int sampleRateInHz = 16000;
int channelConfig = AudioFormat.CHANNEL_IN_MONO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
//20ms audio for 16k/16bit/mono
// int WAVE_FRAM_SIZE = 20 * 2 * 1 * SAMPLE_RATE / 1000;
int minBufferSize = AudioRecord.getMinBufferSize(
sampleRateInHz,
channelConfig,
audioFormat
);
Log.d(TAG, "minBufferSize: " + minBufferSize);
mAudioRecorder = new AudioRecord(MediaRecorder.AudioSource.DEFAULT,
sampleRateInHz, channelConfig,
audioFormat,
minBufferSize);
if (mAudioRecorder.getState() != AudioRecord.STATE_UNINITIALIZED){
mAudioRecorder.startRecording();
mExecutor.execute(() -> {
long startTime = System.currentTimeMillis();
File cacheDir = mContext.getExternalCacheDir();
if (!cacheDir.exists()){
if (!cacheDir.mkdirs()) Log.e(TAG, "mkdirs fail path: " + cacheDir.getAbsolutePath());
}
String pcmName = StringUtils.createFileName("record_", ".pcm");
File pcmFile = new File(cacheDir, pcmName);
try (FileOutputStream outputStream = new FileOutputStream(pcmFile)) {
byte[] data = new byte[minBufferSize];
while (mAudioRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING){
int length = mAudioRecorder.read(data, 0, minBufferSize);
if (length > 0){
outputStream.write(data, 0, length);
if (callback != null){
callback.onReadData(data, 0, length);
}
}
}
Log.d(TAG, "Record done.");
long diff = System.currentTimeMillis() - startTime;
if (callback != null){
if (diff > 200){
callback.onFinish(pcmFile.getAbsolutePath());
} else {
callback.onRecordError(-2, "too short!");
}
}
} catch (Exception e) {
Log.e(TAG, "Record error: " + e);
if (callback != null){
callback.onRecordError(-1, "Record error: " + e);
}
}
});
}
}
public void stop(){
if (mAudioRecorder != null){
if (mAudioRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING){
mAudioRecorder.stop();
}
}
}
public void release(){
if (mAudioRecorder != null){
mAudioRecorder.release();
mAudioRecorder = null;
}
}
public interface RecorderCallback{
void onReadData(byte[] data, int offsetInBytes, int length);
void onRecordError(int code, String message);
void onFinish(String path);
}
}
使用弹窗录音
java
复制代码
class AudioRecordDialog(
private val mContext: Context,
private val listener: Listener
) : Dialog(mContext, R.style.dialog_center) {
private var binding: DialogAudioRecordBinding
private var audioRecorder: AudioRecorder?=null
init {
requestWindowFeature(Window.FEATURE_NO_TITLE)
binding = DialogAudioRecordBinding.inflate(layoutInflater)
setContentView(binding.root)
}
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window?.let {
it.setGravity(Gravity.BOTTOM)
it.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
it.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
binding.tvTouch.setOnTouchListener { _, event ->
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
startRecord()
}
MotionEvent.ACTION_UP -> {
stopRecord()
}
}
false
}
setCancelable(true)
setCanceledOnTouchOutside(true)
}
private fun startRecord(){
audioRecorder = AudioRecorder(mContext, object : AudioRecorder.RecorderCallback{
override fun onReadData(data: ByteArray, offsetInBytes: Int, length: Int) {
}
override fun onRecordError(code: Int, message: String) {
binding.layoutFrame.post {
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show()
audioRecorder?.release()
}
}
override fun onFinish(path: String) {
binding.layoutFrame.post {
audioRecorder?.release()
listener.onFinish(path)
dismiss()
}
}
})
audioRecorder?.start()
}
private fun stopRecord(){
audioRecorder?.stop()
}
override fun dismiss() {
super.dismiss()
audioRecorder?.release()
}
interface Listener {
fun onFinish(path: String)
}
}
弹窗的布局
xml
复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layoutFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="@+id/tvTouch"
android:layout_width="120dp"
android:layout_height="120dp"
android:paddingVertical="12dp"
android:clickable="true"
android:background="@drawable/selector_60_primary"
android:gravity="center"
android:text="@string/touch_me_record"
android:textColor="@color/white"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>