管理音频播放,音频焦点,输出设备切换AudiaoManager

来源:互联网 发布:藏剑二少捏脸数据 编辑:程序博客网 时间:2024/05/22 13:34

参照http://blog.csdn.net/kesenhoo/article/details/7381129,关键东西用红色标记

一个好的用户体验是可预期可控的。如果App是在播放音频,那么显然我们需要做到能够通过硬件按钮,软件按钮,蓝牙耳麦等来控制音量。

同样的,我们需要能够进行play, stop, pause, skip, and previous等动作,并且进行对应的响应。

Identify Which Audio Stream to Use [鉴别使用的是哪个音频流]

  • 首先需要知道的是我们的App会使用到哪些音频流。
  • Android为播放音乐,闹铃,通知铃,来电声音,系统声音,打电话声音与DTMF频道分别维护了一个隔离的音频流。这是我们能够控制不同音频的前提。
  • 其中大多数都是被系统限制的,不能胡乱使用。除了你的App是需要做替换闹钟的铃声的操作,那么几乎其他的播放音频操作都是使用"STREAM_MUSIC"音频流。

Use Hardware Volume Keys to Control Your App’s Audio Volume [使用硬件音量键来控制App的音量]

  • 默认情况下,按下音量控制键会调节当前被激活的音频流,如果你的App没有在播放任何声音,则会调节响铃的声音。
  • 如果是一个游戏或者音乐程序,需要在不管是否目前正在播放歌曲或者游戏目前是否发出声音的时候,按硬件的音量键都会有对应的音量调节。
  • 我们需要监听音量键是否被按下,Android提供了setVolumeControlStream()的方法来直接控制指定的音频流。
  • 在鉴别出App会使用哪个音频流之后,需要在Activity或者Fragment创建的时候就设置音量控制,这样能确保不管App是否可见,音频控制功能都正常工作。
setVolumeControlStream(AudioManager.STREAM_MUSIC);

Use Hardware Playback Control Keys to Control Your App’s Audio Playback [使用硬件的播放控制按键来控制App的音频播放]

  • 媒体播放按钮,例如play, pause, stop, skip, and previous的功能同样可以在一些线控,耳麦或者其他无线控制设备上实现。
  • 无论用户按下上面任何设备上的控制按钮,系统都会广播一个带有ACTION_MEDIA_BUTTON的Intent。
  • 为了响应那些操作,需要像下面一样注册一个BroadcastReceiver在Manifest文件中。
  1. <receiver android:name=".RemoteControlReceiver">  
  2.     <intent-filter>  
  3.         <action android:name="android.intent.action.MEDIA_BUTTON" />  
  4.     </intent-filter>  
  5. </receiver>  
  • Receiver需要判断这个广播是来自哪个按钮的操作,Intent在EXTRA_KEY_EVENT中包含了KEY的信息,同样KeyEvent类包含了一列KEYCODE_MEDIA_*的静态变量来表示不同的媒体按钮,such as KEYCODE_MEDIA_PLAY_PAUSE and KEYCODE_MEDIA_NEXT.
  1. public class RemoteControlReceiver extends BroadcastReceiver {  
  2.     @Override  
  3.     public void onReceive(Context context, Intent intent) {  
  4.         if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {  
  5.             KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);  
  6.             if (KeyEvent.KEYCODE_MEDIA_PLAY == event.getKeyCode()) {  
  7.                 // Handle key press.  
  8.             }  
  9.         }  
  10.     }  
  11. }  
  • 因为可能有多个程序都同样监听了哪些控制按钮,那么必须在代码中特意控制当前哪个Receiver会进行响应。下面的例子显示了如何使用AudioManager来注册监听与取消监听,当Receiver被注册上时,它将是唯一响应Broadcast的Receiver。
  1. AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);  
  2. ...  
  3.   
  4. // Start listening for button presses  
  5. am.registerMediaButtonEventReceiver(RemoteControlReceiver);  
  6. ...  
  7.   
  8. // Stop listening for button presses  
  9. am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);  
  • 通常,App需要在Receiver没有激活或者不可见的时候(比如在onStop的方法里面)取消注册监听。但是在媒体播放的时候并没有那么简单,实际上,我们需要在后台播放歌曲的时候同样需要进行响应。

Managing Audio Focus[管理音频焦点]

  • 很多App都可以播放音频,因此在播放前如何获取到音频焦点就显得很重要了,这样可以避免同时出现多个声音,Android使用audio focus来节制音频的播放,仅仅是获取到audio focus的App才能够播放音频。
  • 在App开始播放音频之前,它需要经过发出请求[request]->接受请求[receive]->音频焦点锁定[Audio Focus]的过程。同样,它需要知道如何监听失去音频焦点[lose of audio focus]的事件并进行合适的响应。

Request the Audio Focus [请求获取音频焦点]

  • 通过call requestAudioFocus() 方法来获取你想要获取到的音频流焦点。如果请求成功这个方法会返回 AUDIOFOCUS_REQUEST_GRANTED 。
  • 我们必须指定正在使用哪个音频流,而且是否想请求短暂还是永久的audio focus。
  • 短暂的焦点锁定(AUDIOFOCUS_GAIN_TRANSIENT):当期待播放一个短暂的音频的时候(比如播放导航指示)
  • 永久的焦点锁定(AUDIOFOCUS_REQUEST_GRANTED):当计划播放可预期到的较长的音频的时候(比如播放音乐)
  • AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK Duck我们应用跟其他应用共用焦点 我们播放的时候其他音频会降低音量
  • 下面是一个在播放音乐的时候请求永久的音频焦点的例子,我们必须在开始播放之前立即请求音频焦点,比如在用户点击播放或者游戏程序中下一关开始的片头音乐。
  1. AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);  
  2. ...  
  3.   
  4. // Request audio focus for playback  
  5. int result = am.requestAudioFocus(afChangeListener,  
  6.                                  // Use the music stream.  
  7.                                  AudioManager.STREAM_MUSIC,  
  8.                                  // Request permanent focus.  
  9.                                  AudioManager.AUDIOFOCUS_GAIN);  
  10.      
  11. if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {  
  12.     am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);  
  13.     // Start playback.  
  14. }  
  • 一旦结束了播放,需要确保call abandonAudioFocus()方法。这样会通知系统说你不再需要获取焦点并且取消注册AudioManager.OnAudioFocusChangeListener的监听。在这样释放短暂音频焦点的case下,可以允许任何打断的App继续播放。
  1. // Abandon audio focus when playback complete      
  2. am.abandonAudioFocus(afChangeListener);  
  • 当请求短暂音频焦点的时候,我们可以选择是否开启“ducking”。
  • Ducking是一个特殊的机制使得允许音频间歇性的短暂播放。
  • 通常情况下,一个好的App在失去音频焦点的时候它会立即保持安静。如果我们选择在请求短暂音频焦点的时候开启了ducking,那意味着其它App可以继续播放,仅仅是在这一刻降低自己的音量,在短暂重新获取到音频焦点后恢复正常音量[也就是说:不用理会这个请求短暂焦点的请求,这并不会导致目前在播放的音频受到牵制,比如在播放音乐的时候突然出现一个短暂的短信提示声音,这个时候仅仅是把播放歌曲的音量暂时调低,好让短信声能够让用户听到,之后立马恢复正常播放]。
  1. // Request audio focus for playback  
  2. int result = am.requestAudioFocus(afChangeListener,  
  3.                              // Use the music stream.  
  4.                              AudioManager.STREAM_MUSIC,  
  5.                              // Request permanent focus.  
  6.                              AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);  
  7.      
  8. if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {  
  9.     // Start playback.  
  10. }  

Handle the Loss of Audio Focus[处理失去音频焦点]

  • 如果A程序可以请求获取音频焦点,那么在B程序请求获取的时候,A获取到的焦点就会失去。显然我们需要处理失去焦点的事件。
  • 在音频焦点的监听器里面,当接受到描述焦点改变的事件时会触发 onAudioFocusChange()回调方法。对应于获取焦点的三种类型,我们同样会有三种失去焦点的类型。
    • 失去短暂焦点(AUDIOFOCUS_LOSS_TRANSIENT  ):通常在失去这种焦点的情况下,我们会暂停当前音频的播放或者降低音量,同时需要准备恢复播放在重新获取到焦点之后。
    • 失去永久焦点(AUDIOFOCUS_LOSS):假设另外一个程序开始播放音乐等,那么我们的程序就应该有效的结束自己。实用的做法是停止播放,移除button监听,允许新的音频播放器独占监听那些按钮事件,并且放弃自己的音频焦点。
    • 在重新播放器自己的音频之前,我们需要确保用户重新点击自己App的播放按钮等。
  1. OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {  
  2.     public void onAudioFocusChange(int focusChange) {  
  3.         if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT  
  4.             // Pause playback  
  5.         } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {  
  6.             // Resume playback   
  7.         } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {  
  8.             am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);  
  9.             am.abandonAudioFocus(afChangeListener);  
  10.             // Stop playback  
  11.         }  
  12.     }  
  13. };  
在上面失去短暂焦点的例子中,如果允许ducking,那么我们可以选择“duck”的行为而不是暂停当前的播放。

Duck! [闪避]

Ducking是一个特殊的机制使得允许音频间歇性的短暂播放。在Ducking的情况下,正常播放的歌曲会降低音量来凸显这个短暂的音频声音,这样既让这个短暂的声音比较突出,又不至于打断正常的声音。
请看例子:
  1. OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {  
  2.     public void onAudioFocusChange(int focusChange) {  
  3.         if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK  
  4.             // Lower the volume  
  5.         } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {  
  6.             // Raise it back to normal  
  7.         }  
  8.     }  
  9. };  

监听失去音频焦点是最重要的广播之一,但不是唯一的方法。系统广播了一系列的intent来警示你去改变用户的音频使用体验。

Dealing with Audio Output Hardware [处理音频输出硬件设备]

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

【补充:A2DP全名是Advanced Audio Distribution Profile 蓝牙音频传输模型协定! A2DP是能够采用耳机内的芯片来堆栈数据,达到声音的高清晰度。有A2DP的耳机就是蓝牙立体声耳机。声音能达到44.1kHz,一般的耳机只能达到8kHz。如果手机支持蓝牙,只要装载A2DP协议,就能使用A2DP耳机了。还有消费者看到技术参数提到蓝牙V1.0 V1.1 V1.2 V2.0——这些是指蓝牙的技术版本,是指通过蓝牙传输的速度,他们是否支持A2DP具体要看蓝牙产品制造商是否使用这个技术。来自百度百科http://baike.baidu.com/view/551149.htm】

Check What Hardware is Being Used[检测目前正在使用的硬件设备]

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

  1. if (isBluetoothA2dpOn()) {  
  2.     // Adjust output for Bluetooth.  
  3. else if (isSpeakerphoneOn()) {  
  4.     // Adjust output for Speakerphone.  
  5. else if (isWiredHeadsetOn()) {  
  6.     // Adjust output for headsets  
  7. else {   
  8.     // If audio plays and noone can hear it, is it still playing?  
  9. }  

Handle Changes in the Audio Output Hardware[处理音频输出设备的改变]

  • 当有线耳机被拔出或者蓝牙设备断开连接的时候,音频流会自动输出到内置的扬声器上。假设之前播放声音很大,这个时候突然转到扬声器播放会显得非常嘈杂。
  • 幸运的是,系统会在那种事件发生时会广播带有ACTION_AUDIO_BECOMING_NOISY的intent。无论何时播放音频去注册一个BroadcastReceiver来监听这个intent会是比较好的做法。
  • 在音乐播放器下,用户通常希望发生那样事情的时候能够暂停当前歌曲的播放。在游戏里,通常会选择减低音量。
  1. private class NoisyAudioStreamReceiver extends BroadcastReceiver {  
  2.     @Override  
  3.     public void onReceive(Context context, Intent intent) {  
  4.         if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {  
  5.             // Pause the playback  
  6.         }  
  7.     }  
  8. }  
  9.   
  10. private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);  
  11.   
  12. private void startPlayback() {  
  13.     registerReceiver(myNoisyAudioStreamReceiver(), intentFilter);  
  14. }  
  15.   
  16. private void stopPlayback() {  
  17.     unregisterReceiver(myNoisyAudioStreamReceiver);  
  18. }  
另一实例添加:如:http://blog.csdn.net/chenchuntong/article/details/8813719

平时开发的时候 遇到这样的一个问题: 我开着系统的音乐播放器,我在我的应用程序里面播一段音乐 两个音乐会同时播放通过在stackoverflow查资料后才发行我们得自己音乐需要获取焦点,在我们音乐播完了以后释放掉焦点。这样系统的歌曲会继续播放下面介绍一下步骤

1.获取AudioManager对象

 AudioManager   am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

2. 注册OnAudioFocusChangeListener监听

 OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
  public void onAudioFocusChange(int focusChange) {
   if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
    if(mediaPlayer.isPlaying()){
     mediaPlayer.pause();
    }
    
   } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
    if(mediaPlayer==null){
     initBeepSound();
    }else if(!mediaPlayer.isPlaying()){
     
     mediaPlayer.start();
     
    }
    // Resume playback
   } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
    if(mediaPlayer.isPlaying()){
     
     mediaPlayer.stop();
    }
    am.abandonAudioFocus(afChangeListener);
    // Stop playback
   }else if(focusChange==AudioManager.AUDIOFOCUS_REQUEST_GRANTED){
    if (mediaPlayer.isPlaying()) {
     mediaPlayer.stop();
    }
    
   }else if(focusChange==AudioManager.AUDIOFOCUS_REQUEST_FAILED){
    if(mediaPlayer.isPlaying()){
     mediaPlayer.stop();
    }
    
   }
  }
 };
3.就是在我们播放音乐的时候为AudioManager添加获取焦点的监听

int result = am.requestAudioFocus(afChangeListener,
    // Use the music stream.
      AudioManager.STREAM_MUSIC, // Request permanent focus.
      AudioManager.AUDIOFOCUS_GAIN);

    if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
     mediaPlayer.start();
     // Start playback. // 开始播放音乐文件
    }

4.在我们不用的时候将焦点释放掉一般我们是在ondestroy()方法中释放

 @Override
 protected void onStop() {
  // TODO Auto-generated method stub
  super.onStop();
  am.abandonAudioFocus(afChangeListener);

 }

下面介绍一下AudioManager的几个常量

AUDIOFOCUS_REQUEST_GRANTED   永久获取媒体焦点(播放音乐)

AUDIOFOCUS_GAIN_TRANSIENT  暂时获取焦点 适用于短暂的音频

AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK Duck我们应用跟其他应用共用焦点 我们播放的时候其他音频会降低音量

下面奉上代码

[java] view plaincopyprint?
  1. package com.example.mediatest;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import android.app.Activity;  
  6. import android.content.BroadcastReceiver;  
  7. import android.content.ComponentName;  
  8. import android.content.Context;  
  9. import android.content.Intent;  
  10. import android.content.res.AssetFileDescriptor;  
  11. import android.media.AudioManager;  
  12. import android.media.AudioManager.OnAudioFocusChangeListener;  
  13. import android.media.MediaPlayer;  
  14. import android.media.MediaPlayer.OnCompletionListener;  
  15. import android.os.Bundle;  
  16. import android.view.KeyEvent;  
  17. import android.view.Menu;  
  18. import android.view.View;  
  19. import android.view.View.OnClickListener;  
  20. import android.widget.Button;  
  21. import android.widget.Toast;  
  22.   
  23. public class MainActivity extends Activity {  
  24.   
  25.     private Button play;  
  26.     private Button stop;  
  27.     private AudioManager am;  
  28.     @Override  
  29.     protected void onCreate(Bundle savedInstanceState) {  
  30.         super.onCreate(savedInstanceState);  
  31.         setContentView(R.layout.activity_main);  
  32.         play = (Button) findViewById(R.id.button1);  
  33.         stop = (Button) findViewById(R.id.button2);  
  34.         am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);  
  35.         initBeepSound();  
  36.         mode = am.getMode();  
  37.         if (mode == AudioManager.MODE_NORMAL) {  
  38.             Toast.makeText(this"MODE_NORMAL", Toast.LENGTH_LONG).show();  
  39.         } else if (mode == AudioManager.MODE_IN_CALL) {  
  40.             Toast.makeText(this"MODE_IN_CALL", Toast.LENGTH_LONG).show();  
  41.   
  42.         } else if (mode == AudioManager.MODE_RINGTONE) {  
  43.             Toast.makeText(this"MODE_RINGTONE", Toast.LENGTH_LONG).show();  
  44.   
  45.         }  
  46.         remoteControlReceiver = new ComponentName(" com.example.mediatest",  
  47.                 " com.example.mediatest.RemoteControlReceiver");  
  48.         play.setOnClickListener(new OnClickListener() {  
  49.   
  50.             @Override  
  51.             public void onClick(View v) {  
  52.                 if (mediaPlayer != null) {  
  53.                     mediaPlayer.start();  
  54.   
  55.                 } else {  
  56.                     initBeepSound();  
  57.                     mediaPlayer.start();  
  58.                 }  
  59.                 // Request audio focus for playback  
  60.                 int result = am.requestAudioFocus(afChangeListener,  
  61.                 // Use the music stream.  
  62.                         AudioManager.STREAM_MUSIC, // Request permanent focus.  
  63.                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);  
  64.                 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {  
  65.                     mediaPlayer.start();  
  66.                     // Start playback. // 开始播放音乐文件  
  67.                 }  
  68.             }  
  69.   
  70.         });  
  71.         stop.setOnClickListener(new OnClickListener() {  
  72.   
  73.             @Override  
  74.             public void onClick(View v) {  
  75.                 if (mediaPlayer != null && mediaPlayer.isPlaying()) {  
  76.                     mediaPlayer.stop();  
  77.                     mediaPlayer.release();  
  78.                     mediaPlayer = null;  
  79.                 }  
  80.                 am.abandonAudioFocus(afChangeListener);  
  81.             }  
  82.         });  
  83.     }  
  84.   
  85.     OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {  
  86.         public void onAudioFocusChange(int focusChange) {  
  87.             if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {  
  88.                 if (mediaPlayer.isPlaying()) {  
  89.                     mediaPlayer.pause();  
  90.                 }  
  91.   
  92.             } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {  
  93.                 if (mediaPlayer == null) {  
  94.                     initBeepSound();  
  95.                 } else if (!mediaPlayer.isPlaying()) {  
  96.   
  97.                     mediaPlayer.start();  
  98.   
  99.                 }  
  100.                 // Resume playback  
  101.             } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {  
  102.                 if (mediaPlayer.isPlaying()) {  
  103.   
  104.                     mediaPlayer.stop();  
  105.                 }  
  106.                 am.abandonAudioFocus(afChangeListener);  
  107.                 // Stop playback  
  108.             } else if (focusChange == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {  
  109.                 if (mediaPlayer.isPlaying()) {  
  110.                     mediaPlayer.stop();  
  111.                 }  
  112.   
  113.             } else if (focusChange == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {  
  114.                 if (mediaPlayer.isPlaying()) {  
  115.                     mediaPlayer.stop();  
  116.                 }  
  117.   
  118.             }  
  119.         }  
  120.     };  
  121.     /** 
  122.      * When the beep has finished playing, rewind to queue up another one. 
  123.      */  
  124.     private final OnCompletionListener beepListener = new OnCompletionListener() {  
  125.         public void onCompletion(MediaPlayer mediaPlayer) {  
  126.             mediaPlayer.seekTo(0);  
  127.         }  
  128.     };  
  129.     private MediaPlayer mediaPlayer;  
  130.     private ComponentName remoteControlReceiver;  
  131.     private int mode;  
  132.   
  133.     private void initBeepSound() {  
  134.         if (mediaPlayer == null) {  
  135.             // The volume on STREAM_SYSTEM is not adjustable, and users found it  
  136.             // too loud,  
  137.             // so we now play on the music stream.  
  138.             setVolumeControlStream(AudioManager.STREAM_MUSIC);  
  139.             mediaPlayer = new MediaPlayer();  
  140.             mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);  
  141.             mediaPlayer.setOnCompletionListener(beepListener);  
  142.   
  143.             AssetFileDescriptor file = getResources().openRawResourceFd(  
  144.                     R.raw.demo);  
  145.             try {  
  146.                 mediaPlayer.setDataSource(file.getFileDescriptor(),  
  147.                         file.getStartOffset(), file.getLength());  
  148.                 file.close();  
  149.                 mediaPlayer.prepare();  
  150.             } catch (IOException e) {  
  151.                 mediaPlayer = null;  
  152.             }  
  153.         }  
  154.     }  
  155.   
  156.     @Override  
  157.     protected void onStop() {  
  158.         // TODO Auto-generated method stub  
  159.         super.onStop();  
  160.         am.abandonAudioFocus(afChangeListener);  
  161.   
  162.     }  
  163.   
  164.     @Override  
  165.     public boolean onCreateOptionsMenu(Menu menu) {  
  166.         // Inflate the menu; this adds items to the action bar if it is present.  
  167.         getMenuInflater().inflate(R.menu.main, menu);  
  168.         return true;  
  169.     }  
  170.   
  171. }  

对一个展讯平台有一个音频焦点的运用,设置里面对调节音量。

main/packages/apps/AudioProfile/src/com/sprd/audioprofile/AudioProfileRingerVolumePreference.java里面对应的有。




0 0