Android réalise la fonction d'envoi et de lecture de la voix et un exemple de code

Lien vers cet article: https://blog.csdn.net/qq_40785165/article/details/109658968

Bonjour à tous, je suis Xiao Hei, un programmeur qui n'est pas encore chauve ~~~

C'est la première fois que j'écris un article, et j'espère également partager ma future expérience d'apprentissage avec vous, j'espère que vous l'aimerez!

Si vous répétez les choses simples, vous êtes l'expert; si vous faites les choses répétées sérieusement, vous êtes le gagnant.

Après avoir réalisé l'envoi de photos dans un projet de salle de chat, j'ai pensé à implémenter une fonction d'envoi de voix, comprenant l'enregistrement, la synchronisation, la lecture, la commutation casque et haut-parleur, premier regard sur les rendus

On peut voir que la fonction d'envoi de voix est réalisée par la boîte de dialogue qui apparaît après avoir cliqué sur le module de fonction vocale. Cliquez sur le bouton Démarrer pour démarrer l'enregistrement, cliquez sur Terminer pour libérer la ressource et télécharger l'enregistrement sur le serveur, et enfin actualisez la liste des enregistrements et l'événement de clic sur la ligne de liste se poursuivra Enregistrez la lecture et surveillez la diffusion de la connexion du casque.

* Remarque: les couleurs et les tailles dans les codes suivants sont toutes définies dans le projet et peuvent être remplacées par les valeurs souhaitées.

Les autorisations qui doivent être ajoutées pour ce développement de fonctionnalités sont les suivantes:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

(1) Définissez d'abord un dialogue de style dialog_microphone, le code est le suivant:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:background="@drawable/frame_grey_white_edge"
    android:gravity="center"
    android:orientation="vertical"
    android:paddingLeft="@dimen/b20"
    android:paddingTop="@dimen/b50"
    android:paddingRight="@dimen/b20"
    android:paddingBottom="@dimen/b50">
​
    <ImageView
        android:layout_width="@dimen/b120"
        android:layout_height="@dimen/b120"
        android:src="@mipmap/icon_microphone" />
​
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/b50"
        android:text="点击外部区域,取消发送" />
​
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/b20">
​
        <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton
            android:id="@+id/btn_start"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="@dimen/b10"
            android:text="开始"
            android:textColor="@color/color_white"
            android:textSize="@dimen/b28"
            app:qmui_backgroundColor="@color/color_orange_main"
            app:qmui_borderColor="@color/color_white"
            app:qmui_radius="@dimen/b10" />
​
        <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton
            android:id="@+id/btn_ok"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="@dimen/b10"
            android:text="完成"
            android:textColor="@color/color_white"
            android:textSize="@dimen/b28"
            app:qmui_backgroundColor="@color/color_orange_main"
            app:qmui_borderColor="@color/color_white"
            app:qmui_radius="@dimen/b10" />
​
    </LinearLayout>
</LinearLayout>

Le QMUIRoundButton dans le code est le contrôle de bouton du framework QMUI. Les amis intéressés peuvent le découvrir par eux-mêmes sur Baidu ou le remplacer par un contrôle de bouton normal. Le code frame_grey_white_edge est le suivant:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/color_gray_e8" />
    <corners android:radius="@dimen/b20" />
    <stroke android:color="@color/color_white" />
</shape>

(2) Nous pouvons utiliser l'API de MediaRecorder pour l'enregistrement. Je vais organiser toutes les API utilisées dans la classe-MediaHelper, le code est le suivant:

public class MediaHelper {
    private MediaRecorder mMediaRecorder;
    private String mPath;//文件夹
    private String mFilePath;//文件
​
    private static MediaHelper mInstance;
​
    private MediaHelper(String path) {
        mPath = path;
    }
​
    /**
     * 准备播放后的回调
     * 这个时候文件夹里已经有文件生成了
     * 如果不去释放资源将会一直进行录音
     */
    public interface MediaStateListener {
        void preparedDone();
    }
​
    public MediaStateListener mMediaStateListener;
​
    public void setMediaStateListener(MediaStateListener mediaStateListener) {
        mMediaStateListener = mediaStateListener;
    }
​
​
    /**
     * 单例模式获取 MediaHelper
     * 双检锁/双重校验锁
     *
     * @param path
     * @return
     */
    public static MediaHelper getInstance(String path) {
        if (mInstance == null) {
            synchronized (MediaHelper.class) {
                if (mInstance == null) {
                    mInstance = new MediaHelper(path);
                }
            }
        }
​
        return mInstance;
    }
​
    /**
     * 准备录音
     */
    public void prepare() {
​
        try {
            File fileDir = new File(mPath);
            boolean b = !fileDir.exists();
            if (b) {
                fileDir.mkdirs();
            }
​
            String fileName = System.currentTimeMillis() + ".amr"; // 文件名字
            File file = new File(fileDir, fileName);  // 文件路径
​
            mMediaRecorder = new MediaRecorder();
            mFilePath = file.getAbsolutePath();
            //设置保存文件的路径
            if (Build.VERSION.SDK_INT < 26) {
                //若api低于26,调用setOutputFile(String path)
                mMediaRecorder.setOutputFile(file.getAbsolutePath());
            } else {
                //若API高于26 使用setOutputFile(File path)
                mMediaRecorder.setOutputFile(new File(file.getAbsolutePath()));
            }
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);    // 设置MediaRecorder的音频源为麦克风
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);    // 设置音频的格式
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);    // 设置音频的编码为AMR_NB
​
            mMediaRecorder.prepare();
            mMediaRecorder.start();
​
            if (mMediaStateListener != null) {
                mMediaStateListener.preparedDone();
            }
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
​
    }
​
    /**
     * 释放资源
     */
    public void release() {
        mMediaRecorder.stop();
        mMediaRecorder.release();
        mMediaRecorder = null;
    }
​
    /**
     * 取消
     */
    public void cancel() {
        release();
        //删除相应的录音
        if (mFilePath != null) {
            File file = new File(mFilePath);
            if (file.exists()) {
                file.delete();
            }
            mFilePath = null;
        }
    }
    //获取生成的文件路径
    public String getFilePath() {
        return mFilePath;
        }
}

  (3) Dans la boîte de dialogue, cliquez sur le bouton correspondant pour appeler la méthode correspondante dans la classe ci-dessus. J'ai une classe de boîte de dialogue appelée MicrophoneDialog. Le code est le suivant:

public class MicrophoneDialog extends BaseDialog implements MediaHelper.MediaStateListener {
    public static final int EXTRA_START = 1;//开始录制
    public static final int EXTRA_UPDATE_TIME = 2;//更新时长
    private AudioListener mAudioListener;
    private long mTime;//时长
    private String mPath = Constants.APK_PATH;
    private boolean authDismiss;//是否是自动关闭的,自动关闭的不触发关闭监听
​
    private MediaHelper mMediaHelper;
    private boolean isRecording;
    private String TAG = "MicrophoneDialog";
​
    public void setAudioListener(AudioListener audioListener) {
        mAudioListener = audioListener;
    }
​
    @Override
    public void preparedDone() {
        mHandler.sendEmptyMessage(EXTRA_START);
    }
​
    public interface AudioListener {
        void finish(long time, String filePath);
​
        void cancel();
    }
​
    public void dismiss(boolean authDismiss) {
        this.authDismiss = authDismiss;
        dismiss();
    }
​
    public MicrophoneDialog(Context context) {
        super(context);
    }
​
    @Override
    public int getViewId() {
        return R.layout.dialog_microphone;
    }
​
    @Override
    public void initBasic(Bundle savedInstanceState) {
        mMediaHelper = MediaHelper.getInstance(mPath);
​
        setOnDismissListener(new OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                if (!authDismiss) {
                    mMediaHelper.release();
                    isRecording = false;
                    mTime = 0;
                    if (mAudioListener != null) {
                        mAudioListener.cancel();
                    }
                }
            }
        });
        mMediaHelper.setMediaStateListener(this);
    }
​
    @OnClick({R.id.btn_ok, R.id.btn_start})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn_start:
                QToast.showToast("开始录音");
                mMediaHelper.prepare();
                break;
            case R.id.btn_ok:
                mMediaHelper.release();//要上传音频前释放资源,否则没办法上传音频
                QToast.showToast("结束录音");
                isRecording = false;
                Log.e(TAG, "onViewClicked: " + mTime + "," + mMediaHelper.getFilePath());
                if (mAudioListener != null) {
                    mAudioListener.finish(mTime / 1000, mMediaHelper.getFilePath());
                    mTime = 0;
                }
                break;
        }
    }
​
    /**
     * 这里使用Handle是为了在子线程中更新ui
     * 尽管我现在子线程只用来计时,没有更新ui
     * 但是万一以后会更新ui呢
     */
    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
​
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
                case EXTRA_START:
                    isRecording = true;
                    //开始计时
                    postDelayed(mRunnable, 1000);
                    break;
                case EXTRA_UPDATE_TIME:
                    postDelayed(mRunnable, 1000);
                    break;
​
            }
        }
    };
    /**
     * 开启个子线程计算时长
     */
    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            if (isRecording) {
                mTime += 1000;
                mHandler.sendEmptyMessage(EXTRA_UPDATE_TIME);//TODO 通知修改时长显示
            }
        }
    };

Dans le code ci-dessus, j'ai pratiqué l'utilisation de Handle. Il s'agissait à l'origine de mettre à jour l'interface utilisateur dans le thread enfant, mais j'étais toujours paresseux. Il est également possible d'utiliser Thread.sleep pour retarder le chronométrage. Le framework Butterknife est utilisé pour le contrôle déclaration et déclaration d'événement de clic, BaseDialog Il s'agit d'une classe de base encapsulée. Les amis peuvent déplacer le code de la méthode initBasic () vers onCreate (). Il convient de noter que les ressources doivent être libérées avant de télécharger le fichier, sinon cela affectera le appel de l'interface de téléchargement du fichier. Une fois la boîte de dialogue terminée, affichez simplement la boîte de dialogue dans l'activité ou le fragment. Une fois l'enregistrement terminé, appelez l'interface d'arrière-plan correspondante pour télécharger le fichier, puis revenez à l'adresse d'enregistrement, utilisez la vue de recyclage pour développer la liste d'enregistrement, et cliquez pour lire l'enregistrement. Les effets obtenus par la lecture sont:

1.点击相同子项播放与重置播放,点击不同子项需要释放上一个资源重置下一个资源
2.监听有线耳机与蓝牙耳机的拔插,修改播放参数

(4) Le code à lire est le suivant:

//变量声明以及定义
private MediaPlayer mMediaPlayer;
private HeadSetReceiver mHeadSetReceiver;//广播监听
private AudioManager mAudioManager = null;
private int index;//当前点击的是不是本身
......
registerHeadsetReceiver();
mAudioManager = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);//切换耳机等播放模式
mAudioManager.setMode(AudioManager.MODE_NORMAL);//普通模式
mMediaPlayer = new MediaPlayer();//播放
......
//点击事件,先判断是否是音频类型的消息
 if (item.getMessageType() == 3) {
   if (!mMediaPlayer.isPlaying()) {//没在播放就播放
        play(item);
    } else {//同样的音频在播放就释放并重置,如果是新的音频就接着播放新的
            mMediaPlayer.reset();
            mMediaPlayer.stop();
            mMediaPlayer.release();
            mMediaPlayer = new MediaPlayer();
            if (position != index) {
                play(item);//播放
            }
      }
   }
   index = position;
   ......
   //播放录音的方法
     private void play(MessageBean item) {
        try {
            mMediaPlayer = new MediaPlayer();
            mMediaPlayer.setDataSource(HttpHelper.picDomain + item.getMessage_content());//域名+文件路径
            mMediaPlayer.prepare();
            mMediaPlayer.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

(5) Le code de surveillance pour débrancher et brancher les écouteurs (écouteurs filaires et écouteurs bluetooth) et les codes de surveillance d'enregistrement sont les suivants:

 class HeadSetReceiver extends BroadcastReceiver {
​
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
                BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter();
                //记得加上蓝牙权限
                if (BluetoothHeadset.STATE_AUDIO_DISCONNECTED == defaultAdapter.getProfileConnectionState(BluetoothHeadset.HEADSET)) {
                    QToast.showToast("耳机未连接");
                    mAudioManager.setSpeakerphoneOn(true);
                } else {
                    QToast.showToast("耳机已连接");
                    mAudioManager.setSpeakerphoneOn(false);
                }
            } else if (intent.hasExtra("state")) {
                if (intent.getIntExtra("state", 0) == 0) {
                    QToast.showToast("耳机未连接");
                    mAudioManager.setSpeakerphoneOn(true);
                } else {
                    QToast.showToast("耳机已连接");
                    mAudioManager.setSpeakerphoneOn(false);
​
                }
            }
        }
    }
    private void registerHeadsetReceiver() {
        mHeadSetReceiver = new HeadSetReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("android.intent.action.HEADSET_PLUG");
        registerReceiver(mHeadSetReceiver, intentFilter);
        IntentFilter bluetoothFilter = new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
        registerReceiver(mHeadSetReceiver, bluetoothFilter);
    }

(6) Libérer les ressources lorsque la page est détruite (onDestroy)

if (mMediaPlayer != null) {
            mMediaPlayer.stop();
            mMediaPlayer.reset();
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
        unregisterReceiver(mHeadSetReceiver);

Jusqu'à présent, les fonctions d'envoi de voix et de lecture par clic ont été réalisées. L'effet est constitué des deux images statiques au début. Comme il n'y a pas d'effet d'interaction d'interface sophistiqué, l'effet gif n'est pas activé et les amis intéressés peuvent le faire Essayez-le eux-mêmes. Le recto et le verso de ce projet sont réalisés par moi-même. Si vous avez des doutes, vous pouvez scanner le code QR ci-dessous pour ajouter mon WeChat. Bienvenue à tous pour communiquer avec moi à propos de la technologie avant et arrière d'Android , et tout le monde peut progresser ensemble! Vous êtes également invités à vous abonner à mon compte public WeChat (qui vient également de commencer), je continuerai à partager des expériences d'apprentissage intéressantes, et enfin, je vous souhaite à tous la meilleure et une bonne santé, merci pour votre soutien et votre lecture!

Je suppose que tu aimes

Origine blog.csdn.net/qq_40785165/article/details/109658968
conseillé
Classement