Android 音频录制-AudioRecord

Posted xyTianZhao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

以上是关于Android 音频录制-AudioRecord的主要内容,如果未能解决你的问题,请参考以下文章

Android:使用 audiorecord 类录制音频播放快进

Android 开发 AudioRecord音频录制

Android 检测 AudioRecord/在不阻塞其他应用程序的情况下录制音频的方式

Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能

Android多媒体功能开发(11)——使用AudioRecord类录制音频

无法在 Android 中使用 AudioRecord 进行录制