저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의

안드로이드의 MediaPlayer가 스트리밍 프로토콜로 RTSP를 지원하지만 RTSP를 사용해서 미디어를 플레이해보면, 스트리밍이 얼마나 불안정한지 알 수 있다. 안드로이드 3 부터는 HLS를 지원하다고는 하지만 실제로 해보면 기기에 따라 지원이 안 되는 경우도 있다. 그리고, 여전히 2.2~2.3 버전의 기기를 사용하는 사람들도 아직은 상당수 존재할 것 같다. 이런 이류로 예전에 구현 작업을 진행할 때 VOD를 플레이하기 위해 RTSP 대신 HTTP PDL(Progressive download) 방식을 사용했지만, 여전히 LIVE 방송 같은 것을 구현하려면 RTSP를 사용해야했다.


필자가 FFmpeg을 사용해가면서 할 만큼 안드로이드만 집중적으로 팔 수 있는 상황이 아니였기에 포기하고 있었는데, 검색을 하던 중 우연히 Vitamio라는 안드로이드 라이브러리를 알게 되었다. Vitamio는 ARM 기반 프로세스를 위한 ffmpeg 모듈과 이를 이용한 미디어 플레이어 기반 코드를 제공하는 안드로이드 라이브러리로서, 이를 사용하면 비교적 쉽게 안드로이드 기기에서 RTMP, HLS 등의 스트리밍을 플레이할 수 있다. 물론, HTTP PDL도 지원한다.


Vitamio는 안드로이드 2.1 이상을 지원하고 ARMv6, VFP, ARMv7, NEON 등을 지원하기 때문에 현재 시중에 나온 대다수의 안드로이드 기기에서 동작한다.


Vitamio 사용을 위한 개발 환경 설정


아주 간단하다. 다음의 순서대로 진행하면 된다.

  1. http://vitamio.org/vitamios/android-3-dot-0?locale=en 에서 3.0 버전을 다운로드 받는다. (3.0 버전 기준)
  2. 압축받은 파일을 풀면 VitamioBundle 폴더와 VitamioDemo 폴더가 생긴다.
  3. 이클립스에서 VitamioBundle을 안드로이드 프로젝트로 임포트한다.
    1. 프로젝트 이름이 InitActivity로 임포트 되는데, VitamioBundle로 바꿔준다. (안 바꿔줘도 상관은 없다.)
  4. 안드로이드 프로젝트를 생성한다. A프로젝트라고 하자.
  5. A 프로젝트 선택 후, [Project 메뉴] -> [Properties] 메뉴 실행
    1. [Android] 항목 -> 레퍼런스 프로젝트에 VitamioBundle를 추가
    2. [Project References] 항목 -> VitamioBundle 추가
  6. A프로젝트의 AndroidManifest.xml 파일에 다음 Activity 설정을 추가한다.
  7. <activity

    android:name="io.vov.vitamio.activity.InitActivity"

    android:launchMode="singleTop"

    android:theme="@android:style/Theme.NoTitleBar"

    android:windowSoftInputMode="stateAlwaysHidden" />

  8. 이제 A프로젝트에서 Vitamio가 제공하는 VideoView, MediaPlayer 등을 이용해서 구현하면 된다.

Vitamio는 안드로이드가 기본으로 제공하는 VideoView, MediaPlayer 등과 (패키지만 다른) 동일한 이름의 클래스를 제공하고 있다. 따라서, 기존에 안드로이드의 미디어 관련 기능을 사용하고 있다면, 아주 작은 코드 수정만으로도 Vitamio의 기능을 사용할 수 있다. (다운로드 배포판에 함께 포함된 VitamioDemo에 사용코드 예제가 포함되어 있으니 참고하기 바란다.)


Vitamio가 제공하는 기능을 사용하려면 최초에 네이티브 라이브러리를 로딩하는 과정을 거쳐야 하는데, 이 과정은 다음의 코드를 사용하여 처리한다.


import io.vov.vitamio.LibsChecker;


public class MainActivity extends Activity {


@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

if (! LibsChecker.checkVitamioLibs(this)) {

return;

}


따라서, 앱이 실행될 때 최초에 한 번 위 코드를 실행해 주어야 Vitamio가 제공하는 기능을 올바르게 사용할 수 있다.


VitamioBundle 프로젝트에 대한 레퍼런스 없애기


VitamioBundle에 대한 의존을 하려면 함께 하는 개발자들의 이클립스 프로젝트 VitamioBundler을 임포트 해 주어야 하는 불편함이 있다. 여럿이 공동으로 작업을 한다면 VitamioBundler을 사용하지 않고 작업할 수 있으면 더 좋을 것이다.


이를 위한 방법은 역시 간단하다. 다음의 순서대로 진행하면 된다. (참고로, 아래의 코드들은 VitamioBundle을 이용해서 개발하고 있는 OPlayer의 소스 코드 http://code.taobao.org/p/oplayer/src/trunk/OPlayer/ 에서 참고한 것이다.)

  1. 안드로이드 프로젝트의 libs 폴더에 다음의 파일을 복사한다.
    1. VitamioBundle/lib/vitamio.jar
    2. VitamioBundle/lib/armeabi 폴더 및 armeabi-v7a 폴더
  2. 안드로이드 프로젝트의 res/raw 폴더에 다음의 파일을 복사한다.
    1. VitamioBundle/res/raw/libarm.so
  3. io.vov.vitamio 패키지를 생성하고 그 곳에 R.java 클래스를 생성한다. [소스 코드는 아래 참고]
  4. [프로젝트패키지].vitamio 패키지에 다음의 두 클래스를 생성한다.
    1. LibsChecker.java [소스 코드는 아래 참고]
    2. InitActivity.java [소스 코드는 아래 참고]
  5. AndroidManifest.xml 파일에 Vitamio의 InitActivity가 아닌 4-2 과정에서 생성한 InitActivity를 이용해서 액티비티 설정을 추가한다.
그 다음에 Vitamio에 포함된 LibsChecker가 아닌 위 과정에서 생성한 LibsChecker를 이용해서 초기화작업을 진행하면 된다.

import [마이패키지].vitamio.LibsChecker;

public class MainActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!LibsChecker.checkVitamioLibs(this)) {
return;
}

앞서 과정에서 소개한 각 코드는 다음과 같다.

- R.java
package io.vov.vitamio;

public class R {
public static final class raw {
public static final int libarm = [마이패키지].R.raw.libarm;
}
}

- InitActivity.java
/*
 * Copyright (C) 2012 YIXIA.COM
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package [마이패키지].vitamio;

import io.vov.vitamio.Vitamio;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.WindowManager;

public class InitActivity extends Activity {
public static final String FROM_ME = "fromVitamioInitActivity";
public static final String EXTRA_MSG = "EXTRA_MSG";
public static final String EXTRA_FILE = "EXTRA_FILE";
private ProgressDialog mPD;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

new AsyncTask<Object, Object, Object>() {
@Override
protected void onPreExecute() {
mPD = new ProgressDialog(InitActivity.this);
mPD.setCancelable(false);
mPD.setMessage("Initializing decoders...");
mPD.show();
}

@Override
protected Object doInBackground(Object... params) {

Vitamio.initialize(getApplicationContext());
uiHandler.sendEmptyMessage(0);
return null;
}
}.execute();
}

private Handler uiHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mPD.dismiss();
Intent src = getIntent();
Intent i = new Intent();
i.setClassName(src.getStringExtra("package"),
src.getStringExtra("className"));
i.setData(src.getData());
i.putExtras(src);
i.putExtra(FROM_ME, true);
startActivity(i);

finish();
}
};
}

- LibsChecker.java
package [마이패키지].vitamio;

import android.app.Activity;
import android.content.Intent;
import io.vov.vitamio.Vitamio;

public final class LibsChecker {
public static final String FROM_ME = "fromVitamioInitActivity";

public static final boolean checkVitamioLibs(Activity ctx) {
if ((!Vitamio.isInitialized(ctx))
&& (!ctx.getIntent().getBooleanExtra("fromVitamioInitActivity",
false))) {
Intent i = new Intent();
i.setClassName(ctx.getPackageName(),
"com.scgs.vitamio.InitActivity");
i.putExtras(ctx.getIntent());
i.setData(ctx.getIntent().getData());
i.putExtra("package", ctx.getPackageName());
i.putExtra("className", ctx.getClass().getName());
ctx.startActivity(i);
ctx.finish();
return false;
}
return true;
}
}

미디어 플레이하기


가장 쉬운 방법은 VitamioBundle에 포함된 io.vov.vitamio.widget.VideoView 클래스를 사용하는 것이다. VitamioDemo에 포함된 VideoViewDemo 클래스에 VideoView의 사용 예제가 포함되어 있다.


또한, VideoView 소스 코드를 보면 io.vov.vitamio.MediaPlayer를 어떻게 사용하는지 알 수 있으므로, MediaPlayer를 직접 이용해서 자신에 맞는 플레이어 화면을 구현할 수도 있다.




Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 팔팔청춘 2013.03.21 10:05 신고  댓글주소  수정/삭제  댓글쓰기

    좋은 정보 감사드립니다!!

    • 최범균 madvirus 2013.03.25 09:55 신고  댓글주소  수정/삭제

      플레이어 기능 구현에 도움이 되셨으면 좋겠네요.

    • 정한욱 2015.10.11 21:54 신고  댓글주소  수정/삭제

      안녕하세요 Wowza 스트리밍 서버를 통해서 안드로이드 Videoview 로 RTSP URL 받아오려고 하는 초보 개발자 입니다. ㅠㅠ 영상을 보내는데 android 에서 Videoview로 받을때 아예 회색으로 다 깨져버리는 현상이 있는데 어떻게 처리해야하는지 정말로 도움 부탁드립니다. ㅠㅠ 꼭 부탁드리고 귀찮더라도 01094960568연락주시면 감사하겠습니다.

  2. 흐아아앜 2013.06.11 19:30 신고  댓글주소  수정/삭제  댓글쓰기

    demo 코드로 미디어플레이어에서 RTMP 재생을 해봤는데 안되네요

    다른방법을 써야하나요?

  3. 고맙습니다 2013.07.14 16:47 신고  댓글주소  수정/삭제  댓글쓰기

    어떻게 구현할지 막막했는데 좋은 정보 감사합니다!!!
    fms에 있는 동영상을 앱으로 스트리밍 재생하는 연습을 해볼건데 이 라이브러리로 가능한거겠죠??

  4. 지젝 2013.12.09 19:22 신고  댓글주소  수정/삭제  댓글쓰기

    잘 돌아가네요 ^^ 감사합니다.

  5. wind~~~ 2013.12.11 13:37 신고  댓글주소  수정/삭제  댓글쓰기

    감사합니다~
    덕분에 보틀넥 하나를 풀었네요...

    더불어...

    RTSP 서버에 붙을 때 vitamio Fatal signal 11 (SIGSEGV) 라는 에러가 발생할 수 있습니다. 이 때 재생이 안되죠.

    source file 중 MediaPlayerDemo_Video.java에서 mMediaPlayer.getMetadata();를 제거하면 데모를 돌릴 수 있습니다.
    참고 하세요....

    • 정한욱 2015.10.11 21:55 신고  댓글주소  수정/삭제

      안녕하세요 Wowza 스트리밍 서버를 통해서 안드로이드 Videoview 로 RTSP URL 받아오려고 하는 초보 개발자 입니다. ㅠㅠ 영상을 보내는데 android 에서 Videoview로 받을때 아예 회색으로 다 깨져버리는 현상이 있는데 어떻게 처리해야하는지 정말로 도움 부탁드립니다. ㅠㅠ 꼭 부탁드리고 귀찮더라도 01094960568연락주시면 감사하겠습니다.

  6. allday 2014.03.04 11:01 신고  댓글주소  수정/삭제  댓글쓰기

    좋은글 잘 보고 갑니다~

  7. 정한욱 2015.10.11 21:54 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 Wowza 스트리밍 서버를 통해서 안드로이드 Videoview 로 RTSP URL 받아오려고 하는 초보 개발자 입니다. ㅠㅠ 영상을 보내는데 android 에서 Videoview로 받을때 아예 회색으로 다 깨져버리는 현상이 있는데 어떻게 처리해야하는지 정말로 도움 부탁드립니다. ㅠㅠ 꼭 부탁드리고 귀찮더라도 01094960568연락주시면 감사하겠습니다.

  8. minsus 2016.01.07 14:26 신고  댓글주소  수정/삭제  댓글쓰기

    이거 유료 라이브러리죠?

  9. kc 2017.02.24 07:29 신고  댓글주소  수정/삭제  댓글쓰기

    글 잘봤습니다. 감사합니다.ㅋ

    저 하나 질문이 있는데요. vitamio Videoview 및 vitamio Mediaplayer를 다중으로 사용해서 여러 영상을 못 띄우나요?

동영상 스트리밍을 위해 최초에 선택한 방법은 다음과 같았다.

  • PC 웹 브라우저: RTMP 프로토콜 이용 스트리밍/플래시 플레이어 이용.
  • 안드로이드: RTSP 프로토콜 이용 스트리밍, 안드로이드 MediaPlayer 이용.

미디어 서버로는 Wowza를 선택했는데, 그 이유는 위 두 가지 프로토콜을 모두 지원하기 때문이었다. 


안드로이드와 RTSP의 나쁜 궁합


그런데, 기능 구현을 진행하다보니 안드로이드에서 다음의 문제점들이 드러났다.

  • 스트리밍 품질
  • seeking 기능의 문제
우선, 스트리밍 자체의 품질이 좋지 않았다. RTSP 자체가 데이터 송수신에 UDP를 사용하는 것에서 비롯되는 것도 있겠지만, 안드로이드 2.2 기반 폰, 3.2 기반 태블릿, 4.0 기반 폰, 4.1 기반 넥서스 7에서 화면이 일부 깨지거나 하는 등의 현상이 발생했다.

특히 문제되는 부분은 시간바의 이동, 즉 seeking 기능에 있었다. seek bar를 이동하는 동안 플레이어가 해당 시점으로 이동하고 버퍼링을 하는 등의 작업을 하는데, RTSP의 경우 시간 이동과 버퍼링 등이 신속하게/원활하게 동작하지 않는 경우가 많았다. 특히, 허니콤 기반의 갤럭시 탭은 시간 이동이 안 될 정도로 문제가 심각했다. (갤럭시 탭을 아이스크림으로 업그레이드 해야 그나마 seek bar 이동이 동작했다.)

안드로이드의 MediaPlayer와 RTSP와의 궁합이 그닥 좋지 않다는 점, 특히 RTSP를 사용할 때 seeking이 부드럽게 되지 않는다는 점 때문에 스트리밍 방식을 교체하기로 결정했다.

HTTP 기반 스트리밍으로의 전환

여러 방법을 고민하다가 HTTP 기반의 스트리밍으로 처리하기로 결정했다. 필요한 건 다음과 같은 것들이다.
  • HTTP range 헤더를 지원하는 웹 서버. 아파치 httpd는 당연히 지원하므로, 아파치 웹 서버를 사용 (seeking과 관련)
  • 힌팅(hinting)된 MP4 파일 (비디오는 H.264, 오디오는 AAC로 인코딩 된 버전)
안드로이드의 MediaPlayer는 힌팅된 MP4 파일을 HTTP 기반으로 스트리밍으로 플레이 할 수 있는 기능을 제공하고 있다. 따라서, 아파치 웹 서버의 문서 디렉토리에 MP4 파일을 업로드 하고, MediaPlayer의 데이터 소스로 다음과 같이 MP4 파일에 대한 URL을 지정하면 해당 MP4를 플레이할 수 있게 된다.

path = "http://mediaserver/starwords_1.mp4";
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(path);
...

MediaPlayer의 Seeking 기능을 사용할 경우 HTTP의 range 헤더를 이용해서 원하는 위치로 빠르게 이동할 수 있게 된다. 즉, 프로그레시브 다운로드Progressive Download 방식의 비디오 플레이와 달리 원하는 위치로 빠르게 이동할 수 있게 된다.

안드로이드 앱에서 동영상 미디어를 스트리밍으로 플레이하는 기능이 필요하다면, 현재 수준에서는 폼질/안정성 측면을 고려한다면 여러모로 불안한 RTSP 보다는 HTTP 기반 스트리밍을 사용하는 것이 현명할 것이다.

참고:


Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 2012.10.31 21:04  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다