IT_Programming/Android_Java

오디오 포커스에 대하여

JJun ™ 2015. 11. 10. 10:15



 출처
 : http://android-developers-kr.blogspot.kr/2013/10/blog-post_27.html
 : http://shadowxx.egloos.com/m/10987471




오디오 포커스에 대하여


구글 개발자 관계 팀의 크리스탄 우첼로(Kristan Uccello) 작성     
원문: Respecting Audio Focus    


발표 동안에 떠드는 것은 무례합니다. 발표자에게 무례하고 관객을 짜증나게 하죠. 만약 애플리케이션이 오디오 포커스에 관한 규율을 존중하지 않으면
다른 애플리케이션을 무시하고 사용자를 짜증나게 하는 것입니다. 만약 오디오 포커스에 대해 들어본 적이 없다면 
안드로이드 개발자 트레이닝 내용
볼 필요가 있습니다.


만약 여러 앱에서 음악을 재생한다면 이들이 어떻게 상호작용할지 생각하는 것이 중요합니다.
모든 음악 앱들이 동시에 재생하는 것을 막기 위해 안드로이드는 재생을 조정하는 오디오 포커스를 사용합니다. 앱은 오디오 포커스를 가질 때만
음악을 재생해야 합니다. 이 글은 사용자에게 가능한 좋은 경험을 보장하기 위해 오디오 포커스의 변화를 적절히 다루는 몇가지 팁을 제공합니다.



오디오 포커스 요구하기


앱이 시작할 때 오디오 포커스를  요청해서는 안됩니다. (욕심내지 마세요.) 대신 애플리케이션이 오디오 스트림으로 무언가 할 때까지 요청을 미루세요.
애플리케이션은 원하는 포커스 수준을 지정할 수 있는 
AUDIOFOCUS_GAIN* 상수 (표 1 참고)를 AudioManager 시스템 서비스에서 사용할 수 있습니다.

목록 1. 오디오 포커스 요청하기


1. AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
2.     
3.  int result = am.requestAudioFocus(mOnAudioFocusChangeListener,
4.    // Hint: the music stream.
5.    AudioManager.STREAM_MUSIC,
6.    // Request permanent focus.
7.    AudioManager.AUDIOFOCUS_GAIN);
8.  if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
9.    mState.audioFocusGranted = true;
10. } else if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
11.   mState.audioFocusGranted = false;
12. }


7번째 줄 위에서 영속적인 오디오 포커스를 요청한 걸 볼 수 있습니다.
45초 미만의 오디오 포커스를 이용할 때는 
AUDIOFOCUS_GAIN_TRANSIENT를 대신 사용하여 일시적인 포커스를 요청할 수 있습니다.

현재 오디오를 재생하는 다른 애플리케이션과 오디오 시스템을 공유하는데 적합한 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK를 대신 사용할 수도 있습니다. (예, 피트니스 애플리케이션에서 "계속 하라"는 메시지가 재생되며 그 동안 배경음악을 잠시 줄이고 싶을 때.)
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK를 호출하는 앱은 15초 이상 오디오 시스템의 포커스를 잡아선 안됩니다.
(주: 더킹(ducking)은 다른 오디오 출력 크기를 잠시 작게 변경하여 같이 재생을 하는 것을 의미하는 말입니다.)




오디오 포커스 변화 다루기


오디오 포커스 변경 이벤트를 다루기 위해 애플리케이션은 OnAudioFocusChangeListener의 인스턴스를 생성해야 합니다.
이 리스너에서 애플리케이션은  theAUDIOFOCUS_GAIN* 이벤트와 AUDIOFOCUS_LOSS* 이벤트 (
표 1)을 다룰 필요가 있습니다.  
AUDIOFOCUS_GAIN는 몇가지 미묘한 차이들을 가지는 데,아래의 
목록 2에 강조된 것을 참고하세요.

목록 2. 오디오 포커스 변화 다루기


1. mOnAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {  
2.   
3. @Override
4. public void onAudioFocusChange(int focusChange) {
5.   switch (focusChange) {
6.   case AudioManager.AUDIOFOCUS_GAIN:
7.     mState.audioFocusGranted = true;
8.        
9.     if(mState.released) {
10.   initializeMediaPlayer();
11.    }
12.
13.    switch(mState.lastKnownAudioFocusState) { 14.    case UNKNOWN: 15.      if(mState.state == PlayState.PLAY && !mPlayer.isPlaying()) { 16.        mPlayer.start(); 17.      } 18.      break; 19.    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 20.      if(mState.wasPlayingWhenTransientLoss) { 21.        mPlayer.start(); 22.      } 23.      break; 24.    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 25.      restoreVolume(); 26.      break; 27.    } 28.        
29.    break; 30.  case AudioManager.AUDIOFOCUS_LOSS: 31.    mState.userInitiatedState = false; 32.    mState.audioFocusGranted = false; 33.    teardown(); 34.    break; 35.  case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 36.    mState.userInitiatedState = false; 37.    mState.audioFocusGranted = false; 38.    mState.wasPlayingWhenTransientLoss = mPlayer.isPlaying(); 39.    mPlayer.pause(); 40.    break; 41.  case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 42.   mState.userInitiatedState = false; 43.   mState.audioFocusGranted = false; 44.   lowerVolume(); 45.   break; 46.  } 47.  mState.lastKnownAudioFocusState = focusChange; 48.  } 49.};



AUDIOFOCUS_GAIN는 애플리케이션 코드의 두가지 분명한 영역에서 사용됩니다.
첫째로, 
목록 1에서 보여진 것 처럼 오디오 포커스를 등록할 때 사용할 수 있습니다.
이것은 등록된 
OnAudioFocusChangeListener를 위한 이벤트로 변환되지는 않습니다.
성공적인 오디오 포커스 요청이 될때 리스너는 등록에 쓰였던 AUDIOFOCUS_GAIN 이벤트를 얻지는 못한다는 거죠.


AUDIOFOCUS_GAIN은 또 OnAudioFocusChangeListener의 구현 내의 이벤트 상태로 쓰입니다.
위에 언급된 것 처럼, AUDIOFOCUS_GAIN 이벤트는 오디오 포커스 요청에 의해 작동되지는 않습니다.
대신 
AUDIOFOCUS_LOSS* 이벤트가 나타난 이후에 AUDIOFOCUS_GAIN가 발생합니다. 
표 1에 보이는 것 중  양쪽 모두(주: 등록할 때, 이벤트의 상태)에서 사용되는 상수는 AUDIOFOCUS_GAIN 밖에 없습니다.

포커스 변경 리스너를 제어할 네 가지 경우가 있습니다.
애플리케이션이 AUDIOFOCUS_LOSS를 받는 것은 일반적으로 포커스를 다시 받지 않을 것을 의미합니다.
이 경우 앱은 오디오 시스템에 관련된 에셋을 해제하고 재생을 멈춰야 합니다.

예를 들어 사용자가 음악을 앱을 사용하여 재생한 후, 음악 앱에서 오디오 포커스를 가져갈 게임을 수행한다고 상상해보세요.
사용자가 게임을 언제 종료할 지 예상할 수도 없습니다. 아마도 사용자는 홈 런쳐로 갈 수 있습니다 (게임을 백그라운드 모드로 내버려두고요.)
그리고 아예 다른 앱을 켜거나 음악앱으로 돌아와 재개시켜 오디오 포커스를 다시 요청할 수 있겠죠.


그러나 다른 경우들은 논쟁의 여지가 있습니다. (위에 설명된 것 처럼) 영구적으로 오디오 포커스를 잃는 것과 일시적으로 잃는 것에서는 차이가 있습니다.
애플리케이션이 
AUDIOFOCUS_LOSS_TRANSIENT를 받으면 앱은 AUDIOFOCUS_GAIN 이벤트를 받을 때까지 오디오 시스템의 사용을 멈추어야
합니다. AUDIOFOCUS_LOSS_TRANSIENT가 발생하면 앱은 포커스를 일시적으로 잃는다고 기억해야 합니다.
이것이 오디오 포커스를 얻었을 때 올바른 행동이 무엇인지 추론할 수 있게 합니다. (
목록 2의 13-27 행을 보세요.)

가끔 앱이 오디오 포커스를 잃고 (AUDIOFOCUS_LOSS를 받고) 끼어든 앱이 종료되거나 그렇지 않으면 오디오 포커스를 버리기도 합니다.
이 경우 오디오 포커스를 가졌던 이전 애플리케이션이 AUDIOFOCUS_GAIN 이벤트를 받기도 합니다.
이어지는 AUDIOFOCUS_GAIN 이벤트에서 앱은 임시적인 상실 뒤에 얻어서 이래저래 오디오 시스템을 재개할 수 있는지 영속적인 상실로 부터
복구해서 재생을 설정해야하는 지를 확인해야 합니다.


애플리케이션이 (45초 미만의) 짧은 시간 만 오디오를 사용한다면  AUDIOFOCUS_GAIN_TRANSIENT 포커스 요청을 사용하고
재생이나 캡쳐가 종료된 후 포커스를 풀어야 합니다. 오디오 포커스는 시스템에서 스택처럼 다루어집니다.
오디오 포커스를 요청한 가장 마지막 프로세스가 이기는 식이죠.


오디오 포커스를 얻으면 MediaPlayer나 MediaEncorder를 생성하고 리소스를 할당할 적절한 시기입니다.
비슷하게 앱이 AUDIOFOCUS_LOSS를 받으면 할당된 어떤 리소스를 정리하기에 좋은 차례입니다.
오디오 포커스를 얻는 것은 3가지 유형이 있는데 
표 1에 나와있는 세가지 오디오 포커스 상실과 관련있습니다. 
OnAudioFocusChangeListener에서 모든 상실 경우를 명시적으로 다루는 것은 좋은 습관입니다.


표 1. 오디오 포커스 획득과 상실 결과
획득상실
AUDIOFOCUS_GAINAUDIOFOCUS_LOSS
AUDIOFOCUS_GAIN_TRANSIENTAUDIOFOCUS_LOSS_TRANSIENT
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCKAUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK


노트: AUDIOFOCUS_GAIN은 두 부분에서 사용됩니다.
오디오 포커스를 요청할 때 AudioManager에 힌트로 전달되고, onAudioFocusChangeListener의 이벤트 경우로 사용됩니다.
녹색으로 강조된 획특 이벤트는 오디오 포커스를 요청했을 때만 사용됩니다. 상실 이벤트는 오로지 onAudioFocusChangeListener에서만 사용됩니다.


표 2. 오디오 스트림 타입 >
스트림 타입설명
STREAM_ALARM알람을 의한 오디오 스트림The audio stream for alarms
STREAM_DTMFDTMF 톤을 위한 오디오 스트림
STREAM_MUSIC"미디어"(음악, 팟캐스트, 비디오) 재생을 오디오 스트림
STREAM_NOTIFICATION알림을 위한 오디오 스트림
STREAM_RING폰 링을 위한 오디오 스트림
STREAM_SYSTEM시스템 사운드를 위한 오디오 스트림


애플리케이션은 AudioManager (목록 1 첫번째 줄)에 오디오 포커스를 요청합니다.
(아래에 링크된 샘플 코드의 예제를 보세요.) 제공되는 3개의 인자는 오디오 포커스 체인지 리스너 객체 (선택적), 사용할 오디오 채널이 무엇인지
힌트 (표 2, 대부분의 앱은 STREAM_MUSIC를 써야 합니다.), 
표 1 왼편의오디오 포커스 타입입니다.
오디오 포커스가 시스템에 의해 허용(
AUDIOFOCUS_REQUEST_GRANTED) 되었다면 초기화를 하면 됩니다. (목록 1의 9번째 줄을 보세요.)

노트: 현재 프로세스에서 폰 전화가 있을 때 오디오 포커스가 허용되지 않으며 (AUDIOFOCUS_REQUEST_FAILED) 애플리케이션은 전화가 끝난 후에 
AUDIOFOCUS_GAIN를 받지 않습니다.


OnAudioFocusChange() 구현에서 애플리케이션이 애플리케이션이 받는 onAudioFocusChange() 이벤트가 무엇인지는 표 3에 정리되어 있습니다.

오디오 포커스를 잃는 경우에 상실이 사실상 최종적인지 확인해야 합니다.
앱이 AUDIOFOCUS_LOSS_TRANSIENT, AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK를 받으면 금방 다른 오디오 포커스 변경 이벤트가 있어서
생성할 미디어 리소스를 (relase()를 불러 해제하지 않고) 붙잡아 둘 수 있습니다.
앱은 어떤 상태 변수나 단순한 상태 머신등을 이용해서 임시적인 상실을 경험했다는 것을 기록해야 합니다.


앱이 AUDIOFOCUS_GAIN로 영구적인 오디오 포커스를 요청했고 AUDIOFOCUS_LOSS_TRANSIENT나 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 이벤트를 받았다면 애플리케이션에 적절한 행동은 (원래 볼륨 상태를 어딘가 확실히 저장해두고) 스트림의 볼륨을 낮추고
AUDIOFOCUS_GAIN 이벤트를 받았을 때 볼륨을 다시 올리는 것입니다. (아래 그림 1을 보세요.)






표 3. 포커스 변경 종류에 따른 적절한 행동

포커스 변경 종류적절한 행동
AUDIOFOCUS_GAIN상실 이벤트 후 획득 이벤트. 
애플리케이션에 의해 설정된 다른 상태가 없다면 재생을 재개한다.
예를 들어 상실 이베트 이전에 사용자가 명시적으로 일시 정지한 경우.
AUDIOFOCUS_LOSS재생을 정지한다. 에셋을 해제한다.
AUDIOFOCUS_LOSS_TRANSIENT재생을 일시정지하고 상실이 일시적이란 것의 상태를 유지한다. 
AUDIOFOCUS_GAIN  이벤트가 나타나면 적절히 재생을 재개할 수 있다.
에셋을 해제하지 마라.
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK

볼륨을 줄이거나 재생을 일시정지한다.
AUDIOFOCUS_LOSS_TRANSIENT의 상태를 저장한다. 애셋을 해제하지 마라.



결론과 더 읽을거리


안드로이드에서 좋은 오디오 시민 애플리케이션 법을 이해하는 것은 시스템 오디오 포커스 규칙과 각 상황에 적합하게 다루는 것을
존중하는 것을 의미합니다. 애플리케이션의 행동을 일관성있게 하고 사용자를 부정적으로 놀라게 하지 않게 노력하세요.
안드로이드 오디오 시스템에 대해 이야기할 더 많은 내용들이 있습니다.

아래에 자료들에서 추가적인 내용들을 찾으십시요.



예제 소스 코드는 여기있습니다.









[Android] Audio Focus (오디오 포커스). 




현재 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) 에서 설명한 것을 읽어보면
쉽게 이해할 수 있으리라 생각됩니다.



development-master-samples-RandomMusicPlayer.tar.zip
0.11MB