天天看點

Android AudioRecord 和MediaRecord 音頻開發

Android AudioRecord 和MediaRecord 音頻開發

臨時需求做了一個錄音上傳功能,如上圖,但要求上傳16k采樣率以及16Kbps比特率的wav音頻。

先使用的是MediaRecord :

1.開始錄制按鈕監聽事件

record.setOnClickListener(new View.OnClickListener() {
          @Override
        public void onClick(View v) {
            if (!isStart) {
                //開始錄制
                startrecord();
                record.setText("停止錄制");
                isStart = true;
            } else {
                stopRecord();
                record.setText("開始錄制");
                isStart = false;
            }
        }
    });
           

2.開始錄音和停止錄音方法

String filename;
File soundFile;
private void startrecord() {
    if (mr == null) {      
        File dir = new File(Environment.getExternalStorageDirectory(), "sounds");
        if (!dir.exists()) {
            dir.mkdirs();
        }
         filename = System.currentTimeMillis() + ".wav";
        soundFile = new File(dir, filename);
       
        if (!soundFile.exists()) {
            try {
                soundFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        mr = new MediaRecorder();
        mr.setAudioSource(MediaRecorder.AudioSource.MIC);  //音頻輸入源
        mr.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); //錄音檔案儲存的格式  AAC_ADTS AMR_NB
        mr.setOutputFile(soundFile.getAbsolutePath());
        mr.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);//  檔案輸出格式  default,AAC,HE_AAC,AAC_ELD,AMR_NB,AMR_WB,VORBIS
        mr.setAudioSamplingRate(16000);  //采樣率16K
        mr.setAudioChannels(1);//比特率16bit
        mr.setAudioEncodingBitRate(AudioFormat.ENCODING_PCM_16BIT);//音頻編碼比特率
        try {
            mr.prepare();
            mr.start();  //開始錄制
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

//停止錄制,資源釋放
private void stopRecord() {
    if (mr != null) {
        try {
            mr.stop();
        } catch (IllegalStateException e) {
            // TODO 如果目前java狀态和jni裡面的狀态不一緻,
            e.printStackTrace();
            mr = null;
            mr = new MediaRecorder();
        }
        mr.release();
        mr = null;
    }
}
           

傳入背景發現一個問題 MediaRecord其實不支援wav格式,以及MediaRecord設定任何編碼比特率都不起作用,是以改用AudioRecord,(當然如果沒有那麼多要求,還是用MediaRecord友善)。

下面介紹AudioRecord

1.audiorecord錄音的工具類奉上

public class AudioUtil {

private AudioRecord recorder;
//錄音源
private static int audioSource = MediaRecorder.AudioSource.MIC;
//錄音的采樣頻率  16k采樣率
private static int audioRate = 16000;
//錄音的聲道,單聲道
private static int audioChannel = AudioFormat.CHANNEL_IN_MONO;
//量化的深度  16bps比特率
private static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
//緩存的大小
private static int bufferSize = AudioRecord.getMinBufferSize(audioRate, audioChannel, audioFormat);
//記錄播放狀态
private boolean isRecording = false;
//數字信号數組
private byte[] noteArray;
//PCM檔案
private File pcmFile;
//WAV檔案
private File wavFile;
//檔案輸出流
private OutputStream os;
//檔案根目錄
private String basePath = Environment.getExternalStorageDirectory().getAbsolutePath();
 //rootFolder 子檔案夾
public AudioUtil(String rootFolder) {
    basePath = basePath + "/" + rootFolder;
    createFile();//建立檔案
    recorder = new AudioRecord(audioSource, audioRate, audioChannel, audioFormat, bufferSize);
}
//調用時 公用
public String getBasePath() {
    return basePath;
}

//讀取錄音數字資料線程
class WriteThread implements Runnable {
    public void run() {
        writeData();
    }
}

//開始錄音
public void startRecord() {
    isRecording = true;
    recorder.startRecording();
}

//停止錄音
public void stopRecord() {
    isRecording = false;
    recorder.stop();
}

//将資料寫入檔案夾,檔案的寫入沒有做優化
public void writeData() {
    noteArray = new byte[bufferSize];
    //建立檔案輸出流
    try {
        os = new BufferedOutputStream(new FileOutputStream(pcmFile));
    } catch (IOException e) {

    }
    while (isRecording == true) {
        int recordSize = recorder.read(noteArray, 0, bufferSize);
        if (recordSize > 0) {
            try {
                os.write(noteArray);
            } catch (IOException e) {

            }
        }
    }
    if (os != null) {
        try {
            os.close();
        } catch (IOException e) {

        }
    }
}

// 這裡得到可播放的音頻檔案
public void convertWaveFile(String outFileName) {
    FileInputStream in = null;
    FileOutputStream out = null;
    long totalAudioLen = 0;
    long totalDataLen = totalAudioLen + 36;
    long longSampleRate = AudioUtil.audioRate;
    int channels = 1;
    long byteRate = 16 * AudioUtil.audioRate * channels / 8;
    byte[] data = new byte[bufferSize];
    try {
        in = new FileInputStream(pcmFile);
        out = new FileOutputStream(basePath + "/" + outFileName);
        totalAudioLen = in.getChannel().size();
        //由于不包括RIFF和WAV
        totalDataLen = totalAudioLen + 36;
        WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
        while (in.read(data) != -1) {
            out.write(data);
        }
    } catch (FileNotFoundException e) {
        Log.d("Regist", "convertWaveFile: " + e.getMessage());
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
        Log.d("Regist", "IOException: " + e.getMessage());
    }finally {
        try {
            if (in!=null){
                in.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            if (out!=null){
                out.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        if(pcmFile.isFile() && pcmFile.exists()) {
            pcmFile.delete();
        }
    }


}

/* 任何一種檔案在頭部添加相應的頭檔案才能夠确定的表示這種檔案的格式,wave是RIFF檔案結構,
每一部分為一個chunk,其中有RIFF WAVE chunk, FMT Chunk,Fact chunk,Data chunk,其中Fact chunk是可以選擇的, */
private 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);
}

//建立檔案夾,首先建立目錄,然後建立對應的檔案
public void createFile() {
    File baseFile = new File(basePath);
    if (!baseFile.exists())
        baseFile.mkdirs();
    pcmFile = new File(basePath + "/tmp_" + UUID.randomUUID().toString().replaceAll("-", "") + ".pcm");

    if (pcmFile.exists()) {
        pcmFile.delete();
    }
    try {
        pcmFile.createNewFile();
    } catch (IOException e) {
    }
}

//記錄資料
public void recordData() {
    new Thread(new WriteThread()).start();
}
           

}

2. 調用如下

record.setOnClickListener(new View.OnClickListener() {

@Override
        public void onClick(View v) {

            if (!isStart) {
                //開始錄制
                util = new AudioUtil("audio");
                filename = System.currentTimeMillis() + ".wav";
                util.startRecord();
                util.recordData();
                record.setText("停止錄制");
                isStart = true;
            } else {
                util.stopRecord();
                util.convertWaveFile(filename);
                record.setText("開始錄制");
                isStart = false;
            }
        }
    });
           

這裡用AudioRecord錄制pcm音頻,加上wav頭即可得到.wav的音頻檔案,也可以随集設定AudioFormat.ENCODING_PCM_16BIT;比特率等,最後完美解決我的問題,嘻嘻嘻,以此記錄今日收獲。