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() { @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代码: