当前位置: 代码迷 >> Android >> 【Android应用开发技术:传媒开发】音频
  详细解决方案

【Android应用开发技术:传媒开发】音频

热度:18   发布时间:2016-04-27 23:40:13.0
【Android应用开发技术:媒体开发】音频

作者:郭孝星
微博:郭孝星的新浪微博
邮箱:[email protected]
博客:http://blog.csdn.net/allenwells
Github:https://github.com/AllenWells

Android为播放音乐、闹铃、通知铃、来电声音、系统声音、打电话声音和DTMF频道都分别维护了一个隔离的音频流,这是我们能够控制不同音频的前提,这其中大多数的音频流都是被系统限制的,不能胡乱使用。

一 音频控制

默认情况下,按下音量控制键会调节当前被激活的音频流,如果我们的App没有播放任何声音,则会调节闹铃的声音。如果是一个游戏或音乐程序,则需要不管是否正在播放歌曲或者游戏目前是否发出声音,按硬件的音量键都会有相应的音量调节。

Android提供了setVolumeControlStream()的方法来直接控制指定的音频流,我们需要在鉴别出App使用了哪个音频流之后,在Activity或Fragment创建的时候就设置音量控制,这样保证App是否可见,音频功能都能正常工作,如下所示:

setVolumeControlStream(AudioManager.STREAM_MUSIC);

常见的媒体播放控制如下所示:

  • play
  • pause
  • stop
  • skip
  • previous

当我们在软件或者硬件(耳麦线控等)进行上述操作时,系统都会广播一个ACTION_MEDIA_BUTTON的Intent,为了响应那些操作,需要在AndroidManifest.xml文件中注册一个BroadcastReceiver,如下所示:

<receiver android:name=".RemoteControlReceiver">    <intent-filter>        <action android:name="android.intent.action.MEDIA_BUTTON" />    </intent-filter></receiver>

Receiver需要判断这个广播是来自哪个按钮的操作,Intent在 EXTRA_KEY_EVENT 中包含了KEY的信息,同样KeyEvent类包含了一列 KEYCODE_MEDIA_ 的静态变量来表示不同的媒体按钮,如下所示:

public class RemoteControlReceiver extends BroadcastReceiver {    @Override    public void onReceive(Context context, Intent intent) {        if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {            KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);            if (KeyEvent.KEYCODE_MEDIA_PLAY == event.getKeyCode()) {                // Handle key press.            }        }    }}

因为可能有多个程序都同样监听了哪些控制按钮,那么必须在代码中特意控制当前哪个Receiver会进行响应,就就是需要进行适时的注册监听和取消监听,如下所示:

AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);...// Start listening for button pressesam.registerMediaButtonEventReceiver(RemoteControlReceiver);...// Stop listening for button pressesam.unregisterMediaButtonEventReceiver(RemoteControlReceiver);

二 音频焦点

当有多个音频播放音频时,它们的交互对于用户体验就显得很重要,为了防止多个App同时播放音频,Android使用焦点Audio Focus来控制音频播放,只有获得Audio Focus的App才能播放音频,App 播放音频的过程如下所示:

  • 发出请求
  • 接受请求
  • 音频焦点锁定

2.1 请求获取音频焦点

在App播放音频之前。我们需要获取它将要使用的音频流的音频焦点。通过使用requsetAudioFocus()方法来获取想要的音频焦点,如果请求成功,该方法会返回AUDIOFOCUS_REQUEST_GRANTED。

请求焦点的时候,我们需要确定请求的是哪种焦点类型,如下所示:

  • 短暂的焦点锁定:当期待播放一个短暂的音频的时候,例如,播放导航提示。
  • 开启Ducking的短暂焦点锁定:当请求短暂音频焦点的时候,我们可以选择是否开启Ducking。

Ducking是一个特殊的机制使得允许音频间歇性的短暂播放。 通常情况下,一个好的App在失去音频焦点的时候它会立即保持安静。如果我们选择在请求短暂音频焦点的时候开启了Ducking,那意味着其它App可以继续播放,仅仅是在这一刻降低自己的音量,在短暂重新获取到音频焦点后恢复正常音量。

也就是说,不用理会这个请求短暂焦点的请求,这并不会导致目前在播放的音频受到牵制,比如在播放音乐的时候突然出现一个短暂的短信提示声音,这个时候仅仅是把播放歌曲的音量暂时调低,好让短信声能够让用户听到,之后立马恢复正常播放。

  • 永久的焦点锁定:当计划播放可预期到的较长的音频的时候,例如播放音乐。

举例1

在播放音乐的时候请求永久的音频焦点,如下所示:

AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);...// Request audio focus for playbackint result = am.requestAudioFocus(afChangeListener,                                 // Use the music stream.                                 AudioManager.STREAM_MUSIC,                                 // Request permanent focus.                                 AudioManager.AUDIOFOCUS_GAIN);if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {    am.registerMediaButtonEventReceiver(RemoteControlReceiver);    // Start playback.}

一旦结束了播放,我们需要确保调用abandonAudioFocus()方法。这样会通知系统说你不再需要获取焦点并且取消注册AudioManager.OnAudioFocusChangeListener的监听。在释放短暂音频焦点的情况下,这会允许任何被打断的App继续播放。

// Abandon audio focus when playback completeam.abandonAudioFocus(afChangeListener);

举例2

开启Ducking的短暂焦点锁定,如下所示:

// Request audio focus for playbackint result = am.requestAudioFocus(afChangeListener,                             // Use the music stream.                             AudioManager.STREAM_MUSIC,                             // Request permanent focus.                             AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {    // Start playback.}

2.2 处理失去音频焦点

在音频焦点的监听器里面,当接受到描述焦点改变的事件时会触发onAudioFocusChange()回调方法,对于失去焦点有三种类型,如下所示:

  • 短暂的失去焦点:通常在失去焦点的情况下,我们会暂停当前音频的播放或者降低音量,同时需要准备在重新获取焦点之后恢复播放。
  • 开启Ducking的短暂的失去焦点:Ducking是一个特殊的机制使得允许音频间歇性的短暂播放。在Ducking的情况下,正常播放的歌曲会降低音量来凸显**这个短暂的音频声音,这样既让这个短暂的声音比较突出,又不至于打断正常的声音。
  • 永久的失去焦点:假设另外一个程序开始播放音乐,那么我们的程序就应该有效的结束自己。常见的做法是:移除Button监听,允许新的音频播放器独占监听那些按钮时间,并且放弃自己的音频焦点,这种情况下,重新播放你的音频之前。我们需要确保用户重新点击了App的播放按钮。

举例1

短暂的失去焦点,如下所示:

OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {    public void onAudioFocusChange(int focusChange) {        if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT            // Pause playback        } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {            // Resume playback        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {            am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);            am.abandonAudioFocus(afChangeListener);            // Stop playback        }    }};

举例2

播放器在暂时失去焦点时降低音量,并在重新获得音频焦点之后恢复原来的音量,如下所示:

OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {    public void onAudioFocusChange(int focusChange) {        if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK            // Lower the volume        } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {            // Raise it back to normal        }    }};

三 音频设备

用户在播放音乐时有多个选择,可以使用内置的扬声器,有线耳机或者A2DP蓝牙耳机。

A2DP(Advanced Audio Distribution Profile)即蓝牙音频传输模型协定,A2DP是能够采用耳机内的芯片来堆栈数据,达到声音的高清晰度。有A2DP的耳机就是蓝牙立体声耳机。声音能达到44.1kHz,一般的耳机只能达到8kHz。如果手机支持蓝牙,只要装载A2DP协议,就能使用A2DP耳机了。

3.1 检测音频输出设备

选择音频输出设备会影响App的行为,可以使用AudioManager来查询某个音频是输出到扬声器,有线耳机还是蓝牙,如下所示:

if (isBluetoothA2dpOn()) {    // Adjust output for Bluetooth.} else if (isSpeakerphoneOn()) {    // Adjust output for Speakerphone.} else if (isWiredHeadsetOn()) {    // Adjust output for headsets} else {    // If audio plays and noone can hear it, is it still playing?}

2.2 处理音频设备的改变

当有线耳机被拔出或者蓝牙设备断开连接的时候,音频流会自动输出到内置的扬声器上。假设之前播放声音很大,这个时候突然转到扬声器播放会显得非常嘈杂。Android系统会在那种事件发生时会广播带有ACTION_AUDIO_BECOMING_NOISY的intent。无论何时播放音频去注册一个BroadcastReceiver来监听这个intent会是比较好的做法。

在音乐播放器下,用户通常希望发生那样事情的时候能够暂停当前歌曲的播放。在游戏里,通常会选择减低音量。

private class NoisyAudioStreamReceiver extends BroadcastReceiver {    @Override    public void onReceive(Context context, Intent intent) {        if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {            // Pause the playback        }    }}private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);private void startPlayback() {    registerReceiver(myNoisyAudioStreamReceiver(), intentFilter);}private void stopPlayback() {    unregisterReceiver(myNoisyAudioStreamReceiver);}

版权声明:本文为博主原创文章,未经博主允许不得转载。

  相关解决方案