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
- 使用IDEA 生成一个 Android 项目,SDK 就选用 22 即可
将Android VLC源码中的
vlc-android
项目src
中的org.videolan.libvlc
全部拷到自己的项目工程里,同时将org.videolan.vlc.util
里的Preference
和VLCInstance
两个类一并拷过来,最后再把生成的 jni 拷到libs
下,如此操作后,项目应该是这样的:
figure 1. JNI 在Libs中的位置
figure 2. Java 代码结构视图
figure 3. Demo界面修改主体代码
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 文件,在真机可以正常播放视频了。
实现上述功能,本人参考了以下链接,在此表示感谢。