最近公司搞的项目中涉及到流媒体播放,并且需要硬解码,所以想到了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等状态时,会接收到。
项目中碰到的问题就这些让我困惑了一阵,其余的可以通过谷歌或着度娘找到相应的方法。