android 蓝牙语音转换成pcm文件,进行播放暂停停止操作

需求描述

最近在做蓝牙与android之间互联通信,有个需求,是通过指令,控制蓝牙开启录音,结束录音,录音过程中,将蓝牙传回的数据,转换成pcm文件,然后再做个文件列表,点击播放pcm,包括暂停,重新播放之类的操作。

蓝牙数据转pcm

蓝牙传输的数据,先转换成Byte[],然后再转换成pcm文件,保存到本地。
我们蓝牙协议的真正数据是从字节第4位开始

public class ByteKit {

    private BitOperator bitOperator;
    private BCD8421Operater bcd8421Operater;
    private static final ByteKit mInstance = new ByteKit();


    public static ByteKit getInstance() {
        return mInstance;
    }
    private ByteKit(){
        bitOperator = new BitOperator();
        bcd8421Operater = new BCD8421Operater();
    }


    public float parseFloatFromBytes(byte[] data, int startIndex, int length) {
        return this.parseFloatFromBytes(data, startIndex, length, 0f);
    }

    private float parseFloatFromBytes(byte[] data, int startIndex, int length, float defaultVal) {
        try {
            // 字节数大于4,从起始索引开始向后处理4个字节,其余超出部分丢弃
            final int len = length > 4 ? 4 : length;
            byte[] tmp = new byte[len];
            System.arraycopy(data, startIndex, tmp, 0, len);
            return bitOperator.byte2Float(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return defaultVal;
        }
    }


    public String parseHexStringFormBytes(byte[] data, int startIndex, int length) {
        return this.parseHexStringFormBytes(data, startIndex, length, null);
    }

    private String parseHexStringFormBytes(byte[] data, int startIndex, int length, String defaultValue) {
        try {
            byte[] tmp = new byte[length];
            System.arraycopy(data, startIndex, tmp, 0, length);
            return ConvertUtils.bytes2HexString(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return defaultValue;
        }
    }

    public String parseStringFromBytes(byte[] data, int startIndex, int lenth) {
        return this.parseStringFromBytes(data, startIndex, lenth, null);
    }

    private String parseStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) {
        try {
            byte[] tmp = new byte[lenth];
            System.arraycopy(data, startIndex, tmp, 0, lenth);
            return new String(tmp, "gbk");
        } catch (Exception e) {
            e.printStackTrace();
            return defaultVal;
        }
    }

    public String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth) {
        return this.parseBcdStringFromBytes(data, startIndex, lenth, null);
    }

    private String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) {
        try {
            byte[] tmp = new byte[lenth];
            System.arraycopy(data, startIndex, tmp, 0, lenth);
            return this.bcd8421Operater.bcd2String(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return defaultVal;
        }
    }


    public int parseIntFromBytes(byte[] data, int startIndex, int length) {
        return this.parseIntFromBytes(data, startIndex, length, 0);
    }

    private int parseIntFromBytes(byte[] data, int startIndex, int length, int defaultVal) {
        try {
            // 字节数大于4,从起始索引开始向后处理4个字节,其余超出部分丢弃
            final int len = length > 4 ? 4 : length;
            byte[] tmp = new byte[len];
            System.arraycopy(data, startIndex, tmp, 0, len);
            return bitOperator.byteToInteger(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return defaultVal;
        }
    }

    public Long parseLongFromBytes(byte[] data, int startIndex, int length) {
        try {
            // 字节数大于4,从起始索引开始向后处理4个字节,其余超出部分丢弃
            final int len = length > 4 ? 4 : length;
            byte[] tmp = new byte[len];
            System.arraycopy(data, startIndex, tmp, 0, len);
            return bitOperator.fourBytesToLong(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return 0L;
        }
    }

    public byte[] str2Bcd(String asc) {
        int len = asc.length();
        int mod = len % 2;

        if (mod != 0) {
            asc = "0" + asc;
            len = asc.length();
        }

        byte abt[] = new byte[len];
        if (len >= 2) {
            len = len / 2;
        }

        byte bbt[] = new byte[len];
        abt = asc.getBytes();
        int j, k;

        for (int p = 0; p < asc.length()/2; p++) {
            if ( (abt[2 * p] >= '0') && (abt[2 * p] <= '9')) {
                j = abt[2 * p] - '0';
            } else if ( (abt[2 * p] >= 'a') && (abt[2 * p] <= 'z')) {
                j = abt[2 * p] - 'a' + 0x0a;
            } else {
                j = abt[2 * p] - 'A' + 0x0a;
            }

            if ( (abt[2 * p + 1] >= '0') && (abt[2 * p + 1] <= '9')) {
                k = abt[2 * p + 1] - '0';
            } else if ( (abt[2 * p + 1] >= 'a') && (abt[2 * p + 1] <= 'z')) {
                k = abt[2 * p + 1] - 'a' + 0x0a;
            }else {
                k = abt[2 * p + 1] - 'A' + 0x0a;
            }

            int a = (j << 4) + k;
            byte b = (byte) a;
            bbt[p] = b;
        }
        return bbt;
    }
}

语音协议的数据是从第10位开始,数据长度是4,5两位,做如下处理

  int dataLength = ByteKit.getInstance().parseIntFromBytes(data, 4, 2);
                    byte[] result = new byte[dataLength];
                    System.arraycopy(data, 10, result, 0, result.length);

                    ibleResponse.CONTROL_AUDIO(result);

byte[]转pcm文件

public class ByteToPcmConverter {

    public static void convertByteToPcm(byte[] bytes, String filePath) throws IOException {

        File file = new File(filePath);
        FileOutputStream outputStream = new FileOutputStream(file,true);
        outputStream.write(bytes);
        outputStream.close();
    }

//    // 使用示例
//    public static void main(String[] args) {
//        byte[] bytes = {/* 这里填入你的byte数组 */};
//        String pcmFilePath = "/path/to/your/pcmfile.pcm";
//        String outputPath= FileUtil.getSDPath(App.getInstance(),"");
//        try {
//            convertByteToPcm(bytes, pcmFilePath);
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }


}

在获取数据之前,比如在点击录音按钮的时候,需要生产文件,文件保存到应用私密空间,在android高版本上,需要特殊处理

outputPath= FileUtil.getSDPath(App.getInstance(),name+".pcm");
      File file=new File(outputPath);
        if(!file.exists()){
            try {
                file.createNewFile();

            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

接收数据,并转换,我这还做了个页面提示

 @Override
    public void CONTROL_AUDIO(byte[] bytes) {

            try {

                ByteToPcmConverter.convertByteToPcm(bytes, outputPath);
                File file = new File(outputPath);
                if (file.exists() && file.isFile()) {
                    long length = file.length();
                    double mb = (double) length / (1024 * 1024);
                    String format = String.format("%.4f", mb);
                    tv_write_progress.setText("录音成功,文件大小"+ format +"mb");
                }

            } catch (IOException e) {
                e.printStackTrace();

            }
    }
    /**
     * 获取SDCard文件路径
     * @param ctx
     * @return
     */
    public static String getSDPath(Context ctx,String fileName) {
       return getSDPath(ctx,"",fileName);
    }
    public static String getSDPath(Context ctx,String path,String fileName) {
        File sdDir = null;
        boolean sdCardExist = Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED);// 判断sd卡是否存在
        if (sdCardExist) {
            if (Build.VERSION.SDK_INT >= 29) {
                //Android10之后
                sdDir = ctx.getExternalFilesDir(null);
//                sdDir=  ctx.getExternalCacheDir();
            } else {
                sdDir= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
            }
        } else {
            sdDir = Environment.getRootDirectory();// 获取跟目录
        }
        if(!TextUtils.isEmpty(fileName)){
            if(!TextUtils.isEmpty(path))
                return sdDir.toString()+path+"/"+fileName;
            return sdDir.toString()+"/"+fileName;
        }
        if(!TextUtils.isEmpty(path))
            return sdDir.toString()+path;
        return sdDir.toString();
    }

录音文件页面播放,暂停和文件切换播放

重点是播放pcm文件

public class PcmAudioUtils {

    private AudioTrack audioTrack;

    private String filePath;
    private File file;
    private  int bufferSizeInBytes;
    private Thread audioTrackThread;
    private int currentPosition;
    private double lastTime;
    private double elapsedSeconds;//已播放时间
    private double audioDurationInSeconds;//总时长
    int sampleRateInHz = 8000; // 采样率
    int channelConfig = AudioFormat.CHANNEL_OUT_MONO; // 声道配置
    int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 音频格式
    boolean pauseTag=false;
    private IAudioProgress iAudioProgress;
    private String  playState="none";


    public PcmAudioUtils(String filePath) {
        file = new File(filePath);
        init();
    }

    /**
     * 初始化
     */
    public void init() {

        if (!file.exists()) return;

        int streamType = AudioManager.STREAM_MUSIC; // 音频流类型

         bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); // 缓冲区大小

        /**
         * 设置音频信息属性
         * 1.设置支持多媒体属性,比如audio,video
         * 2.设置音频格式,比如 music
         */
        AudioAttributes attributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build();
        /**
         * 设置音频格式
         * 1. 设置采样率
         * 2. 设置采样位数
         * 3. 设置声道
         */
        AudioFormat format = new AudioFormat.Builder()
                .setSampleRate(sampleRateInHz)
                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                .setChannelMask(channelConfig)
                .build();
        audioTrack = new AudioTrack(attributes,format,bufferSizeInBytes,AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE);

    }

    /**
     * 停止播放录音,并释放资源
     */
    public void stopPlay
上一篇:Kali Linux语言设置成中文


下一篇:如何在算家云搭建MVSEP-MDX23(音频分离)