Android(安卓)AudioRecord、AudioTrack录制播放音频
16lz
2021-12-04
AudioRecord 录制PCM
AudioRecord 是 Android 提供的用于实现录音功能,录制得到无损的PCM音频数据。
从AudioRecord构造函数就可以看出:
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)
- audioSource 音频源,如麦克风MIC
- sampleRateInHz 采样率,每秒采样次数,常用有8000、44100
- channelConfig 声道,有单声道MONO和立体声STEREO
- audioFormat 采样大小,8bit或16bit,采样大小越大,音质越好
- bufferSizeInBytes 采集数据的缓冲区,可以通过getMinBufferSize()获得最小的buffer size
(参考:https://www.jianshu.com/p/80a140cf3d99)
1.初始化
private final static int AudioSource = MediaRecorder.AudioSource.DEFAULT;private final static int AudioRate = 44100;private final static int AudioInChannel = AudioFormat.CHANNEL_IN_MONO;private final static int AudioOutChannel = AudioFormat.CHANNEL_OUT_MONO;private final static int AudioFormater = AudioFormat.ENCODING_PCM_16BIT;private AudioRecord mAudioRecord; private int recordBufferMinSize;private AudioTrack mAudioTrack;
2.开始录制
private void startRecord() { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, MICROPHONE, 2); return; } if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, STORAGE, 2); return; } isRecording = true; mExecutor.execute(new RecordRunnable()); // mExecutor是一个简单的线程池}
RecordRunnable实现:
- 调用 startRecording() 开始录制
- 读取 AudioRecord音频数据:int readSize = mAudioRecord.read(bufferbytes, 0, recordBufferMinSize);
- 往 FileOutputStream写数据:mOutputStream.write(bufferbytes, 0, readSize);
- 关闭 AudioRcord
private class RecordRunnable implements Runnable { private OutputStream mOutputStream; private byte[] bufferbytes; private File mFile; public RecordRunnable() { try { mFile = getRecordFile(true); bufferbytes = new byte[recordBufferMinSize]; mOutputStream = new FileOutputStream(mFile); } catch (FileNotFoundException e) { e.printStackTrace(); } } @Override public void run() { try { mAudioRecord.startRecording(); while(isRecording) { int readSize = mAudioRecord.read(bufferbytes, 0, recordBufferMinSize); if (readSize > 0) { mOutputStream.write(bufferbytes, 0, readSize); } } mOutputStream.close(); View contentView = RecordingActivity.this.getWindow().getDecorView().findViewById(android.R.id.content); contentView.post(new Runnable() { @Override public void run() { Toast.makeText(RecordingActivity.this, "录制完成: " + mFile.getAbsolutePath(),Toast.LENGTH_SHORT).show(); } }); } catch (IOException e) { e.printStackTrace(); } } };
3.停止录制
private void stopRecord() { if (isRecording) { isRecording = false; mAudioRecord.stop(); }}
AudioTrack 播放PCM
Android中可以使用AudioTrack播放PCM,播放的流程跟AudioRecord很类似:
AudioTrack 有两种数据加载模式:MODE_STREAM 和 MODE_STATIC,
MODE_STREAM 数据加载模式,将音频数据不断写入AudioTrack中,缺点是会有延迟
MODE_STATIC 音频流类型,将音频数据一次性写入AudioTrack中,不会有延迟,适合小文件,缺点是对大文件可能内存不足,需要 先write写数据,最后再调用 play()
1 初始化
playBufferMinSize = AudioTrack.getMinBufferSize(AudioRate, AudioOutChannel, AudioFormater);mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, AudioRate, AudioOutChannel, AudioFormater, playBufferMinSize, AudioTrack.MODE_STREAM);
####2 播放
private void playPcm() { isPlaying = true; mExecutor.execute(new PlayerRunnable(PlayerMode.PCM)); }
PlayerRunnable 是同样三个流程:
- 开始播放 mAudioTrack.play()
- 输出数据
int readSize = mInputStream.read(bufferbytes);
mAudioTrack.write(bufferbytes, 0, readSize); - 关闭 mAudioTrack
public enum PlayerMode { WAV, PCM};private class PlayerRunnable implements Runnable { private InputStream mInputStream; private File mFile; private byte[] bufferbytes; private RecordingActivity.PlayerMode playerMode; public PlayerRunnable(RecordingActivity.PlayerMode playerMode) { this.playerMode = playerMode; } @Override public void run() { View contentView = RecordingActivity.this.getWindow().getDecorView().findViewById(android.R.id.content); try { mFile = playerMode == RecordingActivity.PlayerMode.PCM ? getPlayerFile() : getWAVFile(); if (mFile == null || !mFile.exists()) { contentView.post(new Runnable() { @Override public void run() { Toast.makeText(RecordingActivity.this, "音频不存在", Toast.LENGTH_SHORT).show(); } }); return; } mAudioTrack.play(); bufferbytes = new byte[playBufferMinSize]; mInputStream = new FileInputStream(mFile); if (playerMode == WAV) { mInputStream.skip(44); // 去除WAV头部 } while (mInputStream.available() > 0) { int readSize = mInputStream.read(bufferbytes); mAudioTrack.write(bufferbytes, 0, readSize); } mInputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } mAudioTrack.stop(); isPlaying = false; }}
PCM转WAV
在PCM增加WAV的头部就可以了,因为pcm 是wav 文件中音频数据的一种编码方式,事实上 wav 还有很多其它的方式编码
public class Pcm2WavUtil { private int mSampleRate; private int minBufferSize; public Pcm2WavUtil(int mSampleRate, int mChannels, int mFormater) { this.mSampleRate = mSampleRate; //this.mChannels = mChannels; this.minBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannels, mFormater); } public void pcm2wav(File inFile, File outFile) { FileInputStream pcmFileStream; FileOutputStream wavFileStream; byte[] buffer = new byte[minBufferSize]; try { wavFileStream = new FileOutputStream(inFile); pcmFileStream = new FileInputStream(outFile); long pcmLen = pcmFileStream.getChannel().size(); byte[] head = wavHeader(pcmLen, 1, mSampleRate, 16); wavFileStream.write(head); while(pcmFileStream.available() > 0) { int readSize = pcmFileStream.read(buffer); wavFileStream.write(buffer); } pcmFileStream.close(); wavFileStream.close(); } catch (IOException e) { e.printStackTrace(); } } /** * @param pcmLen pcm 数据长度 * @param numChannels 声道设置, mono = 1, stereo = 2 * @param sampleRate 采样频率 * @param bitPerSample 单次数据长度, 例如 8bits * @return wav 头部信息 */ public static byte[] wavHeader(long pcmLen, int numChannels, int sampleRate, int bitPerSample) { byte[] header = new byte[44]; // ChunkID, RIFF, 占 4bytes header[0] = 'R'; header[1] = 'I'; header[2] = 'F'; header[3] = 'F'; // ChunkSize, pcmLen + 36, 占 4bytes long chunkSize = pcmLen + 36; header[4] = (byte) (chunkSize & 0xff); header[5] = (byte) ((chunkSize >> 8) & 0xff); header[6] = (byte) ((chunkSize >> 16) & 0xff); header[7] = (byte) ((chunkSize >> 24) & 0xff); // Format, WAVE, 占 4bytes header[8] = 'W'; header[9] = 'A'; header[10] = 'V'; header[11] = 'E'; // Subchunk1ID, 'fmt', 占 4bytes header[12] = 'f'; header[13] = 'm'; header[14] = 't'; header[15] = ' '; // Subchunk1Size, 16, 占 4bytes header[16] = 16; header[17] = 0; header[18] = 0; header[19] = 0; // AudioFormat, pcm = 1, 占 2bytes header[20] = 1; header[21] = 0; // NumChannels, mono = 1, stereo = 2, 占 2bytes header[22] = (byte) numChannels; header[23] = 0; // SampleRate, 占 4bytes header[24] = (byte) (sampleRate & 0xff); header[25] = (byte) ((sampleRate >> 8) & 0xff); header[26] = (byte) ((sampleRate >> 16) & 0xff); header[27] = (byte) ((sampleRate >> 24) & 0xff); // ByteRate = SampleRate * NumChannels * BitsPerSample / 8, 占 4bytes long byteRate = sampleRate * numChannels * bitPerSample / 8; header[28] = (byte) (byteRate & 0xff); header[29] = (byte) ((byteRate >> 8) & 0xff); header[30] = (byte) ((byteRate >> 16) & 0xff); header[31] = (byte) ((byteRate >> 24) & 0xff); // BlockAlign = NumChannels * BitsPerSample / 8, 占 2bytes header[32] = (byte) (numChannels * bitPerSample / 8); header[33] = 0; // BitsPerSample, 占 2bytes header[34] = (byte) bitPerSample; header[35] = 0; // Subhunk2ID, data, 占 4bytes header[36] = 'd'; header[37] = 'a'; header[38] = 't'; header[39] = 'a'; // Subchunk2Size, 占 4bytes header[40] = (byte) (pcmLen & 0xff); header[41] = (byte) ((pcmLen >> 8) & 0xff); header[42] = (byte) ((pcmLen >> 16) & 0xff); header[43] = (byte) ((pcmLen >> 24) & 0xff); return header; }}
AudioTrack播放PCM编码的WAV
与播放pcm的代码一致,只需要过滤掉wav的头部 mInputStream.skip(44)
更多相关文章
- mybatisplus的坑 insert标签insert into select无参数问题的解决
- python起点网月票榜字体反爬案例
- android 打开移动数据流程
- 基于Android(安卓)6.0修改的音乐播放器可设置卡1卡2铃声
- Android:giraffeplayer2 ConnectException:Failed to connect to
- Android通过AudioFocus机制对音频焦点进行管理
- 如何在Android设备中用NDK编译SQLite并且对SQLite进行操作(增删)-H
- Android查询短信数据库 查询联系人数据库
- Android录音,和实现微信长按录音效果!