音频开发 -- Android音频基础

一、音频录制

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

你可能感兴趣的:(音频开发 -- Android音频基础)