需求描述
最近在做蓝牙与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