가끔씩 안드로이드 애플리케이션을 제작하다보면 소리를 사용해야 하는 경우가 생깁니다. 소리를 사용하는 경우는 크게 음원과 같이 길이가 긴 것과 효과음과 같이 길이가 짧은 것으로 나눌 수 있을 텐데요. soundPool은 길이가 짧은 소리에 대해 효과적으로 사용할 수 있는 안드로이드 내장 라이브러리입니다. 저는 애플리케이션에서 각 효과음별로 소리 조절이 가능해야 한다는 기획 때문에 찾아보게 되었습니다.
developer.android.com/reference/android/media/SoundPool
안드로이드 공식 사이트에서 soundPool에 대해서 확인할 수 있습니다.
이 라이브러리의 사용법은 굉장히 간단해서 예제를 보고 쉽게 이용할 수 있으니 가볍게 살펴보면 될 것 같습니다. 아래에서부터 바로 정리하도록 하겠습니다.
import android.media.SoundPool
SoundPool의 경우 안드로이드 내장 라이브러리이기 때문에 따로 gradle에서 다운받아야 하는 부분은 없습니다! 그저 import 하나로 충분하지만, 이 부분은 대체로 IDE에서 자동적으로 해결해주는 부분이기 때문에 신경쓸 필요가 없겠네요.
object SoundClass {
private var sp: SoundPool? = null
private var usage = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AudioAttributes.USAGE_MEDIA
} else {
0
}
fun init(context: Context) {
if (sp == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
SoundPool.Builder()
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(usage)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build()
)
.setMaxStreams(10)
.build()
} else {
sp = SoundPool(10, AudioManager.STREAM_NOTIFICATION, 0)
}
}
}
/**
* 해제
*/
fun release() {
val s = sp ?: return
CoroutineScope(Dispatchers.Default).launch {
//load된 사운드 id가 있다면 여기서 해제
s.release()
}
sp = null
}
}
SoundPool이 처음 나올 당시에는 SoundPool(int maxStreams, int streamType, int srcQuality) 생성자를 통해 SoundPool 객체를 선언할 수 있었지만, 롤리팝 이후(API 21 버전) 버전부터는 빌더를 통해 생성해주어야 합니다.
위 코드는 object를 이용해서 SoundPool을 사용하기 때문에 sp라는 변수가 따로 존재하지만, 실사용시에 필요하다면 각 클래스 내부에서 따로 선언해서 사용해도 무방합니다. 이 SoundPool은 공식 홈페이지에 따르면 리소스 낭비를 방지하기 위해 onStart에서 생성하고 onStop에서 제거하라고 되어 있습니다. 위의 코드는 object 클래스이기 떄문에 사용할 때 자동으로 sp의 파악으로 init을, 사용이 끝난 후에 release를 적당한 시점에 작성해주면 되겠습니다.
usage는 API 버전에 따라 다르게 적용하는데, AudioAttributes.USAGE_MEDIA는 오디오 스트림을 미디어(음악, 사운드 트랙 등)로 사용하겠다는 것을 의미합니다.
AudioAttributes.CONTENT_TYPE_*
CONTENT_TYPE_MOVIE | 영상과 함께 제공되는 사운드 트랙 |
CONTENT_TYPE_MUSIC | 음악 |
CONTENT_TYPE_SONIFICATION | 사용자 인터페이스 |
CONTENT_TYPE_SPEECH | 음성(아마 전화와 같은 소리를 의미하는 듯) |
CONTENT_TYPE_UNKNOWN | 분류 없음 |
setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 이 부분에 대해서는 각 타입을 변경할 경우 어떤 처리가 되는지 명확한 이해가 되지 않기 떄문에 옵션만 정리하고 넘어가도록 하겠습니다.(혹시 아시는 분이 있다면 댓글로 작성을...)
그리고 MaxStreams는 총 플레이 가능한 사운드의 개수를 정의하는 변수입니다.
이렇게 SoundPool을 초기화 생성하고 해제하는 방법을 알았다면 다음은 사운드를 발생시키는 단계로 가야겠죠. SoundPool의 재생은 play 함수를 통해서 이루어집니다. 그러나 play 하기 전, 재생할 사운드를 먼저 load 해두어야 하는데요. 코드를 살펴보겠습니다.
sp.load(context, sourceId, priority)
음원을 load를 하는 것은 간단합니다. raw 폴더에 넣어둔 사운드를 id를 이용해 load 메소드의 파라미터로 넣고 priority를 이용해 우선순위를 정해두면 파일 load는 끝이 납니다.
sp.play(sourceId, 1f, 1f, 0, 0, 1f)
로드된 음원을 play 메소드를 이용해 재생할 수 있습니다. 맨 처음 sourceId 부분은 로드한 음원의 int값이 될 것이고, 2번째는 왼쪽 음의 크기(leftVolume), 세번째는 오른쪽 음의 크기(rightVolume), 네 번째는 우선 순위(priority), 다섯 번째는 사운드 트랙 재생 횟수(loop), 마지막은 재생 속도(rate)입니다.
이때 주의해야할 것은 load 메소드 바로 아래에 play를 두게 되면 load 메소드가 Asynchronous(비동기) 이기 때문에 기기의 상태에 따라 play가 되지 않을 수 있습니다.(load가 완료되기 전에 play 메소드가 실행될 수 있습니다.)
그래서 이 경우를 방지하기 위해 play 메소드가 애플리케이션 로직상 불러와야 하는 지점보다 더 빠르게 사운드의 사용이 예상되는 지점에서 미리 사운드 파일들을 load 시켜놓을 수 있습니다. (예를 들어 스플래시 화면 -> 메인 화면 동작 후, 메인 화면에서 바로 '어플이 실행되었습니다.'라는 사운드를 내보낸다면, 스플래시 화면에서 load를 통해 사운드를 미리 올려둔 후, 메인 화면에서 바로 play 되도록 코드를 구성할 수 있습니다.)
또, 아래 코드와 같이 OnLoadCompleteListener를 이용해 load가 끝난 경우 이벤트를 발생시켜주는 형태로 진행할 수 있습니다. 이 경우에는 load가 끝난 후 바로 실행시키도록 하기 때문에 필요할 때 마다 load 메소드만 발생시켜주면 됩니다.
sp.setOnLoadCompleteListener { soundPool, sampleId, status ->
soundPool.play(sampleId, 0.7f, 0.7f,0,0,1f)
}
더 효율적인 코드를 위해서는 사운드의 발생이 언제 일어날 지를 파악하고 언제 로드해서 사용하는 것이 좋은지 고민해보는 것이 중요합니다. 여기까지 하면 소리를 발생시키는데 충분한 역할을 할 수 있습니다.
생각보다 하드웨어를 조절할 수 있는 안드로이드 기능은 많은 것 같다는 생각을 문득 하게 되었습니다. 다른 애플리케이션에서 사용되는 것을 본 적이 없다 하더라도 어쩌면 있는 기능이 아닐까 하는 생각도 하게 됐죠... 이러면 기획 쪽에서 아무리 난해한 걸 요구하더라도 바로 안된다고 얘기할 수 없을 것 같습니다.
'안드로이드 > 코틀린' 카테고리의 다른 글
Gson의 기본적인 사용법(with kotlin) (0) | 2021.05.29 |
---|---|
(android) skydoves - Balloon (0) | 2021.05.27 |
(android-jetpack)LiveData (0) | 2021.04.13 |
(android)greenrobot - EventBus (6) | 2021.04.06 |
(android-jetpack)AAC에서의 뷰모델 (0) | 2021.03.30 |
댓글