简单实现Android AudioReccord录音方式
前言
总听前辈们说,写博客是利人利己的好事,这总好事,必然要试试。但是头一次写博客,真不知道如何下手,就当练手了。
因为最近项目当中用到了录音的功能,所以就Android 录音来试试。
Android 小白 ,大神请多指教。
参考链接
https://blog.csdn.net/hesong1120/article/details/79043482
https://www.cnblogs.com/MMLoveMeMM/articles/3444718.html
https://www.jianshu.com/p/90c77197f1d4
https://www.jianshu.com/p/a72deab95b4c
简介
Android提供了两个API用于实现录音功能
一个是AudioRecord 另一个是MedioRecorder
一 AudioRecord
AudioRecord 输出的是PCM语音数据,是无法直接播放的,必须进行编码和压缩才能够播放。可以将PCM语音数据前拼接头文件,转为wav格式进行播放。
二 MedioRecorder
其实是对audioRecord的封装,集成了录音,编码,压缩,获取到的语音数据转换为文件可以直接进行播放
三 WAV和PCM的联系
Android手机要进行音频编辑操作(比如裁剪,插入,合成等),通常都是需要将音频文件解码为WAV格式的音频文件或者PCM文件来进行拼接处理。
PCM(Pulse Code Modulation—-脉码调制录音),PCM录音就是将声音等模拟信号变成数字信号,学过模拟电路和数字电路的人应该比较懂这个,其实就是一个没有压缩的编码方式。
WAV格式是微软公司开发的一种声音文件格式,也叫波形声音文件,是最早的数字音频格式,支持许多压缩算法,支持多种音频位数、采样频率和声道。
AudioRecord实现
audioRecord方式录取音频,获取的时PCM编码的音频流,需要我们在线程中循环的获取音频数据
我们首先定义一部分参数 因为我们项目中使用了科大讯飞的语音识别,所以按照要求我们使用16000的采样率,和单声道
下面上代码
/** * 音频采样率 */public static int SAMPLE_RATE = 16000;/** * 单声道 */public final static int CHANNEL = AudioFormat.CHANNEL_IN_MONO;/** * 16比特 */public final static int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;/** * 音频录制实例 */protected AudioRecord audioRecord;/** * 录制线程 */private Thread recordThread;/** * 输出的文件路径 */private String pcmPath;private String wavPath;/** * 缓冲区大小 */private int bufferSize = 0;/** * 是否正在录制 */private boolean isRecording = false;
/** * 构造方法传递路径 * * @param context */public AudioRecordRecorder(Context context) { pcmPath = AudioRecordFilePath.getRawFilePath(context); wavPath = AudioRecordFilePath.getWavFilePath(context);}/** * 初始化操作 **/public void initRecorder() { if (null != audioRecord) { audioRecord.release(); } try { // 获得缓冲区字节大小 bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL, AUDIO_FORMAT) * 10; //实例化录制实例 audioRecord = new AudioRecord(AUDIO_SOURCE, SAMPLE_RATE, CHANNEL, AUDIO_FORMAT, bufferSize); } catch (Exception e) { e.printStackTrace(); }}
我这里将缓冲区设置的比较大,是为了防止最终读取的数据有失真的情况
/** * 开始录制 **/public int recordStart() { if (isRecording) { return RECORD_STATE.STATE_RECORDING; } else if (audioRecord != null && audioRecord.getState() == AudioRecord.STATE_INITIALIZED) { try { recordThread = new Thread(new AudioRecordRunnable()); recordThread.start(); return RECORD_STATE.STATE_SUCCESS; } catch (Exception e) { e.printStackTrace(); } } return RECORD_STATE.STATE_ERROR;}开始录制,我开启了一个线程 目的时为了循环读取数据
/** * 读取线程 */private class AudioRecordRunnable implements Runnable { @Override public void run() { audioRecord.startRecording(); isRecording = true; writeDateToFile(); copyWaveFile(pcmPath, wavPath); }}
/** * 将录取的音频写入文件 此时的音频并不能播放 */private void writeDateToFile() { FileOutputStream outputStream = null; BufferedOutputStream bufferedOutputStream = null; DataOutputStream dataOutputStream = null; try { if (!TextUtils.isEmpty(pcmPath)) { outputStream = new FileOutputStream(pcmPath); bufferedOutputStream = new BufferedOutputStream(outputStream); dataOutputStream = new DataOutputStream(bufferedOutputStream); } // 此处其实可以使用byte数组来实现 ,这样也能避免为大小端的处理,而我时为了获取声音分贝值回调,偷个懒,使用了short数组,这个看个人 short[] audioBuffer = new short[bufferSize]; while (isRecording && audioRecord != null) { audioSize = audioRecord.read(audioBuffer, 0, bufferSize); if (audioSize > 0) { //写入文件 if (outputStream != null) { for (int i = 0; i < audioSize; i++) { //此处一定要要进行一下大小端处理 不然音频会失真 dataOutputStream.writeShort(Short.reverseBytes(audioBuffer[i])); } } //音量大小回调 getMicState(audioBuffer); } } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { close(outputStream); outputStream = null; }}
/** * 这里得到可播放的音频文件 * * @param inFilename * @param outFilename */private void copyWaveFile(String inFilename, String outFilename) { FileInputStream in; FileOutputStream out; long totalAudioLen; long totalDataLen; long longSampleRate = SAMPLE_RATE; int channels = 1; long byteRate = 16 * SAMPLE_RATE * channels / 8; byte[] data = new byte[bufferSize]; try { in = new FileInputStream(inFilename); out = new FileOutputStream(outFilename); totalAudioLen = in.getChannel().size(); totalDataLen = totalAudioLen + 36; WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate); while (in.read(data) != -1) { out.write(data); out.flush(); } in.close(); out.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { uiPlayer.audioRecordFinish(); AudioRecordFilePath.deleteFile(pcmPath); long duration = getWavLength(wavPath); uiPlayer.getWavFilePath(wavPath); uiPlayer.getWavFileDuration(duration); }}
/** * 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。 * 这个头文件我时上网上搜的一个,如何大家使用不好使,可以自行百度 * @param out * @param totalAudioLen 音频文件长度 * @param totalDataLen 加入头文件以后数据长度 * @param longSampleRate 采样率 * @param channels 通道数 * @param byteRate 位率 * @throws IOException */private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate, int channels, long byteRate) throws IOException { byte[] header = new byte[44]; header[0] = 'R'; // RIFF/WAVE header header[1] = 'I'; header[2] = 'F'; header[3] = 'F'; header[4] = (byte) (totalDataLen & 0xff); header[5] = (byte) ((totalDataLen >> 8) & 0xff); header[6] = (byte) ((totalDataLen >> 16) & 0xff); header[7] = (byte) ((totalDataLen >> 24) & 0xff); header[8] = 'W'; header[9] = 'A'; header[10] = 'V'; header[11] = 'E'; header[12] = 'f'; // 'fmt ' chunk header[13] = 'm'; header[14] = 't'; header[15] = ' '; header[16] = 16; // 4 bytes: size of 'fmt ' chunk header[17] = 0; header[18] = 0; header[19] = 0; header[20] = 1; // format = 1 header[21] = 0; header[22] = (byte) channels; header[23] = 0; header[24] = (byte) (longSampleRate & 0xff); header[25] = (byte) ((longSampleRate >> 8) & 0xff); header[26] = (byte) ((longSampleRate >> 16) & 0xff); header[27] = (byte) ((longSampleRate >> 24) & 0xff); header[28] = (byte) (byteRate & 0xff); header[29] = (byte) ((byteRate >> 8) & 0xff); header[30] = (byte) ((byteRate >> 16) & 0xff); header[31] = (byte) ((byteRate >> 24) & 0xff); header[32] = (byte) (2 * 16 / 8); // block align header[33] = 0; header[34] = 16; // bits per sample header[35] = 0; header[36] = 'd'; header[37] = 'a'; header[38] = 't'; header[39] = 'a'; header[40] = (byte) (totalAudioLen & 0xff); header[41] = (byte) ((totalAudioLen >> 8) & 0xff); header[42] = (byte) ((totalAudioLen >> 16) & 0xff); header[43] = (byte) ((totalAudioLen >> 24) & 0xff); out.write(header, 0, 44);}
@Overridepublic void recordStop() { try { if (audioRecord != null) { isRecording = false; try { if (recordThread != null) { recordThread.join(); recordThread = null; } } catch (InterruptedException e) { e.printStackTrace(); } //释放资源 recordRelease(); } } catch (Exception e) { e.printStackTrace(); uiPlayer.audioRecordFail(); }}/** * 释放资源 */private void recordRelease() { if (audioRecord != null) { if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) { audioRecord.stop(); } audioRecord.release(); audioRecord = null; }}
/** * 声音长度 */private int audioSize;/** * 获取音量分贝值 */private void getMicState(short[] audioVolume) { if (audioSize > 0) { long v = 0; for (int i = 0; i < audioVolume.length; i++) { v += audioVolume[i] * audioVolume[i]; } // 平方和除以数据总长度,得到音量大小。 double mean = v / (double) audioSize; double volume = 10 * Math.log10(mean);}
完结
就先写到这里啦,真的是第一次,如果错误,请大家多多指正。也希望能给刚刚使用的道友一些帮助。
这里面参考了很多前辈的文章,就不一一列举了。很感谢前辈们替我们负重前行。
更多相关文章
- android通过php连接mysql数据库!!!!
- Android接收jsp中动态生成的xml或json数据。
- 在Android中把SQLite的数据库文件存储在SD卡中【转】
- android数据库操作(二)
- Android的NDK开发(3)————JNI数据类型的详解
- Android如何导入已有的外部数据库
- SQlite Android 数据库应用程序系统
- android 通知 手机 媒体 数据库 更新
- 获得android下面,所有的数据库