역할
웹 서비스나 앱 서비스의 역할은 유저가 원하는 데이터를 가공하여 보여주는 것이라고 생각합니다. 백엔드 개발자들은 '데이터를 가공' 하는 것에 대한 책임을 지고, 디자이너와 프론트엔드 개발자(또는 앱 개발자)들은 '보여주는 것' 에 대한 책임을 집니다.
이와 같이 단순하지만 확실한 역할 구분은 각자가 숙지하고 있어야 할 (또는 그러하면 좋을) 지식들을 정립하고 일반화하기 좋게 합니다. 저는 앱 개발자이므로, 인터랙션과 관련한 애니메이션, 또는 이미지 처리, 오디오, 비디오 처리등이 그러한 지식에 속할 수 있다고 생각합니다.
해당 포스트에서는 오디오 처리에 대해 학습한 것을 기록하려 합니다. 학습하게 된 동기는 작은 프로젝트에서 생겨났습니다.
친한 백엔드 개발자 한 분과 단 둘이서 <뮤런> 이라는 앱을 개발하고 있습니다.
러닝 케이던스에 맞춰 음악을 재생해 주는 앱 서비스입니다.
처음 앱을 디자인할 때에는 서버에 저장된 음악 파일을 캐시 하여 들려주고 앱 종료 시에 캐시 메모리를 정리하려고 했습니다. 구현에는 큰 어려움이 없었으나, 음악 파일을 불러오는 것에 많은 시간이 소요된다는 문제점이 존재했습니다.
이를 해결하기 위해 좋아요 기능을 만들었고, 좋아요를 누를 경우 디바이스에 해당 음악을 저장하도록 구현했습니다. 근데 그렇게 구현을 하고 보니, 좋아요를 몇 개만 눌러도 앱 용량이 수 십, 수 백 메가바이트가 되어버립니다.
또 이를 해결하기 위해 방법을 모색하던 중, '다른 뮤직 플레이어 앱들은 어떻게 작동하는가?' 에 대한 궁금증이 생겼고, 조금 찾아보니 모두 스트리밍이라는 형태로 유저가 원하는 데이터를 제공하고 있었습니다.
사실 스트리밍이라 하면 조금 어려운 기술처럼 보이기도 하고(실제로 어려웠습니다), 복잡해 보이기도 합니다(실제로 복잡합니다).
그러나, 파일 단위로 음악을 불러오고 재생하는 것에 여러 문제점이 있으므로, 조금은 무리해서라도 스트리밍과 관련된 지식을 갖기 위해 해당 포스트를 작성하게 되었습니다.
스트리밍
파일 단위로 음악을 불러오는 경우에는 아주 큰 불편이 따릅니다. 일단 파일 다운로드가 완료되어야 음악을 재생할 수 있기 때문입니다. 이미 파일이 디바이스에 보관되어 있는 경우에는 상관없지만, 요즘은 디바이스에 직접 음악 파일을 받는 일이 잘 없기도 합니다. 애초에 PC 에 모바일 기기를 연결하는 일이 과거에 비해 잘 없는 것 같습니다.
스트리밍은 파일을 여러 개의 파일로 나누어 물 흐르듯이 (streaming) 연이어 보내는 방식을 말합니다.
대표적 스트리밍 기술은 Adobe 의 HDS, Apple 의 HLS, MS 의 Smooth Streaming, 그리고 MPEG 의 DASH 등이 있는데, 그 중 가장 자주 언급되며, Android 의 ExoPlayer 에서 적극 지원하는 HLS 와 DASH 에 대해서만 알아볼까 합니다.
HLS
HLS (Http Live Streaming) 는 애플이 개발한 스트리밍 프로토콜입니다. 비디오와 오디오를 작은 Chunk 인 .ts 파일로 분할하고, 이를 순차적으로 전송합니다. 순서에 대해서는 플레이리스트인 .m3u8 (그림의 회색 사각형) 파일에 기록되어 있습니다. 클라이언트가 호출해야 하는 URL 도 .m3u8 파일을 가리키는 URL 입니다.
현대적 스트리밍 프로토콜은 네트워크 상황에 맞게 비트레이트(초당 비트 전송률)를 조절하므로, 네트워크 상황이 좋으면 높은 비트레이트의 파일을, 그렇지 않으면 낮은 비트레이트의 파일을 재생합니다. 또한 프로토콜 수준에서 암호화를 제공하기 때문에, 컨텐츠 보안 측면에서도 장점이 있습니다.
DASH
DASH (Dynamic Adaptive Streaming over HTTP) 는 MPEG 라는 국제표준화단체가 만들었습니다. 우리가 흔히 알고 있는 MP3(MPEG-1 Layer-3) 확장자를 처음 정의한 곳이기도 합니다. 전체적인 컨디션은 HLS 와 비슷합니다. 비트레이트 조절이나 암호화 기능도 제공합니다. 작동 방식 역시 HLS 와 유사합니다.
🤔 그럼 HLS 만 쓰던, DASH 만 쓰던, 하나로 통일할 수는 없었나요? 왜 굳이 두 개가 존재하죠?
이유는 간단한데, iOS 기반 디바이스에서 DASH 를 사용할 수 없기 때문입니다. 애플은 독자적인 프로토콜을 구성하였으며, 현재는 대부분의 오디오 및 비디오 서비스에서 DASH 와 함께 HLS 도 지원하고 있습니다.
수많은 iOS 유저를 놓칠 수는 없으니까요.
🤔 HLS 와 DASH 중 어느 것이 더 나은 선택일까요?
iOS 기기에 대한 지원이 필요하다면 HLS 를 사용하면 되겠습니다. 다만, HLS 의 각 세그먼트 길이는 대개 10초 정도이며, DASH 의 경우 약 2~6초 사이이므로, 네트워크 상황이 좋지 않은 경우에는 DASH 가 조금 더 안정적일 수 있습니다.
또한, DASH 는 국제 표준이므로 호환성이 매우 좋습니다. 그러므로, 개발 비용에 여유가 있다면 MPEG-DASH 를 기본값으로 설정하고, iOS 기기의 경우에만 HLS 를 사용하도록 설계하면 좋겠습니다. 그렇지 않은 경우에는 그냥 HLS 하나만 사용하는 것이 합리적일 것으로 보입니다.
데이터
오디오에 관한 포스팅이니, 비디오 데이터 확장자에 대해서는 굳이 다루지 않습니다.
오디오 데이터 확장자는 정말 다양한데요. 가장 친숙한 것은 WAV 와 MP3 일 것입니다. iOS 기기를 오래 사용하셨던 분들이라면 AAC 에도 친숙하실 거라 생각합니다.
WAV
보통 우리가 웨이브 파일이라고 칭하며, Windows OS 에서 사용되는 표준 PCM(Pulse-Code Modulation) 형식의 파일입니다. Windows OS 의 점유율이 압도적이기 때문에, 대부분의 음원 편집이나 재생 소프트웨어에서도 해당 포맷을 지원합니다. 다만, 무손실 및 무압축 형식이기 때문에 음질은 좋겠으나 용량이 클 수 있습니다.
MP3
MPEG 에서 규정한 국제 표준이며, MPEG-1(또는 2) Layer-3 의 줄임말입니다. LP 음악의 디지털화에 가장 큰 공헌을 했다고 여겨지는 손실형 오디오 압축 포맷입니다. 인간이 인식할 수 없거나 신뢰할 수 없는 부분은 과감하게 제거하고 압축하여 용량을 아주 작게 줄입니다.
MP3 에서 제공하는 비트레이트는 64kbps, 128kbps, 192kbps, 320kbps 등이 있습니다. 비트레이트가 높을수록 초당 더 많은 비트를 전송하므로 더 풍부한 음향을 경험할 수 있습니다. 이는 고정 비트레이트이며, CBR (Constant Bit Rate) 이라고 칭합니다.
CBR 을 사용하지 않는 방법도 있는데요. 음원 정보가 비교적 많은 구간은 높은 비트레이트를 적용하고, 그렇지 않은 구간에는 낮은 비트레이트를 적용하도록 하는 VBR (Variable Bit Rate) 방식이 그것입니다. 이는 용량과 음질 사이에서의 훌륭한 타협점을 찾을 수 있도록 돕습니다. 음질이 중요한 서비스라면 VBR 방식을 사용하여 인코딩하는 것이 유저 경험에 효과적일 것입니다.
AAC
스트리밍 프로토콜에 HLS 가 있다면, 파일 형식에는 AAC 가 있습니다. 다만 Apple 이 개발한 포맷은 아니고, MPEG 과 여러 기관이 협력하여 제작하였습니다.
이름은 Advanced Audio Coding 의 줄임말인데, 이름에서 유추할 수 있듯이 MP3 보다 더 나은 압축률을 보입니다. 음질적으로도 MP3 보다 우위에 있고요. 적용 가능한 비트레이트 범위도 더 넓습니다.
이와 같은 이유들로 스트리밍에서 탁월한 성능을 보이기 때문에 최근 각광받고 있습니다.
오디오 및 비디오 역시 이미지의 경우와 같이 빠른 요청 및 응답, 그리고 저장 공간 활용을 위해 다양한 방법으로 압축하고 있는 것 같습니다. 오디오 및 비디오가 아닌 이미지의 여러 포맷과 압축에 관한 지식은 다음 포스트에서 확인하실 수 있습니다.
Extra
Android 의 ExoPlayer 에서 HLS 방식의 음원을 실행하는 방법은 다음과 같습니다. 더 많은 세팅이 있을텐데, 이는 추후에 학습하여 다시 작성하려고 합니다.
val exoPlayer = ExoPlayer.Builder(this).build()
val dataSourceFactory: DataSource.Factory = DefaultHttpDataSource.Factory()
val hlsMediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(오디오소스_URL))
exoPlayer.setMediaSource(hlsMediaSource)
exoPlayer.prepare()
exoPlayer.play()
Test
12mb 정도 크기의 음원을 파일 단위 로드 방식과 스트리밍 로드 방식, 두 개 방식 모두를 사용하여 불러와봅니다.
먼저 파일 단위 로드 방식입니다. 단 한 번의 요청만 발생하고, 음원 크기에 따라 다르지만, 5~9mb 사이인 보통의 음원인 경우 3~4초 이상 걸리며, 해당 파일은 8.4mb 이고 5초 정도 걸렸습니다.
다음은 HLS 방식으로 같은 파일을 불러왔습니다. 1.2초 만에 음원이 재생됩니다. 이후에도 끊김 없이 잘 재생됩니다. 로드 시간 70% 이상 감소하였습니다.
스트리밍에 관련된 지식을 얻고 싶었는데, 이 기회에 학습하게 되어 정말 기쁩니다! 개발 중인 앱에도 잘 적용할 수 있으니, 이보다 더 좋을 수 없습니다.
'Android > Tech' 카테고리의 다른 글
내부 리소스 접근에 왜 Context 가 필요한 걸까? (0) | 2023.08.21 |
---|---|
우리는 어떻게 Modularization 해야 하는가? (0) | 2023.08.13 |
Android Context Details (feat.LocalContext) (0) | 2023.06.28 |
StateMachine 과 Stackless Coroutine (0) | 2023.05.31 |
[Dagger-Hilt] Dagger-Hilt 의 대표적인 Annotations (0) | 2023.04.03 |