Android音视频开发-AudioRecord
本篇文章主要讲下AudioRecord.
1: 简介
AudioRecord是Android平台上的一个类,用于实时录制音频数据。它提供了一种方便的方式来捕获和处理音频流。
以下是关于AudioRecord的一些介绍:
- 音频源:Record可以从多种音频源中录制音频数据例如麦克风、电话线路、语音识别等。
- 音频格式:可以选择不同的音频格式来录制音频数据,如PCM(脉冲编码调制)、AAC(级音频编码)等。
- 缓冲区AudioRecord使用一个缓冲区来存储录制的音频数据。开发者可以指定缓冲区的大小,以适应不同的录制需求。
- 录制状态:通过调用start()方法开始录制音频数据,并可以通过stop()方法停止录制。还可以使用getState()方法获取当前的录制状态。
- 回调函数:可以注册一个回调函数,当有新的音频数据可用时,系统会自动调用回调函数进行处理。
- 音频参数:可以设置采样率、声道数和位深度等参数,以满足不同的录制需求。
- 权限要求:需要在AndroidManifest.xml文件中添加相应的权限声明android.permission.RECORD_AUDIO权限。
2: AudioRecord对象
首先看下AudioRecord的构造函数:
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes)
throws IllegalArgumentException {
this((new AudioAttributes.Builder())
.setInternalCapturePreset(audioSource)
.build(),
(new AudioFormat.Builder())
.setChannelMask(getChannelMaskFromLegacyConfig(channelConfig,
true/*allow legacy configurations*/))
.setEncoding(audioFormat)
.setSampleRate(sampleRateInHz)
.build(),
bufferSizeInBytes,
AudioManager.AUDIO_SESSION_ID_GENERATE);
}
参数如下:
- audioSource:录制源,具体的参数为MediaRecorder.AudioSource.
- sampleRateInHz:采样率,单位赫兹.44100Hz是目前唯一保证在所有设备上工作的速率.
- channelConfig:音频通道的配置.
- audioFormat:音频数据的格式
- bufferSizeInBytes:缓冲区大小.
创建AudioRecord对象:
int audioSource = MediaRecorder.AudioSource.MIC; // 设置音频源为麦克风
int sampleRateInHz = 44100; // 设置采样率为44100Hz
int channelConfig = AudioFormat.CHANNEL_IN_MONO; // 设置通道配置为单声道
int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 设置音频格式为16位PCM
int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); // 获取缓冲区大小
audioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);
3: 开启录制
开启录制很简单,调用audioRecord的startRecording()方法即可.
audioRecord.startRecording();
另外为了写入文件,我们这里设置个录制状态:
isRecording = true;
创建缓冲区,存储录音文件.
byte[] buffer = new byte[bufferSizeInBytes];
循环读取,写入文件:
new Thread(() -> writeAudioPcm()).start();
writeAudioPcm()详细代码如下:
private void writeAudioPcm() {
byte[] bytes = new byte[bufferSizeInBytes];
FileOutputStream fos = null;
try {
File file = new File("sdcard/audioRecord.pcm");
if (!file.exists()) {
file.createNewFile();
}
fos = new FileOutputStream(file);
while (isRecording) {
int read = audioRecord.read(bytes, 0, bytes.length);
if (read > 0) {
fos.write(read);
}
}
} catch (Throwable e) {
Log.e(TAG, "writeAudioPcm: ", e);
} finally {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4: 结束录制
按需停止录制音频并释放资源.
private void stopAudio() {
isRecording = false;
if (audioRecord != null) {
audioRecord.stop();
audioRecord.release();
}
pcmToWav();
}
5: pcm转Wav.
最后就是将pcm转为WAV格式.
首先我们先根据音频的采样率,声道等参数,获取wav的header信息.
/**
* 头部信息共44字节
* @param sampleRate
* @param channels
* @param bitDepth
* @param dataSize
* @return
* @throws IOException
*/
public byte[] getWavHeader(int sampleRate, int channels, int bitDepth, long dataSize) {
byte[] header = new byte[44];
// ChunkID,RIFF标识
header[0] = 'R';
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
// ChunkSize,文件长度
long totalSize = dataSize + 36;
header[4] = (byte) (totalSize & 0xff);
header[5] = (byte) ((totalSize >> 8) & 0xff);
header[6] = (byte) ((totalSize >> 16) & 0xff);
header[7] = (byte) ((totalSize >> 24) & 0xff);
// Format,WAVE标识
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
// Subchunk1ID,fmt标识
header[12] = 'f';
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
// Subchunk1Size,格式信息长度
header[16] = 16;
header[17] = 0;
header[18] = 0;
header[19] = 0;
// AudioFormat,音频格式(PCM为1)
header[20] = 1;
header[21] = 0;
// NumChannels,声道数
header[22] = (byte) channels;
header[23] = 0;
// SampleRate,采样率
header[24] = (byte) (sampleRate & 0xff);
header[25] = (byte) ((sampleRate >> 8) & 0xff);
header[26] = (byte) ((sampleRate >> 16) & 0xff);
header[27] = (byte) ((sampleRate >> 24) & 0xff);
// ByteRate,比特率
int byteRate = sampleRate * channels * bitDepth / 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,块对齐
int blockAlign = channels * bitDepth / 8;
header[32] = (byte) blockAlign;
header[33] = 0;
// BitsPerSample,采样位深度
header[34] = (byte) bitDepth;
header[35] = 0;
// Subchunk2ID,data标识
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
// Subchunk2Size,音频数据长度 dataHdrLength
header[40] = (byte) (dataSize & 0xff);
header[41] = (byte) ((dataSize >> 8) & 0xff);
header[42] = (byte) ((dataSize >> 16) & 0xff);
header[43] = (byte) ((dataSize >> 24) & 0xff);
return header;
}
转换方法如下:
private void pcmToWav() {
File pcmFile = new File("sdcard/audioRecord.pcm");
File wavFile = new File("sdcard/audioRecord.wav");
// 创建WAV文件头
short i = (short) (channelConfig == AudioFormat.CHANNEL_IN_MONO ? 1 : 2);
byte[] header = getWavHeader(sampleRateInHz,i,16,pcmFile.length());
// 写入WAV文件头
FileOutputStream wavOutputStream = null;
try {
wavOutputStream = new FileOutputStream(wavFile);
wavOutputStream.write(header);
// 写入PCM数据
FileInputStream pcmInputStream = new FileInputStream(pcmFile);
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = pcmInputStream.read(buffer)) != -1) {
wavOutputStream.write(buffer, 0, bytesRead);
}
// 关闭文件流
pcmInputStream.close();
wavOutputStream.close();
} catch (Throwable e) {
Log.e(TAG, "pcmToWav: ", e);
}
}
6:机型适配.
在华为mate50上测试时,授予权限开启录音后崩溃:
2024-04-09 12:00:09.763 1438-1438/? E/ServiceUtilities: Request requires android.permission.MODIFY_AUDIO_SETTINGS
2024-04-09 12:00:09.767 1438-1438/? E/AudioPolicyIntefaceImpl: getInputForAttr permission denied: recording not allowed for AttributionSourceState{pid: 11582, uid: 10157, packageName: com.test.media, attributionTag: (null), token: , renouncedPermissions: [], next: [], uidPidOrigin: -1}
2024-04-09 12:00:09.767 1438-1438/? E/AudioFlinger: createRecord() getInputForAttr return error -1
2024-04-09 12:00:09.767 11582-11582/com.test.media E/AudioRecord: createRecord_l(-1): AudioFlinger could not create record track, status: -1
2024-04-09 12:00:09.767 11582-11582/com.test.media E/AudioRecord-JNI: Error creating AudioRecord instance: initialization check failed with status -1.
2024-04-09 12:00:09.768 11582-11582/com.test.media E/android.media.AudioRecord: Error code -20 when initializing native AudioRecord object.
2024-04-09 12:00:09.770 11582-11582/com.test.media E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.test.media, PID: 11582
java.lang.IllegalStateException: startRecording() called on an uninitialized AudioRecord.
at android.media.AudioRecord.startRecording(AudioRecord.java:1326)
at com.test.media.AudioRecordActivity.startAudio(AudioRecordActivity.java:98)
at com.test.media.AudioRecordActivity.onClick(AudioRecordActivity.java:41)
at android.view.View.performClick(View.java:7682)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:967)
at android.view.View.performClickInternal(View.java:7651)
at android.view.View.access$3700(View.java:886)
at android.view.View$PerformClick.run(View.java:30173)
at android.os.Handler.handleCallback(Handler.java:966)
at android.os.Handler.dispatchMessage(Handler.java:110)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:293)
at android.app.ActivityThread.loopProcess(ActivityThread.java:9934)
at android.app.ActivityThread.main(ActivityThread.java:9923)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:586)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1240)
-
根据日志输出,授予:android.permission.MODIFY_AUDIO_SETTINGS权限.增加权限无效,经过测试无效.
-
Error code -20 when initializing native AudioRecord object.
错误码-20:AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED.
怀疑是配置有问题,尝试切换声道,采样率等,仍然无效.
-
AudioPolicyIntefaceImpl: getInputForAttr permission denied: recording not allowed for AttributionSourceState{pid: 11582, uid: 10157, packageName: com.test.media, attributionTag: (null), token: , renouncedPermissions: [], next: [], uidPidOrigin: -1}
最后发现还是权限问题,由于我未动态申请权限,是通过应用程序直接授予的麦克风权限.
添加动态申请权限代码:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 1101); }
问题解决.