一、音频录制
MediaRecorder
- 录制的音频文件是经过压缩后的,需要设置编码器。
- 录制的音频文件可以用系统自带的音乐播放器播放。
- 不能对录制的音频文件进一步处理,输出格式不多。
使用:
//AndroidManifest
//MainActivity
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private MediaRecorder mRecorder;
private String mPath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
testMethod();
}
private void testMethod() {
String dir = getExternalCacheDir().getAbsolutePath();
mPath = dir + File.separator + "record.3gp";
Log.d(TAG, "zwm, mPath: " + mPath);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
startRecord();
}
}, 3000);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
stopRecord();
}
}, 10000);
}
private void startRecord() {
Toast.makeText(this, "start record...", Toast.LENGTH_LONG).show();
try {
mRecorder = new MediaRecorder();
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); //从麦克风采集声音数据
mRecorder.setAudioSamplingRate(44100); //设置采样率
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); //设置文件保存格式
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); //设置编码格式
mRecorder.setOutputFile(mPath); //设备文件保存路径
mRecorder.prepare();
mRecorder.start();
} catch (IOException e) {
e.printStackTrace();
}
}
private void stopRecord(){
Toast.makeText(this, "stop record", Toast.LENGTH_LONG).show();
mRecorder.stop();
mRecorder.release();
mRecorder = null;
}
}
//输出log
08-31 21:28:05.681 zwm, onCreate
08-31 21:28:05.686 zwm, mPath: /storage/emulated/0/Android/data/com.tomorrow.bustest/cache/record.3gp
//输出文件
/storage/emulated/0/Android/data/com.tomorrow.bustest/cache/record.3gp
AudioRecord
- 录制的音频文件是PCM格式的。
- 录制的音频文件不能用系统自带的音乐播放器播放,需要用AudioTrack来播放。
- 可以对录制的音频文件进一步处理。
使用:
//AndroidManifest
//MainActivity
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private AudioRecord mAudioRecord;
private String mPCMFilePath;
private String mWAVFilePath;
private int mBufferSizeInBytes;
private volatile boolean mRecording = false;// 设置正在录制的状态
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
testMethod();
}
private void testMethod() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
startRecord();
}
}, 3000);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
stopRecord();
}
}, 6000);
}
private void startRecord() {
Toast.makeText(this, "start record...", Toast.LENGTH_LONG).show();
int audioSource = MediaRecorder.AudioSource.MIC;
int sampleRate = 44100;
int channels = AudioFormat.CHANNEL_IN_MONO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
//创建AudioRecord对象
mAudioRecord = createAudioRecord(audioSource, sampleRate, channels, audioFormat);
if(mAudioRecord == null) {
return;
}
//开始录音
mAudioRecord.startRecording();
mRecording = true;
new Thread(new Runnable() {
@Override
public void run() {
writeDateToFile();
pcmToWav(mPCMFilePath, mWAVFilePath, mBufferSizeInBytes);
}
}).start();
}
private AudioRecord createAudioRecord(int audioSource, int sampleRate, int channels, int audioFormat) {
String dir = getExternalCacheDir().getAbsolutePath();
mPCMFilePath = dir + File.separator + "PCMAudioFile.pcm";
mWAVFilePath = dir + File.separator + "WAVAudioFile.wav";
Log.d(TAG, "zwm, mPCMFilePath: " + mPCMFilePath);
Log.d(TAG, "zwm, mWAVFilePath: " + mWAVFilePath);
//获取一帧音频帧的大小
int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channels, audioFormat);
Log.d(TAG, "zwm, minBufferSize:" + minBufferSize);
if (minBufferSize == AudioRecord.ERROR_BAD_VALUE) {
Log.d(TAG, "zwm, getMinBufferSize fail");
return null;
}
//AudioRecord内部缓冲设置为4帧音频帧的大小
mBufferSizeInBytes = minBufferSize * 4;
Log.d(TAG, "zwm, mBufferSizeInBytes:" + mBufferSizeInBytes);
AudioRecord audioRecord = new AudioRecord(audioSource, sampleRate,
channels, audioFormat, mBufferSizeInBytes);
if (audioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
Log.d(TAG, "zwm, init AudioRecord fail");
return null;
}
return audioRecord;
}
private void writeDateToFile() {
Log.d(TAG, "zwm, writeDateToFile");
//new一个byte数组用来存一些字节数据,大小为缓冲区大小
byte[] audiodata = new byte[mBufferSizeInBytes];
FileOutputStream fos = null;
int readsize;
try {
File file = new File(mPCMFilePath);
if (file.exists()) {
file.delete();
}
//创建一个可存取字节的文件
fos = new FileOutputStream(file);
} catch (Exception e) {
e.printStackTrace();
}
if(fos == null) {
return;
}
while (mRecording) {
readsize = mAudioRecord.read(audiodata, 0, mBufferSizeInBytes);
Log.d(TAG, "zwm, read size: " + readsize);
if (readsize != AudioRecord.ERROR_INVALID_OPERATION) {
try {
fos.write(audiodata);
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void pcmToWav(String inFilename, String outFilename, int bufferSizeInBytes) {
Log.d(TAG, "zwm, pcmToWav");
FileInputStream in;
FileOutputStream out;
long totalAudioLen;
long totalDataLen;
long sampleRate = 44100;
int channels = 1; //AudioFormat.CHANNEL_IN_MONO
long byteRate = 16 * sampleRate * channels / 8;
byte[] data = new byte[bufferSizeInBytes];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36;
writeWaveFileHeader(out, totalAudioLen, totalDataLen,
sampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen,
long longSampleRate, int channels, long byteRate) throws IOException {
Log.d(TAG, "zwm, writeWaveFileHeader");
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);
}
private void stopRecord() {
Toast.makeText(this, "stop record", Toast.LENGTH_LONG).show();
mRecording = false;// 停止文件写入
mAudioRecord.stop();
mAudioRecord.release();// 释放资源
mAudioRecord = null;
}
}
//输出log
09-03 17:54:30.169 zwm, onCreate
09-03 17:54:33.269 zwm, mPCMFilePath: /storage/emulated/0/Android/data/com.tomorrow.bustest/cache/PCMAudioFile.pcm
09-03 17:54:33.269 zwm, mWAVFilePath: /storage/emulated/0/Android/data/com.tomorrow.bustest/cache/WAVAudioFile.wav
09-03 17:54:33.273 zwm, minBufferSize:3528
09-03 17:54:33.273 zwm, mBufferSizeInBytes:14112
09-03 17:54:33.310 zwm, writeDateToFile
09-03 17:54:33.527 zwm, read size: 14112
09-03 17:54:33.830 zwm, read size: 14112
09-03 17:54:33.993 zwm, read size: 14112
09-03 17:54:34.148 zwm, read size: 14112
09-03 17:54:34.307 zwm, read size: 14112
09-03 17:54:34.467 zwm, read size: 14112
09-03 17:54:34.627 zwm, read size: 14112
09-03 17:54:34.787 zwm, read size: 14112
09-03 17:54:34.952 zwm, read size: 14112
09-03 17:54:35.107 zwm, read size: 14112
09-03 17:54:35.268 zwm, read size: 14112
09-03 17:54:35.427 zwm, read size: 14112
09-03 17:54:35.590 zwm, read size: 14112
09-03 17:54:35.753 zwm, read size: 14112
09-03 17:54:35.907 zwm, read size: 14112
09-03 17:54:36.072 zwm, read size: 14112
09-03 17:54:36.209 zwm, read size: 13536
09-03 17:54:36.209 zwm, pcmToWav
09-03 17:54:36.213 zwm, writeWaveFileHeader
//输出文件
/storage/emulated/0/Android/data/com.tomorrow.bustest/cache/PCMAudioFile.pcm
/storage/emulated/0/Android/data/com.tomorrow.bustest/cache/WAVAudioFile.wav
AudioRecord
二、音频播放
MediaPlayer
适合在后台长时间播放本地音乐文件或者在线的流式资源,其内部播放音频依赖AudioTrack。
使用:
//MainActivity
private MediaPlayer mMediaPlayer;
private void testMethod() {
play();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
pause();
}
}, 5000);
}
private void play() {
Log.d(TAG, "zwm, play, thread: " + Thread.currentThread().getName());
String dir = getExternalCacheDir().getAbsolutePath();
Log.d(TAG, "zwm, dir: " + dir);
File dirFile = new File(dir);
if(!dirFile.exists()) {
dirFile.mkdirs();
}
String path = dir + File.separator + "Over_the_Horizon.mp3";
Log.d(TAG, "zwm, path: " + path);
File file = new File(path);
if(!file.exists()) {
Log.d(TAG, "zwm, path not exist");
return;
}
mMediaPlayer = new MediaPlayer();
try {
mMediaPlayer.setDataSource(path);
mMediaPlayer.setLooping(true);
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
Log.d(TAG, "zwm, onPrepared, thread: " + Thread.currentThread().getName());
mMediaPlayer.start();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
private void pause() {
Log.d(TAG, "zwm, pause, thread: " + Thread.currentThread().getName());
mMediaPlayer.pause();
}
//输出log
2019-09-05 19:20:34.136 zwm, play, thread: main
2019-09-05 19:20:34.139 zwm, dir: /storage/emulated/0/Android/data/com.tomorrow.testnetworkcache/cache
2019-09-05 19:20:34.140 zwm, path: /storage/emulated/0/Android/data/com.tomorrow.testnetworkcache/cache/Over_the_Horizon.mp3
2019-09-05 19:20:35.536 zwm, onPrepared, thread: main
2019-09-05 19:20:39.167 zwm, pause, thread: main
MediaPlayer
SoundPool
适合播放比较短的音频片段,比如游戏声音、按键声、铃声片段等。
使用:
//MainActivity
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void testMethod() {
Log.d(TAG, "zwm, testMethod");
SoundPool.Builder builder = new SoundPool.Builder();
builder.setMaxStreams(1);
AudioAttributes.Builder attrBuilder = new AudioAttributes.Builder();
attrBuilder.setLegacyStreamType(AudioManager.STREAM_MUSIC);
builder.setAudioAttributes(attrBuilder.build());
SoundPool soundPool = builder.build();
final int voiceId = soundPool.load(this, R.raw.sport_beep, 1);
soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
@Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
Log.d(TAG, "zwm, onLoadComplete, thread: " + Thread.currentThread().getName());
soundPool.play(voiceId, 1, 1, 1, 0, 1);
}
});
}
//输出log
2019-09-05 20:03:32.249 zwm, testMethod
2019-09-05 20:03:32.711 zwm, onLoadComplete, thread: main
SoundPool
AudioTrack
只能播放PCM数据,更接近底层,使用灵活,支持低延迟播放。
使用stream模式播放:
//MainActivity
private void testMethod() {
playInModeStream();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void playInModeStream() {
Log.d(TAG, "zwm, playInModeStream");
final int minBufferSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
Log.d(TAG, "zwm, minBufferSize: " + minBufferSize);
final AudioTrack audioTrack = new AudioTrack(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
new AudioFormat.Builder().setSampleRate(44100)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build(),
minBufferSize,
AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE);
Log.d(TAG, "zwm, play");
audioTrack.play();
String path = getExternalCacheDir() + File.separator + "PCMAudioFile.pcm";
Log.d(TAG, "zwm, path: " + path);
File file = new File(path);
if(!file.exists()) {
Log.d(TAG, "zwm, file not exist");
return;
}
try {
final FileInputStream fileInputStream = new FileInputStream(file);
new Thread(new Runnable() {
@Override public void run() {
Log.d(TAG, "zwm, run thread: " + Thread.currentThread().getName());
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) {
Log.d(TAG, "zwm, write readCount: " + readCount);
audioTrack.write(tempBuffer, 0, readCount);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
//输出log
2019-09-05 20:47:42.558 zwm, playInModeStream
2019-09-05 20:47:42.559 zwm, minBufferSize: 7088
2019-09-05 20:47:42.564 zwm, play
2019-09-05 20:47:42.571 zwm, path: /storage/emulated/0/Android/data/com.tomorrow.testnetworkcache/cache/PCMAudioFile.pcm
2019-09-05 20:47:42.572 zwm, run thread: Thread-12
2019-09-05 20:47:42.576 zwm, write readCount: 7088
2019-09-05 20:47:42.576 zwm, write readCount: 7088
2019-09-05 20:47:42.712 zwm, write readCount: 7088
2019-09-05 20:47:42.746 zwm, write readCount: 7088
2019-09-05 20:47:42.803 zwm, write readCount: 7088
2019-09-05 20:47:42.883 zwm, write readCount: 7088
2019-09-05 20:47:42.963 zwm, write readCount: 7088
2019-09-05 20:47:43.043 zwm, write readCount: 7088
2019-09-05 20:47:43.123 zwm, write readCount: 7088
2019-09-05 20:47:43.203 zwm, write readCount: 7088
2019-09-05 20:47:43.283 zwm, write readCount: 7088
2019-09-05 20:47:43.383 zwm, write readCount: 7088
2019-09-05 20:47:43.683 zwm, write readCount: 7088
2019-09-05 20:47:43.763 zwm, write readCount: 7088
2019-09-05 20:47:43.843 zwm, write readCount: 7088
2019-09-05 20:47:43.923 zwm, write readCount: 7088
2019-09-05 20:47:44.003 zwm, write readCount: 7088
2019-09-05 20:47:44.103 zwm, write readCount: 7088
2019-09-05 20:47:44.165 zwm, write readCount: 7088
2019-09-05 20:47:44.248 zwm, write readCount: 7088
2019-09-05 20:47:44.347 zwm, write readCount: 7088
2019-09-05 20:47:44.407 zwm, write readCount: 7088
2019-09-05 20:47:44.825 zwm, write readCount: 7088
2019-09-05 20:47:44.887 zwm, write readCount: 7088
2019-09-05 20:47:45.051 zwm, write readCount: 7088
2019-09-05 20:47:45.147 zwm, write readCount: 7088
2019-09-05 20:47:45.286 zwm, write readCount: 7088
2019-09-05 20:47:45.390 zwm, write readCount: 2880
使用static模式播放:
//MainActivity
private byte[] audioData;
private void playInModeStatic() {
// static模式,需要将音频数据一次性write到AudioTrack的内部缓冲区
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
String path = getExternalCacheDir() + File.separator + "PCMAudioFile.pcm";
Log.d(TAG, "zwm, doInBackground, path: " + path);
File file = new File(path);
if(!file.exists()) {
Log.d(TAG, "zwm, file not exist");
return null;
}
try {
FileInputStream in = new FileInputStream(file);
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int b; (b = in.read()) != -1; ) {
out.write(b);
}
Log.d(TAG, "zwm, get data");
audioData = out.toByteArray();
} finally {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onPostExecute(Void v) {
Log.d(TAG, "zwm, onPostExecute, data length: " + audioData.length);
AudioTrack audioTrack = new AudioTrack(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
new AudioFormat.Builder().setSampleRate(44100)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build(),
audioData.length,
AudioTrack.MODE_STATIC,
AudioManager.AUDIO_SESSION_ID_GENERATE);
Log.d(TAG, "zwm, write data");
audioTrack.write(audioData, 0, audioData.length);
Log.d(TAG, "zwm, play");
audioTrack.play();
}
}.execute();
}
//输出log
2019-09-06 09:11:11.351 zwm, doInBackground, path: /storage/emulated/0/Android/data/com.tomorrow.testnetworkcache/cache/PCMAudioFile.pcm
2019-09-06 09:11:15.203 zwm, get data
2019-09-06 09:11:15.204 zwm, onPostExecute, data length: 258048
2019-09-06 09:11:15.222 zwm, write data
2019-09-06 09:11:15.223 zwm, play
三、音频焦点(Audio Focus)
在Android设备上有许多App可以播放音频,当所有的音频混合在一起的时候就会导致很差的用户体验。Android系统提供了一个API来让所有的App分享音频焦点,在同一时刻只有一个App可以持有音频焦点。
音频焦点是具有协作性质的,它依赖于各个App遵守音频焦点的使用准则,系统不会强制要求App去遵守这些准则。如果一个应用想在失去音频焦点之后还可以继续的大声播放,这个事情是无法被阻止的,然而这会导致非常糟糕的用户体验,只有在被授权了音频焦点之后,才应该去播放音频。
获取音频焦点与音频焦点丢失
//调用方法
AudioManager#requestAudioFocus:
public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)
//OnAudioFocusChangeListener接口里面只有一个方法:public void onAudioFocusChange(int focusChange),该方法会在音频焦点状态变化的时候被调用。
//streamType用来表示音频流类型,如AudioManager.STREAM_MUSIC。
//durationHint用来表示获取焦点的时长,同时通知其它获取了音频焦点的OnAudioFocusChangeListener该如何相互配合。
//如果获取成功,会返回int值AudioManager.AUDIOFOCUS_REQUEST_GRANTED,如果获取失败,则返回int值AudioManager.AUDIOFOCUS_REQUEST_FAILED。
//例子
int result = mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
durationHint取值:
- AudioManager.AUDIOFOCUS_GAIN
代表此次申请的音频焦点需要长时间持有,原本获取了音频焦点的OnAudioFocusChangeListener接口将会回调onAudioFocusChange(int focusChange)方法,传入的参数为AUDIOFOCUS_LOSS。 - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
代表此次申请的音频焦点只需短暂持有,原本获取了音频焦点的OnAudioFocusChangeListener接口将会回调onAudioFocusChange(int focusChange)方法,传入的参数为AUDIOFOCUS_LOSS_TRANSIENT。按照官方注释:在接收到事件通知等情景时可使用该durationHint。 - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
代表此次申请的音频焦点只需短暂持有,原本获取了音频焦点的OnAudioFocusChangeListener接口将会回调onAudioFocusChange(int focusChange)方法,传入的参数为AUDIOFOCUS_LOSS_TRANSIENT。按照官方注释:在需要录音或语音识别等情景时可使用该durationHint。 - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
代表此次申请不需要暂停其它申请的音频播放,但是需要其降低音量。原本获取了音频焦点的OnAudioFocusChangeListener接口将会回调onAudioFocusChange(int focusChange)方法,传入的参数为AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK。
释放音频焦点
//调用方法
AudioManager#abandonAudioFocus:
public int abandonAudioFocus(OnAudioFocusChangeListener l)
音频焦点状态变化回调(注意:主动获取或释放音频焦点不会收到回调事件)
//回调方法
AudioManager.OnAudioFocusChangeListener#onAudioFocusChange:
public void onAudioFocusChange(int focusChange)
focusChange取值:
- AUDIOFOCUS_GAIN
重新获取到音频焦点时触发的状态。 - AUDIOFOCUS_LOSS
失去音频焦点时触发的状态,且该状态应该会长期保持,此时应当暂停音频并释放音频相关的资源。 - AUDIOFOCUS_LOSS_TRANSIENT
失去音频焦点时触发的状态,但是该状态不会长时间保持,此时应当暂停音频,且当重新获取音频焦点的时候继续播放。 - AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
失去音频焦点时触发的状态,在该状态的时候不需要暂停音频,但是应该降低音频的声音。
例子:
//App1
public class MainActivity extends Activity {
private static final String TAG = "App1";
private Button mPlay;
private Button mPause;
private MediaPlayer mMediaPlayer;
private AudioManager mAudioManager;
private MyAudioFocusChangeListener mAudioFocusChangeListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, onCreate");
setContentView(R.layout.activity_main);
mPlay = (Button)findViewById(R.id.play);
mPause = (Button)findViewById(R.id.pause);
mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
mAudioFocusChangeListener = new MyAudioFocusChangeListener();
String dir = getExternalCacheDir().getAbsolutePath();
String path = dir + File.separator + "Over the Horizon.mp3";
Log.d(TAG, "zwm, path: " + path);
mMediaPlayer = new MediaPlayer();
try {
mMediaPlayer.setDataSource(path);
} catch (IOException e) {
Log.d(TAG, "zwm, IOException: " + e.getMessage());
}
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
Log.d(TAG, "zwm, onPrepared");
}
});
mPlay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "zwm, click play");
if(!mMediaPlayer.isPlaying()) {
int result = mAudioManager.requestAudioFocus(mAudioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
Log.d(TAG, "zwm, result: " + result);
if(result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(TAG, "zwm, start to play");
mMediaPlayer.start();
}
}
}
});
mPause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "zwm, click pause");
if(mMediaPlayer.isPlaying()) {
Log.d(TAG, "zwm, pause playing");
mAudioManager.abandonAudioFocus(mAudioFocusChangeListener);
mMediaPlayer.pause();
}
}
});
}
class MyAudioFocusChangeListener implements AudioManager.OnAudioFocusChangeListener {
private int mOriginalVol;
private int mPreviousState = 0;
@Override
public void onAudioFocusChange(int focusChange) {
Log.d(TAG, "zwm, focusChange: " + focusChange + ", mPreviousState: " + mPreviousState);
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
if(mPreviousState == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
Log.d(TAG, "zwm, mOriginalVol: " + mOriginalVol);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mOriginalVol, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
Log.d(TAG, "zwm, start to play");
mMediaPlayer.start();
break;
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
Log.d(TAG, "zwm, pause playing");
mMediaPlayer.pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
mOriginalVol = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
Log.d(TAG, "zwm, mOriginalVol: " + mOriginalVol);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mOriginalVol/2, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
break;
}
mPreviousState = focusChange;
}
}
}
//App2
public class MainActivity extends Activity {
private static final String TAG = "App2";
private Button mPlay;
private Button mPause;
private MediaPlayer mMediaPlayer;
private AudioManager mAudioManager;
private MyAudioFocusChangeListener mAudioFocusChangeListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, onCreate");
setContentView(R.layout.activity_main);
mPlay = (Button)findViewById(R.id.play);
mPause = (Button)findViewById(R.id.pause);
mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
mAudioFocusChangeListener = new MyAudioFocusChangeListener();
String dir = getExternalCacheDir().getAbsolutePath();
String path = dir + File.separator + "知否知否.mp3";
Log.d(TAG, "zwm, path: " + path);
mMediaPlayer = new MediaPlayer();
try {
mMediaPlayer.setDataSource(path);
} catch (IOException e) {
Log.d(TAG, "zwm, IOException: " + e.getMessage());
}
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
Log.d(TAG, "zwm, onPrepared");
}
});
mPlay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "zwm, click play");
if(!mMediaPlayer.isPlaying()) {
int result = mAudioManager.requestAudioFocus(mAudioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
Log.d(TAG, "zwm, result: " + result);
if(result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(TAG, "zwm, start to play");
mMediaPlayer.start();
}
}
}
});
mPause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "zwm, click pause");
if(mMediaPlayer.isPlaying()) {
Log.d(TAG, "zwm, pause playing");
mAudioManager.abandonAudioFocus(mAudioFocusChangeListener);
mMediaPlayer.pause();
}
}
});
}
class MyAudioFocusChangeListener implements AudioManager.OnAudioFocusChangeListener {
private int mOriginalVol;
private int mPreviousState = 0;
@Override
public void onAudioFocusChange(int focusChange) {
Log.d(TAG, "zwm, focusChange: " + focusChange + ", mPreviousState: " + mPreviousState);
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
if(mPreviousState == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
Log.d(TAG, "zwm, mOriginalVol: " + mOriginalVol);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mOriginalVol, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
Log.d(TAG, "zwm, start to play");
mMediaPlayer.start();
break;
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
Log.d(TAG, "zwm, pause playing");
mMediaPlayer.pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
mOriginalVol = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
Log.d(TAG, "zwm, mOriginalVol: " + mOriginalVol);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mOriginalVol/2, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
break;
}
mPreviousState = focusChange;
}
}
}
//App1输出log
09-07 18:07:29.561 15152-15152/com.tomorrow.androidtest1 D/App1: zwm, onCreate
09-07 18:07:29.719 15152-15152/com.tomorrow.androidtest1 D/App1: zwm, path: /storage/emulated/0/Android/data/com.tomorrow.androidtest1/cache/Over the Horizon.mp3
09-07 18:07:29.804 15152-15152/com.tomorrow.androidtest1 D/App1: zwm, onPrepared
09-07 18:07:36.938 15152-15152/com.tomorrow.androidtest1 D/App1: zwm, click play
09-07 18:07:36.944 15152-15152/com.tomorrow.androidtest1 D/App1: zwm, result: 1
09-07 18:07:36.944 15152-15152/com.tomorrow.androidtest1 D/App1: zwm, start to play
09-07 18:07:45.157 15152-15152/com.tomorrow.androidtest1 D/App1: zwm, focusChange: -3, mPreviousState: 0
09-07 18:07:45.160 15152-15152/com.tomorrow.androidtest1 D/App1: zwm, mOriginalVol: 15
09-07 18:07:51.016 15152-15152/com.tomorrow.androidtest1 D/App1: zwm, focusChange: 1, mPreviousState: -3
09-07 18:07:51.017 15152-15152/com.tomorrow.androidtest1 D/App1: zwm, mOriginalVol: 15
09-07 18:07:51.054 15152-15152/com.tomorrow.androidtest1 D/App1: zwm, start to play
09-07 18:07:55.438 15152-15152/com.tomorrow.androidtest1 D/App1: zwm, click pause
09-07 18:07:55.439 15152-15152/com.tomorrow.androidtest1 D/App1: zwm, pause playing
//App2输出log
09-07 18:07:31.214 15223-15223/com.tomorrow.androidtest2 D/App2: zwm, onCreate
09-07 18:07:31.374 15223-15223/com.tomorrow.androidtest2 D/App2: zwm, path: /storage/emulated/0/Android/data/com.tomorrow.androidtest2/cache/知否知否.mp3
09-07 18:07:31.457 15223-15223/com.tomorrow.androidtest2 D/App2: zwm, onPrepared
09-07 18:07:45.151 15223-15223/com.tomorrow.androidtest2 D/App2: zwm, click play
09-07 18:07:45.157 15223-15223/com.tomorrow.androidtest2 D/App2: zwm, result: 1
09-07 18:07:45.157 15223-15223/com.tomorrow.androidtest2 D/App2: zwm, start to play
09-07 18:07:51.014 15223-15223/com.tomorrow.androidtest2 D/App2: zwm, click pause
09-07 18:07:51.014 15223-15223/com.tomorrow.androidtest2 D/App2: zwm, pause playing
四、读取MP3元数据
//MainActivity
public class MainActivity extends Activity {
private static final String TAG = "App1";
private ImageView imageView;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, onCreate");
setContentView(R.layout.activity_main);
imageView = (ImageView)findViewById(R.id.imageview);
imageView.setImageResource(R.mipmap.ic_launcher);
String dir = getExternalCacheDir().getAbsolutePath();
String path = dir + File.separator + "Over the Horizon.mp3";
Log.d(TAG, "zwm, path: " + path);
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(path);
Log.d(TAG, "zwm, parseMp3File名称: " + mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
Log.d(TAG, "zwm, parseMp3File专辑: " + mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
Log.d(TAG, "zwm, parseMp3File歌手: " + mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST));
Log.d(TAG, "zwm, parseMp3File码率: " + mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
Log.d(TAG, "zwm, parseMp3File时长: " + mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
Log.d(TAG, "zwm, parseMp3File类型: " + mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
MediaExtractor mex = new MediaExtractor();
try {
mex.setDataSource(path);
} catch (IOException e) {
e.printStackTrace();
}
MediaFormat mf = mex.getTrackFormat(0);
int sampleRate = mf.getInteger(MediaFormat.KEY_SAMPLE_RATE);
mex.release();
Log.d(TAG, "zwm, parseMp3File频率: " + sampleRate);
byte[] data = mmr.getEmbeddedPicture();
if(data != null) {
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
imageView.setImageBitmap(bitmap);
}
mmr.release();
}
}
//输出log
09-07 20:56:04.817 28146-28146/com.tomorrow.androidtest1 D/App1: zwm, onCreate
09-07 20:56:05.075 28146-28146/com.tomorrow.androidtest1 D/App1: zwm, path: /storage/emulated/0/Android/data/com.tomorrow.androidtest1/cache/Over the Horizon.mp3
09-07 20:56:05.106 28146-28146/com.tomorrow.androidtest1 D/App1: zwm, parseMp3File名称: Over the Horizon
09-07 20:56:05.106 28146-28146/com.tomorrow.androidtest1 D/App1: zwm, parseMp3File专辑: Brand Music
09-07 20:56:05.107 28146-28146/com.tomorrow.androidtest1 D/App1: zwm, parseMp3File歌手: Samsung
09-07 20:56:05.107 28146-28146/com.tomorrow.androidtest1 D/App1: zwm, parseMp3File码率: 192000
09-07 20:56:05.108 28146-28146/com.tomorrow.androidtest1 D/App1: zwm, parseMp3File时长: 191242
09-07 20:56:05.108 28146-28146/com.tomorrow.androidtest1 D/App1: zwm, parseMp3File类型: audio/mpeg
09-07 20:56:05.129 28146-28146/com.tomorrow.androidtest1 D/App1: zwm, parseMp3File频率: 44100