Android语音录制和播放

楼主本来打算昨晚写的,但是昨天是万圣节,晚上不想动弹,所以推到今天来写了!简单说明一下,由于项目中用到了语音的录制、播放和上传,所以将功能抽取了出来,整理一下,也方便小伙伴们使用。效果就跟微信上语音聊天一样,长按按钮录制,松手就结束录制,并且能播放。录的时候,有个小话筒随着声音的大小而展现上下浮动的一个效果。大概效果如下图:
这里写图片描述
这是录完的效果,一开始就是一个录音的按钮,录完后才有下面的显示,可以点击播放,录制过程中,会有个小的话筒出现,如下图:
这里写图片描述
有时间的记录和图标根据声音分贝大小展示上下波动的一个效果。

首先,展示2个工具类,一个是录制的,一个是播放的。
录制:

package com.thtj.demo;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//


import android.media.MediaRecorder;
import android.media.MediaRecorder.OnInfoListener;
import android.os.Handler;

import java.io.File;
import java.io.IOException;

public class AudioRecorderUtil {
    private final String TAG = AudioRecorderUtil.class.getName();
    public static final int MAX_LENGTH = 60000;
    private String filePath;
    private String folderPath;
    private MediaRecorder mMediaRecorder;
    private int maxLength;
    private long startTime;
    private long endTime;
    private AudioRecorderUtil.OnAudioStatusUpdateListener audioStatusUpdateListener;
    private final Handler mHandler = new Handler();
    private Runnable mUpdateMicStatusTimer = new Runnable() {
        public void run() {
            AudioRecorderUtil.this.updateMicStatus();
        }
    };
    private int BASE = 1;
    private int SPACE = 100;

    public AudioRecorderUtil(String folderPath) {
        File path = new File(folderPath);
        if(!path.exists()) {
            path.mkdirs();
        }

        this.folderPath = folderPath;
        this.maxLength = '\uea60';
    }

    public void start() {
        if(this.mMediaRecorder == null) {
            this.mMediaRecorder = new MediaRecorder();
        } else {
            try {
                this.mMediaRecorder.stop();
                this.mMediaRecorder.reset();
                this.mMediaRecorder.release();
            } catch (Exception var2) {
                this.mMediaRecorder.reset();
            }
        }

        this.mHandler.removeCallbacks(this.mUpdateMicStatusTimer);

        try {
            this.mMediaRecorder.setAudioSource(1);
            this.mMediaRecorder.setOutputFormat(0);
            this.mMediaRecorder.setAudioEncoder(1);
            this.filePath = this.folderPath + File.separator + System.currentTimeMillis() + ".aac";
            this.mMediaRecorder.setOutputFile(this.filePath);
            this.mMediaRecorder.setMaxDuration(this.maxLength);
            this.mMediaRecorder.prepare();
            this.mMediaRecorder.setOnInfoListener(new OnInfoListener() {
                public void onInfo(MediaRecorder mr, int what, int extra) {
                    if(what == 800) {
                        AudioRecorderUtil.this.stop();
                    }

                }
            });
            this.mMediaRecorder.start();
            this.startTime = System.currentTimeMillis();
            this.updateMicStatus();
            if(this.audioStatusUpdateListener != null) {
                this.audioStatusUpdateListener.onStart();
            }
        } catch (IllegalStateException var3) {
            if(this.audioStatusUpdateListener != null) {
                this.audioStatusUpdateListener.onError(var3);
            }

            this.cancel();
        } catch (IOException var4) {
            if(this.audioStatusUpdateListener != null) {
                this.audioStatusUpdateListener.onError(var4);
            }

            this.cancel();
        }

    }

    public long getSumTime() {
        return this.startTime == 0L?0L:System.currentTimeMillis() - this.startTime;
    }

    public long stop() {
        if(this.mMediaRecorder == null) {
            return 0L;
        } else {
            this.endTime = System.currentTimeMillis();

            try {
                this.mMediaRecorder.stop();
                this.mMediaRecorder.reset();
                this.mMediaRecorder.release();
                this.mMediaRecorder = null;
                if(this.audioStatusUpdateListener != null) {
                    this.audioStatusUpdateListener.onStop(this.filePath);
                }
            } catch (RuntimeException var3) {
                this.mMediaRecorder.reset();
                this.mMediaRecorder.release();
                this.mMediaRecorder = null;
                File file = new File(this.filePath);
                if(file.exists()) {
                    file.delete();
                }

                if(this.audioStatusUpdateListener != null) {
                    this.audioStatusUpdateListener.onError(var3);
                }
            }

            this.filePath = "";
            return this.endTime - this.startTime;
        }
    }

    public void cancel() {
        try {
            this.mMediaRecorder.stop();
            this.mMediaRecorder.reset();
            this.mMediaRecorder.release();
            this.mMediaRecorder = null;
        } catch (RuntimeException var2) {
            this.mMediaRecorder.reset();
            this.mMediaRecorder.release();
            this.mMediaRecorder = null;
        }

        File file = new File(this.filePath);
        if(file.exists()) {
            file.delete();
        }

        this.filePath = "";
        if(this.audioStatusUpdateListener != null) {
            this.audioStatusUpdateListener.onCancel();
        }

    }

    public void setMaxLength(int maxLength) {
        this.maxLength = maxLength;
    }

    private void updateMicStatus() {
        if(this.mMediaRecorder != null) {
            double ratio = (double)this.mMediaRecorder.getMaxAmplitude() / (double)this.BASE;
            double db = 0.0D;
            if(ratio > 1.0D) {
                db = 20.0D * Math.log10(ratio);
                if(null != this.audioStatusUpdateListener) {
                    this.audioStatusUpdateListener.onProgress(db, System.currentTimeMillis() - this.startTime);
                }
            }

            this.mHandler.postDelayed(this.mUpdateMicStatusTimer, (long)this.SPACE);
        }

    }

    public void setOnAudioStatusUpdateListener(AudioRecorderUtil.OnAudioStatusUpdateListener audioStatusUpdateListener) {
        this.audioStatusUpdateListener = audioStatusUpdateListener;
    }

    public interface OnAudioStatusUpdateListener {
        void onStart();

        void onProgress(double var1, long var3);

        void onError(Exception var1);

        void onCancel();

        void onStop(String var1);
    }
}

播放:

package com.thtj.demo;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//


import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;

public class AudioPlayerUtil {
    private static final String TAG = "AudioRecordTest";
    private MediaPlayer mPlayer;

    public AudioPlayerUtil() {
    }

    public void start(String mFileName, OnCompletionListener listener) {
        if(this.mPlayer == null) {
            this.mPlayer = new MediaPlayer();
        } else {
            this.mPlayer.reset();
        }

        try {
            this.mPlayer.setDataSource(mFileName);
            this.mPlayer.prepare();
            this.mPlayer.start();
            if(listener != null) {
                this.mPlayer.setOnCompletionListener(listener);
            }
        } catch (Exception var4) {
        }

    }

    public void stop() {
        if(this.mPlayer != null) {
            this.mPlayer.stop();
            this.mPlayer.release();
            this.mPlayer = null;
        }

    }
}

下面就是开始录制的MainActivity了:

package com.thtj.demo;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;

import static android.content.ContentValues.TAG;

/**
 * 录制音频按钮,实现按下录制,松开停止
 */
public class MainActivity extends Activity {
    private boolean audioRecorder = false;
    private AudioPlayerUtil player;
    private Button recordBtn;//录音按钮
    private String ROOT_PATH;
    ImageView mImageView;
    TextView mTextView, tv_time;
    PopupWindowFactory mPop;
    View view;
    LinearLayout record_contentLayout;
    ImageView recordDetailView;
    private String audioFilePath;// 录音文件保存路径
    private AnimationDrawable animationDrawable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recordBtn = (Button) findViewById(R.id.recordBtn);
        record_contentLayout = (LinearLayout) findViewById(R.id.record_contentLayout);
        recordDetailView = (ImageView) findViewById(R.id.record_detailView);
        tv_time = (TextView) findViewById(R.id.tv_time);
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO,
                Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
        init(this);
        initAudioRecorderBtn();
    }

    public void init(Context context) {
        try {
            ROOT_PATH = context.getExternalFilesDir(null).getAbsolutePath();
        } catch (Exception e) {
            Log.e(TAG, e.getMessage() + "");
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                ROOT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + context.getPackageName();
            } else {
                ROOT_PATH = context.getFilesDir().getAbsolutePath();
            }
        } catch (Throwable e) {
            ROOT_PATH = context.getFilesDir().getAbsolutePath();
        }
    }

    private void initAudioRecorderBtn() {
        view = View.inflate(this, R.layout.layout_microphone, null);
        mPop = new PopupWindowFactory(this, view);
        //PopupWindow布局文件里面的控件
        mImageView = (ImageView) view.findViewById(R.id.iv_recording_icon);
        mTextView = (TextView) view.findViewById(R.id.tv_recording_time);
        final AudioRecorderUtil audioRecorderUtil = new AudioRecorderUtil(ROOT_PATH + File.separator + "audio");
        audioRecorderUtil.setOnAudioStatusUpdateListener(new AudioRecorderUtil.OnAudioStatusUpdateListener() {
            @Override
            public void onStart() {
            }

            @Override
            public void onProgress(double db, long time) {
                //根据分贝值来设置录音时话筒图标的上下波动,同时设置录音时间
                mImageView.getDrawable().setLevel((int) (3000 + 6000 * db / 100));
                mTextView.setText(TimeUtils.long2String(time));
            }

            @Override
            public void onError(Exception e) {
            }

            @Override
            public void onCancel() {
            }

            @Override
            public void onStop(String filePath) {
                mPop.dismiss();
                record_contentLayout.setVisibility(View.VISIBLE);
                audioFilePath = filePath;
                Log.e("===path", audioFilePath);
                // TODO 上传音频文件
            }
        });

        recordBtn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // 停止播放
                if (player != null) {
                    player.stop();
                }
                audioRecorder = true;//正在录音
                // 处理动作
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        audioRecorderUtil.start();
                        mPop.showAtLocation(view.getRootView(), Gravity.CENTER, 0, 0);
                        break;
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL:
                        long time = audioRecorderUtil.getSumTime();
                        if (time < 1000) {
                            audioRecorderUtil.cancel();
                            Toast.makeText(MainActivity.this, "录音时间太短!", Toast.LENGTH_SHORT).show();
                            audioFilePath = "";
                        } else {
                            tv_time.setText(time / 1000 + "s");
                        }
                        mImageView.getDrawable().setLevel(0);
                        mTextView.setText(TimeUtils.long2String(0));
                        audioRecorderUtil.stop();
                        mPop.dismiss();
                        break;
                }
                return true;
            }
        });

        record_contentLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (TextUtils.isEmpty(audioFilePath) || !audioRecorder) {
                    return;
                }
                if (player == null) {
                    player = new AudioPlayerUtil();
                } else {
                    player.stop();
                }
                recordDetailView.setImageResource(R.drawable.play_voice_right_anim);
                animationDrawable = (AnimationDrawable) recordDetailView.getDrawable();
                animationDrawable.start();
                player.start(audioFilePath, new MediaPlayer.OnCompletionListener() {
                    @Override
                    public void onCompletion(MediaPlayer mp) {
                        animationDrawable.stop();
                        recordDetailView.setImageResource(R.mipmap.voice_right);
                    }
                });
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (player != null) {
            player.stop();
        }
    }
}

说明:话筒那个布局是用PopupWindow来实现的

package com.thtj.demo;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//


import android.content.Context;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
import android.widget.PopupWindow;

public class PopupWindowFactory {
    private Context mContext;
    private PopupWindow mPop;

    public PopupWindowFactory(Context mContext, View view) {
        this(mContext, view, -2, -2);
    }

    public PopupWindowFactory(Context mContext, View view, int width, int height) {
        this.init(mContext, view, width, height);
    }

    private void init(Context mContext, View view, int width, int height) {
        this.mContext = mContext;
        view.setFocusable(true);
        view.setFocusableInTouchMode(true);
        this.mPop = new PopupWindow(view, width, height, true);
        this.mPop.setFocusable(true);
        view.setOnKeyListener(new OnKeyListener() {
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if(keyCode == 4) {
                    PopupWindowFactory.this.mPop.dismiss();
                    return true;
                } else {
                    return false;
                }
            }
        });
        view.setOnTouchListener(new OnTouchListener() {
            public boolean onTouch(View v, MotionEvent event) {
                if(PopupWindowFactory.this.mPop != null && PopupWindowFactory.this.mPop.isShowing()) {
                    PopupWindowFactory.this.mPop.dismiss();
                    return true;
                } else {
                    return false;
                }
            }
        });
    }

    public PopupWindow getPopupWindow() {
        return this.mPop;
    }

    public void showAtLocation(View parent, int gravity, int x, int y) {
        if(!this.mPop.isShowing()) {
            this.mPop.showAtLocation(parent, gravity, x, y);
        }
    }

    public void showAsDropDown(View anchor) {
        this.showAsDropDown(anchor, 0, 0);
    }

    public void showAsDropDown(View anchor, int xoff, int yoff) {
        if(!this.mPop.isShowing()) {
            this.mPop.showAsDropDown(anchor, xoff, yoff);
        }
    }

    public void dismiss() {
        if(this.mPop.isShowing()) {
            this.mPop.dismiss();
        }

    }
}

也用到了时间转换:

package com.thtj.demo;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//


import java.text.SimpleDateFormat;

public class TimeUtils {
    public TimeUtils() {
    }

    public static String long2String(long time) {
        int sec = (int)time / 1000;
        int min = sec / 60;
        sec %= 60;
        return min < 10?(sec < 10?"0" + min + ":0" + sec:"0" + min + ":" + sec):(sec < 10?min + ":0" + sec:min + ":" + sec);
    }

    public static String getCurrentTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        return sdf.format(Long.valueOf(System.currentTimeMillis()));
    }
}

最后上一下布局:
话筒布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/record_microphone_bj"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="16dp">

    <ImageView
        android:id="@+id/iv_recording_icon"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:src="@drawable/record_microphone" />

    <TextView
        android:id="@+id/tv_recording_time"
        android:layout_width="114dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="6dp"
        android:gravity="center"
        android:text="00:00"
        android:textColor="#FFFFFF"
        android:textSize="16sp" />

</LinearLayout>

主activity布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.thtj.demo.MainActivity">

    <Button
        android:id="@+id/recordBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="录音"/>

    <LinearLayout
        android:id="@+id/record_contentLayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:gravity="center_vertical"
        android:visibility="gone">
        <FrameLayout
            android:layout_width="100dp"
            android:layout_height="40dp"
            android:background="@mipmap/voice_right_bg">
            <ImageView
                android:id="@+id/record_detailView"
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:layout_gravity="left|center_vertical"
                android:layout_marginLeft="20dp"
                android:src="@mipmap/voice_right" />
            <TextView
                android:id="@+id/tv_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="left|center_vertical"
                android:textColor="@android:color/white"
                android:layout_marginLeft="50dp"
                android:text="2s"/>
        </FrameLayout>
    </LinearLayout>
</LinearLayout>

对了!还用到了帧动画:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:drawable="@mipmap/v_right_anim1" android:duration= "300"></item>
    <item android:drawable="@mipmap/v_right_anim2" android:duration= "300"></item>
    <item android:drawable="@mipmap/v_right_anim3" android:duration= "300"></item>
</animation-list>

关于图片资源什么的就不上传了,重点是领会代码即可!也不做说明了!你们能看懂吧?!

猜你喜欢

转载自blog.csdn.net/AndroidStudioo/article/details/78418466