Mac 下編譯使用 VLC-Android

1. 編譯環境

  • 操作系統: Mac (OSX 10.10 Yosemite)
  • JDK : 1.7
  • JAVA IDE:JetBrains Intellji IDE Ultimate 14.02
  • Android NDK:android-ndk-r10d
  • Android SDN:android-sdk_r24.0.2
  • Android VLC Git 版本:
commit 180149353d6cce63f7e55e345f5289b9b2e856d6
Author: VideoLAN <videolan@videolan.org>
Date:   Tue Mar 17 15:18:55 2015 -0400

   l10n: Thai update

   Signed-off-by: Christoph Miebach <christoph.miebach@web.de>

commit fc3bb722db5407624b14b9e06684008f124a199c
Author: Alexander Lakhin <exclusion@gmail.com>
Date:   Tue Mar 17 15:18:49 2015 -0400

    l10n: Russian update

    Signed-off-by: Christoph Miebach <christoph.miebach@web.de>
  • VLC Git 版本:
commit 3ca68a656282c5dcb4c2ebbb9ec9e8ec6069576f
Author: Rémi Denis-Courmont <[email protected]>
Date:   Tue Mar 17 19:16:44 2015 +0200

    block: simplify block_FifoRelease

    Since the FIFO is being destroyed, there is no need to lock and to
    update the FIFO internal state.

commit efc27f360357ee8ae65f64d20dd5e3d1e7280fa0
Author: Rémi Denis-Courmont <[email protected]>
Date:   Tue Mar 17 19:41:04 2015 +0200

    udp: avoid alloc/free on I/O error

2. 編譯 vlc 及 libvlcjni.so

  • 設置環境變量
export ANDROID_SDK=/path/to/android-sdk
export ANDROID_NDK=/path/to/android-ndk
export PATH=$PATH:$ANDROID_SDK/platform-tools:$ANDROID_SDK/tools
  • 下載 Android VLC 代碼
git clone git://git.videolan.org/vlc-ports/android.git
  • 編譯 VLC 及相關 Jni 程序
# 題外話,真是拜服寫腳本的人,一個腳本直接搞定所有事
sh compile.sh -a armeabi-v7a

如果你的環境與我類似,那麼應該能編譯成功 vlc 且生成了 libvlcjni.so ,此時腳本會繼續生成其 Example 的 apk 但應該不會生成成功,會提示需要 SDK 21 而我們的是 SDK 22,當然最好在編譯之前用 Android SDK Manager 下載 SDK 21,誠然對於懂點 Android 的程序員來說,這也無所謂,我們自己建個項目來生成一個簡化的 VLC DEMO

3. VLC Android Demo

  1. 使用IDEA 生成一個 Android 項目,SDK 就選用 22 即可
  2. 將Android VLC源碼中的 vlc-android 項目 src 中的org.videolan.libvlc 全部拷到自己的項目工程裏,同時將 org.videolan.vlc.util 裏的PreferenceVLCInstance 兩個類一併拷過來,最後再把生成的 jni 拷到 libs 下,如此操作後,項目應該是這樣的:
    figure 1. JNI 在Libs中的位置
    項目Libs
    figure 2. Java 代碼結構視圖
    項目結構視圖
    figure 3. Demo界面
    界面

  3. 修改主體代碼

package com.example.TestVlc;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.app.Activity;
import android.graphics.PixelFormat;
import android.media.AudioManager;
import android.os.Bundle;
import android.util.Log;
import android.view.*;
import org.videolan.libvlc.EventHandler;
import org.videolan.libvlc.IVideoPlayer;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.LibVlcException;
import org.videolan.vlc.util.VLCInstance;

public class MainActivity extends Activity implements SurfaceHolder.Callback, IVideoPlayer {

    private final static String TAG = "[VlcVideoActivity]";

    private SurfaceView mSurfaceView;
    private LibVLC mMediaPlayer;
    private SurfaceHolder mSurfaceHolder;
    private Surface mSurface = null;

//    private View mLoadingView;

    private int mVideoHeight;
    private int mVideoWidth;

    private static final int SURFACE_LAYOUT = 3;

    @Override
    public void setSurfaceLayout(int width, int height, int visible_width, int visible_height, int sar_num, int sar_den) {
        mVideoHeight = height;
        mVideoWidth = width;
        mVideoVisibleHeight = visible_height;
        mVideoVisibleWidth = visible_width;
        mSarNum = sar_num;
        mSarDen = sar_den;

//        Message msg = mHandler.obtainMessage(SURFACE_LAYOUT);
//        mHandler.sendMessage(msg);
        mHandler.removeMessages(HANDLER_SURFACE_SIZE);
        mHandler.sendEmptyMessage(HANDLER_SURFACE_SIZE);

        Log.w(TAG, " video height: " + height + " width: " + width + "vheight: "
                + visible_height + " vwidth: " + visible_width);
    }

    @Override
    public int configureSurface(Surface surface, final int width, final int height, final int hal) {
//        Log.w(TAG, " ready to configure Surface");
        Log.w(TAG, "configureSurface: " + width +"x"+height);

        final ConfigureSurfaceHolder holder = new ConfigureSurfaceHolder(surface);

        final Handler handler = new Handler(Looper.getMainLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (mSurface == holder.surface && mSurfaceHolder != null) {
                    if (hal != 0)
                        mSurfaceHolder.setFormat(hal);
                    mSurfaceHolder.setFixedSize(width, height);
                }

                synchronized (holder) {
                    holder.configured = true;
                    holder.notifyAll();
                }
            }
        });

        try {
            synchronized (holder) {
                while (!holder.configured)
                    holder.wait();
            }
        } catch (InterruptedException e) {
            return 0;
        }
        return 1;
    }

    @Override
    public void eventHardwareAccelerationError() {

    }

    private int mVideoVisibleHeight;
    private int mVideoVisibleWidth;
    private int mSarNum;
    private int mSarDen;

    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mSurfaceView = (SurfaceView) findViewById(R.id.video);
//        mLoadingView = findViewById(R.id.loading_view);
        try {
            mMediaPlayer = VLCInstance.getLibVlcInstance();
        } catch (LibVlcException e) {
            e.printStackTrace();
        }

        mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceHolder.setFormat(PixelFormat.RGBX_8888);
        mSurfaceHolder.addCallback(this);

        mMediaPlayer.eventVideoPlayerActivityCreated(true);

        EventHandler em = EventHandler.getInstance();
        em.addHandler(mVlcHandler);

        this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
        mSurfaceView.setKeepScreenOn(true);
        //        mMediaPlayer.setMediaList();
        //        mMediaPlayer.getMediaList().add(new Media(mMediaPlayer, "http://live.3gv.ifeng.com/zixun.m3u8"), false);
        //        mMediaPlayer.playIndex(0);

        String mp4Url = "http://112.25.51.77/youku/69767840D9047788B56AD24FE/" +
                        "0300080100546EA5DE021C178D04965BA862E1-ED7B-1D4D-C3F7-EC28B3554A1D.mp4";
        mMediaPlayer.playMRL(mp4Url);
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        if (mMediaPlayer != null) {
            mSurfaceHolder = surfaceHolder;
            mMediaPlayer.attachSurface(surfaceHolder.getSurface(), this);
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        mSurfaceHolder = holder;
        if (mMediaPlayer != null) {
            mMediaPlayer.attachSurface(holder.getSurface(), this);//, width, height
        }
        if (width > 0) {
            mVideoHeight = height;
            mVideoWidth = width;
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        if (mMediaPlayer != null) {
            mMediaPlayer.detachSurface();
        }
    }

    private static final int HANDLER_BUFFER_START = 1;
    private static final int HANDLER_BUFFER_END = 2;
    private static final int HANDLER_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 Handler mVlcHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg == null || msg.getData() == null)
                return;

            int playEvent = msg.getData().getInt("event");
            switch (playEvent) {
                case EventHandler.MediaPlayerTimeChanged:
                    break;
                case EventHandler.MediaPlayerPositionChanged:
                    break;
                case EventHandler.MediaPlayerPlaying:
                    Log.w(TAG, " playing");
                    mHandler.removeMessages(HANDLER_BUFFER_END);
                    mHandler.sendEmptyMessage(HANDLER_BUFFER_END);
                    break;
                case EventHandler.MediaPlayerBuffering:
                    Log.w(TAG, " buffering");
                    break;
                case EventHandler.MediaPlayerLengthChanged:
                    break;
                case EventHandler.MediaPlayerEndReached:
                    //播放完成
                    break;
                default:
                    Log.w(TAG, " the handler is : " + playEvent);
            }

        }
    };

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case HANDLER_BUFFER_START:
                    showLoading();
                    break;
                case HANDLER_BUFFER_END:
                    hideLoading();
                    break;
                case HANDLER_SURFACE_SIZE:
                    changeSurfaceSize();
                    break;
            }
        }
    };

    private void showLoading() {
//        mLoadingView.setVisibility(View.VISIBLE);
        Log.w(TAG, " ready to show loading");
    }

    private void hideLoading() {
//        mLoadingView.setVisibility(View.GONE);
        Log.w(TAG, " ready to hide loading");
    }

    private void changeSurfaceSize() {
        // get screen size
        int dw = getWindowManager().getDefaultDisplay().getWidth();
        int dh = getWindowManager().getDefaultDisplay().getHeight();

        Log.w(TAG, " display width :" + dw + " display height: " + dh);
        // calculate aspect ratio
        double ar = (double) mVideoWidth / (double) mVideoHeight;
        // calculate 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 = mVideoWidth;
                break;
        }

        Log.w(TAG, " after configure, display width :" + dw + " display height: " + dh);
        mSurfaceHolder.setFixedSize(mVideoWidth, mVideoHeight);
        ViewGroup.LayoutParams lp = mSurfaceView.getLayoutParams();
        lp.width = dw;
        lp.height = dh;
        mSurfaceView.setLayoutParams(lp);
        mSurfaceView.invalidate();
    }

    private static class ConfigureSurfaceHolder {
        private final Surface surface;
        private boolean configured;

        private ConfigureSurfaceHolder(Surface surface) {
            this.surface = surface;
        }
    }
}

4. 鳴謝

如按上述操作應該能正常編譯且能生成 apk 文件,在真機可以正常播放視頻了。
實現上述功能,本人蔘考了以下鏈接,在此表示感謝。

  1. VLC Android官方編譯文檔
  2. Mac下編譯vlc-android
  3. vlc-android簡例
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章