안드로이드 개발자를 위한 MPEG-DASH

@onseok· March 25, 2025 · 13 min read

요즘 모바일 중심 시대에서 효율적인 동영상 스트리밍은 좋은 사용자 경험을 제공하기 위해 필수적인 기능이라고 생각합니다. MPEG-DASH(Dynamic Adaptive Streaming over HTTP)는 모바일 환경에서 발생하는 다양한 스트리밍 문제들을 해결해주는 주요 표준으로 자리잡았습니다. 안드로이드 개발자라면 MPEG-DASH를 제대로 이해하는 것이 불안정한 네트워크 상황에서도 끊김 없는 동영상 재생 기능을 구현하는 데 정말 큰 도움이 됩니다.

MPEG-DASH가 뭐길래?

MPEG-DASH는 HTTP를 통한 적응형 비트레이트 스트리밍을 위한 ISO 표준(ISO/IEC 23009-1)입니다. 예전 스트리밍 방식들은 특별한 서버와 네트워크 설정이 필요했지만, DASH는 일반 HTTP 웹 서버만으로도 미디어를 전송할 수 있습니다.

DASH의 가장 큰 장점은 이름에서도 알 수 있듯이 "Dynamic Adaptive"(동적 적응형) 특성입니다. 이 기능 덕분에 동영상 스트림이 실시간으로 네트워크 상황에 맞춰 품질을 조절하기 때문에, 와이파이가 불안정하거나 데이터 연결이 좋지 않아도 버퍼링 없이 영상을 볼 수 있습니다.

핵심 개념: 프로토콜 관점에서 살펴보기

DASH를 제대로 이해하려면 이 프로토콜이 어떻게 미디어 콘텐츠를 구성하는지 알아볼 필요가 있습니다.

MPD (Media Presentation Description)

DASH의 심장이라고 할 수 있는 건 바로 MPD 파일입니다. 이건 XML 형식의 문서인데, 쉽게 말하면 동영상에 대한 모든 정보를 담고 있는 설명서라고 보면 됩니다.

  • 어떤 비디오/오디오 트랙이 있는지
  • 각각 어떤 품질(representations)이 제공되는지
  • 세그먼트는 어떻게 나뉘어 있는지
  • 재생 시간 정보는 어떻게 되는지

간소화된 MPD 구조는 다음과 같습니다.

<MPD>
  <Period>
    <AdaptationSet mimeType="video/mp4">
      <Representation id="720p" bandwidth="2000000" width="1280" height="720">
        <SegmentTemplate timescale="1000" duration="2000" 
                         initialization="init-$RepresentationID$.mp4" 
                         media="segment-$RepresentationID$-$Number$.m4s"/>
      </Representation>
      <Representation id="480p" bandwidth="1000000" width="854" height="480">
        <!-- 유사한 구조 -->
      </Representation>
    </AdaptationSet>
    <AdaptationSet mimeType="audio/mp4">
      <!-- 오디오 representations -->
    </AdaptationSet>
  </Period>
</MPD>

세그먼트와 초기화 방식

DASH 콘텐츠는 크게 두 종류의 조각으로 나뉩니다.

  1. 초기화 세그먼트: 영상을 재생하는 데 필요한 기본 정보들이 들어있습니다. 코덱은 무엇을 쓰는지, 타이밍은 어떻게 되는지, 그 밖의 메타데이터가 여기 담겨 있습니다.
  2. 미디어 세그먼트: 실제 우리가 보는 동영상 내용이 들어있습니다. 보통 2초에서 10초 정도 길이로 잘게 쪼개져 있습니다.

이렇게 콘텐츠를 작은 조각으로 나누는 이유는 뭘까요? 바로 네트워크 상황에 따라 적절한 품질의 조각만 골라서 내려받을 수 있기 때문입니다. 네트워크 상태가 좋을 때는 고화질 세그먼트를, 신호가 약해지면 저화질 세그먼트를 요청하는 식입니다.

비디오 인코딩 관점에서 들여다보기

이번엔 미디어 인코딩 측면에서 DASH 콘텐츠를 준비할 때 꼭 고려해야 할 사항들을 알아보겠습니다.

인코딩 래더란?

DASH에서는 보통 "인코딩 래더"라는 개념을 사용합니다. 쉽게 말하면 하나의 동영상을 여러 품질로 인코딩해서 사다리처럼 쌓아놓는 겁니다. 네트워크 상황에 따라 이 사다리를 올라가거나 내려가면서 적절한 품질을 선택하는 것이고, 효과적인 인코딩 래더는 대략 이런 구성을 가집니다.

해상도 비트레이트 사용 사례
320x180 200 Kbps 열악한 네트워크 환경
640x360 500 Kbps 기본 모바일 시청
854x480 1000 Kbps 표준 시청
1280x720 2000 Kbps HD 시청
1920x1080 4000 Kbps 풀 HD 시청

코덱 선택하기

DASH의 장점 중 하나는 어떤 코덱이든 사용할 수 있다는 것입니다. 하지만 보통은 아래 코덱들이 많이 사용됩니다.

  • H.264/AVC: 거의 모든 기기에서 지원되지만, 압축 효율은 좀 떨어집니다.
  • H.265/HEVC: H.264보다 훨씬 효율적으로 압축하지만, 지원하는 기기가 제한적입니다.
  • VP9: 구글이 만든 코덱으로, HEVC의 대안이라고 볼 수 있습니다.
  • AV1: 최신 코덱으로 압축률이 매우 뛰어나지만, 인코딩/디코딩에 리소스가 많이 필요합니다.

안드로이드에서는 이 코덱들의 지원 여부가 기기와 안드로이드 버전에 따라 제각각입니다. 그래도 H.264는 거의 모든 기기에서 돌아가니 안전하게 선택할 수 있습니다.

세그먼트 길이 최적화하기

세그먼트를 얼마나 잘게 쪼갤지도 중요한데, 이건 장단점이 명확합니다.

  • 짧은 세그먼트 (2-4초): 네트워크 상황이 바뀌면 바로바로 대응할 수 있지만, 단점은 세그먼트가 많아져서 요청 횟수가 늘고 서버 부하가 커진다는 점입니다.
  • 긴 세그먼트 (6-10초): 요청 횟수가 줄어들어 효율적이지만, 네트워크 상황이 급변할 때 대응이 느립니다. 갑자기 신호가 약해지면 버퍼링이 발생할 수 있습니다.

안드로이드에서 실제로 구현해보기

이제 실제로 안드로이드 앱에서 DASH를 어떻게 구현하는지 살펴보겠습니다. 다행히도 요즘은 정말 쉬워졌습니다.

ExoPlayer 사용하기

구글에서 제공하는 ExoPlayer가 안드로이드에서 DASH 재생을 위한 최적의 선택입니다. 간단한 구현 예시를 보여드리겠습니다.

// 플레이어 인스턴스 생성
val player = SimpleExoPlayer.Builder(context).build()

// 미디어 소스 설정
val dashUri = Uri.parse("https://example.com/stream.mpd")
val mediaItem = MediaItem.fromUri(dashUri)
val dataSourceFactory = DefaultDataSourceFactory(context, "user-agent")
val mediaSource = DashMediaSource.Factory(dataSourceFactory)
    .createMediaSource(mediaItem)

// 플레이어 준비
player.setMediaSource(mediaSource)
player.prepare()

// PlayerView에 연결
playerView.player = player

// 준비되면 재생 시작
player.playWhenReady = true

내 맘대로 적응 로직 조절하기

ExoPlayer는 기본적으로도 적응형 스트리밍을 잘 처리하지만, 진행하고 있는 프로젝트에 맞게 더 세밀하게 조정하고 싶다면 TrackSelectorBandwidthMeter를 직접 커스터마이징할 수 있습니다.

// 커스텀 트랙 선택기 생성
val trackSelector = DefaultTrackSelector(context)
trackSelector.parameters = trackSelector.buildUponParameters()
    .setMaxVideoSize(1280, 720) // 최대 해상도 제한
    .setForceHighestSupportedBitrate(false)
    .build()

// 플레이어 생성에 사용
val player = SimpleExoPlayer.Builder(context)
    .setTrackSelector(trackSelector)
    .build()

오프라인으로도 볼 수 있게 만들기

DASH 콘텐츠는 인터넷 연결이 없을 때도 볼 수 있도록 미리 다운로드해둘 수 있습니다. ExoPlayer의 DownloadManager를 사용하면 쉽게 구현할 수 있습니다.

val downloadManager = DownloadManager(
    context,
    databaseProvider,
    DefaultDownloaderFactory(dataSourceFactory)
)

// 다운로드 요청 생성
val dashUri = Uri.parse("https://example.com/stream.mpd")
val downloadRequest = DownloadRequest(
    "download-id",
    DownloadRequest.TYPE_DASH,
    dashUri,
    Collections.emptyList(),
    null,
    null
)

// 다운로드 시작
downloadManager.addDownload(downloadRequest)

최고의 성능을 뽑아내기 위한 팁

안드로이드 앱에서 DASH를 구현할 때 성능을 극대화하려면 몇 가지 중요한 부분을 신경 써야 합니다.

버퍼 관리하기

버퍼 설정은 사용자 경험에 직접적인 영향을 주는 중요한 요소입니다. 앱의 성격에 맞게 버퍼 크기를 조절해볼 수 있습니다.

val loadControl = DefaultLoadControl.Builder()
    .setBufferDurationsMs(
        minBufferMs,
        maxBufferMs,
        bufferForPlaybackMs,
        bufferForPlaybackAfterRebufferMs
    )
    .build()

val player = SimpleExoPlayer.Builder(context)
    .setLoadControl(loadControl)
    .build()

네트워크 상황 실시간으로 감지하기

DASH의 진가는 네트워크 상황 변화에 얼마나 빠르게 대응하느냐에 달려있습니다. 아래처럼 네트워크 변화를 감지해서 대응하면 더 좋은 경험을 제공할 수 있습니다.

// 네트워크 콜백 등록
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
    override fun onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities) {
        // 네트워크 기능에 따라 플레이어 설정 업데이트
        if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) {
            // 무제한 네트워크에서는 더 높은 품질 허용
            trackSelector.parameters = trackSelector.buildUponParameters()
                .setMaxVideoBitrate(4_000_000)
                .build()
        } else {
            // 제한된 네트워크에서는 더 보수적으로 설정
            trackSelector.parameters = trackSelector.buildUponParameters()
                .setMaxVideoBitrate(1_000_000)
                .build()
        }
    }
})

HLS와의 비교

DASH가 훌륭한 프로토콜이지만, 스트리밍 서비스를 개발할 때 가장 많이 고려되는 프로토콜인 HLS도 있습니다. HLS (HTTP Live Streaming)는 Apple이 자사 제품에 사용하기 위해 2009년에 개발했지만, 현재는 다양한 장치에서 사용되고 있는 비디오 스트리밍 프로토콜입니다. 각각의 기술적 특성과 장단점을 심층적으로 비교해 보겠습니다.

특성 MPEG-DASH HLS
세그먼트 형식 MP4, WebM TS, fMP4(최근)
세그먼트 길이 유연함(일반적으로 2-10초) 일반적으로 6-10초(최근 Low-Latency HLS에서 개선)
코덱 지원 코덱 독립적(H.264, H.265, VP9, AV1 등) 원래는 H.264 중심, 최근 HEVC, AV1 지원 추가
지연 시간 일반적으로 20-30초(Low-Latency DASH에서 개선) 일반적으로 30초+(Low-Latency HLS에서 개선)
DRM 지원 Common Encryption(CENC) 표준으로 다중 DRM 지원 FairPlay(Apple), 최근 CENC 부분 지원
적응 로직 클라이언트 측 구현에 따라 다양 비트레이트 기반 적응
텍스트 트랙 TTML, WebVTT WebVTT, CEA-608/708

마무리

MPEG-DASH는 네트워크 상황이 수시로 바뀌는 모바일 환경에서 정말 강력한 무기가 되는 기술이라고 생각합니다. 이 글에서 살펴본 것처럼 네트워킹, 인코딩, 실제 구현까지 여러 각도에서 DASH를 이해하면 사용자들에게 끊김 없는 부드러운 동영상 경험을 제공할 수 있습니다.

앞으로도 저지연 DASH, 더 강력한 콘텐츠 보호 기능, 다양한 분석 지표 등 MPEG-DASH는 계속 발전할 것이고, 동영상 관련 안드로이드 앱을 개발한다면, 이런 최신 트렌드를 계속 따라가는 것이 중요해질 것 같습니다.

@onseok
배움을 배포하기