Android 音频录制(二)-AudioRecord

Android 音频录制(二)-AudioRecord

Android 系统为我们提供了三种录制音频的方式

  1. MediaRecord( Java API)
  2. AudioRecord( Java API)
  3. OpenSL ES( Native API)

这次我们来说复杂一点的 AudioRecord官方API 介绍

我们在上节说的 使用MeidaRecord录制,系统已经为我们将数据处理完成,我们只需要传入一个目标文件路径,音频文件的录制,保存就全部完成了。

AudioRecord 则是可以影响到我们在 音频基础知识 中说的 模拟信号到数字信号 的转换流程。

初始化

先来看一下它的构造函数

public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
            int bufferSizeInBytes){
}
//或者
AudioRecord recorder = new AudioRecord.Builder()
         .setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
         .setAudioFormat(new AudioFormat.Builder()
                 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                 .setSampleRate(32000)
                 .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
                 .build())
         .setBufferSizeInBytes(2*minBuffSize)
         .build();

其中四个参数分别代表,这四个参数对于信号的处理我们在 音频基础知识 中已经说到,这里就不在多说

  1. audioSource:录制音频来源
  2. sampleRateInHz:采样率,单位Hz(赫兹)
  3. channelConfig:声道数
  4. audioFormat:返回的音频数据的编码格式

录制

AudioRecord 创建完成后。需要注意的是,录制需要在子线程中,不能再主线程中进行录制。

/**
 * Author silence.
 * Time:2019-09-20.
 * Desc:pcm 录制,使用 Android 原生的 AudioRecord 进行录制
 */
final class PcmRecord implements Runnable {

    private static final String TAG = "PcmRecord";

    //录制的音频来源
    private static final int RECORD_SOURCE = MediaRecorder.AudioSource.MIC;
    //采样率,单位Hz(赫兹)
    private static final int SAMPLE_RATE_IN_HZ = 16000;
    //音频声道的配置(输入) AudioFormat.CHANNEL_IN_MONO 单声道
    private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
    //返回的音频数据的编码格式,每份采样数据为PCM 16bit,保证所有设备支持
    private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
    //在录音时期,音频数据写入的缓冲区的整体大小(单位字节),即缓冲区的大小
    private static int BUFFER_SIZE_IN_BYTES = 1280;

    private static final Object mLock = new Object();

    @NonNull
    private AudioRecord mAudioRecord;
    private boolean isRecording = false;
    private Thread mRecordThread;

    PcmRecord() {
        try {
            mAudioRecord = new AudioRecord(RECORD_SOURCE, SAMPLE_RATE_IN_HZ,
                    CHANNEL_CONFIG, AUDIO_FORMAT, BUFFER_SIZE_IN_BYTES);
        } catch (Throwable t) {
            BUFFER_SIZE_IN_BYTES = AudioRecord.getMinBufferSize(SAMPLE_RATE_IN_HZ, CHANNEL_CONFIG, AUDIO_FORMAT);
            SLog.e(TAG, "audio record init error, use minBufferSize = " + BUFFER_SIZE_IN_BYTES, t);
            mAudioRecord = new AudioRecord(RECORD_SOURCE, SAMPLE_RATE_IN_HZ,
                    CHANNEL_CONFIG, AUDIO_FORMAT, BUFFER_SIZE_IN_BYTES);
        }
    }

    void start() {
        synchronized (mLock) {
            if (isRecording) {
                SLog.d(TAG, "正在录音,重复调用 start()");
                return;
            }
            try {
                mAudioRecord.startRecording();
                isRecording = true;
                startReadRecord();
            } catch (Throwable t) {
                SLog.e(TAG, "开始录音失败", t);
            }
        }
    }

    @Override
    public void run() {
        SLog.d(TAG, "读取录音数据-开始");
        mLastRecordTimeMillis = System.currentTimeMillis();
        dispatchRecordResult(SpeechUtility.START, null);
        try {
            byte[] recordBytes = new byte[BUFFER_SIZE_IN_BYTES];
            while (isRecording) {
                int size = mAudioRecord.read(recordBytes, 0, BUFFER_SIZE_IN_BYTES);
                dispatchRecordResult(SpeechUtility.RECOGNIZING, recordBytes);
            }
            SLog.d(TAG, "读取录音数据-结束");
        } catch (Throwable t) {
            SLog.e(TAG, "读取录音数据-异常", t);
            dispatchRecordResult(SpeechUtility.ERROR, null);
            stop();
        }
        dispatchRecordResult(SpeechUtility.END, null);
    }

    private void dispatchRecordResult(int state, byte[] data) {
        RecordResult recordResult = new RecordResult();
        recordResult.recordData = data;
        recordResult.status = state;
        Pcm2File.write(recordResult);
    }

    /**
     * 开始读取录音的数据
     */
    private void startReadRecord() {
        cancelReadRecordThread();
        mRecordThread = new Thread(this);
        mRecordThread.start();
    }

    /**
     * 是否正在录音
     */
    public boolean isRecording() {
        return isRecording;
    }

    private void cancelReadRecordThread() {
        try {
            if (mRecordThread != null && !mRecordThread.isInterrupted()) {
                mRecordThread.interrupt();
            }
        } catch (Throwable t) {
        }
    }


    void stop() {
        synchronized (mLock) {
            if (!isRecording) {
                SLog.d(TAG, "已经停止录音,重复调用 stop()");
                return;
            }
            isRecording = false;
            try {
                mAudioRecord.stop();
            } catch (Throwable t) {
                SLog.e(TAG, "停止录音异常", t);
            }
            mRecordThread = null;
        }
    }

}

这里的 RecordResult 是对录音数据的一个封装

/**
 * Author silence.
 * Time:2019-09-23.
 * Desc:录音数据计算音量大小
 */
public class RecordResult extends SpeechResult{

    public int state;
    public byte[] recordData = null;

}

其中的 Pcm2File 负责将录制的 PCM 数据写入文件中,并在录制完成后,将 PCM 源数据转换成可以直接播放的 wav 音频文件。

/**
 * Author silence.
 * Time:2019-09-25.
 * Desc:todo release 不需要
 */
public class Pcm2File {

    private static String path = SpeechSystem.application.getExternalFilesDir("pcm") + "/record.pcm";
    private static String wavPath = SpeechSystem.application.getExternalFilesDir("pcm") + "/record.wav";
    private static DataOutputStream dos;

    private static void createFile() {
        File pcmFile = new File(path);
        if (pcmFile.exists()) {
            pcmFile.delete();
        }
        try {
            pcmFile.createNewFile();
            dos = new DataOutputStream(new FileOutputStream(pcmFile));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void write(RecordResult recordResult) {
        if (recordResult.status == SpeechUtility.START) {
            createFile();
        } else if (recordResult.status == SpeechUtility.END){
            try {
                dos.flush();
                dos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            new Thread(new Runnable() {
                @Override
                public void run() {
                    convertWaveFile();
                    SLog.d("tianzhao","转码完成");
                }
            }).start();
        } if (recordResult.status == SpeechUtility.RECOGNIZING){
            writeBytes2File(recordResult.recordData);
        }
    }

    private static void writeBytes2File(byte[] recordBytes) {
        if (dos == null) {
            return;
        }
        try {
            dos.write(recordBytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 这里得到可播放的音频文件
    private static void convertWaveFile() {
        FileInputStream in = null;
        FileOutputStream out = null;
        long totalAudioLen = 0;
        long totalDataLen = totalAudioLen + 36;
        long longSampleRate = 16000;
        int channels = 1;
        long byteRate = 16 *longSampleRate * channels / 8;
        byte[] data = new byte[1280];
        try {
            in = new FileInputStream(path);
            out = new FileOutputStream(wavPath);
            totalAudioLen = in.getChannel().size();
            //由于不包括RIFF和WAV
            totalDataLen = totalAudioLen + 36;
            WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
            }
            in.close();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*
    任何一种文件在头部添加相应的头文件才能够确定的表示这种文件的格式,wave是RIFF文件结构,每一部分为一个chunk,其中有RIFF WAVE chunk,
    FMT Chunk,Fact chunk,Data chunk,其中Fact chunk是可以选择的,
     */
    private static void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate,
                                     int channels, long byteRate) throws IOException {
        byte[] header = new byte[44];
        header[0] = 'R'; // RIFF
        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';//WAVE
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        //FMT Chunk
        header[12] = 'f'; // 'fmt '
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';//过渡字节
        //数据大小
        header[16] = 16; // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        //编码方式 10H为PCM编码格式
        header[20] = 1; // format = 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);
        //音频数据传送速率,采样率*通道数*采样深度/8
        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) (1 * 16 / 8);
        header[33] = 0;
        //每个样本的数据位数
        header[34] = 16;
        header[35] = 0;
        //Data chunk
        header[36] = 'd';//data
        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);
    }

}

相关推荐

音频基础知识

Android 音频录制-MeidaRecord

Android 音频录制-AudioRecord

Android 音频录制-OpenSL ES

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用AudioRecord录制音频并转换成wav格式,需要进行以下步骤: 1. 设置录音参数:采样率、音频通道、编码格式等 2. 创建一个AudioRecord对象 3. 开始录制音频,将音频数据写入到一个缓存区 4. 录制完成后,停止录音并释放AudioRecord对象 5. 将缓存区中的音频数据写入到一个wav文件中 下面是一个简单的示例代码,演示如何使用AudioRecord录制音频并将其转换成wav格式: ``` // 设置录音参数 int sampleRateInHz = 44100; int channelConfig = AudioFormat.CHANNEL_IN_MONO; int audioFormat = AudioFormat.ENCODING_PCM_16BIT; int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); // 创建AudioRecord对象 AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes); // 开始录音 audioRecord.startRecording(); // 定义缓存区 byte[] buffer = new byte[bufferSizeInBytes]; // 定义输出文件 String outputFileName = "output.wav"; File outputFile = new File(Environment.getExternalStorageDirectory(), outputFileName); // 定义输出流 FileOutputStream outputStream = new FileOutputStream(outputFile); // 写入wav文件头 WaveHeader waveHeader = new WaveHeader(sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes); waveHeader.write(outputStream); // 写入音频数据 int readSize; while ((readSize = audioRecord.read(buffer, 0, bufferSizeInBytes)) != AudioRecord.ERROR_INVALID_OPERATION) { outputStream.write(buffer, 0, readSize); } // 停止录音并释放资源 audioRecord.stop(); audioRecord.release(); // 关闭输出流 outputStream.close(); ``` 在上述代码中,我们使用了一个自定义的WaveHeader类,用于生成wav文件头信息。该类的实现可以参考下面的示例代码: ``` public class WaveHeader { private int sampleRate; private int channelCount; private int audioFormat; private int audioDataLength; public WaveHeader(int sampleRate, int channelCount, int audioFormat, int audioDataLength) { this.sampleRate = sampleRate; this.channelCount = channelCount; this.audioFormat = audioFormat; this.audioDataLength = audioDataLength; } public void write(OutputStream outputStream) throws IOException { outputStream.write("RIFF".getBytes()); outputStream.write(intToByteArray(36 + audioDataLength), 0, 4); outputStream.write("WAVE".getBytes()); outputStream.write("fmt ".getBytes()); outputStream.write(intToByteArray(16), 0, 4); outputStream.write(shortToByteArray((short) 1), 0, 2); outputStream.write(shortToByteArray((short) channelCount), 0, 2); outputStream.write(intToByteArray(sampleRate), 0, 4); outputStream.write(intToByteArray(sampleRate * channelCount * audioFormat / 8), 0, 4); outputStream.write(shortToByteArray((short) (channelCount * audioFormat / 8)), 0, 2); outputStream.write(shortToByteArray((short) audioFormat), 0, 2); outputStream.write("data".getBytes()); outputStream.write(intToByteArray(audioDataLength), 0, 4); } private byte[] intToByteArray(int value) { byte[] byteArray = new byte[4]; byteArray[0] = (byte) (value & 0xff); byteArray[1] = (byte) ((value >> 8) & 0xff); byteArray[2] = (byte) ((value >> 16) & 0xff); byteArray[3] = (byte) ((value >> 24) & 0xff); return byteArray; } private byte[] shortToByteArray(short value) { byte[] byteArray = new byte[2]; byteArray[0] = (byte) (value & 0xff); byteArray[1] = (byte) ((value >> 8) & 0xff); return byteArray; } } ``` 通过以上代码,我们可以实现将AudioRecord录制音频转换成wav格式并保存到文件中。需要注意的是,由于Android 6.0及以上版本需要动态获取录音权限,因此在使用前需要先请求录音权限。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值