
ExoPlayer에서 FPS 측정하기: 기본 개념부터 커스텀 렌더러까지
안드로이드에서 비디오 재생을 다룰 때, ExoPlayer는 강력하고 유연한 선택지로 자리 잡았습니다.
이번 글에서는 ExoPlayer의 기본적인 개요와 설치 방법부터, FPS(Frame Per Second)를 측정하기 위한 커스텀 렌더러 구현까지 다뤄보겠습니다.
특히 ExoPlayer는 기본적으로 FPS 측정 기능을 제공하지 않으므로, 이를 해결하기 위한 방법을 단계별로 알아보겠습니다.
ExoPlayer란 무엇인가?
우선 ExoPlayer가 뭔가에 대해부터 이야기를 나누고자 합니다.
ExoPlayer는 Google과 Android 팀이 개발한 오픈소스 미디어 플레이어 라이브러리로, 안드로이드의 기본 MediaPlayer보다 더 많은 기능과 커스터마이징 가능성을 제공합니다.
주요 기능은 다음과 같습니다:
- 다양한 포맷 지원: MP4, HLS, DASH 등 스트리밍 및 로컬 재생을 지원.
- 커스터마이징: 렌더러, 트랙 선택기 등을 통해 세부적인 제어 가능.
- 확장성: DRM, 광고 삽입 등 고급 기능 통합 가능.
- 성능 최적화: 하드웨어 가속 및 버퍼링 최적화.
이러한 특징 덕분에 ExoPlayer는 유튜브, 넷플릭스 같은 대규모 앱에서도 사용된다고 합니다. (물론 확인은 안해봤습니다.)
ExoPlayer 기본 설치 방법 (gradle.kts)
ExoPlayer를 프로젝트에 추가하려면 build.gradle.kts 파일에 의존성을 추가해야 합니다.
현재 ExoPlayer는 androidx.media3 패키지로 통합되어 배포됩니다.
아래는 기본 설정 예시입니다:
dependencies {
implementation("androidx.media3:media3-exoplayer:1.3.1")
implementation("androidx.media3:media3-ui:1.3.1") // PlayerView 등 UI 컴포넌트용
}
gradle.kts는 Kotlin DSL을 사용하는 Gradle 스크립트로, 안드로이드 스튜디오에서 점점 표준으로 자리 잡고 있습니다.
설정 후 Sync Project with Gradle Files를 실행하면 ExoPlayer를 사용할 준비가 됩니다.
AndroidX란 무엇인가?
AndroidX는 안드로이드 지원 라이브러리(Support Library)의 진화된 버전으로, Jetpack이라는 이름 아래 통합된 라이브러리 집합입니다.
기존 android.support 패키지를 대체하며, 더 일관된 네이밍(androidx.*)과 모듈화를 제공합니다.
ExoPlayer가 androidx.media3로 이동한 것도 AndroidX의 일환으로, 미디어 관련 기능을 통합 관리하기 위함입니다.
ExoPlayer를 왜 쓰고, 왜 AndroidX Media3로 들어갔나?
ExoPlayer는 기본 MediaPlayer의 한계를 넘어서기 위해 만들어졌습니다.
예를 들어, MediaPlayer는 HLS 같은 적응형 스트리밍을 제대로 지원하지 않거나, 커스터마이징이 제한적입니다.
반면 ExoPlayer는 이러한 문제를 해결하며 앱 개발자에게 더 많은 제어권을 줍니다.
androidx.media3로의 통합은 Google의 미디어 라이브러리 전략 변화 때문입니다.
기존 ExoPlayer는 독립적인 프로젝트였지만, AndroidX 아래로 편입되면서 media, media2, media3로 점진적으로 발전했습니다.
media3는 ExoPlayer를 핵심으로 삼아 안드로이드의 미디어 재생 표준을 강화한 결과물입니다.
FPS 측정: 기본 지원 없음, 커스텀 렌더러 필요
ExoPlayer는 강력하지만, FPS(Frame Per Second)나 드롭 프레임 수를 기본적으로 측정하는 기능을 제공하지 않습니다.
언젠간 생길 수 있겠지만, 현재로썬 기능을 제공하진 않습니다.
성능 모니터링이나 디버깅을 위해 FPS를 알고 싶다면, 직접 커스텀 렌더러를 만들어야 합니다.
여기서 렌더러(Renderer)가 핵심적인 역할을 합니다.
Renderer란 무엇인가?
Renderer는 ExoPlayer에서 미디어 데이터를 처리하고 출력하는 컴포넌트입니다.
예를 들어:
- MediaCodecVideoRenderer: 비디오 디코딩 및 화면 출력.
- MediaCodecAudioRenderer: 오디오 디코딩 및 스피커 출력.
기본 렌더러는 비디오를 화면에 그리거나 소리를 재생하는 역할만 하지만, 이를 상속받아 FPS를 계산하는 로직을 추가할 수 있습니다.
FPS 측정을 위한 커스텀 렌더러 구현
아래는 FPS와 드롭 프레임을 추적하는 FpsTrackingVideoRenderer의 구현 예시입니다.
import android.content.Context;
import android.os.Handler;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.ExoPlaybackException;
import androidx.media3.exoplayer.mediacodec.MediaCodecAdapter;
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
import androidx.media3.exoplayer.video.MediaCodecVideoRenderer;
import androidx.media3.exoplayer.video.VideoRendererEventListener;
@UnstableApi
public class FpsTrackingVideoRenderer extends MediaCodecVideoRenderer {
private long frameCount = 0; // 렌더링된 프레임 수
private long droppedFrameCount = 0; // 드랍된 프레임 수
private long lastUpdateTime = 0; // 마지막 업데이트 시간
private static final long UPDATE_INTERVAL_MS = 1000; // 1초 간격
public FpsTrackingVideoRenderer(Context context,
MediaCodecSelector mediaCodecSelector,
long allowedJoiningTimeMs,
Handler eventHandler,
VideoRendererEventListener eventListener,
int maxDroppedFramesToNotify) {
super(context, MediaCodecSelector.DEFAULT, allowedJoiningTimeMs, eventHandler, eventListener, maxDroppedFramesToNotify);
Log.d("FPS Tracker", "FpsTrackingVideoRenderer initialized");
}
@Override
protected void renderOutputBufferV21(MediaCodecAdapter codec, int index, long presentationTimeUs, long releaseTimeNs) {
super.renderOutputBufferV21(codec, index, presentationTimeUs, releaseTimeNs); // 비디오 렌더링
frameCount++; // 렌더링된 프레임 카운트 증가
updateFpsAndDropStats();
}
@Override
protected void dropOutputBuffer(MediaCodecAdapter codec, int index, long presentationTimeUs) {
super.dropOutputBuffer(codec, index, presentationTimeUs); // 드랍 처리
droppedFrameCount++; // 드랍된 프레임 카운트 증가
updateFpsAndDropStats();
}
private void updateFpsAndDropStats() {
long currentTime = System.currentTimeMillis();
if (currentTime - lastUpdateTime >= UPDATE_INTERVAL_MS) {
float elapsedSeconds = (currentTime - lastUpdateTime) / 1000.0f;
float fps = frameCount / elapsedSeconds; // 실시간 FPS 계산
Log.d("FPS Tracker", "실시간 FPS: " + fps + ", 드랍된 프레임 수: " + droppedFrameCount);
// 초기화
frameCount = 0;
droppedFrameCount = 0;
lastUpdateTime = currentTime;
}
}
}
코드 설명
- renderOutputBufferV21: 프레임이 화면에 렌더링될 때 호출되며, frameCount를 증가시킵니다.
- dropOutputBuffer: 프레임이 드롭될 때 호출되며, droppedFrameCount를 증가시킵니다.
- updateFpsAndDropStats: 1초 간격으로 FPS를 계산하고 로그로 출력합니다.
ExoPlayer 실행에 필요한 항목
ExoPlayer를 사용하려면 몇 가지 필수 컴포넌트가 필요합니다:
- PlayerView: 비디오를 화면에 표시하는 UI 컴포넌트.
- ExoPlayer 인스턴스: ExoPlayer.Builder로 생성하며, 커스텀 렌더러를 설정할 수 있습니다.
- MediaItem: 재생할 미디어 URI 또는 리소스.
아래는 VideoActivity에서 ExoPlayer를 설정하고 FPS를 추적하는 예제입니다.
import androidx.media3.common.MediaItem;
import androidx.media3.common.Player;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.RenderersFactory;
import androidx.media3.ui.PlayerView;
@OptIn(markerClass = UnstableApi.class)
public class VideoActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FrameLayout rootLayout = new FrameLayout(this);
setContentView(rootLayout);
// ExoPlayer로 비디오 추가
addVideoViewByExoPlayer(rootLayout, "android.resource://" + getPackageName() + "/" + R.raw.test1, 50, 100, 500, 300);
}
private void addVideoViewByExoPlayer(FrameLayout parentLayout, String videoPath, int x, int y, int width, int height) {
PlayerView playerView = new PlayerView(this);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, height);
params.leftMargin = x;
params.topMargin = y;
playerView.setLayoutParams(params);
// 커스텀 렌더러 팩토리
RenderersFactory renderersFactory = (eventHandler, videoRendererEventListener, audioRendererEventListener, textRendererOutput, metadataRendererOutput) -> {
return new Renderer[] {
new FpsTrackingVideoRenderer(this, MediaCodecSelector.DEFAULT, 0, eventHandler, videoRendererEventListener, 0),
new MediaCodecAudioRenderer(this, MediaCodecSelector.DEFAULT, eventHandler, audioRendererEventListener)
};
};
// ExoPlayer 생성
ExoPlayer player = new ExoPlayer.Builder(this).setRenderersFactory(renderersFactory).build();
playerView.setPlayer(player);
// 미디어 설정 및 재생
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(videoPath));
player.setMediaItem(mediaItem);
player.prepare();
player.setPlayWhenReady(true);
parentLayout.addView(playerView);
}
}
결론 및 소감
ExoPlayer는 강력한 미디어 재생 도구지만, FPS 측정 같은 세부 기능은 제공하지 않아 직접 구현해야 했습니다.
FpsTrackingVideoRenderer를 통해 렌더링된 프레임과 드롭된 프레임을 추적할 수 있으며, 이를 로그로 확인하거나 UI에 반영할 수 있었고 분명 해당 코드가 언젠가 또 필요할까 싶어 이렇게 개인블로그에 남겨둡니다.
저도 10년전 학부생때나 하던 안드로이드 개발을 다시 시작하려니 답답한 와중에 다양한 검색, 그리고 LLM 등을 통해 문제를 해결해 나가며 알게된 내용을 정리하였습니다.
관련 내용으로 고생하시는 분들이 또 계실까 싶어 도움이 되었으면 하는 바램에 글을 작성합니다. + 미래의 저를 위해
도움이 되셨다면 댓글 하나 남겨주세요
'모바일' 카테고리의 다른 글
React Native Expo 빌드 또는 로컬 빌드 방법 (0) | 2025.02.15 |
---|

ExoPlayer에서 FPS 측정하기: 기본 개념부터 커스텀 렌더러까지
안드로이드에서 비디오 재생을 다룰 때, ExoPlayer는 강력하고 유연한 선택지로 자리 잡았습니다.
이번 글에서는 ExoPlayer의 기본적인 개요와 설치 방법부터, FPS(Frame Per Second)를 측정하기 위한 커스텀 렌더러 구현까지 다뤄보겠습니다.
특히 ExoPlayer는 기본적으로 FPS 측정 기능을 제공하지 않으므로, 이를 해결하기 위한 방법을 단계별로 알아보겠습니다.
ExoPlayer란 무엇인가?
우선 ExoPlayer가 뭔가에 대해부터 이야기를 나누고자 합니다.
ExoPlayer는 Google과 Android 팀이 개발한 오픈소스 미디어 플레이어 라이브러리로, 안드로이드의 기본 MediaPlayer보다 더 많은 기능과 커스터마이징 가능성을 제공합니다.
주요 기능은 다음과 같습니다:
- 다양한 포맷 지원: MP4, HLS, DASH 등 스트리밍 및 로컬 재생을 지원.
- 커스터마이징: 렌더러, 트랙 선택기 등을 통해 세부적인 제어 가능.
- 확장성: DRM, 광고 삽입 등 고급 기능 통합 가능.
- 성능 최적화: 하드웨어 가속 및 버퍼링 최적화.
이러한 특징 덕분에 ExoPlayer는 유튜브, 넷플릭스 같은 대규모 앱에서도 사용된다고 합니다. (물론 확인은 안해봤습니다.)
ExoPlayer 기본 설치 방법 (gradle.kts)
ExoPlayer를 프로젝트에 추가하려면 build.gradle.kts 파일에 의존성을 추가해야 합니다.
현재 ExoPlayer는 androidx.media3 패키지로 통합되어 배포됩니다.
아래는 기본 설정 예시입니다:
dependencies {
implementation("androidx.media3:media3-exoplayer:1.3.1")
implementation("androidx.media3:media3-ui:1.3.1") // PlayerView 등 UI 컴포넌트용
}
gradle.kts는 Kotlin DSL을 사용하는 Gradle 스크립트로, 안드로이드 스튜디오에서 점점 표준으로 자리 잡고 있습니다.
설정 후 Sync Project with Gradle Files를 실행하면 ExoPlayer를 사용할 준비가 됩니다.
AndroidX란 무엇인가?
AndroidX는 안드로이드 지원 라이브러리(Support Library)의 진화된 버전으로, Jetpack이라는 이름 아래 통합된 라이브러리 집합입니다.
기존 android.support 패키지를 대체하며, 더 일관된 네이밍(androidx.*)과 모듈화를 제공합니다.
ExoPlayer가 androidx.media3로 이동한 것도 AndroidX의 일환으로, 미디어 관련 기능을 통합 관리하기 위함입니다.
ExoPlayer를 왜 쓰고, 왜 AndroidX Media3로 들어갔나?
ExoPlayer는 기본 MediaPlayer의 한계를 넘어서기 위해 만들어졌습니다.
예를 들어, MediaPlayer는 HLS 같은 적응형 스트리밍을 제대로 지원하지 않거나, 커스터마이징이 제한적입니다.
반면 ExoPlayer는 이러한 문제를 해결하며 앱 개발자에게 더 많은 제어권을 줍니다.
androidx.media3로의 통합은 Google의 미디어 라이브러리 전략 변화 때문입니다.
기존 ExoPlayer는 독립적인 프로젝트였지만, AndroidX 아래로 편입되면서 media, media2, media3로 점진적으로 발전했습니다.
media3는 ExoPlayer를 핵심으로 삼아 안드로이드의 미디어 재생 표준을 강화한 결과물입니다.
FPS 측정: 기본 지원 없음, 커스텀 렌더러 필요
ExoPlayer는 강력하지만, FPS(Frame Per Second)나 드롭 프레임 수를 기본적으로 측정하는 기능을 제공하지 않습니다.
언젠간 생길 수 있겠지만, 현재로썬 기능을 제공하진 않습니다.
성능 모니터링이나 디버깅을 위해 FPS를 알고 싶다면, 직접 커스텀 렌더러를 만들어야 합니다.
여기서 렌더러(Renderer)가 핵심적인 역할을 합니다.
Renderer란 무엇인가?
Renderer는 ExoPlayer에서 미디어 데이터를 처리하고 출력하는 컴포넌트입니다.
예를 들어:
- MediaCodecVideoRenderer: 비디오 디코딩 및 화면 출력.
- MediaCodecAudioRenderer: 오디오 디코딩 및 스피커 출력.
기본 렌더러는 비디오를 화면에 그리거나 소리를 재생하는 역할만 하지만, 이를 상속받아 FPS를 계산하는 로직을 추가할 수 있습니다.
FPS 측정을 위한 커스텀 렌더러 구현
아래는 FPS와 드롭 프레임을 추적하는 FpsTrackingVideoRenderer의 구현 예시입니다.
import android.content.Context;
import android.os.Handler;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.ExoPlaybackException;
import androidx.media3.exoplayer.mediacodec.MediaCodecAdapter;
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
import androidx.media3.exoplayer.video.MediaCodecVideoRenderer;
import androidx.media3.exoplayer.video.VideoRendererEventListener;
@UnstableApi
public class FpsTrackingVideoRenderer extends MediaCodecVideoRenderer {
private long frameCount = 0; // 렌더링된 프레임 수
private long droppedFrameCount = 0; // 드랍된 프레임 수
private long lastUpdateTime = 0; // 마지막 업데이트 시간
private static final long UPDATE_INTERVAL_MS = 1000; // 1초 간격
public FpsTrackingVideoRenderer(Context context,
MediaCodecSelector mediaCodecSelector,
long allowedJoiningTimeMs,
Handler eventHandler,
VideoRendererEventListener eventListener,
int maxDroppedFramesToNotify) {
super(context, MediaCodecSelector.DEFAULT, allowedJoiningTimeMs, eventHandler, eventListener, maxDroppedFramesToNotify);
Log.d("FPS Tracker", "FpsTrackingVideoRenderer initialized");
}
@Override
protected void renderOutputBufferV21(MediaCodecAdapter codec, int index, long presentationTimeUs, long releaseTimeNs) {
super.renderOutputBufferV21(codec, index, presentationTimeUs, releaseTimeNs); // 비디오 렌더링
frameCount++; // 렌더링된 프레임 카운트 증가
updateFpsAndDropStats();
}
@Override
protected void dropOutputBuffer(MediaCodecAdapter codec, int index, long presentationTimeUs) {
super.dropOutputBuffer(codec, index, presentationTimeUs); // 드랍 처리
droppedFrameCount++; // 드랍된 프레임 카운트 증가
updateFpsAndDropStats();
}
private void updateFpsAndDropStats() {
long currentTime = System.currentTimeMillis();
if (currentTime - lastUpdateTime >= UPDATE_INTERVAL_MS) {
float elapsedSeconds = (currentTime - lastUpdateTime) / 1000.0f;
float fps = frameCount / elapsedSeconds; // 실시간 FPS 계산
Log.d("FPS Tracker", "실시간 FPS: " + fps + ", 드랍된 프레임 수: " + droppedFrameCount);
// 초기화
frameCount = 0;
droppedFrameCount = 0;
lastUpdateTime = currentTime;
}
}
}
코드 설명
- renderOutputBufferV21: 프레임이 화면에 렌더링될 때 호출되며, frameCount를 증가시킵니다.
- dropOutputBuffer: 프레임이 드롭될 때 호출되며, droppedFrameCount를 증가시킵니다.
- updateFpsAndDropStats: 1초 간격으로 FPS를 계산하고 로그로 출력합니다.
ExoPlayer 실행에 필요한 항목
ExoPlayer를 사용하려면 몇 가지 필수 컴포넌트가 필요합니다:
- PlayerView: 비디오를 화면에 표시하는 UI 컴포넌트.
- ExoPlayer 인스턴스: ExoPlayer.Builder로 생성하며, 커스텀 렌더러를 설정할 수 있습니다.
- MediaItem: 재생할 미디어 URI 또는 리소스.
아래는 VideoActivity에서 ExoPlayer를 설정하고 FPS를 추적하는 예제입니다.
import androidx.media3.common.MediaItem;
import androidx.media3.common.Player;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.RenderersFactory;
import androidx.media3.ui.PlayerView;
@OptIn(markerClass = UnstableApi.class)
public class VideoActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FrameLayout rootLayout = new FrameLayout(this);
setContentView(rootLayout);
// ExoPlayer로 비디오 추가
addVideoViewByExoPlayer(rootLayout, "android.resource://" + getPackageName() + "/" + R.raw.test1, 50, 100, 500, 300);
}
private void addVideoViewByExoPlayer(FrameLayout parentLayout, String videoPath, int x, int y, int width, int height) {
PlayerView playerView = new PlayerView(this);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, height);
params.leftMargin = x;
params.topMargin = y;
playerView.setLayoutParams(params);
// 커스텀 렌더러 팩토리
RenderersFactory renderersFactory = (eventHandler, videoRendererEventListener, audioRendererEventListener, textRendererOutput, metadataRendererOutput) -> {
return new Renderer[] {
new FpsTrackingVideoRenderer(this, MediaCodecSelector.DEFAULT, 0, eventHandler, videoRendererEventListener, 0),
new MediaCodecAudioRenderer(this, MediaCodecSelector.DEFAULT, eventHandler, audioRendererEventListener)
};
};
// ExoPlayer 생성
ExoPlayer player = new ExoPlayer.Builder(this).setRenderersFactory(renderersFactory).build();
playerView.setPlayer(player);
// 미디어 설정 및 재생
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(videoPath));
player.setMediaItem(mediaItem);
player.prepare();
player.setPlayWhenReady(true);
parentLayout.addView(playerView);
}
}
결론 및 소감
ExoPlayer는 강력한 미디어 재생 도구지만, FPS 측정 같은 세부 기능은 제공하지 않아 직접 구현해야 했습니다.
FpsTrackingVideoRenderer를 통해 렌더링된 프레임과 드롭된 프레임을 추적할 수 있으며, 이를 로그로 확인하거나 UI에 반영할 수 있었고 분명 해당 코드가 언젠가 또 필요할까 싶어 이렇게 개인블로그에 남겨둡니다.
저도 10년전 학부생때나 하던 안드로이드 개발을 다시 시작하려니 답답한 와중에 다양한 검색, 그리고 LLM 등을 통해 문제를 해결해 나가며 알게된 내용을 정리하였습니다.
관련 내용으로 고생하시는 분들이 또 계실까 싶어 도움이 되었으면 하는 바램에 글을 작성합니다. + 미래의 저를 위해
도움이 되셨다면 댓글 하나 남겨주세요
'모바일' 카테고리의 다른 글
React Native Expo 빌드 또는 로컬 빌드 방법 (0) | 2025.02.15 |
---|