AudioRecord

目录

简介

AudioRecord

AudioRecord对象

开启录制

结束录制

pcm转Wav.

机型适配.

AudioRecord 的使用

构造

开始录音

读取数据

停止录音

Android 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权限。

  1. AudioRecord

  1. 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.MIC指的是麦克风,一般都传递这个,其他值,例如MediaRecorder.AudioSource.VOICE_UPLINK第三方应用不可用。

  • sampleRateInHz:采样率,单位赫兹.44100Hz是目前唯一保证在所有设备上工作的速率.采样率。每秒钟的采样次数,以赫兹表示。采样率越高,音质越高。44100Hz是目前唯一的保证在所有设备上工作的速率,16000和11025可以在部分设备上工作。但不限于这几个参数。例如要采集低质量的音频就可以使用4000、8000等低采样率。

  • channelConfig:音频通道的配置.单声道、双声道等。声道,AudioFormat.CHANNEL_IN_MONO,AudioFormat.CHANNEL_IN_STEREO CHANNEL_IN_MONO 即单声道,CHANNEL_IN_STEREO.立体声也叫双声道(单词stereo:有立体感的),双声道可以有保证的作于所有手机上。

  • audioSampleRate音频采样率

  • audioFormat:音频数据的格式,音频采样精度,指定采样的数据的格式和每次采样的大小,只支持8位和16位。音频采样格式,指定采样的数据的格式和每次采样的大小。AudioFormat.ENCODING_PCM_16BIT,AudioFormat.ENCODING_PCM_8BIT。采集来的数据当然使用PCM编码(脉冲代码调制编码,即PCM编码。PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。) android支持的采样大小16bit 或者8bit。当然采样大小越大,那么信息量越多,音质也越高,现在主流的采样大小都是16bit,在低质量的语音传输的时候8bit足够了。

  • bufferSizeInBytes:缓冲区大小.音频数据写入缓冲区的总数,可以通过AudioRecord.getMinBufferSize获取最小的缓冲区。获取最小的缓冲区大小,用于存放AudioRecord采集到的音频数据。

初始化一个buffer,该buffer大于等于AudioRecord对象用于写声音数据的buffer大小byte data[] = new byte[recordBufSize];

  • AudioRecord对象

public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)

  • 录制相关操作

开始录制startRecording()

停止录制stop()

释放资源release()

read()的使用

建一个数据流,一边从AudioRecord中读取声音数据到初始化的buffer,一边将buffer中的数据导入数据流

  • 关闭数据流

  • 停止录制

创建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);
  1. 开启录制

开启录制很简单,调用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();
        }
    }
}
  1. 结束录制

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

private void stopAudio() {
    isRecording = false;
    if (audioRecord != null) {
        audioRecord.stop();
        audioRecord.release();
    }
    pcmToWav();
}
  1. 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);
    }

}
  1. 机型适配.

例子:在华为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)
  1. 根据日志输出,授予:android.permission.MODIFY_AUDIO_SETTINGS权限.增加权限无效,经过测试无效.

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

  3. 错误码-20:AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED.怀疑是配置有问题,尝试切换声道,采样率等,仍然无效.

  4. 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);
}

问题解决.

以上录制的代码存在问题,后续编写播放时发现. 问题代码:

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);
            }
        }

修改后的写入代码如下:


  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(bytes,0,read);
                }
            }
        } catch (Throwable e) {
            Log.e(TAG, "writeAudioPcm: ", e);
        } finally {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
  1. AudioRecord 的使用

  1. 构造

构造 AudioRecord 需要几个参数:

  • int audioSource 音频源,通常使用麦克风

    MediaRecorder.AudioSource.MIC
  • int sampleRateInHz 采样率,一秒钟对数据的采样次数,采样率越高,音质越好

    16_000 或者 44_100 等
  • int channelConfig 音频通道,单声道或者双声道

    AudioFormat.CHANNEL_IN_MONO AudioFormat.CHANNEL_IN_STEREO
  • int audioFormat 音频格式

    AudioFormat.ENCODING_PCM_16BIT AudioFormat.ENCODING_PCM_8BIT
  • nt bufferSizeInBytes 音频数据写入缓冲区的总数,可以通过AudioRecord.getMinBufferSize获取最小的缓冲区,将音频采集到缓冲区中然后再从缓冲区中读取

  • 开始录音

mAudioRecord.startRecording();
  1. 读取数据

    mAudioRecord.read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes);
    >>audioData 写入的数组
    >>offsetInBytes 偏移
    >>sizeInBytes 读取的数据量
    
    mAudioRecord.read(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts);
  2. 停止录音

    mAudioRecord.stop();

    释放资源

    mAudioRecord.release();
  3. Android AudioRecord 录音并保存到本地

  1. 引入权限

首先,在AndroidManifest.xml文件中添加以下权限:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

其中,RECORD_AUDIO权限用于录制音频,WRITE_EXTERNAL_STORAGE权限用于保存文件到外部存储器。

  1. 创建录音器

在代码中,我们首先需要创建一个AudioRecord对象来进行音频的录制。AudioRecord的构造函数需要传入以下参数:

  • audioSource:音频源,例如麦克风、电话通话等。

  • sampleRateInHz:采样率,表示每秒钟采集的样本数。

  • channelConfig:声道配置,例如单声道、立体声等。

  • audioFormat:音频格式,例如PCM、AAC等。

  • bufferSizeInBytes:缓冲区大小,用于存储录制的音频数据。

下面是一个创建AudioRecord对象的示例代码:

int audioSource = MediaRecorder.AudioSource.MIC;
int sampleRateInHz = 44100;
int channelConfig = AudioFormat.CHANNEL_IN_MONO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);

AudioRecord audioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);
  1. 开始录制音频

创建完AudioRecord对象后,我们可以调用startRecording()方法开始录制音频。录制的音频数据将保存在缓冲区中,我们可以通过read()方法获取缓冲区中的音频数据。下面是一个示例代码:

byte[] buffer = new byte[bufferSizeInBytes];
audioRecord.startRecording();

while (录制中) {
    int bytesRead = audioRecord.read(buffer, 0, bufferSizeInBytes);
    // 处理音频数据
}
  1. 停止录制音频

当我们想要停止录制音频时,可以调用stopRecording()方法停止录制,并释放相关资源。下面是一个示例代码:

audioRecord.stop();
audioRecord.release();
  1. 保存音频到本地

在录制音频时,我们可以将音频数据保存到本地文件中。首先,我们需要创建一个文件对象来表示要保存的文件路径。然后,使用FileOutputStream来写入音频数据到文件中。下面是一个示例代码:

String filePath = "/sdcard/recorded_audio.pcm";
File file = new File(filePath);

try {
    FileOutputStream outputStream = new FileOutputStream(file);

    while (录制中) {
        int bytesRead = audioRecord.read(buffer, 0, bufferSizeInBytes);
        outputStream.write(buffer, 0, bytesRead);
    }

    outputStream.close();
} catch (IOException e) {
    e.printStackTrace();
}
  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值