• Android开发--仿微信语音对讲录音


    原文地址:http://www.2cto.com/kf/201502/378704.html

    自微信出现以来取得了很好的成绩,语音对讲的实现更加方便了人与人之间的交流。今天来实践一下微信的语音对讲的录音实现,这个也比较容易实现。在 此,我将该按钮封装成为一个控件,并通过策略模式的方式实现录音和界面的解耦合,以方便我们在实际情况中对录音方法的不同需求(例如想要实现wav格式的 编码时我们也就不能再使用MediaRecorder,而只能使用AudioRecord进行处理)。
    效果图:
    title=

    实现思路

    1.在微信中我们可以看到实现语音对讲的是通过点按按钮来完成的,因此在这里我选择重新自己的控件使其继承自Button并重写onTouchEvent方法,来实现对录音的判断。
    2.在onTouchEvent方法中,
    当我们按下按钮时,首先显示录音的对话框,然后调用录音准备方法并开始录音,接着开启一个计时线程,每隔0.1秒的时间获取一次录音音量的大小,并通过Handler根据音量大小更新Dialog中的显示图片;
    当我们移动手指时,若手指向上移动距离大于50,在Dialog中显示松开手指取消录音的提示,并将isCanceled变量(表示我们最后是否取消了录音)置为true,上移动距离小于20时,我们恢复Dialog的图片,并将isCanceled置为false;
    当抬起手指时,我们首先关闭录音对话框,接着调用录音停止方法并关闭计时线程,然后我们判断是否取消录音,若是的话则删除录音文件,否则判断计时时间是否太短,最后调用回调接口中的recordEnd方法。
    3.在这里为了适应不同的录音需求,我使用了策略模式来进行处理,将每一个不同的录音方法视为一种不同的策略,根据自己的需要去改写。

    注意问题

    1.在onTouchEvent的返回值中应该返回true,这样才能屏蔽之后其他的触摸事件,否则当手指滑动离开Button之后将不能在响应我们的触摸方法。
    2.不要忘记为自己的App添加权限:

    1
    2
    3
    <code class="hljs" xml="">    <uses-permission android:name="android.permission.RECORD_AUDIO">
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission></uses-permission></uses-permission></code>

    代码参考

    RecordButton 类,我们的自定义控件,重新复写了onTouchEvent方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    <code class="hljs" java="">package com.example.recordtest;
     
    import android.annotation.SuppressLint;
    import android.app.Dialog;
    import android.content.Context;
    import android.os.Handler;
    import android.os.Message;
    import android.util.AttributeSet;
    import android.view.Gravity;
    import android.view.LayoutInflater;
    import android.view.MotionEvent;
    import android.view.View;
    import android.widget.Button;
    import android.widget.ImageView;
    import android.widget.TextView;
    import android.widget.Toast;
     
    public class RecordButton extends Button {
     
        private static final int MIN_RECORD_TIME = 1; // 最短录音时间,单位秒
        private static final int RECORD_OFF = 0; // 不在录音
        private static final int RECORD_ON = 1; // 正在录音
     
        private Dialog mRecordDialog;
        private RecordStrategy mAudioRecorder;
        private Thread mRecordThread;
        private RecordListener listener;
     
        private int recordState = 0; // 录音状态
        private float recodeTime = 0.0f; // 录音时长,如果录音时间太短则录音失败
        private double voiceValue = 0.0; // 录音的音量值
        private boolean isCanceled = false; // 是否取消录音
        private float downY;
     
        private TextView dialogTextView;
        private ImageView dialogImg;
        private Context mContext;
     
        public RecordButton(Context context) {
            super(context);
            // TODO Auto-generated constructor stub
            init(context);
        }
     
        public RecordButton(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            // TODO Auto-generated constructor stub
            init(context);
        }
     
        public RecordButton(Context context, AttributeSet attrs) {
            super(context, attrs);
            // TODO Auto-generated constructor stub
            init(context);
        }
     
        private void init(Context context) {
            mContext = context;
            this.setText(按住 说话);
        }
     
        public void setAudioRecord(RecordStrategy record) {
            this.mAudioRecorder = record;
        }
     
        public void setRecordListener(RecordListener listener) {
            this.listener = listener;
        }
     
        // 录音时显示Dialog
        private void showVoiceDialog(int flag) {
            if (mRecordDialog == null) {
                mRecordDialog = new Dialog(mContext, R.style.Dialogstyle);
                mRecordDialog.setContentView(R.layout.dialog_record);
                dialogImg = (ImageView) mRecordDialog
                        .findViewById(R.id.record_dialog_img);
                dialogTextView = (TextView) mRecordDialog
                        .findViewById(R.id.record_dialog_txt);
            }
            switch (flag) {
            case 1:
                dialogImg.setImageResource(R.drawable.record_cancel);
                dialogTextView.setText(松开手指可取消录音);
                this.setText(松开手指 取消录音);
                break;
     
            default:
                dialogImg.setImageResource(R.drawable.record_animate_01);
                dialogTextView.setText(向上滑动可取消录音);
                this.setText(松开手指 完成录音);
                break;
            }
            dialogTextView.setTextSize(14);
            mRecordDialog.show();
        }
     
        // 录音时间太短时Toast显示
        private void showWarnToast(String toastText) {
            Toast toast = new Toast(mContext);
            View warnView = LayoutInflater.from(mContext).inflate(
                    R.layout.toast_warn, null);
            toast.setView(warnView);
            toast.setGravity(Gravity.CENTER, 0, 0);// 起点位置为中间
            toast.show();
        }
     
        // 开启录音计时线程
        private void callRecordTimeThread() {
            mRecordThread = new Thread(recordThread);
            mRecordThread.start();
        }
     
        // 录音Dialog图片随录音音量大小切换
        private void setDialogImage() {
            if (voiceValue < 600.0) {
                dialogImg.setImageResource(R.drawable.record_animate_01);
            } else if (voiceValue > 600.0 && voiceValue < 1000.0) {
                dialogImg.setImageResource(R.drawable.record_animate_02);
            } else if (voiceValue > 1000.0 && voiceValue < 1200.0) {
                dialogImg.setImageResource(R.drawable.record_animate_03);
            } else if (voiceValue > 1200.0 && voiceValue < 1400.0) {
                dialogImg.setImageResource(R.drawable.record_animate_04);
            } else if (voiceValue > 1400.0 && voiceValue < 1600.0) {
                dialogImg.setImageResource(R.drawable.record_animate_05);
            } else if (voiceValue > 1600.0 && voiceValue < 1800.0) {
                dialogImg.setImageResource(R.drawable.record_animate_06);
            } else if (voiceValue > 1800.0 && voiceValue < 2000.0) {
                dialogImg.setImageResource(R.drawable.record_animate_07);
            } else if (voiceValue > 2000.0 && voiceValue < 3000.0) {
                dialogImg.setImageResource(R.drawable.record_animate_08);
            } else if (voiceValue > 3000.0 && voiceValue < 4000.0) {
                dialogImg.setImageResource(R.drawable.record_animate_09);
            } else if (voiceValue > 4000.0 && voiceValue < 6000.0) {
                dialogImg.setImageResource(R.drawable.record_animate_10);
            } else if (voiceValue > 6000.0 && voiceValue < 8000.0) {
                dialogImg.setImageResource(R.drawable.record_animate_11);
            } else if (voiceValue > 8000.0 && voiceValue < 10000.0) {
                dialogImg.setImageResource(R.drawable.record_animate_12);
            } else if (voiceValue > 10000.0 && voiceValue < 12000.0) {
                dialogImg.setImageResource(R.drawable.record_animate_13);
            } else if (voiceValue > 12000.0) {
                dialogImg.setImageResource(R.drawable.record_animate_14);
            }
        }
     
        // 录音线程
        private Runnable recordThread = new Runnable() {
     
            @Override
            public void run() {
                recodeTime = 0.0f;
                while (recordState == RECORD_ON) {
                    {
                        try {
                            Thread.sleep(100);
                            recodeTime += 0.1;
                            // 获取音量,更新dialog
                            if (!isCanceled) {
                                voiceValue = mAudioRecorder.getAmplitude();
                                recordHandler.sendEmptyMessage(1);
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        };
     
        @SuppressLint(HandlerLeak)
        private Handler recordHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                setDialogImage();
            }
        };
     
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            // TODO Auto-generated method stub
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: // 按下按钮
                if (recordState != RECORD_ON) {
                    showVoiceDialog(0);
                    downY = event.getY();
                    if (mAudioRecorder != null) {
                        mAudioRecorder.ready();
                        recordState = RECORD_ON;
                        mAudioRecorder.start();
                        callRecordTimeThread();
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE: // 滑动手指
                float moveY = event.getY();
                if (downY - moveY > 50) {
                    isCanceled = true;
                    showVoiceDialog(1);
                }
                if (downY - moveY < 20) {
                    isCanceled = false;
                    showVoiceDialog(0);
                }
                break;
            case MotionEvent.ACTION_UP: // 松开手指
                if (recordState == RECORD_ON) {
                    recordState = RECORD_OFF;
                    if (mRecordDialog.isShowing()) {
                        mRecordDialog.dismiss();
                    }
                    mAudioRecorder.stop();
                    mRecordThread.interrupt();
                    voiceValue = 0.0;
                    if (isCanceled) {
                        mAudioRecorder.deleteOldFile();
                    } else {
                        if (recodeTime < MIN_RECORD_TIME) {
                            showWarnToast(时间太短  录音失败);
                            mAudioRecorder.deleteOldFile();
                        } else {
                            if (listener != null) {
                                listener.recordEnd(mAudioRecorder.getFilePath());
                            }
                        }
                    }
                    isCanceled = false;
                    this.setText(按住 说话);
                }
                break;
            }
            return true;
        }
     
        public interface RecordListener {
            public void recordEnd(String filePath);
        }
    }
    </code>

    Dialog布局:

    1
    2
    3
    4
    5
    6
    7
    8
    <code class="hljs" xml=""><!--?xml version=1.0 encoding=utf-8?-->
    <linearlayout android:background="@drawable/record_bg" android:gravity="center" android:layout_gravity="center" android:layout_height="wrap_content" android:layout_width="wrap_content" android:orientation="vertical" android:padding="20dp" xmlns:android="http://schemas.android.com/apk/res/android">
     
        <imageview android:id="@+id/record_dialog_img" android:layout_height="wrap_content" android:layout_width="wrap_content">
     
        <textview android:id="@+id/record_dialog_txt" android:layout_height="wrap_content" android:layout_margintop="5dp" android:layout_width="wrap_content" android:textcolor="@android:color/white">
     
    </textview></imageview></linearlayout></code>

    录音时间太短的Toast布局:

    1
    2
    3
    4
    5
    6
    7
    8
    <code class="hljs" xml=""><!--?xml version=1.0 encoding=utf-8?-->
    <linearlayout android:background="@drawable/record_bg" android:gravity="center" android:layout_height="wrap_content" android:layout_width="wrap_content" android:orientation="vertical" android:padding="20dp" xmlns:android="http://schemas.android.com/apk/res/android">
     
        <imageview android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/voice_to_short">
     
        <textview android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="时间太短" android:textcolor="@android:color/white" android:textsize="15sp">
     
    </textview></imageview></linearlayout></code>

    自定义的Dialogstyle,对话框样式

    1
    2
    3
    4
    5
    6
    7
    8
    <code applescript="" class="hljs"><style name="Dialogstyle" type="text/css"><item name=android:windowBackground>@android:color/transparent</item>
            <item name=android:windowFrame>@null</item>
            <item name=android:windowNoTitle>true</item>
            <item name=android:windowIsFloating>true</item>
            <item name=android:windowIsTranslucent>true</item>
            <item name=android:windowAnimationStyle>@android:style/Animation.Dialog</item>
            <!-- 显示对话框时当前的屏幕是否变暗 -->
            <item name=android:backgroundDimEnabled>false</item></style></code>

    RecordStrategy 录音策略接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    <code class="hljs" java="">package com.example.recordtest;
     
    /**
     * RecordStrategy 录音策略接口
     * @author acer
     */
    public interface RecordStrategy {
     
        /**
         * 在这里进行录音准备工作,重置录音文件名等
         */
        public void ready();
        /**
         * 开始录音
         */
        public void start();
        /**
         * 录音结束
         */
        public void stop();
     
        /**
         * 录音失败时删除原来的旧文件
         */
        public void deleteOldFile();
     
        /**
         * 获取录音音量的大小
         * @return
         */
        public double getAmplitude();
     
        /**
         * 返回录音文件完整路径
         * @return
         */
        public String getFilePath();
     
    }
    </code>

    个人写的一个录音实践策略

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    <code class="hljs" java="">package com.example.recordtest;
     
    import java.io.File;
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
     
    import android.media.MediaRecorder;
    import android.os.Environment;
     
    public class AudioRecorder implements RecordStrategy {
     
        private MediaRecorder recorder;
        private String fileName;
        private String fileFolder = Environment.getExternalStorageDirectory()
                .getPath() + /TestRecord;
     
        private boolean isRecording = false;
     
        @Override
        public void ready() {
            // TODO Auto-generated method stub
            File file = new File(fileFolder);
            if (!file.exists()) {
                file.mkdir();
            }
            fileName = getCurrentDate();
            recorder = new MediaRecorder();
            recorder.setOutputFile(fileFolder + / + fileName + .amr);
            recorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置MediaRecorder的音频源为麦克风
            recorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);// 设置MediaRecorder录制的音频格式
            recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);// 设置MediaRecorder录制音频的编码为amr
        }
     
        // 以当前时间作为文件名
        private String getCurrentDate() {
            SimpleDateFormat formatter = new SimpleDateFormat(yyyy_MM_dd_HHmmss);
            Date curDate = new Date(System.currentTimeMillis());// 获取当前时间
            String str = formatter.format(curDate);
            return str;
        }
     
        @Override
        public void start() {
            // TODO Auto-generated method stub
            if (!isRecording) {
                try {
                    recorder.prepare();
                    recorder.start();
                } catch (IllegalStateException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
     
                isRecording = true;
            }
     
        }
     
        @Override
        public void stop() {
            // TODO Auto-generated method stub
            if (isRecording) {
                recorder.stop();
                recorder.release();
                isRecording = false;
            }
     
        }
     
        @Override
        public void deleteOldFile() {
            // TODO Auto-generated method stub
            File file = new File(fileFolder + / + fileName + .amr);
            file.deleteOnExit();
        }
     
        @Override
        public double getAmplitude() {
            // TODO Auto-generated method stub
            if (!isRecording) {
                return 0;
            }
            return recorder.getMaxAmplitude();
        }
     
        @Override
        public String getFilePath() {
            // TODO Auto-generated method stub
            return fileFolder + / + fileName + .amr;
        }
     
    }
    </code>

    MainActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <code class="hljs" java="">package com.example.recordtest;
     
    import android.os.Bundle;
    import android.app.Activity;
    import android.view.Menu;
     
    public class MainActivity extends Activity {
     
        RecordButton button;
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            button = (RecordButton) findViewById(R.id.btn_record);
            button.setAudioRecord(new AudioRecorder());
        }
     
     
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.main, menu);
            return true;
        }
     
    }
    </code>
  • 相关阅读:
    容器云技术:容器化微服务,Istio占C位出道
    如何用istio实现请求超时管理
    技术进阶:Kubernetes高级架构与应用状态部署
    如何基于 K8S 多租能力构建 Serverless Container
    面试题目<转载>
    PHP面试出场率较高的题目<转载>
    命名规范
    字符串大小写转换(三种方法)
    php反转输出字符串(两种方法)
    获取文件名后缀的方法
  • 原文地址:https://www.cnblogs.com/chenhaib/p/4544009.html
Copyright © 2020-2023  润新知