안드로이드에서 멀티미디어를 재생하는 방법은 여러 가지가 있습니다. 그중에서 안드로이드의 멀티미디어 아키텍처를 바탕으로, 오디오와 비디오 재생을 모두 담당하는 기본 API인 MediaPlayer로 안드로이드에서 비디오를 재생하는 방법을 소개합니다.
소개
안드로이드의 미디어 재생과 관련된 주요 클래스는 MediaPlayer, AudioManager, VideoView입니다. 특히 MediaPlayer는 오디오와 비디오 재생을 담당하는 기본 API로 파일과 스트림을 모두 지원합니다. 이 글에서는 어떻게 SurficeView를 준비하고 MediaPlayer를 생성해서 비디오를 재생할 수 있는지 기초적인 예제를 통해 단계별로 설명합니다.
안드로이드 멀티미디어 아키텍처
제일 먼저 안드로이드 미디어 플레이어와 관련된 멀티미디어 아키텍처를 확인해보겠습니다. 안드로이드 애플리케이션과 미디어 서버는 분리된 프로세스에서 실행되는데, 미디어 서버는 부팅과 함께 시작해서 미디어 재생을 관리합니다. 네이티브 미들웨어 계층의 Surface Flinger는 비디오 하드웨어의 추상화로 여러 애플리케이션의 영상 데이터를 출력 장치로 내보내는 역할을 합니다. Audio Flinger는 비슷하게 오디오 하드웨어의 추상화로 여러 애플리케이션의 오디오 데이터를 출력 장치로 내보냅니다.
스트림이나 파일 등이 미디어 서버로 전달되면 커널 단의 하드웨어 코덱을 거쳐 내부의 네이티브 플레이어들을 통해 Audio Flinger나 Video Flinger로 전달되고 이들에 의해 드라이버로 넘겨져서 하드웨어에서 실제 출력이 일어나게 됩니다.
MediaPlayer 생성하기
MediaPlayer를 생성해서 준비하는 방법은 두 가지입니다. 하나는 기본 생성자를 사용하는 방법이고 하나는 팩토리 메서드를 사용하는 방법입니다.
첫 번째 방법을 사용하려면 기본 생성자인 MediaPlayer를 생성한 후 setDataSource() 메서드로 데이터 소스를 설정하고 setDisplay() 메서드로 서피스홀더를 넘겨준 다음 동기적인 준비 메서드인 prepare()나 비동기적인 준비 메서드 prepareAsync()를 호출하면 됩니다.
한편 팩토리 메서드를 사용하면 이런 메서드 호출 단계를 줄일 수 있습니다. static인 create 메서드로 MediaPlayer를 생성하고 인자로 Context와 데이터 소스, SurfaceHolder 등을 넘겨주면 됩니다. SurfaceHolder를 지정하지 않으면 오디오만 재생되는데, 이 SurfaceHolder에 대해서는 잠시 후에 설명하겠습니다.
팩토리 메서드를 사용하는 것이 편해 보이지만 주의할 점은 MediaPlayer가 성공적으로 로드될 경우 자동으로 동기적 준비 메서드인 prepare()가 불리기 때문에 대용량 미디어에는 비효율적이라는 점입니다.
MediaPlayer를 생성하는 예제를 한 번 볼까요? 설명해 드린 기본 생성자와 팩토리 메서드로 각각 미디어플레이어를 생성해서 준비하는 코드입니다. 둘 중 하나의 방법으로 재생준비를 마친 후 start() 메서드로 동영상 재생을 시작합니다.
MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(path); mediaPlayer.setDisplay(surfaceHolder); mediaPlayer.prepare(); // mediaPlayer.prepareAsync(); mediaPlayer.start(); MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.file1); mediaPlayer.setDisplay(surfaceHolder); mediaPlayer.start();
MediaPlayer의 상태 다이어그램
미디어플레이어에는 많은 상태가 있는데 안드로이드 개발자 사이트에서 이를 표현한 상태 다이어그램을 볼 수 있습니다. 다양한 상태가 있는데다 특정 상태에서만 쓸 수 있는 메서드도 있으므로 미디어플레이어를 처음 구현하는 분이라면 눈여겨 보시길 권장합니다. 만약 상태에 맞지 않는 메서드를 호출할 경우에는 IllegalStateException이 던져집니다.
MediaPlayer의 콜백
MediaPlayer는 용량이 비교적 큰 소스를 다룰 확률이 높으므로 콜백을 많이 제공합니다. 특히 버퍼 관련 콜백이나 준비 관련 콜백이 있고, SeekBar라고 하는 프로그레스바를 움직여서 재생 위치를 변경했을 때 해당 위치를 다 찾은 후 알려주는 OnSeekCompleteListener도 있습니다. 자세한 내용은 공식 문서를 참고하세요.
미디어 재생을 위한 권한 설정
MediaPlayer를 사용할 때 외부 URL에서 스트리밍을 받을 예정이라면 인터넷 권한이 필요합니다. 또 영상 재생 중에 화면이 어두워지거나 꺼지고 잠기는 것을 방지하고 싶다면 웨이크락 권한을 주셔야 합니다. 각각 아래와 같이 Manifest.xml 파일에 설정하면 됩니다.
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
SurfaceView
오디오와는 달리 비디오 재생을 위해서는 뷰의 위계질서 내에 그릴 수 있는 표면이 필요한데, 이를 제공하는 것이 바로 SurfaceView입니다. SurfaceView는 메인 스레드에서 직접 제어해야 하는 캔버스와 달리 백그라운드 스레드에서 화면을 업데이트할 수 있어 ANR(Application Not responding)을 방지할 수 있고 사용자의 입력에 블럭없이 바로 반응할 수 있습니다.
SurfaceHolder는 이런 Surface 객체를 관리하는 홀더입니다. MediaPlayer에서 비디오를 재생하려면 SurfaceView를 지정해야 하는데 SurfaceHolder를 생성한 다음 setDisplay 메서드의 인자로 전달하면 됩니다.
미디어 재생 Surface 준비
미디어가 재생될 SurfaceView를 XML에 선언하는 방법은 다음과 같습니다.
<SurfaceView android:id=“+id/surface” android:layout_width=“400dp” android:layout_height=“240dp” />
위 코드처럼 xml에 SurfaceView를 지정하면 액티비티 내부에서 해당 SurfaceView로부터 SurfaceHolder를 가져올 수 있습니다. 이 때 액티비티는 SurfaceHolder의 콜백을 구현해야 합니다. 아래 예제에서는 홀더의 콜백을 바로 처리하겠다는 의미로 addCallback(this)을 호출했습니다.
public class MediaPlayerActivity extends Activity implements SurfaceHolder.Callback { SurfaceView surfaceView; SurfaceHolder surfaceHolder; MediaPlayer mediaPlayer; @override public void onCreate(Bundle savedInstanceState) { // surfaceView = (SurfaceView) findViewById(R.id.surface); surfaceHolder = surfaceView.getHolder(); surfaceHolder.addCallback(this); // } }
MediaPlayer 준비 및 제어
SurfaceView를 준비했다면 앞서 보여드린 대로 기본 생성자나 팩토리 메서드 중 하나로 MediaPlayer를 준비합니다.
앞서 만든 액티비티에 다음과 같이 SurfaceHolder의 Callback을 구현합니다.
@override public void surfaceCreated(SurfaceHolder holder) { try { mediaPlayer.setDataSource( // ); mediaPlayer.setDisplay(holder); // } catch (Exception e) { // } } }
미디어 플레이어를 제어하는 메서드는 앞서 보여드린 상태 다이어그램에서처럼 상태에 따라 불릴 수 있는 메서드가 각기 다릅니다. 많이 사용하는 메서드는 당연히 start()와 pause(), stop()일 텐데요. 버튼을 만들어 각각 OnClickListener에 연결하면 됩니다. stop()을 불렀다면 다음 영상 재생을 준비하기 위해 미리 prepare()를 부르시는 것을 권장하는데 try catch 문으로 감싸줘야 합니다. 또한 미디어플레이어 자원을 회수하기 위해 액티비티의 OnDistory() 메서드 등에서 MediaPlayer를 release 해주세요.
추가적인 기능
이 글에서는 MediaPlayer의 가장 기본적인 기능만을 소개했기 때문에 SurfaceView의 가로, 세로를 임의로 설정했습니다. 따라서 5:3이 아닌 영상 소스라면 가로세로 비율이 맞지 않을 수 있습니다. 혹시 영상의 비율을 유지하면서 전체 화면으로 재생되길 원하신다면 Stack Overflow를 참고해주세요.
도움된 사이트
Media Playback, MediaPlayer, SurfaceView 등 안드로이드 개발자 사이트를 정독하면 안드로이드에서 멀티미디어를 다루는 방법을 보다 정확하게 이해할 수 있습니다. 미디어 프레임워크와 관련해서는 다음 사이트를 참고했습니다.
- Android Developers-Media Playback
- Android Developers-MediaPlayer
- Android Developers-SurfaceView
- Android Developers-SurfaceHolder
- Android media framework overview
- Android MultiMedia Framwork Overview
'MOBILE' 카테고리의 다른 글
모바일 디바이스에서 가로모드 또는 세로모드 감지 (0) | 2023.10.03 |
---|---|
[Android] Intent 활용 예시 (0) | 2023.10.01 |
모바일 디바이스에서 화면 가로세로 변경 시 화면 크기 고정 (0) | 2023.09.26 |
android permission 정리 (0) | 2023.09.26 |
[ANDROID(안드로이드) 앱 개발 기초] MEDIAPLAYER 음악 재생하기 (0) | 2016.11.07 |
Stream Play online audio mp3 from URL in android without downloading (0) | 2016.11.04 |
Android VideoView error 처리하기 (0) | 2016.11.04 |
안드로이드: 서비스 Service 예제 (0) | 2016.11.04 |
댓글