vlc-android 中呼叫用libvlcjni.so實現流媒體播放
最近公司搞的專案中涉及到流媒體播放,並且需要硬解碼,所以想到了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等狀態時,會接收到。專案中碰到的問題就這些讓我困惑了一陣,其餘的可以通過谷歌或著度娘找到相應的方法。