Andrioid 通过话筒实现吹气功能

今晚完成了模仿魔镜demo的最后一个模块就是使用话筒实例来吹气,让画面形成雾,再用手势擦除,相当于总结一下话筒类的使用。
在工作之前先声明权限

要先构造一个AudioRecordManger类
首先声明变量以及构造方法

 private static final String TAG = "AudioRecord";//标记
    public static final int SAMPLE_RATE_IN_HZ = 8000;//通道配置
    public static final int BUFFER_SIZE = AudioRecord.getMinBufferSize(SAMPLE_RATE_IN_HZ,
            AudioFormat.CHANNEL_IN_DEFAULT, AudioFormat.ENCODING_PCM_16BIT);//用于写入声音的缓存
    private AudioRecord mAudioRecord;//话筒类
    public boolean isGetVoiceRun;//是否录音运行
    private Handler mHandler;//句柄
    private int mWhat;//动作
    public Object mLock;//对象

记下来是构造方法,通过传入handler和what来赋值,mLock是一个同步锁,做延时用

 public AudioRecordManger(Handler handler,int what) {
        mLock = new Object();//同步锁
        this.mHandler = handler;//获得句柄
        this.mWhat = what;//动作ID
    }

实现监听吹气功能,用线程的方式开启话筒录音后,一秒钟10次接收话筒音量,然后将接收的音量转化为float的分贝值,并作为message发送。

  public void getNoiseLevel() {
        if (isGetVoiceRun) {
            Log.e(TAG, "还在录着呢");
            return;
        }
        //创建录音对象,并初始化对象属性
        mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
                SAMPLE_RATE_IN_HZ, AudioFormat.CHANNEL_IN_DEFAULT,
                AudioFormat.ENCODING_PCM_16BIT, BUFFER_SIZE);
        //判断话筒对象是否为空
        if (mAudioRecord == null) {
            Log.e("sound", "mAudioRecord初始化失败");
        }
        isGetVoiceRun = true;//开启录音

        //使用新线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                mAudioRecord.startRecording();//录音启动
                short[] buffer = new short[BUFFER_SIZE];//设置缓存数组

                while (isGetVoiceRun) {
                    int r = mAudioRecord.read(buffer, 0, BUFFER_SIZE);//r是实际读取的数据长度,一般而言r会小于buffersize
                    long v = 0;
                    // 将 buffer 内容取出,进行平方和运算
                    for (int i = 0; i < buffer.length; i++) {
                        v += buffer[i] * buffer[i];
                    }
                    double mean = v / (double) r;          // 平方和除以数据总长度,得到音量大小。
                    double volume = 10 * Math.log10(mean);

                    // 大概一秒十次,锁
                    synchronized (mLock) {
                        try {
                            mLock.wait(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //声明消息类,句柄发送消息到主窗体函数
                    Message message = Message.obtain();
                    message.what = mWhat;
                    message.obj = volume;
                    mHandler.sendMessage(message);
                }
                //话筒对象释放
                if (null !=mAudioRecord) {
                    mAudioRecord.stop();
                    mAudioRecord.release();
                    mAudioRecord = null;
                }
            }
        }).start();//启动线程

    }

mainactivity中接收message,先声明刚刚创建好的类

    private AudioRecordManger audioRecordManger;      //调用话筒实现类
    private static final int RECORD = 2;              //监听话筒

再在onCreate方法中调用实例的方法

        audioRecordManger = new AudioRecordManger(handler,RECORD); //实例化话筒实现类
        audioRecordManger.getNoiseLevel();                         //打开话筒监听声音

我们在xml文件中放置drawview控件用来作为生成雾的画板,并在这上面去擦除雾,这是drawview的类

public class DrawView extends View {
    private Canvas mCanvas;// 画布
    private Path mPath;// 路径
    private Paint mPaint;// 画笔
    private float moveX, moveY;//移动坐标
    private Bitmap mBitmap;//图片变量
    private Bitmap bitmap;//图片变量
    private volatile boolean mComplete = false;// 判断遮盖层区域是否消除达到阈值
    /**
     * 构造函数
     */
    public DrawView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();//初始化
    }

    public DrawView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DrawView(Context context) {
        this(context, null);
    }

    private void init() {
        bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.glasses).copy(Bitmap.Config.ARGB_8888,true);//初始化图片加载
        mPaint = new Paint();  //新建画笔
        mPaint.setColor(Color.RED); //设置画笔颜色
        mPaint.setStyle(Paint.Style.STROKE);//设置画笔样式
        mPaint.setStrokeJoin(Paint.Join.ROUND);//设置结合处样子
        mPaint.setStrokeCap(Paint.Cap.ROUND);//设置画笔笔触风格
        mPaint.setDither(true);// 设置递色
        mPaint.setAntiAlias(true);//设置抗锯齿
        mPaint.setStrokeWidth(100);//设置空心线宽
        mPath = new Path();//创建新路径
    }


    //画布绘制
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.TRANSPARENT);
        if (!mComplete)    //如果还未擦除完成
        {
            mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));   //设定目标图模式
            canvas.drawBitmap(mBitmap, 0, 0, null);//画图
            mCanvas.drawPath(mPath, mPaint);    //画布 路径
            canvas.drawBitmap(mBitmap, 0, 0, null); //画布图片
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);//宽度
        int height = MeasureSpec.getSize(heightMeasureSpec);//高度
        mBitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);//图片
        bitmap = Bitmap.createScaledBitmap(bitmap,width,height,true);//基于原bitmap,创建一个新的宽、高的bitmap
        mCanvas = new Canvas(mBitmap); //画布实例化
        mCanvas.drawColor(Color.TRANSPARENT);//设置颜色
        mCanvas.drawBitmap(bitmap,0,0,null);//画布重绘bitmap
    }
}

接下来是mainactivity开始通过有aduiorecordmanger发送过来的message做处理,如果音量分贝大于50,则产生动画效果形成雾

private void getSoundValues(double values){
        //话筒分贝大于50,屏幕起雾
        if (values >50){

            hideView();//隐藏无关控件

            drawView.setVisibility(View.VISIBLE);  //显示控件

            Animation animation = AnimationUtils.loadAnimation(this, R.anim.in_window);//设置透明度
            drawView.setAnimation(animation);

            audioRecordManger.isGetVoiceRun = false;//设置话筒停止运行

            Log.e("玻璃显示","执行");
        }
    }
    private Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case RECORD://监测话筒
                    double soundValues = (double) msg.obj;
                    getSoundValues(soundValues);//获得话筒声音后,屏幕重绘起雾
                    break;
                default:
                    break;
            }
            return false;
        }
    });

这里的anim中的in_window是在res下建立的动画xml文件,用Alpha的值来表示灰的程度,0是完全没有,1是全灰

<set xmlns:android="http://schemas.android.com/apk/res/android">
      <alpha
          android:fromAlpha="0"
          android:toAlpha="1"
          android:duration="3000"/>
</set>

这个时候当向手机话筒吹气的时候,就会产生雾气。

这时候加入手势擦除功能。
在drawView中加入写一个内部接口让mainactivity来实现

 public interface OnCaYiCaCompleteListener{
        void complete();
    }

    private OnCaYiCaCompleteListener mListener;
    public void setOnCaYiCaCompleteListener(OnCaYiCaCompleteListener mListener){
        this.mListener = mListener;
    }

    //变量重置
    public void setEndValues(){
        moveX = 0;
        moveY = 0;
        mPath.reset();          //路径重置
        mComplete =false;       //恢复未擦除状态
    }

    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            int w = getWidth();
            int h = getHeight();

            float wipeArea = 0;
            float totalArea = w * h;
            Bitmap bitmap = mBitmap;
            int[] mPixels = new int[w * h];

            // 获得Bitmap上所有的像素信息
            bitmap.getPixels(mPixels, 0, w, 0, 0, w, h);

            for (int i = 0; i < w; i++)
            {
                for (int j = 0; j < h; j++)
                {
                    int index = i + j * w;
                    if (mPixels[index] == 0)
                    {
                        wipeArea++;
                    }
                }
            }

            if (wipeArea > 0 && totalArea > 0)
            {
                int percent = (int) (wipeArea * 100 / totalArea);

                Log.e("TAG", percent + "");

                if (percent > 50)
                {
                    // 清除掉图层区域
                    mComplete = true;
                    postInvalidate();

                }

            }
        }
    };

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                moveX = x;
                moveY = y;
                mPath.moveTo(moveX,moveY);
                break;
            case MotionEvent.ACTION_MOVE:
                int dx = (int)Math.abs(moveX - x);
                int dy = (int)Math.abs(moveY - y);
                if(dx > 1 || dy > 1){
                    mPath.quadTo(x,y,(moveX + x) / 2,(moveY + y) / 2);
                }
                moveX = x;
                moveY = y;
                break;
            case MotionEvent.ACTION_UP:
                if(!mComplete){                       //如果擦除未完成,则新线程开始
                    new Thread(mRunnable).start();
                }
                break;

            default:
                break;
        }
        if(!mComplete){
            invalidate();
        }
        return true;
    }

再在ondraw中加入,当还未擦除干净的时候则进行释放操作

if(mComplete){
            if (mListener != null){
                mListener.complete();     //监听结束
                setEndValues();           //变量重置
            }
        }

最后在mainactivity中实现接口

@Override
    public void complete() {
        showView();
        audioRecordManger.getNoiseLevel();
        drawView.setVisibility(View.GONE);
    }
    drawView.setOnCaYiCaCompleteListener(this);

到这里完成吹气起雾和手势擦除功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值