본문 바로가기

OS/Android

Android AudioFocus

반응형

//http://202psj.tistory.com

사운드 포커스로 설정을 해도 사운드가 들리지 않는다면 해당 기기가

wav, mp3 등 코덱을 지원하는지 확인해보자.

 

 

출처: http://shadowxx.egloos.com/10987471

오늘은 안드로이드에서 사용하고 있는 Audio Focus 라는 놈에 대해서 이야기할까 합니다.

사실 처음에 구글 문서만 읽고서 도대체 이 Audio Focus 의 처리의 주체가 누구일까 하고 조금 헷갈렸던 부분이 있었는데요. 그래서 오늘 바로 이 놈에 대해서 이야기를 하려합니다. 

Application programming 을 하시는 분들이라면, 저처럼 헷갈려하지는 않을텐데요. 그럼 시작하겠습니다.

현재 Android Jellybean 에서는 Audio Focus 의 종류가 다음과 같이 3가지로 되어 있습니다.
[GAIN]
AUDIOFOCUS_GAIN
AUDIOFOCUS_GAIN_TRANSIENT
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK

[LOSS]
AUDIOFOCUS_LOSS
AUDIOFOCUS_LOSS_TRANSIENT
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK

먼저 Audio Focus 의 개념을 설명하면 (이건 아시는 분들도 많겠지만),
예를 들어, 어플 A (Music player), B (Alarm) 가 있고, A 가 음악을 play 하고 있는 상황에서 B 가 알람을 울리면,
Audio Focus 가 없는 상태에서는 B 가 알람을 그냥 play 해버리면 음악 소리와 알람 소리가 같이 겹쳐서 들려서 영 듣기가 이상합니다.

그래서 B 를 만드는 개발자는, 알람이 울릴 때 현재 음악이나 다른 소리가 나면 그 소리를 중지하고 알람만 내고 싶어하지만, Application 단에서 다른 app 의 실행을 중지하거나 소리를 mute 시키는 동작을 할 수 없기 때문에 이러한 고민은 해결이 안됩니다.

이런 고민을 해결하고자 나온 것이 바로 Audio Focus 입니다.
즉, 어플 A, B 모두 Audio Focus listener 를 생성해서 각 Audio Focus 에 대한 처리를 한다 합의를 하는 것이죠.

좀 더 상세히 보면,
A 가 음악을 play 하려고 할 때 먼저 Audio Framework 로부터 Audio Focus 를 얻어옵니다 (Request AudioFocus- GAIN). 이때 Audio Framework 는 A 가 음악을 재생해도 되면 Audio Focus 를 A 에게 줍니다 (Grant AudioFocus). 그러다가 B 가 알람을 울릴려고 똑같은 방식으로 Audio Framework 에세 Audio Focus 를 요청합니다. 이때 Audio Framework 은 B 가 우선순위가 더 높다가 판단이 되면 B 에게 Audio Focus 를 주고 (이렇게 되면 A도 Audio Focus 를 갖고, B 도 갖고 있는 상황이 되기에), A 에게 너는 Audio Focus 를 잃었다는 (LOSS) 것을 알립니다.

그러면 A 어플은 Audio Focus 를 잃었기 때문에 음악을 pause 하던가, 혹은 어플에서 Audio Focus LOSS 시에 정의한 동작을 하게 됩니다. 그리고 B 가 알람을 다 울렸으면, Audio Focus 를 버리는데 (Abandon), 이렇게 되면 Audio Framework 에서는 B 가 Audio Focus 를 다 사용했다는 것을 알고, 다시 A 에게 Audio Focus 를 줍니다.
이렇게 A 가 Audio Focus 를 다시 갖게 되면, 중지된 음악을 다시 재생하거나 하게 됩니다.

Code 로 간단히 위의 동작을 설명하면,
[A 어플인 경우] - B 어플인 경우는 아래 music 대신에 alarm 이 되는 방식입니다.
requestAudioFocus();
AudioFocus 갖으면, music play();

OnAudioFocusListener()
{
case AUDIOFOCUS_LOSS:
case AUDIOFOCUS_LOSS_TRANSIENT:
         music pause();
         break;
case AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
         music volume down();
         break;

case AUDIOFOCUS_GAIN:
case AUDIOFOCUS_GAIN_TRANSIENT:
         music resume();
         break;
case AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
         music volume up();
         break;
}

위에 code 로 이해가 되셨나요?
위 code 에서 특이한 점이 있는데요, LOSS 동작에 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 이놈에 대해서 music pause 를 하지 않고 music volume 을 낮추는 것으로 적었습니다.

왜냐하면, 구글에서 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 의 사용은 원래 자신의 소리는 내고, 뒤에 나오는 소리는 작게 하는 것이 그 목적이기 때문에, 구글 컨셉에 맞게 하느라고 제가 code 를 저렇게 적었습니다. 즉, B 어플에서 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 로 Audio Focus 를 요청해서 Focus 를 얻으면, 알람의 소리는 원래 소리로 나오고 뒤에 나오는 음악 소리는 줄여지는 것이 되겠습니다.

물론 A 어플에서 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 을 받았을 때, msuic 소리를 줄이는 대신에 pause 시키거나 다른 동작을 해도 됩니다. 

이정도로 Audio Focus 에 대한 개념이 설명이 되었는지 모르겠습니다.
즉 요청할 때는 위에 [GAIN] 에 정의한 것 중에 하나를 사용해서 (인자값으로 넣어서) 요청하고, 자신이 Audio Focus 를 잃었을 때는 [LOSS] 에 정의한 것이 listener 로 전달이 되니깐 거기에 맞춰서 동작을 해야합니다.

읽어봐서 알겠지만 위에 [GAIN], [LOSS] 는 서로 짝이 맞게 동작되도록 Audio Framework 에서 처리를 합니다.
즉, A 가 음악 재생중에 B 가 알람 소리를 내려고 Audio Focus 를 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 으로 요청하면, Audio Framework 는 B 에게 Audio Focus 를 주고, 기존에 Focus 를 갖고 있던 A 에게는 LOSS 를 나태는 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 을 주게 됩니다. 따라서 A 는 위에 code 에 따라서 music 소리를 줄이게 됩니다. 그러다가 B 알람이 모두 끝나면, Audio Framework 는 A 에게 다시 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 를 전달하게 됩니다. 그럼 A 는 다시 원래 소리로 원복하게 되는 것입니다.

그럼 왜 이렇게 3가지 다른 Audio Focus 를 만들었냐 하면,

AUDIOFOCUS_GAIN - 음악이나 Video 처럼 얼마나 오랫동안 재생을 해야하는지 모르는 경우 사용하도록 권장.
AUDIOFOCUS_GAIN_TRANSIENT - 잠시 동안 Audio Focus 를 얻어야 하는 경우 사용하도록 권장.
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK - Audio Focus 를 얻더라도, 그 뒤에 소리들이 볼륨만 낮게 된 상태에서 내가 낼려고 하는 소리와 같이 나와야 하는 경우 사용하도록 권장.

이는 각 어플리케이션들이 Audio Focus 동작에 대한 대응을 하기 위해서 이렇게 나누었을 것으로 생각됩니다 (제 생각입니다).

즉 위에서 예를 든 것처럼, A 가 음악을 재생중에 B 가 알람을 울리면, 알람은 사실 길지 않기 때문에 B 가 Audio Focus 를 요청할 때 AUDIOFOCUS_GAIN_TRANSIENT 을 사용하다면, A 는 AUDIOFOCUS_LOSS_TRANSIENT 을 받음으로써, "아~ 조금있다가 음악을 다시 resume 할 수 있구나" 하고 생각할 수 있기 때문에 음악을 완전히 stop 시키지 않고 pause 만 걸어 둘 수 있습니다.
하지만, 만약 B 가 알람을 울리는 것이 아니라 Video 를 play 하는 것이고, AUDIOFOCUS_GAIN 으로 요청했다면, A 는 "아~ 다른 어플이 소리를 한참동안 낼려고 하는구나, 음악을 pause 하지 말고 아예 stop 해버려서 시스템 리소스 낭비를 줄이자" 라고 판단을 할 수 있다는 것입니다.

심야에 갑자기 작성하는 것이라서 장황하게 설명했으나, Google 공식 site (http://developer.android.com/training/managing-audio/audio-focus.html) 에서 설명한 것을 읽어보면 쉽게 이해할 수 있으리라 생각됩니다.

 

////////////////////////////////////////////////////////////////////////////////////////////////////////////

//

 

출처: http://developer.android.com/guide/topics/media/mediaplayer.html#audiofocus

 

Handling audio focus

Even though only one activity can run at any given time, Android is a multi-tasking environment. This poses a particular challenge to applications that use audio, because there is only one audio output and there may be several media services competing for its use. Before Android 2.2, there was no built-in mechanism to address this issue, which could in some cases lead to a bad user experience. For example, when a user is listening to music and another application needs to notify the user of something very important, the user might not hear the notification tone due to the loud music. Starting with Android 2.2, the platform offers a way for applications to negotiate their use of the device's audio output. This mechanism is called Audio Focus.

When your application needs to output audio such as music or a notification, you should always request audio focus. Once it has focus, it can use the sound output freely, but it should always listen for focus changes. If it is notified that it has lost the audio focus, it should immediately either kill the audio or lower it to a quiet level (known as "ducking"—there is a flag that indicates which one is appropriate) and only resume loud playback after it receives focus again.

Audio Focus is cooperative in nature. That is, applications are expected (and highly encouraged) to comply with the audio focus guidelines, but the rules are not enforced by the system. If an application wants to play loud music even after losing audio focus, nothing in the system will prevent that. However, the user is more likely to have a bad experience and will be more likely to uninstall the misbehaving application.

To request audio focus, you must call requestAudioFocus() from the AudioManager, as the example below demonstrates:

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
   
AudioManager.AUDIOFOCUS_GAIN);

if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
   
// could not get audio focus.
}

The first parameter to requestAudioFocus() is an AudioManager.OnAudioFocusChangeListener, whoseonAudioFocusChange() method is called whenever there is a change in audio focus. Therefore, you should also implement this interface on your service and activities. For example:

class MyService extends Service
               
implements AudioManager.OnAudioFocusChangeListener {
   
// ....
   
public void onAudioFocusChange(int focusChange) {
       
// Do something based on focus change...
   
}
}

The focusChange parameter tells you how the audio focus has changed, and can be one of the following values (they are all constants defined in AudioManager):

  • AUDIOFOCUS_GAIN: You have gained the audio focus.
  • AUDIOFOCUS_LOSS: You have lost the audio focus for a presumably long time. You must stop all audio playback. Because you should expect not to have focus back for a long time, this would be a good place to clean up your resources as much as possible. For example, you should release the MediaPlayer.
  • AUDIOFOCUS_LOSS_TRANSIENT: You have temporarily lost audio focus, but should receive it back shortly. You must stop all audio playback, but you can keep your resources because you will probably get focus back shortly.
  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: You have temporarily lost audio focus, but you are allowed to continue to play audio quietly (at a low volume) instead of killing audio completely.

Here is an example implementation:

public void onAudioFocusChange(int focusChange) {
   
switch (focusChange) {
       
case AudioManager.AUDIOFOCUS_GAIN:
           
// resume playback
           
if (mMediaPlayer == null) initMediaPlayer();
           
else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();
            mMediaPlayer
.setVolume(1.0f, 1.0f);
           
break;

       
case AudioManager.AUDIOFOCUS_LOSS:
           
// Lost focus for an unbounded amount of time: stop playback and release media player
           
if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
            mMediaPlayer
.release();
            mMediaPlayer
= null;
           
break;

       
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
           
// Lost focus for a short time, but we have to stop
           
// playback. We don't release the media player because playback
           
// is likely to resume
           
if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();
           
break;

       
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
           
// Lost focus for a short time, but it's ok to keep playing
           
// at an attenuated level
           
if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);
           
break;
   
}
}

Keep in mind that the audio focus APIs are available only with API level 8 (Android 2.2) and above, so if you want to support previous versions of Android, you should adopt a backward compatibility strategy that allows you to use this feature if available, and fall back seamlessly if not.

You can achieve backward compatibility either by calling the audio focus methods by reflection or by implementing all the audio focus features in a separate class (say, AudioFocusHelper). Here is an example of such a class:

public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
   
AudioManager mAudioManager;

   
// other fields here, you'll probably hold a reference to an interface
   
// that you can use to communicate the focus changes to your Service

   
public AudioFocusHelper(Context ctx, /* other arguments here */) {
        mAudioManager
= (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
       
// ...
   
}

   
public boolean requestFocus() {
       
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
            mAudioManager
.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC,
           
AudioManager.AUDIOFOCUS_GAIN);
   
}

   
public boolean abandonFocus() {
       
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
            mAudioManager
.abandonAudioFocus(this);
   
}

   
@Override
   
public void onAudioFocusChange(int focusChange) {
       
// let your service know about the focus change
   
}
}

You can create an instance of AudioFocusHelper class only if you detect that the system is running API level 8 or above. For example:

if (android.os.Build.VERSION.SDK_INT >= 8) {
    mAudioFocusHelper
= new AudioFocusHelper(getApplicationContext(), this);
} else {
    mAudioFocusHelper
= null;
}

Performing cleanup

As mentioned earlier, a MediaPlayer object can consume a significant amount of system resources, so you should keep it only for as long as you need and call release() when you are done with it. It's important to call this cleanup method explicitly rather than rely on system garbage collection because it might take some time before the garbage collector reclaims the MediaPlayer, as it's only sensitive to memory needs and not to shortage of other media-related resources. So, in the case when you're using a service, you should always override the onDestroy() method to make sure you are releasing the MediaPlayer:

public class MyService extends Service {
   
MediaPlayer mMediaPlayer;
   
// ...

   
@Override
   
public void onDestroy() {
       
if (mMediaPlayer != null) mMediaPlayer.release();
   
}
}

You should always look for other opportunities to release your MediaPlayer as well, apart from releasing it when being shut down. For example, if you expect not to be able to play media for an extended period of time (after losing audio focus, for example), you should definitely release your existing MediaPlayer and create it again later. On the other hand, if you only expect to stop playback for a very short time, you should probably hold on to your MediaPlayer to avoid the overhead of creating and preparing it again.

Handling the AUDIO_BECOMING_NOISY Intent

Many well-written applications that play audio automatically stop playback when an event occurs that causes the audio to become noisy (ouput through external speakers). For instance, this might happen when a user is listening to music through headphones and accidentally disconnects the headphones from the device. However, this behavior does not happen automatically. If you don't implement this feature, audio plays out of the device's external speakers, which might not be what the user wants.

You can ensure your app stops playing music in these situations by handling theACTION_AUDIO_BECOMING_NOISY intent, for which you can register a receiver by adding the following to your manifest:

<receiver android:name=".MusicIntentReceiver">
   
<intent-filter>
     
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
   
</intent-filter>
</receiver>

This registers the MusicIntentReceiver class as a broadcast receiver for that intent. You should then implement this class:

public class MusicIntentReceiver implements android.content.BroadcastReceiver {
   
@Override
   
public void onReceive(Context ctx, Intent intent) {
     
if (intent.getAction().equals(
                    android
.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
         
// signal your service to stop playback
         
// (via an Intent, for instance)
     
}
   
}
}

Retrieving Media from a Content Resolver

Another feature that may be useful in a media player application is the ability to retrieve music that the user has on the device. You can do that by querying the ContentResolver for external media:

ContentResolver contentResolver = getContentResolver();
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = contentResolver.query(uri, null, null, null, null);
if (cursor == null) {
   
// query failed, handle error.
} else if (!cursor.moveToFirst()) {
   
// no media on the device
} else {
   
int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
   
int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
   
do {
       
long thisId = cursor.getLong(idColumn);
       
String thisTitle = cursor.getString(titleColumn);
       
// ...process entry...
   
} while (cursor.moveToNext());
}

To use this with the MediaPlayer, you can do this:

long id = /* retrieve it from somewhere */;
Uri contentUri = ContentUris.withAppendedId(
        android
.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);

mMediaPlayer
= new MediaPlayer();
mMediaPlayer
.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer
.setDataSource(getApplicationContext(), contentUri);

// ...prepare and start...

 

 

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

출처 : http://stackoverflow.com/questions/10220816/audiofocuschangelistener-cannot-listen

 

I encounter a problem using the AudioFocusChangeListener, but still cannot listen to the audio focus change. Could someone help me find the reasons? Here is what I do:

  1. In my FM app, when FM starts in startFM, I place requestAudioFocus() to get resources:

  2. When quitting the App in onDestroy(), I used abandonAudioFocus() to release resouces.

  3. The member variable mAudioFocusListener is as follows:

  4. According to the log, it prints "Successfull to request audioFocus listener", but doesn't print anyAudioForcus change log when alarm is on or other audio-changed events. Could anyone figure out what is wrong with it?

  5. On the other hand, I place log in AudioService.java and AudioManger.java, where in AudioService.java, requestAudioFocus(): if (!mFocusStack.empty() && (mFocusStack.peek().mFocusDispatcher != null)) {

  6. in AudioManager.java, Here doesn't excute either: private IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() {

That's all my question. Could anybody tell me why this listener cannot listen to alarm events and other audiofocus-changed events? By the way, how could I successfully listen to the events? Maybe I missed some step or used a incorrect way? Many thanks.

android audio listener multimedia audiomanager

1 Answer

activeoldestvotes

up vote1down vote

Try this:

and...

Its works for me.



출처: https://202psj.tistory.com/505 [알레폰드의 IT 이모저모]

반응형