美文网首页
音频开发 -- Android音频基础

音频开发 -- Android音频基础

作者: TomyZhang | 来源:发表于2019-09-14 12:12 被阅读0次

    一、音频录制

    MediaRecorder

    • 录制的音频文件是经过压缩后的,需要设置编码器。
    • 录制的音频文件可以用系统自带的音乐播放器播放。
    • 不能对录制的音频文件进一步处理,输出格式不多。

    使用:

    //AndroidManifest
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    
    //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
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    
    //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<Void, Void, Void>() {
            @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音频基础

          本文链接:https://www.haomeiwen.com/subject/uzhuyctx.html