Mac 下编译使用 VLC-Android

时间:2023-01-11 15:57:35

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 <remi@remlab.net>
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 <remi@remlab.net>
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中的位置
    Mac 下编译使用 VLC-Android
    figure 2. Java 代码结构视图
    Mac 下编译使用 VLC-Android
    figure 3. Demo界面
    Mac 下编译使用 VLC-Android

  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简例