任务:2. 在 Android 平台使用 AudioRecord 和 AudioTrack API 完成音频 PCM 数据的采集和播放,并实现读写音频 wav 文件
记得加权限:
1 2 3
| <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
AudioRecord使用流程:
确定好采样率,通道类型,编码方式,录制来源
由AudioRecord.getMinbufferSize获得能接受的最小buffer大小
new一个AudioRecord对象 audioRecord ,第一个参数表示的是音频录制来源。注意的是record和track都有通道(channel)参数,但一个是in一个是out,不要搞反了
开始录制:audioRecord.startRecord() 同时要开启一个线程,该线程所做的工作是打开一个output流创建一个文件。不断用audioRecord.read把录制的数据写到一个比特数组,然后把比特数组写到output流中
暂停录制:audioRecord.stop,但不release,而且output流也停止写入,但不释放
恢复录制,audioRecord.startRecord() ,继续读取audioRecord的数据并写入output流,
停止录制:audioRecord.stop,audioRecord.release释放资源,output流flush一下然后关闭
这样录制的音频是pcm格式的,也就是没有文件头的原始文件,要转成wav文件,只需要保持数据部分不变,然后再文件开始加入wav的文件头
AudioTrack使用流程:
确定好采样率,通道类型,编码方式
由AudioTrack.getMinbufferSize获得能接受的最小buffer大小
new一个AudioTrack对象 audioTrack ,第一个参数表示的是音频播放的类型,可选的有演讲、音乐、影视等(可能是针对每种不同的场景类型做一些优化) 。最后一个参数mode是表示创建的模式
MODE_STATIC 其中音频数据从Java传输到本地层仅一次,然后音频开始播放。
MODE_STREAM 当音频播放时,音频数据从Java流到本地层。(两个先后顺序不同)
开始播放:audioTrack.play() 同时要开启一个线程,该线程所做的工作是打开一个input流读取音频文件(audioTrack既可以读原始的pcm文件,也可以读加入文件头的wav文件), 不断把文件到一个比特数组,然后用audioTrack.write把比特数组写入.
暂停播放:audioTrack.stop,但不release,而且input流也停止读入,但不释放(文件指针还停留在此)
恢复播放,audioTrack.play,让input流在停止的地方继续入读,
停止播放:audioTrack.stop,audioTrack.release释放资源,关闭input流(这样input流保留的文件指针也就丢失了,下一次就会从头读)
AudioRecord的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| import android.app.Activity; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.Environment; import android.util.Log; import android.view.View; import android.widget.Button;
import com.example.lll.va.R;
import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException;
public class AudioRecorder {
private String tag = "AudioRecorder"; private final static int AUDIO_INPUT = MediaRecorder.AudioSource.MIC; private final static int AUDIO_SAMPLE_RATE = 16000; private final static int AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_MONO; private final static int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
private byte data[]; private boolean isRecording = false;
private AudioRecord audioRecord = null; private int recordBufSize = 0; private Button btnStart; private Button btnStop;
public static void startTask2byAudioRecord(Activity activity){ final AudioRecorder audioRecorder = new AudioRecorder(); audioRecorder.createAudioRecord(); audioRecorder.btnStart = activity.findViewById(R.id.btn_start_record_audio); audioRecorder.btnStop = activity.findViewById(R.id.btn_stop_record_audio); audioRecorder.btnStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { audioRecorder.startRecord(); } }); audioRecorder.btnStop.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { audioRecorder.stopRecord(); } }); }
public void createAudioRecord() { recordBufSize = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING); audioRecord = new AudioRecord(AUDIO_INPUT, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING, recordBufSize); data = new byte[recordBufSize]; }
public void startRecord() { audioRecord.startRecording(); isRecording = true; thread_w.start(); }
private String filename = Environment.getExternalStorageDirectory() + "/test"; Thread thread_w = new Thread(new Runnable() { @Override public void run() {
FileOutputStream os = null;
try { os = new FileOutputStream(filename); } catch (FileNotFoundException e) { e.printStackTrace(); }
if (null != os) { Log.d(tag, "isRecording = " + isRecording); while (isRecording) { int read = audioRecord.read(data, 0, recordBufSize); Log.d(tag, "read size = " + read); if (AudioRecord.ERROR_INVALID_OPERATION != read) { try { os.write(data); Log.d(tag, "os writr = " + data); } catch (IOException e) { e.printStackTrace(); } } }
try { os.flush(); os.close(); } catch (IOException e) { e.printStackTrace(); } } } });
public void stopRecord() { isRecording = false; audioRecord.stop(); audioRecord.release();
PcmToWavUtil util = new PcmToWavUtil(AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING); util.pcmToWav(filename, filename + ".wav"); } }
|
给生成的pcm文件加入wav文件头的工具类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
| import android.media.AudioFormat; import android.media.AudioRecord;
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException;
public class PcmToWavUtil {
private int mBufferSize;
private int mSampleRate;
private int mChannel;
PcmToWavUtil(int sampleRate, int channel, int encoding) { this.mSampleRate = sampleRate; this.mChannel = channel; this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding); }
public void pcmToWav(String inFilename, String outFilename) { FileInputStream in; FileOutputStream out; long totalAudioLen; long totalDataLen; long longSampleRate = mSampleRate; int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2; long byteRate = 16 * mSampleRate * channels / 8; byte[] data = new byte[mBufferSize]; 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); } in.close(); out.close(); } catch (IOException e) { e.printStackTrace(); } }
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'; 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'; header[13] = 'm'; header[14] = 't'; header[15] = ' '; header[16] = 16; header[17] = 0; header[18] = 0; header[19] = 0; header[20] = 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); header[33] = 0; header[34] = 16; 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); } }
|
AudioTrack的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
| import android.app.Activity; import android.content.Context; import android.media.AudioAttributes; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.AudioTrack; import android.media.MediaRecorder; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.widget.Button;
import com.example.lll.va.R;
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException;
public class AudioTracker { private String tag = "AudioTracker"; private final static int AUDIO_SAMPLE_RATE = 16000; private final static int AUDIO_CHANNEL = AudioFormat.CHANNEL_OUT_MONO; private final static int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT; private AudioTrack audioTrack; private Activity activity; private int buffersize; private byte[] data; private boolean isPlay = false; private boolean isFirstPlay = true;
public static void startTask2byAudioTrack(Activity activity) { final AudioTracker audioTracker = new AudioTracker(); audioTracker.activity = activity; Button btnPlay = activity.findViewById(R.id.btn_play_audio); Button btnStop = activity.findViewById(R.id.btn_stop_audio); Button btnPause = activity.findViewById(R.id.btn_pause_audio); btnPlay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { audioTracker.startPlay(); } }); btnStop.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { audioTracker.stopPlay(); } }); btnPause.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { audioTracker.pause(); } });
}
public void initAudioTrack() { if (isFirstPlay) { buffersize = AudioTrack.getMinBufferSize(AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING); audioTrack = new AudioTrack(AudioAttributes.CONTENT_TYPE_MUSIC, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL , AudioFormat.ENCODING_PCM_16BIT, buffersize, AudioTrack.MODE_STREAM); data = new byte[buffersize]; }
}
public void startPlay() { initAudioTrack(); audioTrack.play(); isPlay = true; playThread.start();
}
public void pause() { audioTrack.stop(); isPlay = false; Log.d(tag, "pasue "); }
public void stopPlay() { audioTrack.stop(); isPlay = false; isFirstPlay = true; audioTrack.release(); Log.d(tag, "stop "); is = null; }
FileInputStream is = null; String fileName = Environment.getExternalStorageDirectory() + "/test.wav"; Thread playThread = new Thread(new Runnable() { @Override public void run() { File file = new File(fileName);
try { if (isFirstPlay) { is = new FileInputStream(file); isFirstPlay = false; } Log.d(tag, "is = " + is); } catch (FileNotFoundException e) { e.printStackTrace(); } writeData(); } });
private void writeData() { if (is == null) return; while (isPlay) { try { int read = is.read(data); Log.d(tag, "read = " + read); if (read != 0 && read != -1) { audioTrack.write(data, 0, read); } else { stopPlay(); } } catch (IOException e) { e.printStackTrace(); } } } }
|
布局就是很简单的layout和几个分别控制开始、暂停、结束的button,就不贴了