解释: AudioTrack是什么? AudioRecord是可以播放原始音频数据pcm的api,pcm一般的播放器都是无法播放的,AudioRecord可以播放pcm,不过需要制定播放时候的采样率、声道数位宽,现在在android下面做了一个demo,主要是播放pcm录音文件。pcm录音时候需要制定几个重要参数,播放的时候还需要设置录制缓冲区大小,缓存区越大,内存溢出风险越小。 pcm参数: 1、采样率 2、声道数 3、位宽 详细代码: public class MyAudio extends AppCompatActivity implements View.OnClickListener { private static final int MY_PERMISSIONS_REQUEST = 1001; private static final String TAG = "jqd"; private Button mBtnControl; private Button mBtnPlay; /** * 需要申请的运行时权限 */ private String[] permissions = new String[]{ Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE }; /** * 被用户拒绝的权限列表 */ private List<String> mPermissionList = new ArrayList<>(); private boolean isRecording; private AudioRecord audioRecord; private Button mBtnConvert; private AudioTrack audioTrack; private byte[] audioData; private FileInputStream fileInputStream; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mBtnControl = (Button) findViewById(R.id.btn_control); mBtnControl.setOnClickListener(this); mBtnConvert = (Button) findViewById(R.id.btn_convert); mBtnConvert.setOnClickListener(this); mBtnPlay = (Button) findViewById(R.id.btn_play); mBtnPlay.setOnClickListener(this); checkPermissions(); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.btn_control: Button button = (Button) view; if (button.getText().toString().equals(getString(R.string.start_record))) { button.setText(getString(R.string.stop_record)); startRecord(); } else { button.setText(getString(R.string.start_record)); stopRecord(); } break; case R.id.btn_convert: PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT); File pcmFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm"); File wavFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.wav"); if (!wavFile.mkdirs()) { Log.e(TAG, "wavFile Directory not created"); } if (wavFile.exists()) { wavFile.delete(); } pcmToWavUtil.pcmToWav(pcmFile.getAbsolutePath(), wavFile.getAbsolutePath()); break; case R.id.btn_play: Button btn = (Button) view; String string = btn.getText().toString(); if (string.equals(getString(R.string.start_play))) { btn.setText(getString(R.string.stop_play)); playInModeStream(); //playInModeStatic(); } else { btn.setText(getString(R.string.start_play)); stopPlay(); } break; default: break; } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == MY_PERMISSIONS_REQUEST) { for (int i = 0; i < grantResults.length; i++) { if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { Log.i(TAG, permissions[i] + " 权限被用户禁止!"); } } // 运行时权限的申请不是本demo的重点,所以不再做更多的处理,请同意权限申请。 } } public void startRecord() { 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; // TODO: 2018/3/10 pcm数据无法直接播放,保存为WAV格式。 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(); } public void stopRecord() { isRecording = false; // 释放资源 if (null != audioRecord) { audioRecord.stop(); audioRecord.release(); audioRecord = null; //recordingThread = null; } } private void checkPermissions() { // Marshmallow开始才用申请运行时权限 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { for (int i = 0; i < permissions.length; i++) { if (ContextCompat.checkSelfPermission(this, permissions[i]) != PackageManager.PERMISSION_GRANTED) { mPermissionList.add(permissions[i]); } } if (!mPermissionList.isEmpty()) { String[] permissions = mPermissionList.toArray(new String[mPermissionList.size()]); ActivityCompat.requestPermissions(this, permissions, MY_PERMISSIONS_REQUEST); } } } /** * 播放,使用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); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { 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); } else { audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 48000, channelConfig, AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STREAM); } 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 if (Build.VERSION.SDK_INT >Build.VERSION_CODES.KITKAT) { 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); } else { audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 48000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, audioData.length, AudioTrack.MODE_STREAM); } Log.d(TAG, "Writing audio data..."); audioTrack.write(audioData, 0, audioData.length); Log.d(TAG, "Starting playback"); audioTrack.play(); Log.d(TAG, "Playing"); } }.execute(); } /** * 停止播放 */ private void stopPlay() { if (audioTrack != null) { Log.d(TAG, "Stopping"); audioTrack.stop(); Log.d(TAG, "Releasing"); audioTrack.release(); Log.d(TAG, "Nulling"); } } }
工具类:
/** * 将pcm音频文件转换为wav音频文件 */ public class PcmToWavUtil { /** * 缓存的音频大小 */ private int mBufferSize; /** * 采样率 */ private int mSampleRate; /** * 声道数 */ private int mChannel; /** * @param sampleRate sample rate、采样率 * @param channel channel、声道 * @param encoding Audio data format、音频格式 */ public PcmToWavUtil(int sampleRate, int channel, int encoding) { this.mSampleRate = sampleRate; this.mChannel = channel; this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding); } /** * pcm文件转wav文件 * * @param inFilename 源文件路径 * @param outFilename 目标文件路径 */ public void pcmToWav(String inFilename, String outFilename) { FileInputStream in; FileOutputStream out; long totalAudioLen; long totalDataLen; long longSampleRate = mSampleRate; int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2; long byteRate = 16 * mSampleRate * channels / 8; byte[] data = new byte[mBufferSize]; try { in = new FileInputStream(inFilename); out = new FileOutputStream(outFilename); totalAudioLen = in.getChannel().size(); totalDataLen = totalAudioLen + 36; writeWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate); while (in.read(data) != -1) { out.write(data); } in.close(); out.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 加入wav文件头 */ private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate, int channels, long byteRate) throws IOException { 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); out.write(header, 0, 44); } }
config类
public class Config { /** * 采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。 */ public static final int SAMPLE_RATE_INHZ = 44100; /** * 声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。 */ public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; /** * 返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT. */ public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; }
总结: android相对底层的录制、播放api,了解用法即可。建议先学习音频基础知识,再学会使用api。