PCM(Pulse Code Modulation)脈沖編碼調制 —— 音頻的采集與量化過程。
PCM數據是最原始的音頻數據完全無損,所以PCM數據雖然音質優秀但體積龐大。
為了解決這個問題先后誕生了一系列的音頻格式,這些音頻格式運用不同的方法對音頻數據進行壓縮,其中有無損壓縮(ALAC、APE、FLAC)和有損壓縮(MP3、AAC、OGG、WMA)兩種。
代碼實現邏輯過程:
使用AudioRecord
錄制pcm音頻 ——> PCM轉WAV(只要加上wav頭文件即可)——> 使用AudioTrack
播放pcm音頻
——> 使用 AudioTrack 播放音頻
使用AudioRecord錄制PCM音頻代碼: /** * 采樣率,現在能夠保證在所有設備上使用的采樣率是44100Hz, 但是其他的采樣率(22050, 16000, 11025)在一些設備上也可以使用。 */ private static final int SAMPLE_RATE_INHZ = 44100; /** * 聲道數。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保證在所有設備能夠使用的。 */ private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; /** * 返回的音頻數據的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT. */ private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; final int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT); audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize); final byte data[] = new byte[minBufferSize]; final File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm"); if (!file.mkdirs()) { Log.e(TAG, "Directory not created"); } if (file.exists()) { file.delete(); } audioRecord.startRecording(); isRecording = true; new Thread(new Runnable() { @Override public void run() { FileOutputStream os = null; try { os = new FileOutputStream(file); } catch (FileNotFoundException e) { e.printStackTrace(); } if (null != os) { while (isRecording) { int read = audioRecord.read(data, 0, minBufferSize); // 如果讀取音頻數據沒有出現錯誤,就將數據寫入到文件 if (AudioRecord.ERROR_INVALID_OPERATION != read) { try { os.write(data); } catch (IOException e) { e.printStackTrace(); } } } try { Log.i(TAG, "run: close file output stream !"); os.close(); } catch (IOException e) { e.printStackTrace(); } } } }).start();
PCM轉WAV: // 音頻數據的大小 long totalAudioLen = fileInputStream.getChannel().size(); // wav總區塊大小 long totalDataLen = totalAudioLen + 36; // 聲道數量 int channels; // 采樣率 long longSampleRate; // 位元率 long byteRate = 16 * longSampleRate * channels / 8; byte[] header = new byte[44]; // RIFF/WAVE header header[0] = 'R'; 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); //WAVE header[8] = 'W'; header[9] = 'A'; header[10] = 'V'; header[11] = 'E'; // 'fmt ' chunk header[12] = 'f'; header[13] = 'm'; header[14] = 't'; header[15] = ' '; // 4 bytes: size of 'fmt ' chunk header[16] = 16; header[17] = 0; header[18] = 0; header[19] = 0; // format = 1 header[20] = 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); header[28] = (byte) (byteRate & 0xff); header[29] = (byte) ((byteRate >> 8) & 0xff); header[30] = (byte) ((byteRate >> 16) & 0xff); header[31] = (byte) ((byteRate >> 24) & 0xff); // block align header[32] = (byte) (2 * 16 / 8); header[33] = 0; // bits per sample header[34] = 16; header[35] = 0; //data header[36] = 'd'; 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);
使用AudioTrack 播放音頻
/** * 播放,使用stream模式 */ private void playInModeStream() { /* * SAMPLE_RATE_INHZ 對應pcm音頻的采樣率 * channelConfig 對應pcm音頻的聲道 * AUDIO_FORMAT 對應pcm音頻的格式 * */ int channelConfig = AudioFormat.CHANNEL_OUT_MONO; final int minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE_INHZ, channelConfig, AUDIO_FORMAT); audioTrack = new AudioTrack( new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build(), new AudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ) .setEncoding(AUDIO_FORMAT) .setChannelMask(channelConfig) .build(), minBufferSize, AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE); audioTrack.play(); File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm"); try { fileInputStream = new FileInputStream(file); new Thread(new Runnable() { @Override public void run() { try { byte[] tempBuffer = new byte[minBufferSize]; while (fileInputStream.available() > 0) { int readCount = fileInputStream.read(tempBuffer); if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) { continue; } if (readCount != 0 && readCount != -1) { audioTrack.write(tempBuffer, 0, readCount); } } } catch (IOException e) { e.printStackTrace(); } } }).start(); } catch (IOException e) { e.printStackTrace(); } } /** * 播放,使用static模式 */ private void playInModeStatic() { // static模式,需要將音頻數據一次性write到AudioTrack的內部緩沖區 new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { InputStream in = getResources().openRawResource(R.raw.ding); try { ByteArrayOutputStream out = new ByteArrayOutputStream(); for (int b; (b = in.read()) != -1; ) { out.write(b); } Log.d(TAG, "Got the data"); audioData = out.toByteArray(); } finally { in.close(); } } catch (IOException e) { Log.wtf(TAG, "Failed to read", e); } return null; } @Override protected void onPostExecute(Void v) { Log.i(TAG, "Creating track...audioData.length = " + audioData.length); // R.raw.ding鈴聲文件的相關屬性為 22050Hz, 8-bit, Mono audioTrack = new AudioTrack( new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build(), new AudioFormat.Builder().setSampleRate(22050) .setEncoding(AudioFormat.ENCODING_PCM_8BIT) .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) .build(), audioData.length, AudioTrack.MODE_STATIC, AudioManager.AUDIO_SESSION_ID_GENERATE); Log.d(TAG, "Writing audio data..."); audioTrack.write(audioData, 0, audioData.length); Log.d(TAG, "Starting playback"); audioTrack.play(); Log.d(TAG, "Playing"); } }.execute(); }
demo代碼: https://i.cnblogs.com/Files.aspx