精华内容
下载资源
问答
  • 这些是我前前后后遇到的所有问题,有很多问题都困扰了我很长时间,在我我不断努力坚持下克服种种困难,终于大功告成了! 收集语音素材问题解决 一开始这个问题我很头疼,去哪里找骚里骚气的语音呢?这个问题一度困扰我,一...
  • 虽然我很喜欢网易的云音乐,但环信使用起来确实比较简单,就选择了它,今天这篇文章主要是讲的关于语音聊天的功能的实现。先来看一下我做的效果图吧~ 由于为了录制这个动态图,我用的是模拟器,所以在录制过程...

    最近公司项目中需要用到聊天功能,在通过对比网易云信和环信之后呢还是选择了使用环信。虽然我很喜欢网易的云音乐,但环信使用起来确实比较简单,就选择了它,今天这篇文章主要是讲的关于语音聊天的功能的实现。先来看一下我做的效果图吧~


    由于为了录制这个动态图,我用的是模拟器,所以在录制过程中的麦克风中间的波形没有波动,但是使用真机的话麦克风的波形是可以随着说话的音量波动起来的。在用户松开按钮后将录音文件保存在本地,没做其他操作,如果你也和我一样正在做这一块的话可以尝试去将这个功能去完善起来。

    首先呢来整理一下思路,用户在按住按钮之后会进行调用麦克风进行录音,松开按钮后将语音文件保存在本地。虽然看起来比较简单,但是为了实现这个功能,我也参考了其他大神的方法,也花了不少时间,觉得写的不错的话,就点个赞呗,嘿嘿嘿。~

    闲话不多说,现在来看看我是如何实现的吧。

    一共写了三个类:



    首先自定义了一个AudioRecorderButton继承Button,重写了onTouch事件:

    /**
     * 控制录音Button
     * 1、重写onTouchEvent;(changeState方法、wantToCancel方法、reset方法);
     * 2、编写AudioDialogManage、并与该类AudioRecorderButton进行整合;
     * 3、编写AudioManage、并与该类AudioRecorderButton进行整合;
     */
    
    public class AudioRecorderButton extends Button implements AudioManage.AudioStateListener {
    
        /**
         * AudioRecorderButton的三个状态
         */
        private static final int STATE_NORMAL = 1;           //默认状态
        private static final int STATE_RECORDERING = 2;      //录音状态
        private static final int STATE_WANT_TO_CALCEL = 3;   //取消状态
    
        private int mCurState = STATE_NORMAL;    // 当前录音状态
        private boolean isRecordering = false;   // 是否已经开始录音
        private boolean mReady;    // 是否触发onLongClick
    
        private static final int DISTANCE_Y_CANCEL = 50;
    
        private AudioDialogManage audioDialogManage;
    
        private AudioManage mAudioManage;
    
        /**
         * 正常录音完成后的回调
         */
        public interface AudioFinishRecorderListener{
            void onFinish(int seconds, String FilePath);
        }
    
        private AudioFinishRecorderListener mListener;
    
        public void setAudioFinishRecorderListener(AudioFinishRecorderListener listener){
            this.mListener=listener;
        }
    
        //构造方法
        public AudioRecorderButton(Context context) {
            super(context, null);
            // TODO Auto-generated constructor stub
        }
        public AudioRecorderButton(final Context context, AttributeSet attrs) {
            super(context, attrs);
    
            audioDialogManage = new AudioDialogManage(getContext());
    
            String dir = Environment.getExternalStorageDirectory()
                    + "/kairui/VoiceCache";                             // 此处需要判断是否有存储卡(外存)
            mAudioManage = AudioManage.getInstance(dir);
            mAudioManage.setOnAudioStateListener(this);
    
            setOnLongClickListener(new OnLongClickListener() {
    
                @Override
                public boolean onLongClick(View v) {
                    mReady = true;
                    // 真正显示应该在audio end prepared以后
                    mAudioManage.prepareAudio();
                    //return true;
                    return false;
                }
            });
    
            mAudioManage.setOnAudioStatusUpdateListener(new AudioManage.OnAudioStatusUpdateListener() {
    
                //录音中....db为声音分贝,time为录音时长
                @Override
                public void onUpdate(double db, long time) {
                    //根据分贝值来设置录音时话筒图标的上下波动
                    audioDialogManage.mIcon.getDrawable().setLevel((int) (3000 + 6000 * db / 100));
                }
    
            });
    
    
            // TODO Auto-generated constructor stub
        }
    
        /*
         * 复写onTouchEvent
         * @see android.widget.TextView#onTouchEvent(android.view.MotionEvent)
         */
        @Override
        public boolean onTouchEvent(MotionEvent event) {
    
            int action = event.getAction();   //获取当前Action
            int x = (int) event.getX();       //获取当前的坐标
            int y = (int) event.getY();
    
            switch (action) {
    
                case MotionEvent.ACTION_DOWN:
                    changeState(STATE_RECORDERING);
                    break;
    
                case MotionEvent.ACTION_MOVE:
    
                    // 已经开始录音状态时,根据X、Y的坐标,判断是否想要取消
                    if (isRecordering) {
                        if (wantToCancel(x, y)) {
                            changeState(STATE_WANT_TO_CALCEL);
                        } else {
                            changeState(STATE_RECORDERING);
                        }
                    }
                    break;
    
                case MotionEvent.ACTION_UP:
                    if (!mReady) {   //没有触发onLongClick
                        reset();
                        return super.onTouchEvent(event);
                    }
    
                    if (!isRecordering || mTime < 900) {  //录音时间过短
                        audioDialogManage.tooShort();
                        mAudioManage.cancel();
                        mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DISMISS, 1300);// 延迟,1.3秒以后关闭“时间过短对话框”
                    }
    
                    else if (mCurState == STATE_RECORDERING) { //正常录制结束
                        audioDialogManage.dismissDialog();
                        // release
                        mAudioManage.release();
                        // callbackToAct
                        // 正常录制结束,回调录音时间和录音文件完整路径——在播放的时候需要使用
                        if(mListener!=null){
                            mListener.onFinish(mTime /1000, mAudioManage.getCurrentFilePath());
                        }
    
                    } else if (mCurState == STATE_WANT_TO_CALCEL) {
                        // cancel
                        audioDialogManage.dismissDialog();
                        mAudioManage.cancel();
                    }
                    reset();
                    break;
            }
            return super.onTouchEvent(event);
        }
    
        /**
         * 恢复状态以及一些标志位
         */
        private void reset() {
            isRecordering = false;
            mReady = false;                 //是否触发onLongClick
            mTime = 0;
            changeState(STATE_NORMAL);
        }
    
        private boolean wantToCancel(int x, int y) {
            // 判断手指的滑动是否超出范围
            if (x < 0 || x > getWidth()) {
                return true;
            }
            if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) {
                return true;
            }
            return false;
        }
    
        /**
         * 改变Button的背景和文本、展示不同状态的录音提示对话框
         * @param state
         */
        private void changeState(int state) {
            if (mCurState != state) {
                mCurState = state;
                switch (state) {
                    case STATE_NORMAL:
                        setBackgroundResource(R.drawable.send_speech_btn_normal_style);
                        setText(R.string.push_to_speak);
                        break;
    
                    case STATE_RECORDERING:
                        setBackgroundResource(R.drawable.send_speech_btn_pres_style);
                        setText(R.string.release_to_send);
                        if (isRecordering) {
                            // 更新Dialog.recording()
                            audioDialogManage.recording();
                        }
                        break;
    
                    case STATE_WANT_TO_CALCEL:
                        setBackgroundResource(R.drawable.send_speech_btn_pres_style);
                        setText(R.string.release_to_cancel_send);
                        // 更新Dialog.wantCancel()
                        audioDialogManage.wantToCancel();
                        break;
                }
            }
        }
    
        /*
         * 实现“准备完毕”接口
         * (non-Javadoc)
         */
        @Override
        public void wellPrepared() {
            // TODO Auto-generated method stub
            mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
        }
    
        private static final int MSG_AUDIO_PREPARED = 0x110;   //准备完全
        private static final int MSG_CURRENT_TIME = 0x111;     //当前语音时长
        private static final int MSG_DIALOG_DISMISS = 0x112;    //销毁对话框
        private static final int MSG_COUNT_DOWN_DONE = 0x113;    //录音倒计时结束
    
        /**
         * 接收子线程数据,并用此数据配合主线程更新UI
         * Handler运行在主线程(UI线程)中,它与子线程通过Message对象传递数据。
         * Handler接受子线程传过来的(子线程用sedMessage()方法传弟)Message对象,把这些消息放入主线程队列中,配合主线程进行更新UI。
         */
        private Handler mHandler = new Handler() {
    
            public void handleMessage(android.os.Message msg) {
                switch (msg.what) {
                    case MSG_AUDIO_PREPARED:        //216:mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
                        audioDialogManage.showRecorderingDialog();
                        isRecordering = true;
                        //已经在录制,同时开启一个获取音量、并且计时的线程
                        new Thread(mUpdateCurTimeRunnable).start();
                        break;
    
                    case MSG_CURRENT_TIME:          //265:mHandler.sendEmptyMessage(MSG_VOICE_CHANGE);
                        audioDialogManage.updateCurTime(TimeUtils.countDown(mTime));
                        break;
    
                    //这里在Handler里面处理DIALOG_DIMISS,是因为想让该对话框显示一段时间,延迟关闭,——详见125行
                    case MSG_DIALOG_DISMISS:         //125:mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DISMISS, 1300);
                        audioDialogManage.dismissDialog();
                        break;
                    //处理录音时间结束
                    case MSG_COUNT_DOWN_DONE:
                        mAudioManage.release();
                        // callbackToAct
                        // 正常录制结束,回调录音时间和录音文件完整路径——在播放的时候需要使用
                        if(mListener!=null){
                            mListener.onFinish(mTime /1000, mAudioManage.getCurrentFilePath());
                        }
                        audioDialogManage.dismissDialog();
                        reset();
                        break;
                }
            }
        };
    
        private int mTime;  //开始录音计时,计时;(在reset()中置空) 单位为毫秒
        /**
         * 更新当前录音时长的runnable
         */
        private Runnable mUpdateCurTimeRunnable = new Runnable() {
    
            @Override
            public void run() {
    
                while (isRecordering) {
                    try {
                        Thread.sleep(100);
                        mTime += 100;
                        mHandler.sendEmptyMessage(MSG_CURRENT_TIME);
    
                        if(mTime == 60 * 1000){
                            mHandler.sendEmptyMessage(MSG_COUNT_DOWN_DONE);
                        }
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
    
                }
            }
        };
    }
    

    接下来是AudioDialogManage:

    /**
     * 录制语音弹窗管理类
     */
    
    public class AudioDialogManage {
        private Dialog mDialog;
        public ImageView mIcon;     //麦克风及删除图标
        private TextView mTime;     //录音时长
        private TextView mLabel;    //录音提示文字
        private Context mContext;
    
    
        public AudioDialogManage(Context context) {
            this.mContext = context;
        }
    
        /**
         * 默认的对话框的显示
         */
        public void showRecorderingDialog() {
            mDialog = new Dialog(mContext, R.style.Theme_AudioDialog);
    
            LayoutInflater inflater = LayoutInflater.from(mContext);
            View view = inflater.inflate(
                    R.layout.voicenotes_recorder_dialog, null);
            mDialog.setContentView(view);
    
            mIcon = (ImageView) mDialog.findViewById(R.id.recorder_dialog_icon);
            mTime = (TextView) mDialog.findViewById(R.id.recorder_dialog_time_tv);
            mLabel = (TextView) mDialog.findViewById(R.id.recorder_dialog_label);
    
            mDialog.show();
        }
    
        //下面在显示各种对话框时,mDialog已经被构造,只需要控制ImageView、TextView的显示即可
        /**
         * 正在录音时,Dialog的显示
         */
        public void recording() {
            if (mDialog != null && mDialog.isShowing()) {
                mIcon.setVisibility(View.VISIBLE);
                mTime.setVisibility(View.VISIBLE);
                mLabel.setVisibility(View.VISIBLE);
    
                mIcon.setImageResource(R.drawable.record_microphone);
                mLabel.setBackgroundColor(Color.parseColor("#00000000"));
                mLabel.setText(R.string.slide_up_cancel_send);
            }
        }
    
        /**
         * 取消录音提示对话框
         */
        public void wantToCancel() {
            if (mDialog != null && mDialog.isShowing()) {
                mIcon.setVisibility(View.VISIBLE);
                mTime.setVisibility(View.GONE);
                mLabel.setVisibility(View.VISIBLE);
    
                mIcon.setImageResource(R.drawable.delete_speech_anim_list);
                mLabel.setBackgroundColor(Color.parseColor("#AF2831"));
                mLabel.setText(R.string.release_to_cancel_send);
            }
        }
    
        /**
         * 录音时间过短
         */
        public void tooShort() {
            if (mDialog != null && mDialog.isShowing()) {
                mIcon.setVisibility(View.VISIBLE);
                mTime.setVisibility(View.GONE);
                mLabel.setVisibility(View.VISIBLE);
    
                mIcon.setImageResource(R.drawable.speech_is_too_short);
                mLabel.setBackgroundColor(Color.parseColor("#00000000"));
                mLabel.setText("说话时间太短");
            }
        }
    
        /**
         * mDialog.dismiss();
         */
        public void dismissDialog() {
            if (mDialog != null && mDialog.isShowing()) {
                mDialog.dismiss();
                mDialog = null;
            }
        }
    
        /**
         * 更新显示当前录音秒数
         * @param time
         */
        public void updateCurTime(String time) {
            if (mDialog != null && mDialog.isShowing()) {
    
                mTime.setText(time);
            }
        }
    }
    

    提示框的xml代码:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <LinearLayout
            android:background="@drawable/record_microphone_bj"
            android:layout_width="140dp"
            android:layout_height="140dp"
            android:gravity="center"
            android:orientation="vertical">
    
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    
                <ImageView
                    android:layout_centerInParent="true"
                    android:id="@+id/recorder_dialog_icon"
                    android:layout_width="55dp"
                    android:layout_height="65dp"
                    android:src="@drawable/record_microphone"
                    android:visibility="visible" />
    
    
                <TextView
                    android:id="@+id/recorder_dialog_time_tv"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textSize="15sp"
                    android:layout_alignBottom="@id/recorder_dialog_icon"
                    android:layout_toRightOf="@id/recorder_dialog_icon"
                    android:textColor="@color/white"
                    android:text="60''"/>
    
            </RelativeLayout>
    
            <TextView
                android:id="@+id/recorder_dialog_label"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:textSize="14sp"
                android:gravity="center"
                android:paddingLeft="5dp"
                android:paddingRight="5dp"
                android:text="@string/slide_up_cancel_send"
                android:textColor="@color/white" />
    
        </LinearLayout>
    
    </LinearLayout>

    最后是AudioManage代码:

    /**
     * Audio管理类
     */
    
    public class AudioManage {
        private MediaRecorder mMediaRecorder;  //MediaRecorder可以实现录音和录像。需要严格遵守API说明中的函数调用先后顺序.
        private String mDir;             // 文件夹的名称
        private String mCurrentFilePath;
    
        private static AudioManage mInstance;
    
        private boolean isPrepared; // 标识MediaRecorder准备完毕
    
        private AudioManage(String dir) {
            mDir = dir;
        }
    
        private OnAudioStatusUpdateListener audioStatusUpdateListener;
    
        private long startTime;
    
        /**
         * 回调“准备完毕”
         * @author songshi
         *
         */
        public interface AudioStateListener {
            void wellPrepared();    // prepared完毕
        }
    
        public AudioStateListener mListener;
    
        public void setOnAudioStateListener(AudioStateListener audioStateListener) {
            mListener = audioStateListener;
        }
    
    
        /**
         * 使用单例实现 AudioManage
         * @param dir
         * @return
         */
        //DialogManage主要管理Dialog,Dialog主要依赖Context,而且此Context必须是Activity的Context,
        //如果DialogManage写成单例实现,将是Application级别的,将无法释放,容易造成内存泄露,甚至导致错误
        public static AudioManage getInstance(String dir) {
            if (mInstance == null) {
                synchronized (AudioManage.class) {   // 同步
                    if (mInstance == null) {
                        mInstance = new AudioManage(dir);
                    }
                }
            }
    
            return mInstance;
        }
    
        /**
         * 准备录音
         */
        public void prepareAudio() {
    
            try {
                isPrepared = false;
    
                File dir = new File(mDir);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
    
                String fileName = GenerateFileName(); // 文件名字
                File file = new File(dir, fileName);  // 路径+文件名字
    
                //MediaRecorder可以实现录音和录像。需要严格遵守API说明中的函数调用先后顺序.
                mMediaRecorder = new MediaRecorder();
                mCurrentFilePath = file.getAbsolutePath();
                mMediaRecorder.setOutputFile(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();
                startTime = System.currentTimeMillis();
                updateMicStatus();
    
                isPrepared = true; // 准备结束
    
                if (mListener != null) {
                    mListener.wellPrepared();
                }
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
        /**
         * 生成文件名称
         * @return
         */
        private String GenerateFileName() {
            // TODO Auto-generated method stub
    //        return UUID.randomUUID().toString() + ".amr"; // 音频文件格式 ,随机生成名字
            return TimeUtils.getCurrentTime() + ".amr";     // 生成带有时间的名字
    
        }
    
        /**
         * 释放资源
         */
        public void release() {
            mMediaRecorder.stop();
            mMediaRecorder.release();
            mMediaRecorder = null;
        }
    
        /**
         * 取消(释放资源+删除文件)
         */
        public void cancel() {
    
            release();
    
            if (mCurrentFilePath != null) {
                File file = new File(mCurrentFilePath);
                file.delete();    //删除录音文件
                mCurrentFilePath = null;
            }
        }
    
        public String getCurrentFilePath() {
            // TODO Auto-generated method stub
            return mCurrentFilePath;
        }
    
        private int BASE = 1;
        private int SPACE = 100;// 间隔取样时间
    
        public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {
            this.audioStatusUpdateListener = audioStatusUpdateListener;
        }
    
        private final Handler mHandler = new Handler();
        private Runnable mUpdateMicStatusTimer = new Runnable() {
            public void run() {
                updateMicStatus();
            }
        };
    
        /**
         * 更新麦克状态
         */
        private void updateMicStatus() {
    
            if (mMediaRecorder != null) {
                double ratio = (double)mMediaRecorder.getMaxAmplitude() / BASE;
                double db;// 分贝
                if (ratio > 1) {
                    db = 20 * Math.log10(ratio);
                    if(null != audioStatusUpdateListener) {
                        audioStatusUpdateListener.onUpdate(db,System.currentTimeMillis()-startTime);
                    }
                }
                mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);
            }
        }
    
        public interface OnAudioStatusUpdateListener {
            /**
             * 录音中...
             * @param db 当前声音分贝
             * @param time 录音时长
             */
            public void onUpdate(double db,long time);
    
        }
    }
    

    然后再放上Dialog的style代码:

    <!-- 录音对话框 <style name="Theme_AudioDialog"> -->
        <style name="Theme_AudioDialog" parent="@android:style/Theme.Dialog">
            <item name="android:windowBackground">@android:color/transparent</item>
            <item name="android:windowFrame">@null</item>
            <item name="android:windowIsFloating">true</item>
            <item name="android:windowIsTranslucent">true</item>
            <item name="android:backgroundDimEnabled">false</item>
        </style>

    最后呢记得在使用的过程中调用以下方法:

    //发送录音结束接口
            sendSpeechBtn.setAudioFinishRecorderListener(new AudioRecorderButton.AudioFinishRecorderListener() {
    
                @Override
                public void onFinish(int seconds, String FilePath) {
    
                    ToastUtils.showShort(ChattingActivity.this,"语音文件为:"+FilePath+"时长:"+seconds);
                    //拿到文件地址和时长后就可以去做发送语音的操作了
                    
                }
            });


    以上就是今天跟大家分享的语音功能的全部代码了,目前还未完成将发送的语音文件显示到列表上以及点击list  Item之后可以去播放语音文件,因为急着要去完成别的功能,这个就过几天再去实现。需要源码的话就说一下,我好先整理整理,毕竟全部写在了公司的项目中。如果你在使用的过程中遇到问题欢迎私信我,说不定我们之间还能发生些什么,嘿嘿嘿~(●ˇ∀ˇ●)

    对了,要实现麦克风图形可以波动的效果记得使用类似这样的图片


    好了,就先写到这里了。


    展开全文
  • 百度语音合成接口的调用以及根据语音播放语音,文中接口请自行申请。
  • 基于语音控制的智能家居控制系统(安卓,蓝牙,语音控制,单片机) 上位机是安卓机,下位机是51,上位机(语音识别出指令如开关灯)与下位机(接蓝牙模块BLE:CC2451)通过蓝牙通信进行控制。 初步设想是语音识别...

    基于语音控制的智能家居控制系统(安卓,蓝牙,语音控制,单片机)

    上位机是安卓机,下位机是51,上位机(语音识别出指令如开关灯)与下位机(接蓝牙模块BLE:CC2451)通过蓝牙通信进行控制。
    初步设想是语音识别部分用科大讯飞的sdk,然后再找个开源的蓝牙通信Demo来进行二次开发,无奈本人小白,无安卓开发经验,java零基础,虽然论坛上有很多现成的蓝牙控制单片机的例子,但是好像没有语音识别这一部分的,做得很难受。

    以下贴出我的进展图、App闪退错误代码,以及半成品Demo(只实现了语音识别部分),希望有大牛指导一下。

    开发环境:Android Studio 2.1.3
    Gradle版本:com.android.tools.build:gradle:2.1.3
    SDK版本:android-sdk_r24.4.1-windows
    JDK版本:jdk1.8.0_201
    Compile Sdk Version:API 25
    Build Tools Version:28.0.3
    半成品Demo:(https://download.csdn.net/download/qq_40093925/10975515)

    **

    AndroidManifest.xml

    **

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.luoyn.speechdemo">
    
        <!--连接网络权限,用于执行云端语音能力 -->
        <uses-permission android:name="android.permission.INTERNET" />
        <!--获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 -->
        <uses-permission android:name="android.permission.RECORD_AUDIO" />
        <!--读取网络信息状态 -->
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        <!--获取当前wifi状态 -->
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
        <!--允许程序改变网络连接状态 -->
        <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
        <!--读取手机信息权限 -->
        <uses-permission android:name="android.permission.READ_PHONE_STATE" />
        <!--读取联系人权限,上传联系人需要用到此权限 -->
        <uses-permission android:name="android.permission.READ_CONTACTS" />
        <!--外存储写权限,构建语法需要用到此权限 -->
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <!--外存储读权限,构建语法需要用到此权限 -->
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
        <!--配置权限,用来记录应用配置信息 -->
        <uses-permission android:name="android.permission.WRITE_SETTINGS" />
        <!--手机定位信息,用来为语义等功能提供定位,提供更精准的服务--> <!--定位信息是敏感信息,可通过Setting.setLocationEnable(false)关闭定位请求 -->
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        <!--蓝牙权限-->
        <uses-permission android:name="android.permission.BLUETOOTH" />
        <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="智能家居控制面板"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity><!-- ATTENTION: This was auto-generated to add Google Play services to your project for
         App Indexing.  See https://g.co/AppIndexing/AndroidStudio for more information. -->
            <meta-data
                android:name="com.google.android.gms.version"
                android:value="@integer/google_play_services_version" />
        </application>
    
    </manifest>
    

    MainActivitiy.java

    package com.example.luoyn.speechdemo;
    
    import android.bluetooth.BluetoothAdapter;
    import android.bluetooth.BluetoothDevice;
    import android.bluetooth.BluetoothServerSocket;
    import android.bluetooth.BluetoothSocket;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.content.SharedPreferences;
    import android.net.Uri;
    import android.os.Environment;
    import android.os.Message;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.support.v7.widget.LinearLayoutManager;
    import android.support.v7.widget.RecyclerView;
    import android.text.TextUtils;
    import android.util.Log;
    import android.view.View;
    import android.widget.EditText;
    import android.widget.TextView;
    import android.widget.Toast;
    import android.os.Handler;
    
    import com.google.android.gms.appindexing.Action;
    import com.google.android.gms.appindexing.AppIndex;
    import com.google.android.gms.common.api.GoogleApiClient;
    import com.iflytek.cloud.ErrorCode;
    import com.iflytek.cloud.InitListener;
    import com.iflytek.cloud.RecognizerListener;
    import com.iflytek.cloud.RecognizerResult;
    import com.iflytek.cloud.SpeechConstant;
    import com.iflytek.cloud.SpeechError;
    import com.iflytek.cloud.SpeechRecognizer;
    import com.iflytek.cloud.SpeechUtility;
    import com.iflytek.cloud.ui.RecognizerDialog;
    import com.iflytek.cloud.ui.RecognizerDialogListener;
    
    import org.json.JSONException;
    import org.json.JSONObject;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.io.PrintWriter;
    import java.io.UnsupportedEncodingException;
    import java.util.HashMap;
    import java.util.LinkedHashMap;
    import java.util.UUID;
    
    
    
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        //显示听写结果
        private TextView textView;
    
        //语音听写对象
        private SpeechRecognizer speechRecognizer;
    
        //语音听写UI
        private RecognizerDialog recognizerDialog;
    
        //是否显示听写UI
        private boolean isShowDialog = true;
    
        //缓存
        private SharedPreferences sharedPreferences;
    
        //用hashmap存储听写结果
        private HashMap<String, String> hashMap = new LinkedHashMap<String, String>();
    
        //引擎类型(云端或本地)
        private String mEngineType = null;
    
        //函数返回值
        private int ret = 0;
    
        private Toast toast;
    
    
        //插入部分--------------------------------------------------------------------------------------
        public static final int REQUEST_BT_ENABLE_CODE = 200;
        public static final String BT_UUID = "00001101-0000-1000-8000-00805F9B34FB";//YahBoom_uuid
    
        private BluetoothAdapter mBluetoothAdapter;//蓝牙适配器
        private BlueToothStateReceiver mReceiver;//广播接收器
        private ConnectThread mConnectThread; //客户端线程
        private AcceptThread mAcceptThread; //服务端线程
    
        private RecyclerView mRecyclerView;
        private RvAdapter mRvAdapter;
    
        private RecyclerView mMessageView;
        private static MsgAdapter mMessageAdapter;
    
        private EditText inputEt;
    
        private static Handler mHandler = new Handler() {
            @Override
            public void dispatchMessage(Message msg) {
                mMessageAdapter.addMessage((String) msg.obj);
            }
        };
        /**
         * ATTENTION: This was auto-generated to implement the App Indexing API.
         * See https://g.co/AppIndexing/AndroidStudio for more information.
         */
        private GoogleApiClient client;
        //----------------------------------------------------------------------------------------------
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initView();
            initDate();
            //插入部分--------------------------------------------------------------------------------------
            initUI();
            registerRec();
            //------------------------------------------------------------------------------------------
            // ATTENTION: This was auto-generated to implement the App Indexing API.
            // See https://g.co/AppIndexing/AndroidStudio for more information.
            client = new GoogleApiClient.Builder(this).addApi(AppIndex.API).build();
        }
    
        private void initDate() {
            //初始化sdk 将自己申请的appid放到下面
            //此句代码应该放在application中的,这里为了方便就直接放代码中了
            SpeechUtility.createUtility(this, "appid=5c6abcfc");
            speechRecognizer = SpeechRecognizer.createRecognizer(this, initListener);
            recognizerDialog = new RecognizerDialog(this, initListener);
            sharedPreferences = getSharedPreferences(this.getPackageName(), Context.MODE_PRIVATE);
            toast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
            //这里我直接将引擎类型设置为云端,因为本地需要下载讯飞语记,这里为了方便直接使用云端
            //有需要的朋友可以加个单选框 让用户选择云端或本地
            mEngineType = SpeechConstant.TYPE_CLOUD;
        }
    
        private void initView() {
            textView = (TextView) findViewById(R.id.tv);
        }
    
        //插入部分--------------------------------------------------------------------------------------
        private void initUI() {
            findViewById(R.id.open).setOnClickListener(this);
            findViewById(R.id.close).setOnClickListener(this);
            findViewById(R.id.start).setOnClickListener(this);
            findViewById(R.id.stop).setOnClickListener(this);
            findViewById(R.id.send).setOnClickListener(this);
    
            inputEt = (EditText) findViewById(R.id.input);
    
            mRecyclerView = (RecyclerView) findViewById(R.id.devices);
            mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
            mRvAdapter = new RvAdapter(this);
            mRecyclerView.setAdapter(mRvAdapter);
            mRvAdapter.setOnItemClickListener(new RvAdapter.OnItemClickListener() {
                @Override
                public void onClick(BluetoothDevice device) {
                    mConnectThread = new ConnectThread(device);
                    mConnectThread.start();
                }
            });
    
            mMessageView = (RecyclerView) findViewById(R.id.msglist);
            mMessageView.setLayoutManager(new LinearLayoutManager(this));
            mMessageAdapter = new MsgAdapter(this);
            mMessageView.setAdapter(mMessageAdapter);
        }
    
        private void openBT() {
            if (mBluetoothAdapter == null) {
                mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
            }
            //1.设备不支持蓝牙,结束应用
            if (mBluetoothAdapter == null) {
                finish();
                return;
            }
            //2.判断蓝牙是否打开
            if (!mBluetoothAdapter.enable()) {
                //没打开请求打开
                Intent btEnable = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                startActivityForResult(btEnable, REQUEST_BT_ENABLE_CODE);
            }
        }
    
        private void registerRec() {
            //3.注册蓝牙广播
            mReceiver = new BlueToothStateReceiver();
            IntentFilter filter = new IntentFilter();
            filter.addAction(BluetoothDevice.ACTION_FOUND);//搜多到蓝牙
            filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//搜索结束
            registerReceiver(mReceiver, filter);
        }
    
        @Override
        protected void onDestroy() {
            if (mReceiver != null) {
                unregisterReceiver(mReceiver);
            }
            super.onDestroy();
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            if (requestCode == REQUEST_BT_ENABLE_CODE) {
                if (resultCode == RESULT_OK) {
                    //用户允许打开蓝牙
                    mMessageAdapter.addMessage("用户同意打开蓝牙");
                } else if (resultCode == RESULT_CANCELED) {
                    //用户取消打开蓝牙
                    mMessageAdapter.addMessage("用户拒绝打开蓝牙");
                }
            }
            super.onActivityResult(requestCode, resultCode, data);
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.open:
                    openBT();
                    mMessageAdapter.addMessage("打开蓝牙");
                    if (mAcceptThread == null && mBluetoothAdapter != null) {
                        mAcceptThread = new AcceptThread();
                        mAcceptThread.start();
                        mMessageAdapter.addMessage("启动服务线程");
                    }
                    break;
                case R.id.close:
                    mBluetoothAdapter.disable();
                    break;
                case R.id.start:
                    if (mBluetoothAdapter != null) {
                        mRvAdapter.clearDevices();//开始搜索前清空上一次的列表
                        mBluetoothAdapter.startDiscovery();
                        mMessageAdapter.addMessage("开始搜索蓝牙");
                    } else {
                        openBT();
                        if (mBluetoothAdapter != null) {
                            mRvAdapter.clearDevices();//开始搜索前清空上一次的列表
                            mBluetoothAdapter.startDiscovery();
                            mMessageAdapter.addMessage("开始搜索蓝牙");
                        }
                    }
                    break;
                case R.id.stop:
                    if (mBluetoothAdapter != null && mBluetoothAdapter.isDiscovering()) {
                        mBluetoothAdapter.cancelDiscovery();
                    }
                    break;
                case R.id.send:
                    String msg = inputEt.getText().toString();
                    if (TextUtils.isEmpty(msg)) {
                        Toast.makeText(this, "消息为空", Toast.LENGTH_SHORT).show();
                        return;
                    }
                    if (mConnectThread != null) {//证明我主动去链接别人了
                        mConnectThread.write(msg);
                    } else if (mAcceptThread != null) {
                        mAcceptThread.write(msg);
                    }
                    mMessageAdapter.addMessage("发送消息:" + msg);
                    break;
            }
        }
    
        @Override
        public void onStart() {
            super.onStart();
    
            // ATTENTION: This was auto-generated to implement the App Indexing API.
            // See https://g.co/AppIndexing/AndroidStudio for more information.
            client.connect();
            Action viewAction = Action.newAction(
                    Action.TYPE_VIEW, // TODO: choose an action type.
                    "Main Page", // TODO: Define a title for the content shown.
                    // TODO: If you have web page content that matches this app activity's content,
                    // make sure this auto-generated web page URL is correct.
                    // Otherwise, set the URL to null.
                    Uri.parse("http://host/path"),
                    // TODO: Make sure this auto-generated app URL is correct.
                    Uri.parse("android-app://com.example.luoyn.speechdemo/http/host/path")
            );
            AppIndex.AppIndexApi.start(client, viewAction);
        }
    
        @Override
        public void onStop() {
            super.onStop();
    
            // ATTENTION: This was auto-generated to implement the App Indexing API.
            // See https://g.co/AppIndexing/AndroidStudio for more information.
            Action viewAction = Action.newAction(
                    Action.TYPE_VIEW, // TODO: choose an action type.
                    "Main Page", // TODO: Define a title for the content shown.
                    // TODO: If you have web page content that matches this app activity's content,
                    // make sure this auto-generated web page URL is correct.
                    // Otherwise, set the URL to null.
                    Uri.parse("http://host/path"),
                    // TODO: Make sure this auto-generated app URL is correct.
                    Uri.parse("android-app://com.example.luoyn.speechdemo/http/host/path")
            );
            AppIndex.AppIndexApi.end(client, viewAction);
            client.disconnect();
        }
    
        class BlueToothStateReceiver extends BroadcastReceiver {
            @Override
            public void onReceive(Context context, Intent intent) {
                Toast.makeText(MainActivity.this, "触发广播", Toast.LENGTH_SHORT).show();
                String action = intent.getAction();
                switch (action) {
                    case BluetoothDevice.ACTION_FOUND:
                        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                        Toast.makeText(MainActivity.this, "找到设备" + device.getName(), Toast.LENGTH_SHORT).show();
                        if (mRvAdapter != null) {
                            mRvAdapter.addDevice(device);
                        }
                        break;
                    case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
                        mMessageAdapter.addMessage("搜索结束");
                        break;
                }
            }
        }
    
    
        class ConnectThread extends Thread {
            private BluetoothDevice mDevice;
            private BluetoothSocket mSocket;
            private InputStream btIs;
            private OutputStream btOs;
            private boolean canRecv;
            private PrintWriter writer;
    
            public ConnectThread(BluetoothDevice device) {
                mDevice = device;
                canRecv = true;
            }
    
            @Override
            public void run() {
                if (mDevice != null) {
                    try {
                        //获取套接字
                        BluetoothSocket temp = mDevice.createInsecureRfcommSocketToServiceRecord(UUID.fromString(BT_UUID));
                        //mDevice.createRfcommSocketToServiceRecord(UUID.fromString(BT_UUID));//sdk 2.3以下使用
                        mSocket = temp;
                        //发起连接请求
                        if (mSocket != null) {
                            mSocket.connect();
                        }
                        sendHandlerMsg("连接 " + mDevice.getName() + "成功!");
                        //获取输入输出流
                        btIs = mSocket.getInputStream();
                        btOs = mSocket.getOutputStream();
    
                        //通讯-接收消息
                        BufferedReader reader = new BufferedReader(new InputStreamReader(btIs, "UTF-8"));
                        String content = null;
                        while (canRecv) {
                            content = reader.readLine();
                            sendHandlerMsg("收到消息:" + content);
                        }
    
    
                    } catch (IOException e) {
                        e.printStackTrace();
                        sendHandlerMsg("错误:" + e.getMessage());
                    } finally {
                        try {
                            if (mSocket != null) {
                                mSocket.close();
                            }
                            //btIs.close();//两个输出流都依赖socket,关闭socket即可
                            //btOs.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                            sendHandlerMsg("错误:" + e.getMessage());
                        }
                    }
                }
            }
    
            private void sendHandlerMsg(String content) {
                Message msg = mHandler.obtainMessage();
                msg.what = 1001;
                msg.obj = content;
                mHandler.sendMessage(msg);
            }
    
            public void write(String msg) {
                if (btOs != null) {
                    try {
                        if (writer == null) {
                            writer = new PrintWriter(new OutputStreamWriter(btOs, "UTF-8"), true);
                        }
                        writer.println(msg);
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                        writer.close();
                        sendHandlerMsg("错误:" + e.getMessage());
                    }
                }
            }
        }
    
        class AcceptThread extends Thread {
            private BluetoothServerSocket mServerSocket;
            private BluetoothSocket mSocket;
            private InputStream btIs;
            private OutputStream btOs;
            private PrintWriter writer;
            private boolean canAccept;
            private boolean canRecv;
    
            public AcceptThread() {
                canAccept = true;
                canRecv = true;
            }
    
            @Override
            public void run() {
                try {
                    //获取套接字
                    BluetoothServerSocket temp = mBluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord("TEST", UUID.fromString(BT_UUID));
                    mServerSocket = temp;
                    //监听连接请求 -- 作为测试,只允许连接一个设备
                    if (mServerSocket != null) {
                        // while (canAccept) {
                        mSocket = mServerSocket.accept();
                        sendHandlerMsg("有客户端连接");
                        // }
                    }
                    //获取输入输出流
                    btIs = mSocket.getInputStream();
                    btOs = mSocket.getOutputStream();
                    //通讯-接收消息
                    BufferedReader reader = new BufferedReader(new InputStreamReader(btIs, "UTF-8"));
                    String content = null;
                    while (canRecv) {
                        content = reader.readLine();
                        sendHandlerMsg("收到消息:" + content);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (mSocket != null) {
                            mSocket.close();
                        }
                        // btIs.close();//两个输出流都依赖socket,关闭socket即可
                        // btOs.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                        sendHandlerMsg("错误:" + e.getMessage());
                    }
                }
            }
    
            private void sendHandlerMsg(String content) {
                Message msg = mHandler.obtainMessage();
                msg.what = 1001;
                msg.obj = content;
                mHandler.sendMessage(msg);
            }
    
            public void write(String msg) {
                if (btOs != null) {
                    try {
                        if (writer == null) {
                            writer = new PrintWriter(new OutputStreamWriter(btOs, "UTF-8"), true);
                        }
                        writer.println(msg);
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                        writer.close();
                        sendHandlerMsg("错误:" + e.getMessage());
                    }
                }
            }
        }
    
        //----------------------------------------------------------------------------------------------
    
    
        //开始听写
        public void start(View view) {
            textView.setText("");
            hashMap.clear();
            setParams();
    
            if (isShowDialog) {
                recognizerDialog.setListener(dialogListener);
                recognizerDialog.show();
            } else {
                ret = speechRecognizer.startListening(recognizerListener);
                if (ret != ErrorCode.SUCCESS) {
                    Log.e("tag", "听写失败,错误码" + ret);
                }
            }
    
        }
    
        //结束听写
        public void stop(View view) {
            Toast.makeText(this, "停止听写", Toast.LENGTH_SHORT).show();
            if (isShowDialog) {
                recognizerDialog.dismiss();
            } else {
                speechRecognizer.stopListening();
            }
        }
    
        //初始化监听器
        private InitListener initListener = new InitListener() {
            @Override
            public void onInit(int i) {
                if (i != ErrorCode.SUCCESS) {
                    Log.e("tag", "初始化失败,错误码" + i);
                }
            }
        };
    
        //无UI监听器
        private RecognizerListener recognizerListener = new RecognizerListener() {
            @Override
            public void onVolumeChanged(final int i, byte[] bytes) {
                Log.e("tag", "返回数据大小" + bytes.length);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        toast.setText("当前音量" + i);
                    }
                });
            }
    
            @Override
            public void onBeginOfSpeech() {
                Log.e("tag", "开始说话");
            }
    
            @Override
            public void onEndOfSpeech() {
                Log.e("tag", "结束说话");
            }
    
            @Override
            public void onResult(RecognizerResult recognizerResult, boolean b) {
                if (recognizerResult != null) {
                    Log.e("tag", "听写结果:" + recognizerResult.getResultString());
                    printResult(recognizerResult);
    
                }
    
            }
    
            @Override
            public void onError(SpeechError speechError) {
                Log.e("tag", "错误信息" + speechError.getPlainDescription(true));
    
            }
    
            @Override
            public void onEvent(int i, int i1, int i2, Bundle bundle) {
                // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
                // 若使用本地能力,会话id为null
                //  if (SpeechEvent.EVENT_SESSION_ID == eventType) {
                //      String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
                //      Log.d(TAG, "session id =" + sid);
                //  }
            }
        };
    
        //有UI监听器
        private RecognizerDialogListener dialogListener = new RecognizerDialogListener() {
            @Override
            public void onResult(RecognizerResult recognizerResult, boolean b) {
                if (recognizerResult != null) {
                    Log.e("tag", "听写结果:" + recognizerResult.getResultString());
                    printResult(recognizerResult);
    
                }
            }
    
            @Override
            public void onError(SpeechError speechError) {
                Log.e("tag", speechError.getPlainDescription(true));
    
            }
        };
    
        //输出结果,将返回的json字段解析并在textVie中显示
        private void printResult(RecognizerResult results) {
            String text = JsonParser.parseIatResult(results.getResultString());
    
            String sn = null;
            // 读取json结果中的sn字段
            try {
                JSONObject resultJson = new JSONObject(results.getResultString());
                sn = resultJson.optString("sn");
            } catch (JSONException e) {
                e.printStackTrace();
            }
    
            hashMap.put(sn, text);
    
            StringBuffer resultBuffer = new StringBuffer();
            for (String key : hashMap.keySet()) {
                resultBuffer.append(hashMap.get(key));
            }
    
            textView.setText(resultBuffer.toString());
        }
    
        private void setParams() {
            //清空参数
            speechRecognizer.setParameter(SpeechConstant.PARAMS, null);
            //设置引擎
            speechRecognizer.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
            //设置返回数据类型
            speechRecognizer.setParameter(SpeechConstant.RESULT_TYPE, "json");
            //设置中文 普通话
            speechRecognizer.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
            speechRecognizer.setParameter(SpeechConstant.ACCENT, "mandarin");
    
            // 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理
            speechRecognizer.setParameter(SpeechConstant.VAD_BOS,
                    sharedPreferences.getString("iat_vadbos_preference", "4000"));
    
            // 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音
            speechRecognizer.setParameter(SpeechConstant.VAD_EOS,
                    sharedPreferences.getString("iat_vadeos_preference", "1000"));
    
            // 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
            speechRecognizer.setParameter(SpeechConstant.ASR_PTT,
                    sharedPreferences.getString("iat_punc_preference", "0"));
    
            // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
            // 注:AUDIO_FORMAT参数语记需要更新版本才能生效
            speechRecognizer.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
            speechRecognizer.setParameter(SpeechConstant.ASR_AUDIO_PATH,
                    Environment.getExternalStorageDirectory() + "/msc/iat.wav");
    
        }
    }
    
    
    

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/activity_recognition"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:weightSum="1">
    
        <TextView
            android:id="@+id/tv"
            android:layout_width="match_parent"
            android:layout_height="36dp"
            android:background="#55cccccc" />
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="开始听写"
                android:onClick="start" />
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="停止听写"
                android:onClick="stop" />
    
        </LinearLayout>
    
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:weightSum="1">
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:onClick="led"
                android:text="灯开关"
                android:id="@+id/button1"
                android:layout_weight="0.5" />
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:onClick="fan"
                android:text="风扇开关"
                android:id="@+id/button2"
                android:layout_weight="0.5" />
        </LinearLayout>
    
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:weightSum="1">
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:onClick="open"
                android:text="打开蓝牙"
                android:id="@+id/open"
                android:layout_weight="0.50" />
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:onClick="close"
                android:text="关闭蓝牙"
                android:id="@+id/close"
                android:layout_weight="0.50" />
        </LinearLayout>
    
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal">
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:text="搜索设备"
                android:id="@+id/start"
                android:layout_weight="0.50"
                android:onClick="start" />
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:text="停止搜索"
                android:id="@+id/stop"
                android:layout_weight="0.50"
                android:onClick="stop" />
        </LinearLayout>
    
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal">
    
            <EditText
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:id="@+id/input"
                android:layout_weight="0.5" />
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:onClick="send"
                android:text="发送命令"
                android:id="@+id/send"
                android:layout_weight="0.5" />
    
        </LinearLayout>
    
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">
    
            <LinearLayout
                android:orientation="vertical"
                android:layout_width="wrap_content"
                android:layout_height="match_parent">
    
                <TextView
                    android:layout_width="192dp"
                    android:layout_height="wrap_content"
                    android:text="消息列表" />
    
                <android.support.v7.widget.RecyclerView
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:id="@+id/msglist"
                    android:layout_weight="1" />
    
    
            </LinearLayout>
    
            <LinearLayout
                android:orientation="vertical"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="right">
    
                <TextView
                    android:layout_width="192dp"
                    android:layout_height="wrap_content"
                    android:text="设备列表" />
    
                <android.support.v7.widget.RecyclerView
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:id="@+id/devices"
                    android:layout_weight="1" />
    
    
    
            </LinearLayout>
    
        </LinearLayout>
    
    
    </LinearLayout>
    
    

    这是在科大讯飞的SDK基础上改的,可以看到主要的工程文件
    只实现了语音识别功能
    在这里插入图片描述

    App闪退错误日志logcat

    在这里插入图片描述

    在这里插入图片描述

    展开全文
  • 基于MATLAB的语音信号处理

    万次阅读 多人点赞 2018-07-15 01:21:20
    基于MATLAB的语音信号处理摘要:语音信号处理是目前发展最为迅速的信息科学研究领域中的一个,是目前极为活跃和热门的研究领域,其研究成果具有重要的学术及应用价值。语音信号处理的研究,对于机器语言、语音识别、...

    基于MATLAB的语音信号处理


    摘要:语音信号处理是目前发展最为迅速的信息科学研究领域中的一个,是目前极为活跃和热门的研究领域,其研究成果具有重要的学术及应用价值。语音信号处理的研究,对于机器语言、语音识别、语音合成等领域都具有很大的意义。MATLAB软件以其强大的运算能力可以很好的完成对语音信号的处理。通过MATLAB可以对数字化的语音信号进行时频域分析,方便地展现语音信号的时域及频域曲线,并且根据语音的特性对语音进行分析。本文主要研究了基于MATLAB软件对语音信号进行的一系列特性分析及处理,帮助我们更好地发展语音编码、语音识别、语音合成等技术。本文通过应用MATLAB对语音信号进行处理仿真,包括短时能量分析、短时自相关分析等特性分析,以及语音合成等。

    关键词:语音信号;MATLAB;特性分析;语音合成

    引言

            人类交换信息最方便的、最快捷的一种方式是语言。在高度发达的信息社会中,用数字化的方法进行语音的识别、合成、增强、传送和储存等是整个数字化通信网中最重要、最基本的组成部分之一。数字电话通信、高音质的窄带语音通信系统、智能机器人、声控打字机、语言学习机、自动翻译机等,都要用到语音信号处理技术,随着现在集成电路和微电子技术的飞速发展,语音信号处理系统逐步走向实用化[1]

            语音信号处理是一个新兴的交叉学科,是语音和数字信号处理两个学科的结合产物。与认知科学、心理学、语言学、计算机科学、模式识别和人工智能学科有着密切的联系。语音信号处理技术的发展依赖于这些学科的发展,语音信号处理技术的进步也将促进这些领域的进展。语音信号处理目的是得到一些语音特征参数,以便高效的传输或存储,或通过某种处理以达到特定目的,如语音合成,辨识出讲话者、识别出讲话的内容等。随着现代科学技术和计算机技术的发展,除了人与人的自然语言的沟通,人机对话和智能机领域也开始使用语言。这些人造的语言拥有词汇,语法,语法结构和语义内容等。

            语音信号处理的研究可以追溯到1876年贝尔电话的发明,其在真正意义上首次用声电,电声转换技术实现了远距离语音传输。 1939Homer Dudley提出并研制成功第一个声码器,奠定了语音产生模型的基础,其在语音信号处理领域具有划时代的意义。在20世纪40年代,一种语言声学的专用仪器语谱图仪问世。它可以让你把语音的时变频谱用语图表示出来,得到一个“可见的语言 1984年哈斯金斯实验室研制成功语音回放机,此仪器可以自动转换手工绘制的语谱图成为语言,并进行语音合成。随着计算机的出现,语音分析技术可以在计算机上进行。此时语音信号处理无论是在基础研究或在技术应用,都已取得了突破性进展。现在语音信号可分为三个主要分支,即语音编码,语音识别和语音合成技术[10]

            语音编码技术。语音编码的目的就是在保证一定语音质量的前提下,尽可能降低编码比特率来节省频率资源。语音编码技术的研究开始于1939年, Homer Dudley提出并实现了在低带宽电话电报上传输语音信号的通道声码器,第一个研制成功了语音编码技术。到20世纪70年代,国际电联于1972年发布了64kbit/s脉冲编码调制(PCM)语音编码算法的G.711建议,它被广泛应用于数字交换机、数字通信等领域,从而占据统治地位。在199511ITU-T SG15全会上共轭代数码激励线性预测(CS-ACELP)的8kbit/s语音编码G.729建议被通过,并于19966ITU-T SG15会议上通过G.729附件A:减少复杂度的8kbit/s CS-ACELP语音编解码器,正式成为国际标准[1]

            语音识别技术。语音识别的研究开始于20世纪50年代贝尔实验室的Audry系统,它是第一个可以识别10个英文数字的语音识别系统, 1959FryDenes等人采用频谱分析和模式匹配来进行识别决策构建音素识别器来辨别9个辅音和4个元音。20世纪60年代末单语音识别的研究取得实质性进展,并将其作为一个重要的课题。一方面是因为计算机的计算能力有了迅速的提高,计算机能够提供实现复杂算法的硬件、软件;另一方面,数字信号处理在当时有了蓬勃发展,从而自20世纪60年代末开始引起了语音识别的研究热潮。

            语音合成技术。第一个合成器是在1835年由W.von Kempelen发明,经过Weston改进的机械讲话机。机器完全模仿人的生理过程,分别应用了特别设计的哨和软管模拟肺部空气动力和口腔。Homer Dudley1939年发明了第一台电子语音合成器,它不是一个简单的生理过程的模拟,而是在电子电路基础上来实现语音产生源。本文关于语音信号处理方面主要研究了语音合成。语音合成已经在许多方面得到了实际应用,方便了人们的生活,创造了很好的经济效益和社会效益,如公共交通中的自动报站、各种场合的自动报警、电话自动查询服务、文本校对中的语音提示等。综观语言合成技术的研究,语音合成发展方向为提高合成语音的自然度、丰富合成语音的表现力、降低语音合成技术的复杂度等。

    一、语音信号处理基本知识与仿真环境介绍

    1.1 语音信号处理基本知识

    1.1.1语音信号分析技术

            语音信号分析是语音信号处理的前提和基础,只有分析出可表示语音信号本质特征的参数,才有可能利用这些参数进行高效的语音通信、语音合成和语音识别等处理。而且,语音合成的音质好坏,语音识别率的高低,也都取决于对语音信号分析的准确性和精确性。因此语音信号分析在语音信号处理应用中具有举足轻重的地位。

            贯穿于语音分析全过程的是“短时分析技术”。语音信号从整体来看其本质特征的参数是随时间而变化的,所以它是一个非稳态过程,不能用处理稳信号的数字信号处理技术对其进行分析处理。但是,由于不同的语音是由人的口腔肌肉运动构成声道某种形状而产生的响应,而这种口腔肌肉运动相对于语音频率来说是非常缓慢的,所以从另一方面看,虽然语音倍号具有时变特性,但是在一个短时间范围内(一般认为在1030ms的短时间内),其特性基本保持不变即相对稳定,因可以将其看作是一个准稳态过程,即语音信号具有短时平稳性。所以任何语音信号的分析和处理必须建立在“短时”的基础上.即进行“短时分析”将语音信号分为一段一段来分析其特征参数,其中每一段称为一“帧”,帧长一般取为1030ms。这样,对于整体的语音信号来讲,分析出的是由每一帧特征参数组成的特征参数时间序列[4]

            根据所分析参数的性质的不同,可将语音信号分析分为时域分析、频域分析、倒领域分析等;时域分析方法具有简单、计算量小、物理意义明确等优点,但由于语音信号最重要的感知特性反映在功率谱中,而相位变化只起着很小的作用,所以相对于时域分析来说频域分析更为重要。

    1.1.2语音信号处理理论依据

        采样定理。在进行模拟/数字信号的转换过程中,当采样频率大于信号中最高频率的2倍时,采样之后的数字信号完整地保留了原始信号中的信息,一般实际应用中保证采样频率为信号最高频率的5~10倍。采样定理又称奈奎斯特定理。

            采样位数。采样位数即采样值或取样值,用来衡量声音波动变化的参数,是指声卡在采集和播放声音文件时所使用数字声音信号的二进制位数。

        采样频率。样频率是指计算机每秒钟采样多少个声音样本,是描述声音文件的音质、音调,衡量声卡、声音文件的质量标准。采样频率越高,即采样的间隔时间越短,则在单位时间内计算机得到的声音样本数据就越多,对声音波形的表示也越精确。采样频率与声音频率之间有一定的关系,根据奈奎斯特理论,只有采样频率高于声音信号最高频率的两倍时,才能把数字信号表示的声音还原成为原来的声音。这就是说采样频率是衡量声卡采集、记录和还原声音文件的质量标准。

            采样位数与采样频率的关系。采样位数和采样率对于音频接口来说是最为重要的两个指标,也是选择音频接口的两个重要标准。无论采样频率如何,理论上来说采样的位数决定了音频数据最大的力度,每增加一个采样位数相当于力度范围增加了6dB,采样位数越多则捕捉到的信号越精确,对于采样率来说你可以想象它类似于一个照相机,44.1khz意味着音频流进入计算机时计算机每秒会对其拍照达441000次。显然采样率越高,计算机提取的图片越多,对于原始音频的还原也越加精确。

    1.2 实现平台MATLAB 7.0介绍

    1.2.1 MatLab软件基本介绍

            MATLAB产生于1982年,是一种效率高、功能强的数值计算和可视化计算机高级语言,它将信号处理、数值分析和图形显示结合一体,形成了一个极其方便又强大的操作环境,为科学研究、工程设计以及必须进行有效数值计算的众多科学领域提供了一种全面的解决方案,并在很大程度上摆脱了传统非交互式程序设计语言的编辑模式,代表了当今国际科学计算软件的先进水平[7]

            MATLAB7.0是美国MathWorks公司出品的商业数学软件,用于算法开发、数据可视化、数据分析以及数值计算的高级技术计算语言和交互式环境。MATLAB 7.0 的应用范围非常广,包括信号和图像处理、通讯、控制系统设计、测试和测量、财务建模和分析以及计算生物学等众多应用领域

    1.2.2 MatLab与语音处理的关系

            MATLAB软件以其强大的运算能力可以很好的完成对语音信号的处理。通过MATLAB可以对数字化的语音信号进行时频域分析,方便地展现语音信号的时域及频域曲线,并且根据语音的特性对语音进行分析。例如,请浊音的幅度差别、语音信号的端点、信号在频域中的共振峰频率、加不同窗和不同窗长对信号的影响、LPC分析、频谱分析等[3]

            同时,通过MATLAB可以对数字化的语音信号进行估计和判别。例如,根据语音信号的短时参数,一级不同语音信号的短时参数的性质对一段给定的信号进行有无声和请浊音的判断、对语音信号的基音周期进行估计等。另外,通过利用MATLAB编程可以对语音信号进行各种处理。由于MATLAB是一种面向科学和工程计算的高级语言,允许用数学形式的语言编程,又有大量的库函数,所以编程简单、编程效率高、易学易懂,我们可以对信号进行加噪去噪、滤波、截取语音等,也可进行语音编码、语音识别、语音合成的处理等。总之,对于语音信号进行分析处理,MATLAB软件是当今比较高效的平台。

    二、语音信号的特点与采集

    2.1语音信号的特点分析

            语音信号的特点可以分为时域方面和频域方面。

            在时域内,语音信号具有短时性的特点,即在总体上,语音信号的特征是随着时间而变化的,但在一段较短的时间间隔内,语音信号保持平稳。

            在频域内,语音信号的频谱分量主要集中在300~3400Hz的范围内。利用这个特点,可以按8kHz的采样率对语音信号进行采样,得到离散的语音信号。语音信号的这两种特点均可通过MATLAB软件表现出来,如图2.1和图2.2所示。


    图2.1 语音信号时域图


    图2.2 语音信号频域分析

    2.2语音信号的采集

    2.2.1语音信号的量化编码采样

            在将语音信号进行数字化前,必须先进行防混叠预滤波,预滤波的目的有两个,一是抑制输入信导各领域分量中频率超出/2的所有分量(为采样频率),以防止混叠干扰;二是抑制50Hz的电源工频干扰。这样,预滤波器必须是一个带通滤波器,设其上、下截止颜率分别是和,则对于绝人多数语音编译码器,=3400Hz、=60~100Hz、采样率为=8kHz;而对于语音识别而言,当用于电话用户时,指标与语音编译码器相同。当使用要求较高或很高的场合时=4500Hz或8000Hz、=60Hz、=10kHz或20kHz。

            为了将原始模拟语音信号变为数字信号,必须经过采样和量化两个步骤,从而得到时间和幅度上均为离散的数字语音信号。采样也称抽样,是信号在时间上的离散化,即按照一定时间间隔△t在模拟信号x(t)上逐点采取其瞬时值。采样时必须要注意满足奈奎斯特定理,即采样频率必须以高于受测信号的最高频率两倍以上的速度进行取样,才能正确地重建波它是通过采样脉冲和模拟信号相乘来实现的。

            在采样的过程中应注意采样间隔的选择和信号混淆:对模拟信号采样首先要确定采样间隔。如何合理选择△t涉及到许多需要考虑的技术因素。一般而言,采样频率越高,采样点数就越密,所得离散信号就越逼近于原信号。但过高的采样频率并不可取,对固定长度(T)的信号,采集到过大的数据量(N=T/△t),给计算机增加不必要的计算工作量和存储空间;若数据量(N)限定,则采样时间过短,会导致一些数据信息被排斥在外。采样频率过低,采样点间隔过远,则离散信号不足以反映原有信号波形特征,无法使信号复原,造成信号混淆。根据采样定理,当采样频率大于信号的两倍带宽时,采样过程不会丢失信息,利用理想滤波器可从采样信号中不失真地重构原始信号波形。量化是对幅值进行离散化,即将振动幅值用二进制量化电平来表示。量化电平按级数变化,实际的振动值是连续的物理量。具体振值用舍入法归到靠近的量化电平上。

            语音信号经过预滤波和采样后,由A/D变换器变换为二进制数字码。这种防混叠滤波通常与模数转换器做在一个集成块内,因此目前来说,语音信号的数字化的质量还是有保证的。市面上购买到的普通声卡在这方面做的都很好,语音声波通过话筒输入到声卡后直接获得的是经过防混叠滤波、A/D变换、量化处理的离散的数字信号。

    2.2.2利用Windows录音器采集语音信号

    在本次设计中,可以利用Windows自带的录音机录制语音文件,图2.3是基于PC机的语音信号采集过程,声卡可以完成语音波形的A/D转换,获得WAV文件,为后续的处理储备原材料。调节录音机保存界面的更改选项,可以存储各种格式的WAV文件。

      

    2.3 基于PC机的语音采集过程

    第三章 语音信号的分析

    3.1 语音信号的短时能量分析
            一定时宽的语音信号,其能量的大小随时间有明显的变化。清音信号和浊音信号之间的能量差别相当显著。其中清音段(以清音为主要成份的语音段),其能量比浊音段小得多[10]。因此,对语音的短时能量进行分析,可以描述语音的这种特征变化情况。定义短时能量为如式(3-1)所示。
                                                     (3-1)
    其中N为窗长。特殊地,当采用矩形窗时,可简化为如式(3-2)所示。
                                                            (3-2)
    也可以从另外一个角度来解释。令
                                                              (3-3)                                         
    则 可表示为如式(3-4)所示。
                                              (3-4)  
            可以理解为,首先语音信号各个样点值平方,然后通过一个冲击响应为h(n)的滤波器,输出为由短时能量构成的时间序列。
            短时能量的计算直接受冲击响应的选择即窗函数的选择的影响。如果冲击响应的幅度是恒定的,它的序列长度N(即窗长)会很长,将其等效为非常窄的低通滤波器,这时冲击响应对 产生的平滑的作用比较明显,使短时能量基本没有很大的变化,将不能表现出语音的时变的特性。相反,如果冲击响应的序列长度过于小,等效窗就不能提供出够用的平滑,以导致语音的振幅在瞬时的变化的许多细节仍被留了下来,进而不能看出振幅包络变化的规律,一般我们要求窗长是几个基音周期的数量级。
            图3.1为采样率8000kHZ,16位,单声道的一个语音信号(单词“earth”)在不同矩形窗长时的短时能量函数,我们会发现:语音信号的幅度变化在被短时能量所反映时,窗长的长短都有影响。
     
    图3.1 不同矩形窗长的短时能量函数
            我们知道,单词earth前半部分是浊音,后半部分是清音。由以上分析结果可知,浊音部分的能量较之清音部分要大得多,而清音部分的能量相当小,几乎为零。
            对语音信号进行短时能量函数运算,可实现以下三点应用:
    (1)可用于区分清音段与浊音段。En值大对应于浊音段,En值小对应于清音段。
    (2)可用于区分浊音变为清音或清音变为浊音的时间(根据En值的变化趋势)。
    (3)对高信噪比的语音信号,也可以用来区分有无语音(语音信号的开始点或终
    止点)。无信号(或仅有噪声能量)时,En值很小,有语音信号时,能量显著增大。
    3.2短时自相关分析
            对于确定性信号序列,自相关函数定义如式(3-5)所示。
                                                      (3-5)
            对于随机性信号序列或周期性信号序列,自相关函数的定义如式(3-6)所示。
                                                (3-6)
            自相关函数具有以下几项性质:
        (1)若序列是周期性的,假设序列周期为 ,那么其自相关函数也是具有相同周期的周期函数,即 
        (2)自相关函数是偶函数,即R(k)=R(-k);
        (3)当k=0时,自相关函数有极大值,即
        (4)R(0)为随机性序列的平均功率或确定性信号序列的能量。
            自相关函数的上述性质,完全可以适用于语音信号的时域分析中。例如,浊音语音波形序列具有周期性,因此可用自相关函数求出这个周期,即是基音周期。此外,自相关函数也可用在语音信号的线性预测分析中。
    短时自相关函数的定义如式(3-7)所示。 
                                        (3-7)
    令 ,并且 ,可以得到如下式子,如(3-8)所示。
                                  (3-8)
            如图3.2是在不同的矩形窗窗长条件下单词earth的语音自相关的函数的波形。
            对两图分析可得:清音信号的短时自相关函数的波形不具有周期性,也没有明显的峰值,且随着延时k的增大迅速变小,因此其接近于随机噪声;浊音是具有周期性的信号,浊音信号的周期为自相关函数的周期,由此可知,语音信号的性质是浊音还是清音,如果是浊音,还可以得出它的基音周期,它的基音周期可由自相关函数波形中的第一个峰值的位置来估计。所以,自相关函数常用作一下两种作用:
    (1) 区分语音信号是清音还是浊音;
    (2) 估计浊音语音信号的基音周期[4]。
     

    图3.2 不同的矩形窗窗长下短时自相关

    第四章 语音合成

    4.1 语音合成技术概述
    4.1.1 语音合成技术的意义                                                   
            语音合成技术涉及声学、语言学、数字信号处理技术、多媒体技术等多个领域, 是当今世界强国竞相研究的热门技术之一。语音合成技术可分为参数合成和波形拼接两种方法。早期的研究主要是采用参数合成方法, 它是计算发音器官的参数, 从而对人的发音进行直接模拟。语音合成已经在许多方面得到了实际应用,方便了人们的生活,创造了很好的经济效益和社会效益,如公共交通中的自动报站、各种场合的自动报警、电话自动查询服务、文本校对中的语音提示等[8]。
            本文主要利用载波调制技术进行语音合成。基于载波调制的语音信号合成是以语音信号处理技术、数字信号处理技术为基础,依托于电子计算机、Windows操作系统、MATLAB处理软件等工具将两个信号合成为一个信号。具有较强的实用性、可操作性等特点。
    4.1.2 基于载波调制语音合成的基本原理
                语音信号合成是一个“分析—存储—合成”的过程。一般是选择合适的基本单元,将基本单元用一定的参数编码方式或波形方式进行存储,形成一个语音库。合成时,根据待合成语音信号,从语音库中取出基本单元进行合成,并将其还原成语音信号。在语音合成中,为了便于存储和后续分析,必须先将语音信号进行预分析、预处理、波形变换等一系列操作。其中,基元是语音合成处理的最小单元,待合成的语音库就是所有语音基元的某中集合。根据基元的选择方式以及其存储形式的不同,可以将合成方法笼统的分为波形合成方法和参数合成方法。
            波形合成是一种相对简单的语音合成技术。它把人的发音波形直接存储或者进行进行简单的波形编码后存储,组成一个合成的语音库;合成时,根据待合成的信息,在语音库中取出相应单元的波形数据,拼接或编辑到一起,经过解码还原成语音。该语音合成技术具有一定的局限和不足,但对语音信号具有数据量庞大的特点,这种误差在某种范围内是可以接受的。
            基于载波调制的语音信号合成是基于信号的振幅调制原理而实现的。将低频信号加载到高频载波信号的过程,或者说把信息加载到信息载体上以便传输的处理过程,称为调制。所谓“加载”,其实质是使高频载波信号(信息载体)的某个特性参数随信息信号幅值的大小程线性变化。基于载波调制的语音信号合成是以语音一信号作为调制信号,语音二信号为载波信号来进行合成一种以语音二信号声色表述语音一内容的新信号。这种调制方式是用传递的低频信号(如代表语言、音乐、图像的电信号)去控制作为传送载体的高频振荡波(称为载波)的幅度,是已调波的幅度随调制信号的大小线性变化,而保持载波的角频率不变。
    4.2 基于载波调制的语音合成基本知识
    4.2.1 关键函数希尔伯特变换介绍
            本文语音合成的设计思路是用一个语音信号的包络去调制另一个语音信号,实现语音的合成。这就用到了一个关键的函数,希尔伯特变换。在数学与信号处理的领域中,一个实值函数的希尔伯特变换是将信号s(t)与1/(πt)做卷积,以得到s'(t)。因此,希尔伯特变换结果s'(t)可以被解读为输入是s(t)的线性时不变系统的输出,而此系统的脉冲响应为1/(πt)。这是一项有用的数学工具,用在描述一个以实数值载波做调制的信号之复数包络,出现在通讯理论中发挥着重要作用[9]。
            希尔伯特变换的频域数学表达式如式(4-1)所示。                       
                                                          (4-1)
    其中F是傅里叶变换,i是虚数单位,ω是角频率。
            希尔伯特变换等效于 π/2的相移,对正频率产生-π/2的相移,对负频率产生π/2相移,或者说,在时域信号每一频率成分移位1/4波长,因此,希尔伯特变换又称为90度移相器。
            MATLAB提供了计算Hilbert变换的函数,其格式为y=Hilbert(x)。但需注意的是,该函数计算出的结果是序列的解析信号,其虚部才是序列的Hilbert变换。
            希尔伯特变换在语音信号处理中具有两个性质: 序列x(n)通过Hilbert变换器后,信号频谱的幅度不发生变化,这是因为Hilbert变换器是全通滤波器,引起频谱变化的只是其相位; 序列x(n)与其Hilbert变换是正交的[6]。
    4.2.2 信号调制
            所谓调制,就是将调制信号加载在三个参数中的某一个参数上,或幅值、或频率、或相位,随调制信号大小成线性或非线性变化的过程。主要有三种基本调制方法,第一种是把调制信号加载在载波信号的幅值上,称为幅度调制 ,简称AM;第二种是把调制信号加载在载波的频率上,称为频率调制,简称FM。 第三种是把调制信号装载在载波的相位上,称为相位调制,简称PM[10]。 本设计采用的是第一种方法,用采集到的语音二信号去对语音一信号进行幅度调制,实现语音合成的目的。
            采用调幅调制是因为其以下特点在语音信号处理中得到很好的应用。一是调幅波的振幅(包络)随调制信号变化,而且包络的变化规律与调制信号波形一致,表明调制信号(信息)记载在调幅波的包络中;二是调制系数反应了调幅的强弱程度,一般情况下,调制系数越大调幅度越深。
            当调制系数为0时,表示未调幅,即无调幅作用;
            当调制系数为1时,此时包络的振幅最小值为0;
            当调制系数大于1时,已调波的包络与调制信号不一样,产生严重的包络失真,称为过量调幅。
    4.3 语音信号合成过程
    4.3.1 语音信号合成流程图
            用MATLAB 处理音频信号的基本流程是:先将WAV 格式音频信号经wavread 函数转换MATLAB 列数组变量;再用MATLAB 强大的运算能力进行数据分析和处理,如时域分析、频域分析、数字滤波、信号合成、信号变换、识别和增强等等;处理后的数据如是音频数据,则可用wavwrite函数 转换成WAV 格式文件或用sound、wavplay 等函数直接回放。本设计的语音合成流程图如图4.1所示。
     
    图4.1  语音信号合成流程图
    4.3.2 语音信号的采集
            分析和处理音频信号,首先要对声音信号进行采集,MATLAB 的数据采集工具箱提供了一整套命令和函数,通过调用这些函数和命令,可直接控制声卡进行数据采集。Windows 自带的录音机程序也可驱动声卡来采集语音信号,并能保存为WAV 格式文件,供MATLAB 相关函数直接读取、写入或播放。本文以WAV 格式音频信号作为分析处理的输入数据。
    4.3.3 语音信号的合成
            声音信号是一种非平稳信号,如果采用简单的时变系统的分析方法,将会产生很大的失真,但是在一小段时间内声音信号完全可以视为平稳信号。因此必须对语音信号做预处理。在本次语音信号合成中采用加窗截断,分帧处理将非平稳信号近似转换为平稳信号。
            声音信号特征量提取。声音信号特征量提取包括语音一声音信号声色(频率)的提取和语音二声音信号包络的提取。语音二声音信号包络的提取采用希尔伯特变换实现,得到语音二声音信号的复数包络。
            获取语音信号起始位置。在录音过程中控制两段声音从相同的起点开始录取并不是一件容易的事,但是如果不确定语音信号的起始位置直接对语音信号进行合成既存在数据量大又会带来较大的误差。本设计语音合成中拟定连续四个时间点的语音信号强度不为0,则认为语音信号开始,也即找到信号起始位置。
            语音信号合成。语音信号合成即是一个调幅载波的过程,是以语音一信号作为载波信号,语音二信号包络作为调幅信号来实现语音合成。实际的载波是一个物理可实现的复杂过程,本语音合成中采用语音一信号点乘包络信号,实际的载波是一个物理可实现的复杂过程,并非简单地乘积运算,然而,得到的合成声音信号效果并不理想,但其波形仍能反应载波过程的实质。
    4.4 语音信号合成结果及分析
    4.4.1 语音信号预处理结果及分析
            该处理过程以语音一信号和语音二声音信号为分析样本。使用Windows系统自带录音器分别录下语音一和语音二,分别命名为one和two,保存为WAV格式。通过MATLAB对所录语音进行采样,采样频率 为16000Hz,获取语音信号并进行加窗。语音一和语音二的时域波形图如图4.2所示,时域图反映出了语音信号的非平稳性。
            对采集到的语音信号分别做傅里叶变换进行频谱分析,并显示频谱图,观察各自的幅频谱特性。语音一和语音二的声音信号幅频特性如图4.3所示,语音一和语音二的声音FFT图如图4.2和图4.3。该频谱图横坐标并未进行对应关系处理,但仍不失其频谱特性的本质,由频谱图可清楚地看到样本声音主要以低频为主。人的语音信号频率一般集中在1kHz之前,从声音频谱的包络来看, 根据采样定理,信号宽度近似取为1kHz,重放语音后仍可较清晰的听出原声, 不存在声音混叠现象。
     
    图4.2  信号预处理之后时域图
     
    图4.3 信号预处理之后频域图
    4.4.2 合成语音信号结果及分析
            合成语音信号的实质是用语音二信号的包络调制语音一信号振幅的结果。语音二信号包络提取结果如图4.4,该图是语音二信号经希尔伯特—黄变换的虚部显示,因为希尔伯特—黄变换是一个时域信号与1/(πt)的卷积,其结果是载波做调制信号之复数包络,必然蕴含虚部成分,取其虚部的结果必然与时域信号有着直观上的差别,但仍是信号的包络成分。
     
    图4.4 语音二信号包络图
            合成信号的时域显示结果如图4.5所示,该合成信号是以语音一信号的特性和语音二信号的幅度变化的,由其快速傅里叶变换的结果更证实了这一点,其幅频特性与语音一信号的幅频特性更接近。
     
    图4.5 合成语音信号的时域波形
     
    图4.6 合成语音信号的幅频特性

     

    图4.7 合成语音信号快速傅里叶变换结果


    结  论

            随着语音技术的逐渐成熟,语音信号处理技术也在不断发展,不断完善。本文主要研究了通过对语音信号短时能量、短时自相关等特性参数的分析,使我进一步了解了语音信号的特性,明白了只有准确分析并提取出语音信号的特征参数,才能够利用这些参数进行语音编码、语音合成等处理。另外在语音处理方面,我选择了语音合成这一处理方式。基于载波调制的语音处理实现简单,运用广泛,研究这一语音合成方法及特性,对于更加深入地进行各种语音处理有着重要的意义。这次设计我是通过了MATLAB这一平台,MATLAB软件以其强大的运算能力可以很好的完成对语音信号的处理,因此,近一步的加强对MATLAB的研究对我以后的学习会起到很大的帮助。
            至此,设计基本符合要求。但是由于个人能力的有限,采集的语音信号清、浊音区分不明显,导致对语音进行短时自相关得出的波形特征不明显。考虑解决方案是通过专业的设备采集语音信号。除此之外,本设计必有其他欠妥之处,请各位老师给予指正!

    参考文献

    [1] 张雪英.数字语音处理[M].北京:电子工业出版社, 2010.
    [2] 郑君里,应启绗,杨为理.信号与系统[M].北京:高等教育出版社,2000.
    [3] 薛年喜.MATLAB在数字信号处理中的应用[M].北京:清华大学出版社,2003.
    [4] 胡航.语音信号处理[M].哈尔滨:哈尔滨工业大学出版社, 2000.
    [5] 易克初,田斌,付强.语音信号处理[M].北京:国防工业出版社, 2000.
    [6] 万永革.数字信号处理的MATLAB实现[M].北京:科学出版社,2007.
    [7] 刘卫国. MATLAB程序设计与应用[M].北京:高等教育出版社,2006.
    [8]王嘉梅.基于MATLAB的数字信号处理与时间开发[J].西安:西安电子科技大学出版社,2007:10-14.
    [9] 程佩青.数字信号处理教程(第二版)[M].北京:清华大学出版社,2010.
    [10] 韩纪庆 张磊 郑铁然.语音信号处理[M].北京:清华大学出版社,2004.
    [11] 徐明远,邵玉斌. Matlab仿真在通信与电子工程中的应用[M].西安:西安电子科技大学出版社,2005. 
    [12] 邓华. Matlab通信仿真及应用实例详解[M].北京:人民邮电出版社,2005. 
    [13] 张照明,刘政波,刘斌等. 应用Matlab实现信号分析处理[C].北京:科学出版社,2006.
    [14] 徐守时. 信号与系统理论方法和应用[M].合肥:中国科学技术大学出版,1999. 

    [15] 高俊斌. Matlab语言与程序设计[M].武汉:华中理工大学出版社,1998. 

    附  录

    附录A   语音信号特性分析程序

    %语音信号时域频域显示%
    [y,Fs,bits]=wavread('biye.wav');%读出信号、采样率和采样位数
    y=y(:,1);%取单声道
    sigLength=length(y);
    Y=fft(y,sigLength); 
    Pyy=Y.* conj(Y) / sigLength;
    halflength=floor(sigLength/2);
    f=Fs*(0:halflength)/sigLength;
    figure;plot(f,Pyy(1:halflength+1));
    xlabel('Frequency(Hz)');
    t=(0:sigLength-1)/Fs;
    figure;
    plot(t,y);
    xlabel('Time(s)');
    
    %语音信号短时能量%
    x=wavread('biye.wav');
    %x=fscanf(fid,'% f');
    %fclose(fid);
    s=fra(50,25,x)
    s2=s.^2;
    energy=sum(s2,2)
    subplot(2,2,1)
    plot(energy);
    xlabel('帧数')
    ylabel('短时能量 E')
    legend('N=50')
    %axis({0,1500,0,10*10^5})
    s=fra(100,50,x)
    s2=s.^2;
    energy=sum(s2,2)
    subplot(2,2,2)
    plot(energy);
    xlabel('帧数')
    ylabel('短时能量 E')
    legend('N=100')
    %axis({0,750,0,2*10^6}) 
    s=fra(400,200,x)
    s2=s.^2;
    energy=sum(s2,2)
    subplot(2,2,3)
    plot(energy);
    xlabel('帧数')
    ylabel('短时能量 E')
    legend('N=400')
    %axis({0,190,0,7*10^6})
    s=fra(800,400,x)
    s2=s.^2;
    energy=sum(s2,2)
    subplot(2,2,4)
    plot(energy);
    xlabel('帧数')
    ylabel('短时能量 E')
    legend('N=800') 
    %axis({0,95,0,14*10^6})
    
    %语音信号短时自相关%
    x=wavread('biye.wav');
    s1=x(1:320);
    N=320;   %选择的窗长,加N=320的矩形窗
    A=[];
    for k=1:320;
    sum=0;
    for m=1:N-(k-1);
    sum=sum+s1(m)*s1(m+k-1);   %计算自相关
    end
    A(k)=sum;
      end
    for k=1:320
    A1(k)=A(k)/A(1);        %归一化A(k)
       end
    N=160;                  %选择的窗长,%加N=160的矩形窗
    B=[];
    for k=1:320;
    sum=0;
    for m=1:N-(k-1);
    sum=sum+s1(m+k-1);   %计算自相关
    end
    B(k)=sum;
    end
    for k=1:320
    B1(k)=B(k)/B(1);      %归一化B(k)
    end
    N=70;                 %选择的窗长,加N=70的矩形窗
    C=[];
    for k=1:320;
    sum=0;
    for m=1:N-(k-1);
    sum=sum+s1(m)*s1(m+k-1);        %计算自相关
    end
    C(k)=sum;
    end
    for k=1:320
    C1(k)=C(k)/C(1);                %归一化C(k)
    end
    s2=s1/max(s1)
    figure(1)
    subplot(4,1,1)
    plot(s2)
    title('语音信号')
    xlabel('样点数')
    ylabel('幅值')
    axis([0,320,-2,2])
    subplot(4,1,2)
    plot(A1)
    xlabel('延时k')
    ylabel('R(k)')
    axis([1,320,-2,2]);
    legend('N=320')
    subplot(4,1,3)
    plot(B1);
    xlabel('延时k')
    ylabel('R(k)')
    axis([1,320,-2,2]);
    legend('N=160')
    subplot(4,1,4)
    plot(C1);
    xlabel('延时k')
    ylabel('R(k)')
    axis([0,320,-2,2]);
    legend('N=70')
    附录B  语音合成主程序
    [y1,fs,bits]=wavread('one');      %读取语音一信号
    [y2,fs,bits]=wavread('two');      %读取语音二信号
    L1=length(y1);                    %测定语音一信号长度
    L2=length(y2);                    %测定语音二信号长度
    a1=y1.*hamming(L1);               %加窗预处理
    a2=y2.*hamming(L2);               %加窗预处理
    L1=length(a1);                    %测定语音一信号长度
    L2=length(a2);                    %测定语音二信号长度
    %采样信号的时域显示
    figure(1);
    subplot(211);
    plot(a1);
    title('语音一载波信号时域波形');
    subplot(212);
    plot(a2);
    title('语音二调幅信号时域波形');
    %傅里叶频谱绘制
    F1=fft(a1,L1);                    
    F2=fft(a2,L2);
    AF1=abs(F1);
    AF2=abs(F2);
    figure(2);
    subplot(211);
    plot(AF1);
    title('语音一载波信号幅频特性显示');
    subplot(212);
    plot(AF2);
    title('语音二调幅信号幅频特性显示');
    figure(3);
    freqz(F1);
    title('语音一载波信号FFT频谱显示');
    figure(4);
    freqz(F2);
    title('语音二载波信号FFT频谱显示');
    %获取语音一信号的开始位置
    for i=1:L1-4
         g(i)=a1(i).*a1(i+1).*a1(i+2).*a1(i+3).*a1(i+4);%认为连续4个幅值不为0的信号即为开始
         if g(i)~=0
             break;
         else i=i+1;
         end
    end
    I=i;
     
    % 获取语音二信号开始位置
    for j=1:L2-4
         m(j)=a2(j).*a2(j+1).*a2(j+2).*a2(j+3).*a2(j+4);
         if m(j)~=0
             break;
         else j=j+1;
         end
    end
    J=j;
    %语音二信号hilbert变换
    H=hilbert(a2);
    figure(5);
    plot(abs(H));
    title('语音二信号包络显示');
    %信号对齐,语音二包络调制语音一振幅
    max1=max(I,J);
    for k=1:L1-max1
        N(k)=a1(i).*H(j);
        i=i+1;
        j=j+1;
    end
    %N=N';
    N = N/(max(abs(N)) * 1.05);
    wavwrite(N,16000,16,'HC.wav');
    figure(6);
    plot(imag(N));
    title('合成信号时域显示');
    pause(1);
    sound(10*N,fs);
    FN=fft(N);
    figure(7);
    freqz(FN);
    title('合成声音信号FFT显示');
    figure(8);
    plot(abs(FN));
    title('合成声音信号的幅频特性');
    

    展开全文
  • 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理
  • 可以创建群,邀请群成员,群成员列表展示,图片发送,设置群公告,踢人,全体禁言,个人禁言,发送语音信息等功能。 效果图: 实现代码: 可以帮忙制作,手机号 、wx:17610241271 wxml <view class=...

    可以创建群,邀请群成员,群成员列表展示,图片发送,设置群公告,踢人,全体禁言,个人禁言,发送语音信息等功能。

    效果图:

    实现代码:

    可以帮忙制作,手机号 、wx:17610241271

    wxml

    <view class="bo">
      <view class="top_bo">
        <view bindtap="number">群成员({{userNumber}})</view>
        <view bindtap="upLoad" wx:if="{{prohibit=='0'}}">发图片</view>
        <view bindtap="prohibitTis" wx:if="{{prohibit=='1'}}">发图片</view>
        <view bindtap="notice">群公告</view>
        <view bindtap="goHome">去首页</view>
      </view>
      <scroll-view scroll-y="true" scroll-with-animation scroll-top="{{scrollTop}}">
        <block wx:if='{{!list}}'>
          <view class="noList">
            <view class="noMsg">暂无聊天数据</view>
          </view>
        </block>
        <block wx:else>
          <view class="body" wx:for='{{list}}' wx:key='index'>
    
            <view wx:if='{{item.type==1}}' class="right_body">
              <view class="flexRoe posRit">
                <view class="textBo">
                  <view class="dataTime">{{item.sendOutname}} {{item.dataTime}}</view>
                  <view wx:if="{{item.text!=''}}" class="ritTxt">{{item.text}}</view>
                  <image wx:if="{{item.img!=''}}" mode='widthFix' src="{{item.img}}" class="textImg"></image>
                  <view wx:if="{{item.voice}}" class="ritTxt2" bindtap='my_audio_click' data-src='{{item.voice}}'>
                    <image class='my_audio' src='/img/play.png'></image>
                  </view>
                </view>
                <image class="head" style=" margin-left:20rpx " src="{{item.sendOutHand}}"></image>
              </view>
            </view>
            <view wx:if='{{item.type==2}}' class="p_r left_body">
              <view class="flexRoe ">
                <image class="head" style=" margin-left:20rpx " src="{{item.sendOutHand}}"></image>
                <view class="lfBo">
                  <view class="dataTime">{{item.sendOutname}} {{item.dataTime}}</view>
                  <view wx:if="{{item.text!=''}}" class="lftTxt">{{item.text}}</view>
                  <image wx:if="{{item.img!=''}}" mode='widthFix' src="{{item.img}}" class="textImg"></image>
                  <view wx:if="{{item.voice}}" class="lftTxt2" bindtap='my_audio_click' data-src='{{item.voice}}'>
                    <image class='my_audio' src='/img/play.png'></image>
                  </view>
                </view>
              </view>
            </view>
          </view>
        </block>
    
      </scroll-view>
    
      <view class="inp">
        <view>
          <view class="p_r" wx:if="{{prohibit=='0'}}">
            <input class="input" placeholder-class='plaCC' confirm-type="send" bindconfirm='sendOut' placeholder='聊天,在这里' value="{{title}}"></input>
            <view class="yuyin" catchtouchstart='voice_ing_start' catchtouchend="voice_ing_end">语音</view>
          </view>
          <view wx:if="{{prohibit=='1'}}" bindtap="prohibitTis" class="prohibit">禁言中</view>
        </view>
      </view>
    </view>

    wxss

    page {
      background: #f2f2f2;
    }
    
    .top_bo {
      display: flex;
      background: white;
      flex-direction: row;
    }
    
    .top_bo view {
      flex: 1;
      font-size: 28rpx;
      text-align: center;
      border: 1rpx solid #ccc;
      line-height: 80rpx;
    }
    
    scroll-view {
      height: 82vh;
      /* padding-bottom: 30rpx; */
    }
    
    .noList {
      background: white;
      margin-top: 30rpx;
      /* height: 230rpx; */
      width: 100%;
      padding-top: 25%;
    }
    
    .noMsg {
      text-align: center;
      color: #999;
      font-size: 28rpx;
    }
    
    .inp {
      position: absolute;
      bottom: 0;
      width: 100%;
      background: white;
      height: 100rpx;
    }
    
    .inp input {
      background: #d0d0d0;
      border-radius: 15rpx;
      padding-left: 20rpx;
      height: 84rpx;
      margin-top: 10rpx;
      width: 94%;
      margin-left: 2%;
    }
    
    .prohibit {
      background: #d0d0d0;
      border-radius: 15rpx;
      padding-left: 20rpx;
      height: 84rpx;
      margin-top: 10rpx;
      width: 94%;
      margin-left: 2%;
      text-align: center;
      line-height: 84rpx;
      color: rgb(34, 34, 34);
    }
    
    .plaCC {
      margin-left: 5rpx;
    }
    
    .p_r {
      display: flex;
      flex-direction: row;
    }
    
    .flexRoe {
      display: flex;
      flex-direction: row;
    }
    
    .body {
      width: 100%;
      position: relative;
      /* border: 1rpx solid #ccc; *//* height: 200rpx */
    }
    
    .right_body {
      width: 100%;
      /* border: 1rpx solid red; */
      padding: 20rpx;
      margin-top: 50rpx;
    }
    
    .left_body {
      margin-top: 50rpx;
    }
    
    .posRit {
      right: 20rpx;
    }
    
    .textBo {
      width: 620rpx;
      text-align: right;
    }
    
    .lfBo {
      margin-left: 10rpx;
    }
    
    .ritTxt {
      background: #44426a;
      padding: 10rpx 20rpx 10rpx 20rpx;
      color: white;
      border-radius: 15rpx 0 15rpx 15rpx;
      margin-top: 20rpx;
      float: right;
      max-width: 500rpx;
      word-wrap: break-word;
      text-align: left;
    }
    
    .ritTxt2 {
      background: white;
      color: white;
      border-radius: 15rpx 0 15rpx 15rpx;
      padding: 0rpx 20rpx 10rpx 20rpx;
      margin-top: 20rpx;
      float: right;
      max-width: 500rpx;
      word-wrap: break-word;
      text-align: left;
    }
    
    .lftTxt {
      background: #44426a;
      padding: 10rpx 20rpx 10rpx 20rpx;
      color: white;
      border-radius: 0rpx 15rpx 15rpx 15rpx;
      margin-top: 20rpx;
      float: left;
      max-width: 500rpx;
      word-wrap: break-word;
      text-align: left;
    }
    .lftTxt2 {
      background: #fff;
      padding: 0rpx 20rpx 10rpx 20rpx;
      color: white;
      border-radius: 0rpx 15rpx 15rpx 15rpx;
      margin-top: 20rpx;
      float: left;
      max-width: 500rpx;
      word-wrap: break-word;
      text-align: left;
    }
    .my_audio {
      height: 60rpx;
      width: 60rpx;
      z-index: 2;
      position: relative;
      top: 10rpx;
      left: 20rpx;
      margin-right: 30rpx;
    }
    
    
    .dataTime {
      font-size: 28rpx;
    }
    
    .textImg {
      width: 200rpx;
      margin-top: 5rpx;
    }
    
    .head {
      width: 80rpx;
      height: 80rpx;
    }
    
    ._ {
      height: 130rpx;
      width: 100%;
      background: #ccc;
    }
    
    .inpBo {
      display: flex;
      flex-direction: row;
    }
    
    .yuyin {
      flex: 1;
      line-height: 104rpx;
      text-align: center;
    }
    
    .input {
      flex: 5;
    }
    

    js

    const DB = wx.cloud.database()
    var util = require('../../utils/util.js');
    var recorder = wx.getRecorderManager();
    const innerAudioContext = wx.createInnerAudioContext() //获取播放对象
    var qunId, that;
    Page({
    
      /**
       * 页面的初始数据
       */
      data: {
        list: [],
        userNumber: '0',
        userList: [],
        c: "",
        prohibit: '',
      },
    
      // 点击录音开始播放事件
      my_audio_click: function (e) {
        var src = e.currentTarget.dataset.src;
          console.log('url地址', src);
          innerAudioContext.src = src
          innerAudioContext.seek(0);
          innerAudioContext.play();
      },
      // 首页
      goHome() {
        wx.switchTab({
          url: '../group/group'
        })
    
      },
      
      // 群成员
      number() {
        let userList = JSON.stringify(this.data.userList)
        wx.navigateTo({
          url: '../member/member?qunId=' + qunId,
        })
      },
      bottom: function() {
        var that = this;
        this.setData({
          scrollTop: 100000
        })
      },
      // 动态监听禁言状态
      prohibit() {
        console.log('8888888888888', qunId)
        const watcher = DB.collection('qunList')
          .where({
            _id: qunId
          })
          .watch({
            onChange: function(res) {
    
              let arr = res.docs[0]
              console.log('动态监听禁言状态', arr._openId + ':' + wx.getStorageSync('openId'))
              let opid = arr._openId
              if (arr.prohibit == '1') {
                if (arr._openId == wx.getStorageSync('openId')) {
                  that.setData({
                    prohibit: '0'
                  })
                } else {
                  that.setData({
                    prohibit: '1'
                  })
                }
              } else {
                const watcher = DB.collection('qunUserList')
                  .where({
                    qunId: qunId,
                    _openId: wx.getStorageSync('openId')
                  })
                  .watch({
                    onChange: function(res) {
    
                      let arrr = res.docs[0]
                      console.log('动态监听禁言状态', arrr._openId + ':' + wx.getStorageSync('openId'))
                      if (arrr.prohibit == '1') {
                        if (arrr._openId == opid) {
                          that.setData({
                            prohibit: '0'
                          })
                        } else {
                          that.setData({
                            prohibit: '1'
                          })
                        }
                      } else {
                        that.setData({
                          prohibit: '0'
                        })
                      }
    
    
                    },
                    onError: function(err) {
                      // console.error('----------------error', err)
                    }
                  })
              }
    
    
            },
            onError: function(err) {
              // console.error('----------------error', err)
            }
          })
      },
    
      // 禁言提示
      prohibitTis() {
        wx.showToast({
          title: '禁言中......',
          icon: 'none'
        })
      },
      // 获取成员消息
      onMsg(qunId) {
        console.log('2222222', qunId)
        const watcher = DB.collection('news')
          // 按 progress 降序
          // .orderBy('progress', 'desc')
          // 取按 orderBy 排序之后的前 10 个
          // .limit(10)
          .where({
            _qunId: qunId
          })
          .watch({
            onChange: function(snapshot) {
              console.log('snapshot', snapshot)
              var listArr = snapshot.docs;
              console.log('---', listArr)
              listArr.forEach((item, idx) => {
                console.log('---', item)
                console.log(wx.getStorageSync('openId') + ':' + item._openId)
                item.type = wx.getStorageSync('openId') == item._openId ? 1 : 2;
                item.sendOutname = wx.getStorageSync('openId') == item._openId ? '我' : '';
              })
    
              that.setData({
                list: listArr
              })
              setTimeout(() => {
                that.bottom()
              }, 500)
              console.log('-----------------------s', listArr)
            },
            onError: function(err) {
              console.error('----------------error', err)
            }
          })
      },
      // 发送图片
      upLoad() {
        var that = this
        // 让用户选择一张图片
        wx.chooseImage({
          success: chooseResult => {
            // 将图片上传至云存储空间
            wx.cloud.uploadFile({
              // 指定上传到的云路径
              cloudPath: util.imgName() + 'textImg.png',
              // 指定要上传的文件的小程序临时文件路径
              filePath: chooseResult.tempFilePaths[0],
              // 成功回调
              success: res => {
                console.log('上传成功', res)
                let imgUrl = res.fileID
                wx.cloud.callFunction({
                  name: "news",
                  // data: {
                  //   imgUrl: imgUrl
                  // },
                  data: {
                    _qunId: qunId,
                    _openId: wx.getStorageSync('openId'),
                    // 消息
                    text: '',
                    // 消息
                    img: imgUrl,
                    // 时间
                    dataTime: util.nowTime(),
                    // 头像
                    sendOutHand: wx.getStorageSync('userInfo').avatarUrl,
                    // 昵称
                    sendOutname: wx.getStorageSync('userInfo').nickName
                  },
                  success(res) {
                    console.log('图片发送成功', res)
                  },
                  fail(res) {
                    console.log('返回失败', res)
                  }
                })
              },
            })
          },
        })
      },
      onLoad: function(options) {
        console.log(options)
        that = this
        qunId = options.qunId
        this.onMsg(options.qunId);
        that.userFun()
        that.prohibit()
    
      },
      // 获取群成员
      userFun() {
        DB.collection('qunUserList').where({
            qunId: qunId
          })
          .get({
            success: function(res) {
              console.log(666666666, res)
              that.setData({
                userNumber: res.data.length,
                userList: res.data
              })
    
            }
          })
      },
    
      // 群公告
      notice() {
        wx.showModal({
          title: '群公告',
          content: this.data.userList[0].qunTitle,
          showCancel: false,
          success(res) {
            if (res.confirm) {
              // console.log('用户点击确定')
            }
          }
        })
      },
      // 发送消息
      sendOut(e) {
        console.log(1111, e)
        let title = e.detail.value
        if (title == '') {
          wx.showToast({
            title: '请输入聊天内容',
            icon: 'none',
          })
        } else {
          var data = {
            _qunId: qunId,
            _openId: wx.getStorageSync('openId'),
            // 消息
            text: title,
            // 消息
            img: '',
            // 时间
            dataTime: util.nowTime(),
            // 头像
            sendOutHand: wx.getStorageSync('userInfo').avatarUrl,
            // 昵称
            sendOutname: wx.getStorageSync('userInfo').nickName
          }
          console.log(data)
          wx.cloud.callFunction({
            name: "news",
            data: data,
            success(res) {
              console.log('消息发送', res)
              that.setData({
                title: ''
              })
    
            },
            fail(res) {
              console.log('登录失败', res)
            }
          })
        }
      },
    
      /**
       * 用户点击右上角分享
       */
      onShareAppMessage: function() {
    
      },
    
      // 手指点击录音
      voice_ing_start: function () {
        console.log('手指点击录音')
        wx.showToast({
          title: '按住录音,松开发送',
          icon: 'none'
        })
        this.setData({
          voice_ing_start_date: new Date().getTime(), //记录开始点击的时间
        })
        const options = {
          duration: 10000, //指定录音的时长,单位 ms
          sampleRate: 8000, //采样率
          numberOfChannels: 1, //录音通道数
          encodeBitRate: 24000, //编码码率
          format: 'mp3', //音频格式,有效值 aac/mp3
          audioSource: 'auto',
          frameSize: 12, //指定帧大小,单位 KB
        }
        recorder.start(options) //开始录音
    
        this.animation = wx.createAnimation({
          duration: 1200,
        }) //播放按钮动画
        that.animation.scale(0.8, 0.8); //还原
        that.setData({
    
          spreakingAnimation: that.animation.export()
        })
      },
      onReady: function () {
        this.on_recorder();
      },
    
      // 录音监听事件
      on_recorder: function () {
        console.log('录音监听事件');
        recorder.onStart((res) => {
          console.log('开始录音');
        })
        recorder.onStop((res) => {
          let {
            tempFilePath
          } = res;
          console.log('停止录音,临时路径', tempFilePath);
          var x = new Date().getTime() - this.data.voice_ing_start_date
          if (x > 1000) {
            let timestamp = new Date().getTime();
            wx.cloud.uploadFile({
              cloudPath: "sounds/" + timestamp + '.mp3',
              filePath: tempFilePath,
              success: res => {
                console.log('上传成功', res)
                that.setData({
                  soundUrl: res.fileID,
                })
    
                var data = {
                  _qunId: 'fb16f7905e4bfa24009098dc34b910c8',
                  _openId: wx.getStorageSync('openId'),
                  // 消息
                  text: '',
                  voice: res.fileID,
                  img: '',
                  // 时间
                  dataTime: util.nowTime(),
                  // 头像
                  sendOutHand: wx.getStorageSync('userInfo').avatarUrl,
                  // 昵称
                  sendOutname: wx.getStorageSync('userInfo').nickName
                }
                console.log(data)
                wx.cloud.callFunction({
                  name: "news",
                  data: data,
                  success(res) {
                    console.log('发送语音发送', res)
                  },
                  fail(res) {
                    console.log('发送语音失败', res)
                  }
                })
              },
            })
          }
        })
        recorder.onFrameRecorded((res) => {
          return
          console.log('onFrameRecorded  res.frameBuffer', res.frameBuffer);
          string_base64 = wx.arrayBufferToBase64(res.frameBuffer)
    
          console.log('string_base64--', string_base64)
        })
      },
      // 手指松开录音
      voice_ing_end: function () {
        console.log('手指松开录音')
    
        that.setData({
          voice_icon_click: false,
          animationData: {}
        })
        this.animation = "";
        var x = new Date().getTime() - this.data.voice_ing_start_date
        if (x < 1000) {
          console.log('录音停止,说话小于1秒!')
          wx.showModal({
            title: '提示',
            content: '说话要大于1秒!',
          })
          recorder.stop();
        } else {
          // 录音停止,开始上传
          recorder.stop();
        }
      },
      // 点击语音图片
      voice_icon_click: function () {
        this.setData({
          voice_icon_click: !this.data.voice_icon_click
        })
      },
    })

     

    展开全文
  • 微信发送自定义语音

    千次阅读 2019-03-08 16:11:02
    很皮微信前景开发遇到的难题收集语音素材问题解决录制音频如何变声音频格式转换amr问题...曾经一直想做一款关于微信的软件,目标就是网上收集很多调皮捣蛋的语音或者录音变声,然后发送给某个微信好友,感觉很有意思 开
  • 微信小程序开发交流qq群 173683895 承接微信小程序开发。... ---1兼容情况:1.1 正常聊天过一段时间 WebSocket 自动断开后重新链接,并且保存之前的聊天记录 ---1兼容情况:1.2 在用户黑屏但是没退出小程序过一...
  • Android 仿微信语音,录音时间不能少于一秒,不超过一分钟,并且设置了录音倒计时提示等.rar [注:本内容来自网络,在此分享仅为帮助有需要的网友,如果侵犯了您的权利,麻烦联系我,我会第一时间删除,谢谢您。]
  • 微信发送语音功能测试用例

    千次阅读 多人点赞 2020-09-03 16:25:49
    录制时间不足最短时限制会发送失败 超出了最长时间限制时, 语音自动保存并发送 语音识别 是否可以录入中文, 英文, 日语… 是否可以录入小动物的声音: 狗叫, 猫叫, 羊叫… 是否可以录入其他声音: 汽笛, 施工声… ...
  • 深度学习(1): 深度学习简介

    万次阅读 多人点赞 2019-08-09 11:10:29
    如果让一辆大卡车去装载很多堆货物,就要等待很时间了,因为要等待大卡车从北京运到广州,然后再回来装货物。设想一下,我们现在拥有了跑车车队和卡车车队(线程并行〉,运载一堆货物(非常大块的内存数据需要...
  • 长语音识别工具

    千次阅读 2018-04-17 11:44:46
    各大公司都开源了语音识别api,然而却很多限制。例如文件格式必须是pcm,每次的时不能超过60s。当然也有一些商业的,例如讯飞。为了突破这些限制,我随手写了一个小工具,可以对音频进行识别了。原理很简单,...
  • 第13章 基于MATLAB的语音识别系统

    万次阅读 多人点赞 2013-12-11 22:08:02
    1)提取的特征参数能有效地代表语音特征,具有很好的区分性; 2)各阶参数之间有良好的独立性; 3)特征参数要计算方便,最好有高效的计算方法,以保证语音识别的实时实现。声带可以有周期振动也可以不震动,分别...
  • 语音识别动态时间规整DTW的Matlab代码 ...动态时间归整是较早的一种模式匹配和模型训练技术,它应用动态规划方法成功地解决了语音信号特征参数序列在进行比较时时不等的难题,在孤立词语音识别中获得了良好的性能。
  • 现在,社交媒体网站已添加了语音推文功能,可让您将个性化音频消息发送给关注者。 At the time of writing, Twitter is still slowly rolling out the voice tweet feature to its iPhone and iPad apps. There’s ...
  • //录音中....db为声音分贝,time为录音时 @Override public void onUpdate ( double db, long time) { //根据分贝值来设置录音时话筒图标的上下波动,下面有讲解 mImageView.getDrawable()....
  • 语音识别的流程

    千次阅读 2019-12-15 17:30:26
    语音合成是用语音方式输出用户想要的信息,用语音实现人与计算机之间的交互,主要包括语音识别、自然语言理解和语音合成。 相对于机器翻译,语音识别是更加困难的问题。机器翻译系统的输入通常是印刷文本,计算机能...
  • 百度免费开放长语音识别功能

    千次阅读 2020-10-27 17:57:09
    据了解,新版本SDK解除了对时间的限制,开发者无需再将长语音切割成60秒以内的分段,进行调用,提升了转写的效率和语音识别的体验。对于用户来说,新版本告别之前60秒的“束缚”,可根据需要自行把控时间,极大地...
  • 在进行语音相关方面的研究的时候,我们就需要统计一下我们各个文件夹下所有语音文件的总长度,但是一个一个的进行计算难免有些麻烦,想着有没有可以使用python批量化计算每个文件夹下的所有语音,于是便有了下边...
  • 语音识别】语音识别技术入门

    千次阅读 2021-01-24 18:25:32
    语音识别入门什么是语音 什么是语音 语音是语言的声学表现形式,是人类自然的交流工具,例如语音通信、人机语音交互。 相关概念有: 声学Acoustics 音频Audio 语音Speech 音频 采样率、量化位数、通道数 16KHZ...
  • 麦克风阵列技术名词解释背景介绍远场拾音声源定位麦克风及音频信号什么是麦克风麦克风的分类麦克风的对比选型麦克风阵列简介语音交互的优势人机交互痛点近场语音和远场语音麦克风阵列的功能麦克风阵列构型 ...
  • 基于matlabGUI的语音处理

    万次阅读 多人点赞 2013-12-31 12:11:25
    基于matlab实现对一段语音信号的处理,这些处理包括时域分析、频域分析、倒谱、能量谱和语谱图,并且可以将图形保存以便后续研究。 先用matlabGUI设计好界面 我的界面: 菜单界面 运行后于是就出现了这样的效果 ...
  • 原标题:百度AI长语音识别技术免费开放,调用时不再受限前几天看到一条祝福:祝大家早日成为可以在公司群里随心所欲发语音的人 。显然,微信群聊(特别是家族群、客户群、公司群)里的语音消息,并不是谁想发就能...
  • 人工智能时代,所需要了解人工智能的基本常识

    万次阅读 多人点赞 2018-12-10 22:49:44
    语音识别的主要应用包括医疗听写、语音书写、电脑系统声控、电话客服等。比如Domino’sPizza最近推出了一个允许用户通过语音下单的移动APP。  上面提到的认知技术进步飞快并吸引了大量投资,其他相对成熟的认知...
  • 语音识别的端点检测

    万次阅读 2019-07-27 15:38:50
    端点检测,也叫语音活动检测,Voice Activity Detection,VAD,它的目的是对语音和非语音的区域进行区分。通俗来理解,端点检测就是为了从带有噪声的语音中准确的定位出语音的开始点,和结束点,去掉静音的部分,...
  •  先从单词语音时间序列的规整问题引入DTW的基本思想。  假设下图两个时间序列对应的是同一个单词的发音(实则不是,只是为了便于理解)。黑色的线表示两个时间序列的相似的点(用幅度差异刻画时间序列点的相似.....
  • 树莓派打造智能语音控制系统

    万次阅读 多人点赞 2019-08-17 21:36:58
    近几年,随着科技迅速发展,智能语音在现实生活中的普及率越来越高,特别是语音识别、语音合成、语音对话、语音控制等方面。树莓派的智能语音控制系统,该系统使用麦克风对语音进行采集,通过调用语音识别云服务对...
  • 语音识别技术的前世今生【前世篇】

    千次阅读 多人点赞 2019-07-22 16:04:42
    目录 1.背景 2.孤立词识别 2.1 特征提取 2.2 动态弯算法 ...2.6 语音识别基本方程 3.连续语音识别 3.1 语言模型 3.2 大词汇量 3.3 语音识别系统结构 3.4 评价指标:WER 4. 潘多拉魔盒 4.1...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 94,611
精华内容 37,844
关键字:

如何发送长时间的语音