AudioTrack
AudioTrack属于更偏底层的音频播放,MediaPlayerService的内部就是使用了AudioTrack。
AudioTrack用于单个音频播放和管理,相比于MediaPlayer具有精炼、高效的优点,更适合实时产生播放数据的情况。如加密的音频, MediaPlayer是束手无策的,AudioTrack却可以。
AudioTrack用于播放PCM(PCM无压缩的音频格式)音乐流的回放,如果需要播放其它格式音频,需要响应的解码器,这也是AudioTrack用的比较少的原因,需要自己解码音频。
AudioTrack实现PCM音频播放
配置基本参数
获取最小缓冲区大小
创建AudioTrack对象
获取PCM文件,转成DataInputStream
开启/停止播放
AudioTrack常见参数
StreamType 音频流类型:区分系统不同功能的音频流。
sampleRateInHz 采样率:播放的音频每秒钟会有多少次采样,MediaRecoder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100为常用的采样率。
channelConfig 声道数(通道数):一般可选的就两种,单声道CHANNEL_IN_MONO,双声道CHANNEL_IN_STEREO,建议选择单声道。
audioFormat 数据位宽:只支持AudioFormat.ENCODING_PCM_8BIT(8bit)和AudioFormat.ENCODING_PCM_16BIT(16bit)两种,后者支持所有Android手机。
bufferSizeInBytes 音频缓冲区大小:建议使用AudioTrack.getMinBufferSize()这个方法获取。
mode 播放模式:MODE_STATIC,一次性将所有数据都写入播放缓冲区中,简单高效,一般用于铃声,系统提醒音,内存比较小的。MODE_STREAM,需要按照一定的时间间隔,不断的写入音频数据,理论上它可以应用于任何音频播放的场景。
AudioTrack例子:播放录音
播放录音playAudio()和播放测试音频playAudioTest()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public void playAudio() { if (!mIsPlaying) { mExecutorService.submit(() -> { mIsPlaying = true; doStartPlay(getLastFile()); }); } } public void playAudioTest() { if (mHasPausing && !mIsPausing && mAudioTrack != null) { mIsPausing = true; mAudioTrack.stop(); mBtStreamPlayerTest.setText("播放测试录音"); return; } if (mIsPlaying) { Toast.makeText(mContext, "正在播放", Toast.LENGTH_SHORT).show(); } else { mBtStreamPlayerTest.setText("暂停测试录音"); mExecutorService.submit(() -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mHasPausing = true; mIsPausing = false; mIsPlaying = true; doStartPlay(new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/AudioRecordDemo/test.pcm")); } }); } }
|
一个算法,获得文件夹里最新更新的文件getLastFile()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| private File getLastFile() { File realFile = null; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { realFile = new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/AudioRecordDemo/"); } if (realFile.isDirectory()) { List files = new ArrayList(); File[] subFiles = realFile.listFiles(); for (File file : subFiles) { files.add(file); } Collections.sort(files, (Comparator<File>) (file, newFile) -> { if (file.lastModified() < newFile.lastModified()) { return 1; } else if (file.lastModified() == newFile.lastModified()) { return 0; } else { return -1; } }); Log.d(TAG, "play " + ((File) files.get(0)).toString()); return (File) files.get(0); } return null; }
|
播放录音具体实现,AudioTrack的使用doStartPlay()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| private void doStartPlay(File audioRecordFile) { if (audioRecordFile == null) { Log.d(TAG, "请先录制音频!"); mIsPlaying = false; return; } int streamType = AudioManager.STREAM_MUSIC; int sampleRate = 44100; int channelConfig = AudioFormat.CHANNEL_OUT_MONO; int audioFormat = AudioFormat.ENCODING_PCM_16BIT; int mode = AudioTrack.MODE_STREAM; int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat); mAudioTrack = new AudioTrack(streamType, sampleRate, channelConfig, audioFormat, Math.max(minBufferSize, BUFFER_SIZE), mode); FileInputStream fis = null; try { fis = new FileInputStream(audioRecordFile); fis.skip(mOffset); Log.d(TAG, audioRecordFile.toString()); int read; mAudioTrack.play(); Log.d(TAG, String.valueOf(mAudioTrack.getPlayState())); while ((read = fis.read(mBuffer)) != -1) { int status = mAudioTrack.write(mBuffer, 0, read); updateOffset(read); Log.d(TAG, String.valueOf(mOffset)); checkStatus(status); } Log.d(TAG, "播放完毕!"); } catch (Exception e) { e.printStackTrace(); playFail(); } finally { mIsPlaying = false; closeInputStream(fis); resetQuietly(mAudioTrack); } } private void updateOffset(int read) { if (mAudioTrack.getPlayState() == mAudioTrack.PLAYSTATE_PLAYING) { mOffset += read; } }
|
关闭输入流closeInputStream()和resetQuietly()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private void closeInputStream(InputStream is) { if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } private void resetQuietly(AudioTrack audioTrack) { try { audioTrack.stop(); audioTrack.release(); } catch (Exception e) { e.printStackTrace(); } }
|
播放失败,更新UI操作playFail()
1 2 3 4 5 6 7 8
| private void playFail() { mHandler.post(new Runnable() { @Override public void run() { Toast.makeText(mContext, "pcm播放失败", Toast.LENGTH_SHORT).show(); } }); }
|
onDestroy()释放资源
1 2 3 4 5 6 7 8 9 10 11
| @Override protected void onDestroy() { super.onDestroy(); if (mAudioTrackTest.getAudioTrack() != null) { mAudioTrackTest.getAudioTrack().stop(); } if (mExecutorService != null) { mExecutorService.shutdownNow(); mExecutorService = null; } }
|
MediaPlayer支持AAC、AMR、FLAC、MP3、MIDI、OGG、PCM等格式,MediaPlayer可以通过设置元数据和播放源来音频。
播放Raw文件夹下面音频的元数据
1 2 3 4
| MediaPlayer mMediaPlayer; mMediaPlayer=MediaPlayer.create(this, R.raw.audio); mMediaPlayer.start();
|
通过设置播放源来播放音频文件的三个方法
- setDataSource(String path)
1 2 3 4 5 6 7 8 9 10 11 12
|
mMediaPlayer.setDataSource(path);
mMediaPlayer.setDataSource("http://..../xxx.mp3");
mMediaPlayer.prepareAsync();
|
- setDataSource(FileDescriptor fd)
1 2 3 4
| AssetFileDescriptor fd = getAssets().openFd("samsara.mp3"); mMediaPlayer.setDataSource(fd.getFileDescriptor()); mMediaPlayer.prepare();
|
- setDataSource(FileDescptor fd,long offset,long length)
1 2 3 4
| AssetFileDescriptor fd = getAssets().openFd("samsara.mp3"); mMediaPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength()); mMediaPlayer.prepare();
|
设置完数据源,不要忘记prepare(),尽量使用异步prepareAync(),这样不会阻塞UI线程。
播放录音playMedia()和播放测试音频playMediaTest()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public void playMedia() { if (!mIsPlaying) { mExecutorService.submit(new Runnable() { @Override public void run() { mIsPlaying = true; doPlay(getLastFile()); } }); } else { Toast.makeText(mContext, "正在播放", Toast.LENGTH_SHORT).show(); } } public void playMediaTest() { if (mHasPausing && mMediaPlayer != null) { if (mMediaPlayer.isPlaying()) { mMediaPlayer.pause(); mBtMediaPlayerTest.setText("播放测试录音"); } else { mMediaPlayer.start(); mBtMediaPlayerTest.setText("暂停测试录音"); } return; } if (mIsPlaying) { Toast.makeText(mContext, "正在播放", Toast.LENGTH_SHORT).show(); } else { mBtMediaPlayerTest.setText("暂停测试录音"); mExecutorService.submit(new Runnable() { @Override public void run() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mHasPausing = true; mIsPlaying = true; doPlay(new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MediaRecorderDemo/test.m4a")); } } }); } }
|
具体播放方法doPlay()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| private void doPlay(File mediaFile) { try { mMediaPlayer = new MediaPlayer(); mMediaPlayer.setDataSource(mediaFile.getAbsolutePath()); mMediaPlayer.setVolume(1, 1); mMediaPlayer.setLooping(false); mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { Log.d(TAG, "播放完毕!"); stopPlayer(); } }); mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { stopPlayer(); Toast.makeText(mContext, "播放失败", Toast.LENGTH_SHORT).show(); return true; } }); mMediaPlayer.prepare(); mMediaPlayer.start(); } catch (Exception e) { e.printStackTrace(); stopPlayer(); } }
|
播放结束关闭播放器stopPlayer()
1 2 3 4 5 6
| private void stopPlayer(){ mIsPlaying = false; mMediaPlayer.reset(); mMediaPlayer.release(); mMediaPlayer = null; }
|
SoundPool
SoundPool支持多个音频文件同时播放(组合音频也是有上限的),延时短,比较适合短促、密集的场景,适合游戏开发中音效播放。
SoundPool实例化方式
new SoundPool(适用与5.0以下)
1 2 3 4 5
| new SoundPool(int maxStreams, int streamType, int srcQuality)
|
SoundPool.Builder(从5.0开始支持)
1 2 3 4 5 6 7 8 9
| AudioAttributes abs = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build(); SoundPool mSoundPoll = new SoundPool.Builder() .setMaxStreams(100) .setAudioAttributes(abs) .build();
|
SoundPool一些重要方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| int load(AssetFileDescriptor afd, int priority) int load(Context context, int resId, int priority) int load(String path, int priority) int load(FileDescriptor fd, long offset, long length, int priority)
final void pause(int streamID)
final int play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)
final void release()
final void resume(int streamID)
final void setLoop(int streamID, int loop)
void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener)
final void setPriority(int streamID, int priority)
final void setRate(int streamID, float rate)
final void stop(int streamID)
final boolean unload(int soundID)
final void autoPause()
final void autoResume()
|
Ringtone
Ringtone为铃声、通知和其他类似声音提供快速播放的方法,Ringtone实例需要从RingtoneManager获取,RingtoneManager提供系统铃声列表检索方法。
获取实例
1 2 3 4 5 6 7
|
static Ringtone getRingtone(Context context, Uri ringtoneUri)
Ringtone getRingtone(int position)
|
RingtoneManager几个重要的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| RingtoneManager(Activity activity) RingtoneManager(Context context)
static Uri getDefaultUri(int type)
Cursor getCursor()
Uri getRingtoneUri(int position)
static boolean isDefault(Uri ringtoneUri)
static int getDefaultType(Uri defaultRingtoneUri)
static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri)
|
Ringtone例子:播放铃声
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
private void playRingtoneDefault(){ Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE) ; Ringtone mRingtone = RingtoneManager.getRingtone(this,uri); mRingtone.play(); }
private void ShufflePlayback(){ RingtoneManager manager = new RingtoneManager(this) ; Cursor cursor = manager.getCursor(); int count = cursor.getCount() ; int position = (int)(Math.random() * count) ; Ringtone mRingtone = manager.getRingtone(position) ; mRingtone.play(); }
|
总结
- 播放大文件音乐,如WAV无损音频和PCM无压缩音频,可使用更底层的播放方式AudioTrack。它支持流式播放,可以读取(可来自本地和网络)音频流,播放延迟较小。
- 对于延迟度要求不高,并且希望能够更全面的控制音乐的播放,MediaPlayer比较适合。
- 声音短小,延迟度小,并且需要几种声音同时播放的场景,适合使用SoundPool。
- 对于系统类声音的播放和操作,Ringtone更适合。