vlc-android 中调用用libvlcjni.so实现流媒体播放
2014-04-06 18:56
489 查看
最近公司搞的项目中涉及到流媒体播放,并且需要硬解码,所以想到了VLC这个开源项目。去官网下载了vlc-android源码进行编译,生成的apk安装在公司的设备上可以运行,不错不错,有现成的东西当然不会再去“造轮胎”,把编译后的android 工程导入eclipse 看了所有的代码,觉得对于我们只需要实现流媒体播放的来说显得有些累赘,这篇文章只需要实现流媒体播放的部分
关于源码下载和编译的部分可以查看:http://wiki.videolan.org/AndroidCompile
下面的代码有多部分是vlc-android工程源码,它们已经为我们封装好了要调用的jni函数和一些配置信息,这部分源码可以拿来就用。
1.创建一个android工程,界面很简单,就一个SurfaceView
MainActivity 的代码如下:
[java] view
plaincopy
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大小的。
如果需要硬件解码,就需要添加以下方法:
[java] view
plaincopy
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”.
[html] view
plaincopy
mEventManger = EventManager.getInstance();
mEventManger.addHandler(mEventHandler);
是用来添加播放事件的,当播放视频出现play,stop,pause等状态时,会接收到。
项目中碰到的问题就这些让我困惑了一阵,其余的可以通过谷歌或着度娘找到相应的方法。
http://blog.csdn.net/zhongyili_sohu/article/details/9225541
关于源码下载和编译的部分可以查看:http://wiki.videolan.org/AndroidCompile
下面的代码有多部分是vlc-android工程源码,它们已经为我们封装好了要调用的jni函数和一些配置信息,这部分源码可以拿来就用。
1.创建一个android工程,界面很简单,就一个SurfaceView
MainActivity 的代码如下:
[java] view
plaincopy
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大小的。
如果需要硬件解码,就需要添加以下方法:
[java] view
plaincopy
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”.
[html] view
plaincopy
mEventManger = EventManager.getInstance();
mEventManger.addHandler(mEventHandler);
是用来添加播放事件的,当播放视频出现play,stop,pause等状态时,会接收到。
项目中碰到的问题就这些让我困惑了一阵,其余的可以通过谷歌或着度娘找到相应的方法。
http://blog.csdn.net/zhongyili_sohu/article/details/9225541
相关文章推荐
- vlc-android 中调用用libvlcjni.so实现流媒体播放
- vlc-android 中调用用libvlcjni.so实现流媒体播放
- vlc-android 中调用用libvlcjni.so实现流媒体播放,自己使用libvlcjni.so
- vlc-android 中调用用libvlcjni.so实现流媒体播放
- android 通过调用VLC的libvlcjni.so解码视频和rtsp流
- android 调用libvlcjni.so例程
- JNI_Android项目中调用.so动态库实现详解
- JNI_Android项目中调用.so动态库实现详解
- JNI_Android项目中调用.so动态库实现详解
- android利用jni调用第三方库——第三篇——编写库android程序整合第三方库libhello.so到自己的库libhelloword.so
- [置顶] android利用jni调用第三方库——第三篇——编写库android程序整合第三方库libhello.so到自己的库libhelloword.so
- JNI_Android项目中调用.so动态库实现详解(初探)
- 调用VLCjni.so播放rtsp视频流并录制成mp4文件
- android利用jni调用第三方库——第二篇——编写库android程序直接调用第三方库libhello.so
- [置顶] android利用jni调用第三方库——第二篇——编写库android程序直接调用第三方库libhello.so
- JNI_Android项目中调用.so动态库实现详解
- JNI_Android项目中调用.so动态库实现详解【转】
- JNI_Android项目中调用.so动态库实现详解
- JNI_Android 项目中调用.so动态库实现详解
- android j使用JNI实现ava语言调用C语言