Android音视频开发-AudioRecord

Android音视频开发-AudioRecord

本篇文章主要讲下AudioRecord.

1: 简介

AudioRecord是Android平台上的一个类,用于实时录制音频数据。它提供了一种方便的方式来捕获和处理音频流。

以下是关于AudioRecord的一些介绍:

  1. 音频源:Record可以从多种音频源中录制音频数据例如麦克风、电话线路、语音识别等。
  2. 音频格式:可以选择不同的音频格式来录制音频数据,如PCM(脉冲编码调制)、AAC(级音频编码)等。
  3. 缓冲区AudioRecord使用一个缓冲区来存储录制的音频数据。开发者可以指定缓冲区的大小,以适应不同的录制需求。
  4. 录制状态:通过调用start()方法开始录制音频数据,并可以通过stop()方法停止录制。还可以使用getState()方法获取当前的录制状态。
  5. 回调函数:可以注册一个回调函数,当有新的音频数据可用时,系统会自动调用回调函数进行处理。
  6. 音频参数:可以设置采样率、声道数和位深度等参数,以满足不同的录制需求。
  7. 权限要求:需要在AndroidManifest.xml文件中添加相应的权限声明android.permission.RECORD_AUDIO权限。

2: AudioRecord对象

首先看下AudioRecord的构造函数:

java 复制代码
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);
}

参数如下:

  1. audioSource:录制源,具体的参数为MediaRecorder.AudioSource.
  2. sampleRateInHz:采样率,单位赫兹.44100Hz是目前唯一保证在所有设备上工作的速率.
  3. channelConfig:音频通道的配置.
  4. audioFormat:音频数据的格式
  5. bufferSizeInBytes:缓冲区大小.

创建AudioRecord对象:

java 复制代码
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()方法即可.

java 复制代码
audioRecord.startRecording();

另外为了写入文件,我们这里设置个录制状态:

java 复制代码
isRecording = true;

创建缓冲区,存储录音文件.

java 复制代码
byte[] buffer = new byte[bufferSizeInBytes];

循环读取,写入文件:

java 复制代码
new Thread(() -> writeAudioPcm()).start();

writeAudioPcm()详细代码如下:

java 复制代码
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: 结束录制

按需停止录制音频并释放资源.

java 复制代码
private void stopAudio() {
    isRecording = false;
    if (audioRecord != null) {
        audioRecord.stop();
        audioRecord.release();
    }
    pcmToWav();
}

5: pcm转Wav.

最后就是将pcm转为WAV格式.

首先我们先根据音频的采样率,声道等参数,获取wav的header信息.

java 复制代码
/**
 * 头部信息共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;
    }

转换方法如下:

java 复制代码
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上测试时,授予权限开启录音后崩溃:

shell 复制代码
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)
  1. 根据日志输出,授予:android.permission.MODIFY_AUDIO_SETTINGS权限.增加权限无效,经过测试无效.

  2. Error code -20 when initializing native AudioRecord object.

    错误码-20:AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED.

    怀疑是配置有问题,尝试切换声道,采样率等,仍然无效.

  3. AudioPolicyIntefaceImpl: getInputForAttr permission denied: recording not allowed for AttributionSourceState{pid: 11582, uid: 10157, packageName: com.test.media, attributionTag: (null), token: , renouncedPermissions: [], next: [], uidPidOrigin: -1}

    最后发现还是权限问题,由于我未动态申请权限,是通过应用程序直接授予的麦克风权限.

    添加动态申请权限代码:

    java 复制代码
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 1101);
    }

    问题解决.

相关推荐
黄林晴6 小时前
如何判断手机是否是纯血鸿蒙系统
android
火柴就是我6 小时前
flutter 之真手势冲突处理
android·flutter
法的空间6 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
循环不息优化不止6 小时前
深入解析安卓 Handle 机制
android
恋猫de小郭7 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
jctech7 小时前
这才是2025年的插件化!ComboLite 2.0:为Compose开发者带来极致“爽”感
android·开源
用户2018792831677 小时前
为何Handler的postDelayed不适合精准定时任务?
android
kaixin_啊啊7 小时前
突破限制:Melody远程音频管理新体验
音视频
叽哥7 小时前
Kotlin学习第 8 课:Kotlin 进阶特性:简化代码与提升效率
android·java·kotlin
Cui晨7 小时前
Android RecyclerView展示List<View> Adapter的数据源使用View
android