项目在调用VLCjni.so的过程中遇到了不少麻烦,最终成功了,所以写一篇学习笔记,今天是感恩节,感谢这些天自己在无数次失望后都没有放弃,哈哈。
先写一下过程吧:
1.包放进自己的项目里
2.启动播放界面
try {
// LibVLC.init(getApplicationContext());
EventHandler em = EventHandler.getInstance();
em.addHandler(handler);
mLibVLC = Util.getLibVlcInstance();
if (mLibVLC != null) {
String pathUri = "rtsp://192.168.56.1:554/CH1";
mLibVLC.playMyMRL(pathUri);
}
} catch (LibVlcException e) {
e.printStackTrace();
}
通过启动了一个handler来调用播放界面
Handler handler = new Handler() {
public void handleMessage(Message msg) {
Log.d(TAG, "Event = " + msg.getData().getInt("event"));
switch (msg.getData().getInt("event")) {
case EventHandler.MediaPlayerPlaying:
case EventHandler.MediaPlayerPaused:
break;
case EventHandler.MediaPlayerStopped:
break;
case EventHandler.MediaPlayerEndReached:
break;
case EventHandler.MediaPlayerVout:
if (msg.getData().getInt("data") > 0) {
Intent intent = new Intent();
intent.setClass(getApplicationContext(),
MakeVideo3Activity.class);
startActivity(intent);
MainActivity.this.finish();
}
break;
case EventHandler.MediaPlayerPositionChanged:
break;
case EventHandler.MediaPlayerEncounteredError:
AlertDialog dialog = new AlertDialog.Builder(MainActivity.this)
.setTitle("提示信息")
.setMessage("无法连接到摄像头,请确保手机已经连接到摄像头所在的wifi热点")
.setNegativeButton("知道了",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
MainActivity.this.finish();
}
}).create();
dialog.setCanceledOnTouchOutside(false);
dialog.show();
break;
default:
Log.d(TAG, "Event not handled ");
break;
}
}
};
注意,这里跳转到的MakeVideo3Activity是一个播放器的界面
public class MakeVideo3Activity extends Activity implements OnClickListener,
IVideoPlayer {
public final static String TAG = "DEBUG/VideoPlayerActivity";
private SurfaceHolder surfaceHolder = null;
private LibVLC mLibVLC = null;
private int mVideoHeight;
private int mVideoWidth;
private int mSarDen;
private int mSarNum;
private int mUiVisibility = -1;
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 String[] mAudioTracks;
private SurfaceView surfaceView = null;
private FrameLayout mLayout;
private TextView mTextTitle;
private TextView mTextTime;
private ImageView btnPlayPause;
private ImageView btnSize;
private TextView mTextShowInfo;
private Button snapShot;//截图
private Button videoRecord;//录像
private boolean isRunRecording; //录像监听
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.video_player);
setupView();
if (Util.isICSOrLater())
getWindow()
.getDecorView()
.findViewById(android.R.id.content)
.setOnSystemUiVisibilityChangeListener(
new OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(
int visibility) {
if (visibility == mUiVisibility)
return;
setSurfaceSize(mVideoWidth, mVideoHeight,
mSarNum, mSarDen);
if (visibility == View.SYSTEM_UI_FLAG_VISIBLE) {
Log.d(TAG, "onSystemUiVisibilityChange");
}
mUiVisibility = visibility;
}
});
try {
//mLibVLC = LibVLC.getInstance();
//用来获取mLIbVLC的实例,其中会初始化LibVLC,在AndroidManifest.xml中要添加android:name="org.videolan.vlc.VLCApplication"这样程序启动时会调用VLCApplication使其生成实例,不会导致LibVLC.getInstance()出错。
mLibVLC = LibVLC.getInstance();
if (mLibVLC != null) {
EventHandler em = EventHandler.getInstance();
em.addHandler(eventHandler);
handler.sendEmptyMessageDelayed(0, 1000);
ListenRecording();
}
} catch (LibVlcException e) {
e.printStackTrace();
}
}
/**
* 录像监听
*/
protected void ListenRecording() {
isRunRecording = true;
new Thread(new Runnable() {
@Override
public void run() {
while(isRunRecording)
try {
if(mLibVLC.videoIsRecording())
{
handlerRecord.sendEmptyMessage(0);
}
else
{
handlerRecord.sendEmptyMessage(1);
}
Thread.sleep(1*1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
super.onResume();
}
Handler handlerRecord = new Handler()
{
@Override
public void handleMessage(Message msg) {
switch(msg.what)
{
case 0:
videoRecord.setText("停止录像");
break;
case 1:
videoRecord.setText("开始录像");
break;
case 2:
break;
}
super.handleMessage(msg);
}
};
/**
* 初始化组件
*/
private void setupView() {
surfaceView = (SurfaceView) findViewById(R.id.main_surface);
surfaceHolder = surfaceView.getHolder();
surfaceHolder.setFormat(PixelFormat.RGBX_8888);
surfaceHolder.addCallback(mSurfaceCallback);
mLayout = (FrameLayout) findViewById(R.id.video_player_overlay);
mTextTitle = (TextView) findViewById(R.id.video_player_title);
btnPlayPause = (ImageView) findViewById(R.id.video_player_playpause);
btnSize = (ImageView) findViewById(R.id.video_player_size);
mTextTime = (TextView) findViewById(R.id.video_player_time);
mTextShowInfo = (TextView) findViewById(R.id.video_player_showinfo);
btnPlayPause.setOnClickListener(this);
btnSize.setOnClickListener(this);
snapShot = (Button) findViewById(R.id.snapShot);
snapShot.setOnClickListener(this);
videoRecord = (Button) findViewById(R.id.videoRecord);
videoRecord.setOnClickListener(this);
mTextTitle.setText(getIntent().getStringExtra("name"));
pathIsExist();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.video_player_playpause:
if (mLibVLC.isPlaying()) {
mLibVLC.pause();
btnPlayPause.setImageResource(R.drawable.ic_play_selector);
} else {
mLibVLC.play();
btnPlayPause.setImageResource(R.drawable.ic_pause_selector);
}
break;
case R.id.video_player_size:
if (mCurrentSize < SURFACE_ORIGINAL) {
mCurrentSize++;
} else {
mCurrentSize = 0;
}
changeSurfaceSize();
break;
case R.id.snapShot:
snapShot();
break;
case R.id.videoRecord:
videoRecord();
break;
}
}
/**
* 路径是否存在 不存在则创建
*/
private void pathIsExist()
{
File file = new File(BitmapUtils.getSDPath()+"/aaa/capture/") ;
if(!file.exists())
file.mkdirs();
File file1 = new File(BitmapUtils.getSDPath()+"/aaa/video/") ;
if(!file1.exists())
file1.mkdirs();
}
/**
* 截图
*/
private void snapShot()
{
try {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
String name = df.format(new Date());
name = BitmapUtils.getSDPath()+"/aaa/capture/"+name+".png";
File file = new File(name);
if(!file.exists())
file.createNewFile();
if(mLibVLC.takeSnapShot(name, 640, 360))
{
Toast.makeText(getApplicationContext(), "已保存", 1000).show();
}
else
{
Toast.makeText(getApplicationContext(), "截图失败", 1000).show();
}
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* 录像和停止录像
*/
private void videoRecord()
{
try {
if(mLibVLC.videoIsRecording())
{
if(mLibVLC.videoRecordStop())
{
Toast.makeText(getApplicationContext(), "停止录像", 1000).show();
}
else
{
Toast.makeText(getApplicationContext(), "停止录像失败", 1000).show();
}
}
else
{
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
String name = df.format(new Date());
if(mLibVLC.videoRecordStart(BitmapUtils.getSDPath()+"/aaa/video/"+name))
{
Toast.makeText(getApplicationContext(), "开始录像", 1000).show();
}
else
{
Toast.makeText(getApplicationContext(), "开始录像失败", 1000).show();
}
}
}catch (Exception e) {
e.printStackTrace();
}
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
int time = (int) mLibVLC.getTime();
int length = (int) mLibVLC.getLength();
showVideoTime(time, length);
handler.sendEmptyMessageDelayed(0, 1000);
}
};
private void showVideoTime(int t, int l) {
mTextTime.setText(millisToString(t));
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
setSurfaceSize(mVideoWidth, mVideoHeight, mSarNum, mSarDen);
super.onConfigurationChanged(newConfig);
}
/**
* attach and disattach surface to the lib
*/
private final SurfaceHolder.Callback mSurfaceCallback = new Callback() {
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
if (format == PixelFormat.RGBX_8888)
Log.d(TAG, "Pixel format is RGBX_8888");
else if (format == PixelFormat.RGB_565)
Log.d(TAG, "Pixel format is RGB_565");
else if (format == ImageFormat.YV12)
Log.d(TAG, "Pixel format is YV12");
else
Log.d(TAG, "Pixel format is other/unknown");
mLibVLC.attachSurface(holder.getSurface(),
MakeVideo3Activity.this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mLibVLC.detachSurface();
}
};
public final Handler mHandler = new VideoPlayerHandler(this);
private static class VideoPlayerHandler extends
WeakHandler<MakeVideo3Activity> {
public VideoPlayerHandler(MakeVideo3Activity owner) {
super(owner);
}
@Override
public void handleMessage(Message msg) {
MakeVideo3Activity activity = getOwner();
if (activity == null) // WeakReference could be GC'ed early
return;
switch (msg.what) {
case SURFACE_SIZE:
activity.changeSurfaceSize();
break;
}
}
};
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;
}
if (dw * dh == 0)
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;
// // 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:
mTextShowInfo.setText(R.string.video_player_best_fit);
if (dar < ar)
dh = (int) (dw / ar);
else
dw = (int) (dh * ar);
break;
case SURFACE_FIT_HORIZONTAL:
mTextShowInfo.setText(R.string.video_player_fit_horizontal);
dh = (int) (dw / ar);
break;
case SURFACE_FIT_VERTICAL:
mTextShowInfo.setText(R.string.video_player_fit_vertical);
dw = (int) (dh * ar);
break;
case SURFACE_FILL:
break;
case SURFACE_16_9:
mTextShowInfo.setText(R.string.video_player_16x9);
ar = 16.0 / 9.0;
if (dar < ar)
dh = (int) (dw / ar);
else
dw = (int) (dh * ar);
break;
case SURFACE_4_3:
mTextShowInfo.setText(R.string.video_player_4x3);
ar = 4.0 / 3.0;
if (dar < ar)
dh = (int) (dw / ar);
else
dw = (int) (dh * ar);
break;
case SURFACE_ORIGINAL:
mTextShowInfo.setText(R.string.video_player_original);
dh = mVideoHeight;
dw = mVideoWidth;
break;
}
surfaceHolder.setFixedSize(mVideoWidth, mVideoHeight);
LayoutParams lp = surfaceView.getLayoutParams();
lp.width = dw;
lp.height = dh;
surfaceView.setLayoutParams(lp);
surfaceView.invalidate();
}
private final Handler eventHandler = new VideoPlayerEventHandler(this);
private static class VideoPlayerEventHandler extends
WeakHandler<MakeVideo3Activity> {
public VideoPlayerEventHandler(MakeVideo3Activity owner) {
super(owner);
}
@Override
public void handleMessage(Message msg) {
MakeVideo3Activity activity = getOwner();
if (activity == null)
return;
Log.d(TAG, "Event = "+msg.getData().getInt("event"));
switch (msg.getData().getInt("event")) {
case EventHandler.MediaPlayerPlaying:
Log.i(TAG, "MediaPlayerPlaying");
break;
case EventHandler.MediaPlayerPaused:
Log.i(TAG, "MediaPlayerPaused");
break;
case EventHandler.MediaPlayerStopped:
Log.i(TAG, "MediaPlayerStopped");
break;
case EventHandler.MediaPlayerEndReached:
Log.i(TAG, "MediaPlayerEndReached");
activity.finish();
break;
case EventHandler.MediaPlayerVout:
activity.finish();
break;
default:
Log.d(TAG, "Event not handled");
break;
}
// activity.updateOverlayPausePlay();
}
}
@Override
protected void onDestroy() {
if (mLibVLC != null) {
mLibVLC.stop();
}
isRunRecording = false;
EventHandler em = EventHandler.getInstance();
em.removeHandler(eventHandler);
super.onDestroy();
};
/**
* Convert time to a string
*
* @param millis
* e.g.time/length from file
* @return formated string (hh:)mm:ss
*/
public static String millisToString(long millis) {
boolean negative = millis < 0;
millis = java.lang.Math.abs(millis);
millis /= 1000;
int sec = (int) (millis % 60);
millis /= 60;
int min = (int) (millis % 60);
millis /= 60;
int hours = (int) millis;
String time;
DecimalFormat format = (DecimalFormat) NumberFormat
.getInstance(Locale.US);
format.applyPattern("00");
if (millis > 0) {
time = (negative ? "-" : "") + hours + ":" + format.format(min)
+ ":" + format.format(sec);
} else {
time = (negative ? "-" : "") + min + ":" + format.format(sec);
}
return time;
}
public void setSurfaceSize(int width, int height, int sar_num, int sar_den) {
if (width * height == 0)
return;
mVideoHeight = height;
mVideoWidth = width;
mSarNum = sar_num;
mSarDen = sar_den;
Message msg = mHandler.obtainMessage(SURFACE_SIZE);
mHandler.sendMessage(msg);
}
@Override
public void setSurfaceSize(int width, int height, int visible_width,
int visible_height, int sar_num, int sar_den) {
mVideoHeight = height;
mVideoWidth = width;
mSarNum = sar_num;
mSarDen = sar_den;
Message msg = mHandler.obtainMessage(SURFACE_SIZE);
mHandler.sendMessage(msg);
}
}
这些都是网上找到的东西,直接拿过来稍微改下播放地址就好,下面的就是关键了
4.修改lib库
关于vlc播放的四个库只能放进armeabi里面,之前我放进arm64里面结果就一直加载出错,所以直接把arm64这个文件夹给删掉了,具体原因还需要学习。
5.修改APPLICATION里的内容
//VLC需要
@Override
public void onLowMemory() {
super.onLowMemory();
Log.w(TAG, "System is running low on memory");
BitmapCache.getInstance().clear();
}
public static Context getAppContext()
{
return instance;
}
/**
* @return the main resources from the Application
*/
public static Resources getAppResources()
{
if(instance == null) return null;
return instance.getResources();
}
//以上是VLC
要在项目原有的APPLICATION里添加进这些内容,不添加会闪退的,因为调用VLC的实例时会使用,这样才能保证运行成功。
问题
vlcjni.so库为什么不能放进arm64里面?
而且就算没有放进arm64文件夹里面,只要项目中存在arm64依然不能运行,还是会报加载.so的错误。原因在网上查到:
a.开发中出现找不到.so不对的Bug,原因是测试机是64位的,而arm64-v8a中的.so是32位的。
配置生成arm64-v8a的.so文件:
在jni/Application.mk中写入:APP_ABI := armeabi armeabi-v7a arm64-v8a,重新编译就可以了,如果没有这个文件就在jni目录下新建一个。
原博客链接
里面讲了很多关于动态库的知识。
b.通过百度查到知乎有一段关于arm64-v8a的解释:
arm64-v8a是可以向下兼容的,但前提是你的项目里面没有arm64-v8a的文件夹,如果你有两个文件夹armeabi和arm64-v8a,两个文件夹,armeabi里面有a.so 和 b.so,arm64-v8a里面只有a.so,那么arm64-v8a的手机在用到b的时候发现有arm64-v8a的文件夹,发现里面没有b.so,就报错了,所以这个时候删掉arm64-v8a文件夹,这个时候手机发现没有适配arm64-v8a,就会直接去找armeabi的so库,所以要么你别加arm64-v8a,要么armeabi里面有的so库,arm64-v8a里面也必须有
原博客链接
安卓中application的作用?
a.百度知道里的回答:
Application和Activity,Service一样是Android框架的一个系统组件,当Android程序启动时系统会创建一个Application对象,用来存储系统的一些信息。
Android系统自动会为每个程序运行时创建一个Application类的对象且只创建一个,所以Application可以说是单例(singleton)模式的一个类。
通常我们是不需要指定一个Application的,系统会自动帮我们创建,如果需要创建自己的Application,那也很简单!创建一个类继承Application并在AndroidManifest.xml文件中的application标签中进行注册(只需要给application标签增加name属性,并添加自己的 Application的名字即可)。
启动Application时,系统会创建一个PID,即进程ID,所有的Activity都会在此进程上运行。那么我们在Application创建的时候初始化全局变量,同一个应用的所有Activity都可以取到这些全局变量的值,换句话说,我们在某一个Activity中改变了这些全局变量的值,那么在同一个应用的其他Activity中值就会改变。