1. 程式人生 > >vlc-android 中呼叫用libvlcjni.so實現流媒體播放

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等狀態時,會接收到。

專案中碰到的問題就這些讓我困惑了一陣,其餘的可以通過谷歌或著度娘找到相應的方法。