最近公司搞的项目中涉及到流媒体播放,并且需要硬解码,所以想到了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();
- }
在调试的过程中还会出现的错误是因为: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);
项目中碰到的问题就这些让我困惑了一阵,其余的可以通过谷歌或着度娘找到相应的方法。