Android 手机做麦克风扩音器demo

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhuxingchong/article/details/80936334

前两天忽略想到的一个功能,手机作为麦克风扩音器,我认为这是一个很有意思的事情,在网上也找到了一个这样的apk但我还是想自己动手实现。

实现麦克风的原来很简单,通过录音功能将声音录下来再通过播放功能将录到音频流实时播放出来,最终达到麦克风扩音功能。

效果如下


布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:voiceView="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_blue_bright">

    <com.carlos.voiceline.mylibrary.VoiceLineView
        android:id="@+id/voicLine"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:background="@android:color/white"
        voiceView:maxVolume="200"
        voiceView:middleLine="@android:color/black"
        voiceView:middleLineHeight="1dp"
        voiceView:fineness="three"
        voiceView:rectSpace="2dp"
        voiceView:rectWidth="5dp"
        voiceView:sensibility="four"
        voiceView:viewMode="line"
        voiceView:lineSpeed="90"
        voiceView:voiceLine="@android:color/holo_green_dark" />
    <Button
        android:id="@+id/start"
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="录音"
        />
</RelativeLayout>
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import com.carlos.voiceline.mylibrary.VoiceLineView;

public class MainActivity extends AppCompatActivity implements Runnable {

    String TAG = "MainActivity";
    static final int frequency = 44100;
    static final int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
    static final int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;

    int recBufSize = AudioRecord.getMinBufferSize(frequency,
            channelConfiguration, audioEncoding)*2;
    int plyBufSize = AudioTrack.getMinBufferSize(frequency,
            channelConfiguration, audioEncoding)*2;
    private boolean isAlive = true;
    private VoiceLineView voiceLineView;
    private Button start;

    AudioRecord audioRecord;
    AudioTrack audioTrack;
    Thread thread = null;

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg != null)
                 voiceLineView.setVolume(msg.what);//主线程更新分贝大小
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        voiceLineView = (VoiceLineView) findViewById(R.id.voicLine);
        start = (Button) findViewById(R.id.start);
        checkPermission(this);
        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(thread == null) {
                    thread = new Thread(MainActivity.this);//启动线程
                    isAlive = true;
                    thread.start();
                    start.setText("停止");
                }else {
                    isAlive = false;//关闭线程
                    thread = null;
                    start.setText("开始");
                }
            }
        });

    }

    //想做一个耳机插入时手机喇叭也能外发功能,这个方法没什么用有待改进
    private void setOutSpeark(){
        AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
        //audioManager.setMicrophoneMute(false);
        audioManager.setSpeakerphoneOn(true);//使用扬声器外放,即使已经插入耳机
        setVolumeControlStream(AudioManager.STREAM_MUSIC);//控制声音的大小
        audioManager.setMode(AudioManager.MODE_NORMAL);
    }

    //需要录音权限
    public static void checkPermission(AppCompatActivity activity) {
        int checkPermission = ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO);
        if (checkPermission != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(activity, new String[]{
                    Manifest.permission.RECORD_AUDIO}, 123);
        }
        return;
    }

    @Override
    public void run() {

        //录音对象
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, recBufSize);
        setOutSpeark();
        //声音播放对象
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, frequency, channelConfiguration, audioEncoding, plyBufSize, AudioTrack.MODE_STREAM);
        audioRecord.startRecording();

        byte[] recBuf = new byte[recBufSize];
        audioRecord.startRecording();
        audioTrack.play();
        while(isAlive){
            int readLen = audioRecord.read(recBuf, 0, recBufSize);//获取录音缓存值
            audioTrack.write(recBuf, 0, readLen);//将获取到录音数据向声音播放对象写入

            //计算声音分贝大小
            long v = 0;
            // 将 buffer 内容取出,进行平方和运算
            for (int i = 0; i < recBuf.length; i++) {
                v += recBuf[i] * recBuf[i];
            }
            // 平方和除以数据总长度,得到音量大小。
            double mean = v / (double) readLen;
            double volume = 10 * Math.log10(mean);
            Message msg = handler.obtainMessage();
            msg.what = (int)volume;
            handler.sendMessage(msg);//发送到主线程更新分贝显示

        }
        audioTrack.stop();//停止播放
        audioRecord.stop();//停止录音
    }

    @Override
    protected void onDestroy() {
        isAlive = false;//退出时停止麦克风功能
        thread = null;
        super.onDestroy();
    }
}

声音大小显示波段是在网上找的一段代码。

package com.carlos.voiceline.mylibrary;

import java.math.BigDecimal;

/**
 * Created by carlos on 2016/4/5.
 */
public class Arith{

    //默认除法运算精度
    private static final int DEF_DIV_SCALE = 10;
    //这个类不能实例化
    private Arith(){
    }

    /**
     * 提供精确的加法运算。
     * @param v1 被加数
     * @param v2 加数
     * @return 两个参数的和
     */
    public static double add(double v1,double v2){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2).doubleValue();
    }
    /**
     * 提供精确的减法运算。
     * @param v1 被减数
     * @param v2 减数
     * @return 两个参数的差
     */
    public static double sub(double v1,double v2){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2).doubleValue();
    }
    /**
     * 提供精确的乘法运算。
     * @param v1 被乘数
     * @param v2 乘数
     * @return 两个参数的积
     */
    public static double mul(double v1,double v2){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2).doubleValue();
    }

    /**
     * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
     * 小数点以后10位,以后的数字四舍五入。
     * @param v1 被除数
     * @param v2 除数
     * @return 两个参数的商
     */
    public static double div(double v1,double v2){
        return div(v1,v2,DEF_DIV_SCALE);
    }

    /**
     * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
     * 定精度,以后的数字四舍五入。
     * @param v1 被除数
     * @param v2 除数
     * @param scale 表示表示需要精确到小数点以后几位。
     * @return 两个参数的商
     */
    public static double div(double v1,double v2,int scale){
        if(scale<0){
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供精确的小数位四舍五入处理。
     * @param v 需要四舍五入的数字
     * @param scale 小数点后保留几位
     * @return 四舍五入后的结果
     */
    public static double round(double v,int scale){

        if(scale<0){
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Double.toString(v));
        BigDecimal one = new BigDecimal("1");
        return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
    }
};
package com.carlos.voiceline.mylibrary;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * Created by carlos on 2016/1/29.
 * 自定义声音振动曲线view
 */
public class VoiceLineView extends View {
    private final int LINE = 0;
    private final int RECT = 1;

    private int middleLineColor = Color.BLACK;
    private int voiceLineColor = Color.BLACK;
    private float middleLineHeight = 4;
    private Paint paint;
    private Paint paintVoicLine;
    private int mode;
    /**
     * 灵敏度
     */
    private int sensibility = 4;

    private float maxVolume = 100;


    private float translateX = 0;
    private boolean isSet = false;

    /**
     * 振幅
     */
    private float amplitude = 1;
    /**
     * 音量
     */
    private float volume = 10;
    private int fineness = 1;
    private float targetVolume = 1;


    private long speedY = 50;
    private float rectWidth = 25;
    private float rectSpace = 5;
    private float rectInitHeight = 4;
    private List<Rect> rectList;

    private long lastTime = 0;
    private int lineSpeed = 90;

    List<Path> paths = null;

    public VoiceLineView(Context context) {
        super(context);
    }

    public VoiceLineView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initAtts(context, attrs);
    }

    public VoiceLineView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAtts(context, attrs);
    }



    private void initAtts(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.voiceView);
        mode = typedArray.getInt(R.styleable.voiceView_viewMode, 0);
        voiceLineColor = typedArray.getColor(R.styleable.voiceView_voiceLine, Color.BLACK);
        maxVolume = typedArray.getFloat(R.styleable.voiceView_maxVolume, 100);
        sensibility = typedArray.getInt(R.styleable.voiceView_sensibility, 4);
        if (mode == RECT) {
            rectWidth = typedArray.getDimension(R.styleable.voiceView_rectWidth, 25);
            rectSpace = typedArray.getDimension(R.styleable.voiceView_rectSpace, 5);
            rectInitHeight = typedArray.getDimension(R.styleable.voiceView_rectInitHeight, 4);
        } else {
            middleLineColor = typedArray.getColor(R.styleable.voiceView_middleLine, Color.BLACK);
            middleLineHeight = typedArray.getDimension(R.styleable.voiceView_middleLineHeight, 4);
            lineSpeed = typedArray.getInt(R.styleable.voiceView_lineSpeed, 90);
            fineness = typedArray.getInt(R.styleable.voiceView_fineness, 1);
            paths = new ArrayList<>(20);
            for (int i = 0; i < 20; i++) {
                paths.add(new Path());
            }
        }
        typedArray.recycle();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mode == RECT) {
            drawVoiceRect(canvas);
        } else {
            drawMiddleLine(canvas);
            drawVoiceLine(canvas);
        }
        run();
    }

    private void drawMiddleLine(Canvas canvas) {
        if (paint == null) {
            paint = new Paint();
            paint.setColor(middleLineColor);
            paint.setAntiAlias(true);
        }
        canvas.save();
        canvas.drawRect(0, getHeight() / 2 - middleLineHeight / 2, getWidth(), getHeight() / 2 + middleLineHeight / 2, paint);
        canvas.restore();
    }

    private void drawVoiceLine(Canvas canvas) {
        lineChange();
        if (paintVoicLine == null) {
            paintVoicLine = new Paint();
            paintVoicLine.setColor(voiceLineColor);
            paintVoicLine.setAntiAlias(true);
            paintVoicLine.setStyle(Paint.Style.STROKE);
            paintVoicLine.setStrokeWidth(2);
        }
        canvas.save();
        int moveY = getHeight() / 2;
        for (int i = 0; i < paths.size(); i++) {
            paths.get(i).reset();
            paths.get(i).moveTo(getWidth(), getHeight() / 2);
        }
        for (float i = getWidth() - 1; i >= 0; i -= fineness) {
            amplitude = 4 * volume * i / getWidth() - 4 * volume * i * i / getWidth() / getWidth();
            for (int n = 1; n <= paths.size(); n++) {
                float sin = amplitude * (float) Math.sin((i - Math.pow(1.22, n)) * Math.PI / 180 - translateX);
                paths.get(n - 1).lineTo(i, (2 * n * sin / paths.size() - 15 * sin / paths.size() + moveY));
            }
        }
        for (int n = 0; n < paths.size(); n++) {
            if (n == paths.size() - 1) {
                paintVoicLine.setAlpha(255);
            } else {
                paintVoicLine.setAlpha(n * 130 / paths.size());
            }
            if (paintVoicLine.getAlpha() > 0) {
                canvas.drawPath(paths.get(n), paintVoicLine);
            }
        }
        canvas.restore();
    }

    private void drawVoiceRect(Canvas canvas) {
        if (paintVoicLine == null) {
            paintVoicLine = new Paint();
            paintVoicLine.setColor(voiceLineColor);
            paintVoicLine.setAntiAlias(true);
            paintVoicLine.setStyle(Paint.Style.STROKE);
            paintVoicLine.setStrokeWidth(2);
        }
        if (rectList == null) {
            rectList = new LinkedList<>();
        }
        int totalWidth = (int) (rectSpace + rectWidth);
        if (speedY % totalWidth < 6) {
            Rect rect = new Rect((int) (-rectWidth - 10 - speedY + speedY % totalWidth),
                    (int) (getHeight() / 2 - rectInitHeight / 2 - (volume == 10 ? 0 : volume / 2)),
                    (int) (-10 - speedY + speedY % totalWidth),
                    (int) (getHeight() / 2 + rectInitHeight / 2 + (volume == 10 ? 0 : volume / 2)));
            if (rectList.size() > getWidth() / (rectSpace + rectWidth) + 2) {
                rectList.remove(0);
            }
            rectList.add(rect);
        }
        canvas.translate(speedY, 0);
        for (int i = rectList.size() - 1; i >= 0; i--) {
            canvas.drawRect(rectList.get(i), paintVoicLine);
        }
        rectChange();
    }

    public void setVolume(int volume) {
        if (volume > maxVolume * sensibility / 25) {
            isSet = true;
            this.targetVolume = getHeight() * volume / 2 / maxVolume;
        }
    }

    private void lineChange() {
        if (lastTime == 0) {
            lastTime = System.currentTimeMillis();
            translateX += 1.5;
        } else {
            if (System.currentTimeMillis() - lastTime > lineSpeed) {
                lastTime = System.currentTimeMillis();
                translateX += 1.5;
            } else {
                return;
            }
        }
        if (volume < targetVolume && isSet) {
            volume += getHeight() / 30;
        } else {
            isSet = false;
            if (volume <= 10) {
                volume = 10;
            } else {
                if (volume < getHeight() / 30) {
                    volume -= getHeight() / 60;
                } else {
                    volume -= getHeight() / 30;
                }
            }
        }
    }

    private void rectChange() {
        speedY += 6;
        if (volume < targetVolume && isSet) {
            volume += getHeight() / 30;
        } else {
            isSet = false;
            if (volume <= 10) {
                volume = 10;
            } else {
                if (volume < getHeight() / 30) {
                    volume -= getHeight() / 60;
                } else {
                    volume -= getHeight() / 30;
                }
            }
        }
    }

    public void run() {
        if (mode == RECT) {
            postInvalidateDelayed(30);
        } else {
            invalidate();
        }
    }

}

attrs.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="voiceView">
        <!--中间线的颜色,就是波形的时候,大家可以看到,中间有一条直线,就是那个-->
        <attr name="middleLine" format="color" />
        <!--中间线的高度,因为宽度是充满的-->
        <attr name="middleLineHeight" format="dimension" />
        <!--波动的线的颜色,如果是矩形样式的话,刚是矩形的颜色-->
        <attr name="voiceLine" format="color" />
        <!--波动线的横向移动速度,线的速度的反比,即这个值越小,线横向移动越快,越大线移动越慢,默认90-->
        <attr name="lineSpeed" format="integer" />
        <!--矩形的宽度-->
        <attr name="rectWidth" format="dimension" />
        <!--矩形之间的间隔-->
        <attr name="rectSpace" format="dimension" />
        <!--矩形的初始高度,就是没有声音的时候,矩形的高度-->
        <attr name="rectInitHeight" format="dimension" />
        <!--所输入音量的最大值-->
        <attr name="maxVolume" format="float" />
        <!--控件的样式,一共有两种,波形或者矩形-->
        <attr name="viewMode">
            <enum name="line" value="0" />
            <enum name="rect" value="1" />
        </attr>
        <!--灵敏度,默认值是4-->
        <attr name="sensibility">
            <enum name="one" value="1" />
            <enum name="two" value="2" />
            <enum name="three" value="3" />
            <enum name="four" value="4" />
            <enum name="five" value="5" />
        </attr>
        <!--精细度,绘制曲线的时候,每几个像素绘制一次,默认是1,一般,这个值越小,曲线越顺滑,
            但在一些旧手机上,会出现帧率过低的情况,可以把这个值调大一点,在图片的顺滑度与帧率之间做一个取舍-->
        <attr name="fineness">
            <enum name="one" value="1" />
            <enum name="two" value="2" />
            <enum name="three" value="3" />
        </attr>
    </declare-styleable>
</resources>
完整代码下载 点击打开链接

猜你喜欢

转载自blog.csdn.net/zhuxingchong/article/details/80936334