最近公司搞的項目中涉及到流媒體播放,并且需要硬解碼,是以想到了VLC這個開源項目。去官網下載下傳了vlc-android源碼進行編譯,生成的apk安裝在公司的裝置上可以運作,不錯不錯,有現成的東西當然不會再去“造輪胎”,把編譯後的android 工程導入eclipse 看了所有的代碼,覺得對于我們隻需要實作流媒體播放的來說顯得有些累贅,這篇文章隻需要實作流媒體播放的部分
關于源碼下載下傳和編譯的部分可以檢視:http://wiki.videolan.org/AndroidCompile
下面的代碼有多部分是vlc-android工程源碼,它們已經為我們封裝好了要調用的jni函數和一些配置資訊,這部分源碼可以拿來就用。
1.建立一個android工程,界面很簡單,就一個SurfaceView
MainActivity 的代碼如下:
public class MainActivity extends Activity implements SurfaceHolder.Callback{
private SurfaceView mSurface;
private SurfaceHolder mSurfaceHolder;
private LibVLC mLibVLC;
private EventManager mEventManger;
private boolean mIsPlaying;
private int mVideoHeight;
private int mVideoWidth;
private int mSarNum;
private int mSarDen;
private int mSurfaceAlign;
private static final int SURFACE_SIZE = 3;
private static final int SURFACE_BEST_FIT = 0;
private static final int SURFACE_FIT_HORIZONTAL = 1;
private static final int SURFACE_FIT_VERTICAL = 2;
private static final int SURFACE_FILL = 3;
private static final int SURFACE_16_9 = 4;
private static final int SURFACE_4_3 = 5;
private static final int SURFACE_ORIGINAL = 6;
private int mCurrentSize = SURFACE_BEST_FIT;
private static final String uri = "rtsp://217.146.95.166:554/live/ch6bqvga.3gp";
private static final String TAG = "DTV";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSurface = (SurfaceView) findViewById(R.id.surface);
mSurfaceHolder = mSurface.getHolder();
mSurfaceHolder.addCallback(this);
mSurface.setKeepScreenOn(true);
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
int pitch;
String chroma = pref.getString("chroma_format", "");
if(Util.isGingerbreadOrLater() && chroma.equals("YV12")) {
mSurfaceHolder.setFormat(ImageFormat.YV12);
pitch = ImageFormat.getBitsPerPixel(ImageFormat.YV12) / 8;
} else if (chroma.equals("RV16")) {
mSurfaceHolder.setFormat(PixelFormat.RGB_565);
PixelFormat info = new PixelFormat();
PixelFormat.getPixelFormatInfo(PixelFormat.RGB_565, info);
pitch = info.bytesPerPixel;
} else {
mSurfaceHolder.setFormat(PixelFormat.RGBX_8888);
PixelFormat info = new PixelFormat();
PixelFormat.getPixelFormatInfo(PixelFormat.RGBX_8888, info);
pitch = info.bytesPerPixel;
}
mSurfaceAlign = 16 / pitch - 1;
enableIOMX(true);
try {
mLibVLC = LibVLC.getInstance();
} catch (LibVlcException e) {
Log.i(TAG, "LibVLC.getInstance() error:"+e.toString());
e.printStackTrace();
return ;
}
mEventManger = EventManager.getInstance();
mEventManger.addHandler(mEventHandler);
}
private void enableIOMX(boolean enableIomx){
SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(VLCApplication.getAppContext());
Editor e = p.edit();
e.putBoolean("enable_iomx", enableIomx);
LibVLC.restart();
}
private DtvCallbackTask mDtvCallbackTask = new DtvCallbackTask(this) {
@Override
public void run() {
// TODO Auto-generated method stub
int n = 25;
while((n-- != 0)&& !mIsPlaying){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(!mIsPlaying){
Log.i(TAG, "could not open media or internet not access");
}
}
};
private final VideoEventHandler mEventHandler = new VideoEventHandler(this);
private class VideoEventHandler extends WeakHandler<MainActivity>{
public VideoEventHandler(MainActivity owner) {
super(owner);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = getOwner();
if(activity == null) return;
switch (msg.getData().getInt("event")) {
case EventManager.MediaPlayerPlaying:
Log.i(TAG, "MediaPlayerPlaying");
mIsPlaying = true;
break;
case EventManager.MediaPlayerPaused:
Log.i(TAG, "MediaPlayerPaused");
mIsPlaying = false;
break;
case EventManager.MediaPlayerStopped:
Log.i(TAG, "MediaPlayerStopped");
mIsPlaying = false;
break;
case EventManager.MediaPlayerEndReached:
Log.i(TAG, "MediaPlayerEndReached");
break;
case EventManager.MediaPlayerVout:
break;
case EventManager.MediaPlayerPositionChanged:
//don't spam the logs
break;
default:
Log.e(TAG, String.format("Event not handled (0x%x)", msg.getData().getInt("event")));
break;
}
super.handleMessage(msg);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {
mLibVLC.attachSurface(mSurfaceHolder.getSurface(), MainActivity.this,width,height);
Log.i(TAG, " width="+ width+" height="+height);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stu
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
mLibVLC.detachSurface();
}
public void setSurfaceSize(int width, int height, int sar_num, int sar_den) {
if (width * height == 0)
return;
// store video size
mVideoHeight = height;
mVideoWidth = width;
mSarNum = sar_num;
mSarDen = sar_den;
Message msg = mHandler.obtainMessage(SURFACE_SIZE);
mHandler.sendMessage(msg);
}
private final Handler mHandler = new VideoPlayerHandler(this);
private static class VideoPlayerHandler extends WeakHandler<MainActivity> {
public VideoPlayerHandler(MainActivity owner) {
super(owner);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = getOwner();
if(activity == null) // WeakReference could be GC'ed early
return;
switch (msg.what) {
case SURFACE_SIZE:
activity.changeSurfaceSize();
break;
}
}
};
@Override
protected void onResume() {
super.onResume();
if(mLibVLC != null){
try{
mLibVLC.readMedia(uri, false);
}catch(Exception e){
Log.i(TAG,e.toString());
return;
}
mDtvCallbackTask.execute();
}else {
return;
}
}
private void changeSurfaceSize() {
// get screen size
int dw = getWindow().getDecorView().getWidth();
int dh = getWindow().getDecorView().getHeight();
// getWindow().getDecorView() doesn't always take orientation into account, we have to correct the values
boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
if (dw > dh && isPortrait || dw < dh && !isPortrait) {
int d = dw;
dw = dh;
dh = d;
}
// sanity check
if (dw * dh == 0 || mVideoWidth * mVideoHeight == 0) {
Log.e(TAG, "Invalid surface size");
return;
}
// compute the aspect ratio
double ar, vw;
double density = (double)mSarNum / (double)mSarDen;
if (density == 1.0) {
/* No indication about the density, assuming 1:1 */
vw = mVideoWidth;
ar = (double)mVideoWidth / (double)mVideoHeight;
} else {
/* Use the specified aspect ratio */
vw = mVideoWidth * density;
ar = vw / mVideoHeight;
}
// compute the display aspect ratio
double dar = (double) dw / (double) dh;
switch (mCurrentSize) {
case SURFACE_BEST_FIT:
if (dar < ar)
dh = (int) (dw / ar);
else
dw = (int) (dh * ar);
break;
case SURFACE_FIT_HORIZONTAL:
dh = (int) (dw / ar);
break;
case SURFACE_FIT_VERTICAL:
dw = (int) (dh * ar);
break;
case SURFACE_FILL:
break;
case SURFACE_16_9:
ar = 16.0 / 9.0;
if (dar < ar)
dh = (int) (dw / ar);
else
dw = (int) (dh * ar);
break;
case SURFACE_4_3:
ar = 4.0 / 3.0;
if (dar < ar)
dh = (int) (dw / ar);
else
dw = (int) (dh * ar);
break;
case SURFACE_ORIGINAL:
dh = mVideoHeight;
dw = (int) vw;
break;
}
// align width on 16bytes
int alignedWidth = (mVideoWidth + mSurfaceAlign) & ~mSurfaceAlign;
// force surface buffer size
mSurfaceHolder.setFixedSize(alignedWidth, mVideoHeight);
// set display size
LayoutParams lp = mSurface.getLayoutParams();
lp.width = dw * alignedWidth / mVideoWidth;
lp.height = dh;
mSurface.setLayoutParams(lp);
mSurface.invalidate();
}
@Override
protected void onDestroy() {
if(mLibVLC.isPlaying()){
mLibVLC.stop();
}
mLibVLC = null;
super.onDestroy();
}
}
2.将vlc-android 中org.videolan.vlc包下面的這幾個class 添加:
Aout.java
BitmapCache.java
EventManager.java
LibVLC.java
LibVlcException.java
TrackInfo.java
Util.java
VLCApplication.java
WeakHandler.java
3.将源碼編譯出的libs下的armeabi-v7a(如果設裝置是arm6 或者以下,是armeabi)檔案夾添加在android工程的libs下面
uri = "rtsp://217.146.95.166:554/live/ch6bqvga.3gp"是我在網上随便找的一個rtsp 流媒體位址
主要的部分是:
a. mLibVLC = LibVLC.getInstance(); 用來擷取mLIbVLC的執行個體,其中會初始化LibVLC,在AndroidManifest.xml中要添加android:name="org.videolan.vlc.VLCApplication"這樣程式啟動時會調用VLCApplication使其生成執行個體,不會導緻LibVLC.getInstance()出錯。
b.mLibVLC.readMedia(uri, false);調用這一句後如果uri位址可用,流媒體就開始在載入,并且播放,并不需要mLibVLC.play()。
c.mLibVLC.attachSurface(mSurfaceHolder.getSurface(), MainActivity.this,width,height);調用這句的時候如果視訊不顯示,界面突然退出,是因為沒有添加:public void setSurfaceSize(int width, int height, int sar_num, int sar_den)這個函數(我編譯源碼的時候ANDROID_ABI=armeabi-v7a,ANDROID_ABI設定不同這個函數的參數不同),它在libvlcjni.c 的jni_SetAndroidSurfaceSize函數中調用,用來設定surfaceview大小的。
如果需要硬體解碼,就需要添加以下方法:
private void enableIOMX(boolean enableIomx){
SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(VLCApplication.getAppContext());
Editor e = p.edit();
e.putBoolean("enable_iomx", enableIomx);
LibVLC.restart();
}
将sharedpreferences 的key "enable_iomx'設定為true,因為libvlcjni.c 中通過函數libvlc_media_t *new_media(jlong instance, JNIEnv *env, jobject thiz, jstring fileLocation, bool noOmx, bool noVideo)調用java 代碼LibVLC.java 中的useIOMX()擷取“enable_iomx”的值,然後判斷是否用硬體解碼。
在調試的過程中還會出現的錯誤是因為:Util.java 中String ANDROID_ABI = properties.getProperty("ANDROID_ABI");調用屬性“ANDROID_ABI”的值時傳回的是null導緻,這主要發生在LibVLC.getInstance();時,自己判斷一下,如果為ANDROID_ABI==null,就根據自己的裝置選擇指派“armeabi-v7a”或者“armeabi”.
mEventManger = EventManager.getInstance();
mEventManger.addHandler(mEventHandler);
是用來添加播放事件的,當播放視訊出現play,stop,pause等狀态時,會接收到。
項目中碰到的問題就這些讓我困惑了一陣,其餘的可以通過谷歌或着度娘找到相應的方法。