一、近期整理了下之前用过的vlc for android;相关代码下载以及编译可以参考官方WIKI
官方Code下载地址,参考Git
个人Demo地址链接
https://github.com/xiaxiaxa/mgtv_vlc_demo:
二、搭建编译环境,编译vlc生成apk以及相对应的解码库等;
在ubuntu 14.04下面搭建编译环境,安装相关编译工具等;
下载代码:
git clone https://code.videolan.org/videolan/vlc-android.git
需要配置NDK、JDK等环境变量等:
ubuntu相关交叉编译工具等可以使用附件sh自动安装
链接: https://pan.baidu.com/s/10s1aoF_SHacT9rEsueJP_A
提取码: aja2
超级用户下,直接运行脚本会安装对应的arm交叉编译工具等;
对应的编译模块以及相关命令:
编译模块 | 命令 | 生成路径 |
---|---|---|
编译apk | sh compile.sh -a armeabi-v7a | vlc-android/build/outputs/apk/vanillaARMv7/debug |
编译aar | sh compile.sh -l -a armeabi-v7a -r | libvlc/build/outputs/aar |
编译so | sh compile-libvlc.sh -a armeabi-v7a | libvlc/private_libs/libs/armeabi-v7a |
aar里面的是libvlc编译出来的相关so库
个人编译的相关库以及vlc apk上传至网盘
提取码: h5hv
三、附上个人的Git使用demo,测试rtp/rtsp等直播流是没问题的;(个人主要用vlc来解决直播rtsp等播放问题)
public class VlcPlayActivity extends AppCompatActivity implements
IVLCVout.OnNewVideoLayoutListener {
private static final boolean ENABLE_SUBTITLES = true;
private static final String TAG = "VlcActivity";
public static String SAMPLE_URL = "rtp://239.76.245.115:1234";
private static final int SURFACE_BEST_FIT = 0;
private static final int SURFACE_FIT_SCREEN = 1;
private static final int SURFACE_FILL = 2;
private static final int SURFACE_16_9 = 3;
private static final int SURFACE_4_3 = 4;
private static final int SURFACE_ORIGINAL = 5;
private static int CURRENT_SIZE = SURFACE_BEST_FIT;
private FrameLayout mVideoSurfaceFrame = null;
private SurfaceView mVideoSurface = null;
private SurfaceView mSubtitlesSurface = null;
private final Handler mHandler = new Handler();
private View.OnLayoutChangeListener mOnLayoutChangeListener = null;
private LibVLC mLibVLC = null;
private MediaPlayer mMediaPlayer = null;
private int mVideoHeight = 0;
private int mVideoWidth = 0;
private int mVideoVisibleHeight = 0;
private int mVideoVisibleWidth = 0;
private int mVideoSarNum = 0;
private int mVideoSarDen = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.vlc_play);
SAMPLE_URL = getIntent().getStringExtra("extra_url");
final ArrayList<String> args = new ArrayList<>();
args.add("-vvv");
mLibVLC = new LibVLC(this, args);
mMediaPlayer = new MediaPlayer(mLibVLC);
mVideoSurfaceFrame = (FrameLayout) findViewById(R.id.video_surface_frame);
mVideoSurface = (SurfaceView) findViewById(R.id.video_surface);
if (ENABLE_SUBTITLES) {
final ViewStub stub = (ViewStub) findViewById(R.id.subtitles_stub);
mSubtitlesSurface = (SurfaceView) stub.inflate();
mSubtitlesSurface.setZOrderMediaOverlay(true);
mSubtitlesSurface.getHolder().setFormat(PixelFormat.TRANSLUCENT);
}
}
@Override
protected void onPause() {
super.onPause();
releaseData();
}
@Override
protected void onDestroy() {
super.onDestroy();
releaseData();
}
private void releaseData() {
if (isFinishing()) {
if (mOnLayoutChangeListener != null) {
if (mVideoSurfaceFrame != null) {
mVideoSurfaceFrame.removeOnLayoutChangeListener(mOnLayoutChangeListener);
}
mOnLayoutChangeListener = null;
}
if (mMediaPlayer != null) {
mMediaPlayer.getVLCVout().detachViews();
mMediaPlayer.pause();
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
}
if (mSubtitlesSurface != null) {
mSubtitlesSurface = null;
}
if (mHandler != null) {
mHandler.removeCallbacks(null);
}
}
}
@Override
protected void onStart() {
super.onStart();
final IVLCVout vlcVout = mMediaPlayer.getVLCVout();
vlcVout.setVideoView(mVideoSurface);
if (mSubtitlesSurface != null)
vlcVout.setSubtitlesView(mSubtitlesSurface);
vlcVout.attachViews(this);
Media media = new Media(mLibVLC, Uri.parse("rtp://239.76.245.115:1234"));
mMediaPlayer.setMedia(media);
media.release();
mMediaPlayer.play();
if (mOnLayoutChangeListener == null) {
mOnLayoutChangeListener = new View.OnLayoutChangeListener() {
private final Runnable mRunnable = new Runnable() {
@Override
public void run() {
updateVideoSurfaces();
}
};
@Override
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
mHandler.removeCallbacks(mRunnable);
mHandler.post(mRunnable);
}
}
};
}
mVideoSurfaceFrame.addOnLayoutChangeListener(mOnLayoutChangeListener);
}
@Override
protected void onStop() {
super.onStop();
releaseData();
}
private void changeMediaPlayerLayout(int displayW, int displayH) {
switch (CURRENT_SIZE) {
case SURFACE_BEST_FIT:
mMediaPlayer.setAspectRatio(null);
mMediaPlayer.setScale(0);
break;
case SURFACE_FIT_SCREEN:
case SURFACE_FILL: {
Media.VideoTrack vtrack = mMediaPlayer.getCurrentVideoTrack();
if (vtrack == null)
return;
final boolean videoSwapped = vtrack.orientation == Media.VideoTrack.Orientation.LeftBottom
|| vtrack.orientation == Media.VideoTrack.Orientation.RightTop;
if (CURRENT_SIZE == SURFACE_FIT_SCREEN) {
int videoW = vtrack.width;
int videoH = vtrack.height;
if (videoSwapped) {
int swap = videoW;
videoW = videoH;
videoH = swap;
}
if (vtrack.sarNum != vtrack.sarDen)
videoW = videoW * vtrack.sarNum / vtrack.sarDen;
float ar = videoW / (float) videoH;
float dar = displayW / (float) displayH;
float scale;
if (dar >= ar)
scale = displayW / (float) videoW;
else
scale = displayH / (float) videoH;
mMediaPlayer.setScale(scale);
mMediaPlayer.setAspectRatio(null);
} else {
mMediaPlayer.setScale(0);
mMediaPlayer.setAspectRatio(!videoSwapped ? "" + displayW + ":" + displayH
: "" + displayH + ":" + displayW);
}
break;
}
case SURFACE_16_9:
mMediaPlayer.setAspectRatio("16:9");
mMediaPlayer.setScale(0);
break;
case SURFACE_4_3:
mMediaPlayer.setAspectRatio("4:3");
mMediaPlayer.setScale(0);
break;
case SURFACE_ORIGINAL:
mMediaPlayer.setAspectRatio(null);
mMediaPlayer.setScale(1);
break;
}
}
private void updateVideoSurfaces() {
int sw = getWindow().getDecorView().getWidth();
int sh = getWindow().getDecorView().getHeight();
// sanity check
if (sw * sh == 0) {
Log.e(TAG, "Invalid surface size");
return;
}
if (mMediaPlayer != null) {
mMediaPlayer.getVLCVout().setWindowSize(sw, sh);
}
if (mVideoSurface != null) {
ViewGroup.LayoutParams lp = mVideoSurface.getLayoutParams();
if (mVideoWidth * mVideoHeight == 0) {
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
mVideoSurface.setLayoutParams(lp);
lp = mVideoSurfaceFrame.getLayoutParams();
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
mVideoSurfaceFrame.setLayoutParams(lp);
changeMediaPlayerLayout(sw, sh);
return;
}
if (lp.width == lp.height && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
if (mMediaPlayer != null) {
mMediaPlayer.setAspectRatio(null);
mMediaPlayer.setScale(0);
}
}
double dw = sw, dh = sh;
final boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
if (sw > sh && isPortrait || sw < sh && !isPortrait) {
dw = sh;
dh = sw;
}
// compute the aspect ratio
double ar, vw;
if (mVideoSarDen == mVideoSarNum) {
vw = mVideoVisibleWidth;
ar = (double) mVideoVisibleWidth / (double) mVideoVisibleHeight;
} else {
vw = mVideoVisibleWidth * (double) mVideoSarNum / mVideoSarDen;
ar = vw / mVideoVisibleHeight;
}
// compute the display aspect ratio
double dar = dw / dh;
switch (CURRENT_SIZE) {
case SURFACE_BEST_FIT:
if (dar < ar)
dh = dw / ar;
else
dw = dh * ar;
break;
case SURFACE_FIT_SCREEN:
if (dar >= ar)
dh = dw / ar;
else
dw = dh * ar;
break;
case SURFACE_FILL:
break;
case SURFACE_16_9:
ar = 16.0 / 9.0;
if (dar < ar)
dh = dw / ar;
else
dw = dh * ar;
break;
case SURFACE_4_3:
ar = 4.0 / 3.0;
if (dar < ar)
dh = dw / ar;
else
dw = dh * ar;
break;
case SURFACE_ORIGINAL:
dh = mVideoVisibleHeight;
dw = vw;
break;
}
// set display size
lp.width = (int) Math.ceil(dw * mVideoWidth / mVideoVisibleWidth);
lp.height = (int) Math.ceil(dh * mVideoHeight / mVideoVisibleHeight);
mVideoSurface.setLayoutParams(lp);
if (mSubtitlesSurface != null)
mSubtitlesSurface.setLayoutParams(lp);
// set frame size (crop if necessary)
lp = mVideoSurfaceFrame.getLayoutParams();
lp.width = (int) Math.floor(dw);
lp.height = (int) Math.floor(dh);
mVideoSurfaceFrame.setLayoutParams(lp);
mVideoSurface.invalidate();
if (mSubtitlesSurface != null)
mSubtitlesSurface.invalidate();
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public void onNewVideoLayout(IVLCVout vlcVout, int width, int height, int visibleWidth, int visibleHeight, int sarNum, int sarDen) {
mVideoWidth = width;
mVideoHeight = height;
mVideoVisibleWidth = visibleWidth;
mVideoVisibleHeight = visibleHeight;
mVideoSarNum = sarNum;
mVideoSarDen = sarDen;
if (isFinishing()){
} else {
updateVideoSurfaces();
}
}
}
**demo播放的控制主要是这两个类实现:**
public class PlayerActivity extends BaseActivity {
private static final String TAG = "PlayerActivity";
@ViewInject(R.id.video_play)
private VideoView mVideoPlay;
@ViewInject(R.id.progress_loading)
private ProgressBar mProgresLoading;
private String mUrl;
@Override
public int getLayoutRes() {
return R.layout.activity_video_player;
}
@Override
public void init() {
mUrl = getIntent().getStringExtra(ConstData.IntentKey.VIDEO_URL);
if(TextUtils.isEmpty(mUrl))
finish();
mVideoPlay.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Log.i(TAG, "onError");
mProgresLoading.setVisibility(View.GONE);
finish();
return true;
}
});
mVideoPlay.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
Log.i(TAG, "onPrepared");
mProgresLoading.setVisibility(View.GONE);
}
});
}
@Override
protected void onResume() {
super.onResume();
playVideo();
}
@Override
protected void onPause() {
super.onPause();
mVideoPlay.pause();
}
@Override
protected void onStop() {
super.onStop();
try{
mVideoPlay.stopPlayback();
}catch (Exception e){
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK){
mProgresLoading.setVisibility(View.GONE);
finish();
return true;
}
return super.onKeyDown(keyCode, event);
}
private void playVideo(){
try{
mVideoPlay.stopPlayback();
}catch (Exception e){
}
mProgresLoading.setVisibility(View.VISIBLE);
mVideoPlay.setVideoURI(Uri.parse(mUrl));
mVideoPlay.start();
}
可以自己把jni部分源码复写,播放主要控制在libvlc
四、前期工作做完后,我们可以先来简要了解vlc的播放器工作;音视频的播放我们一般有几个步骤,主要是:
步骤 | 工作内容 | 简要说明 |
---|---|---|
1 | access访问 | 接收、获取、得到数据资源,包括解析访问源(url), 使用http协议,rtsp协议,ftp协议,建立连接,获取数据 |
2 | demux解复杂 | 音频和视频分离,当然也有可能有字幕。通过分析数据包头来判断是什么数据文件,需要用什么解码格式 |
3 | decode解码 | 包括音频和视频解码,或者软件解码和硬件解码。 |
4 | output输出 | 分为音频和视频的输出(aout和vout) |
VLC源码结构 | 对应功能 |
---|---|
./config/ | 从命令行和配置文件中加载配置 |
./control/ | 提供动作控制功能,如播放等操作 |
./extras/ | 大多是平台的特殊代码 |
./modules/ | 模块管理 |
./network/ | 提供网络接口(socket管理,网络接口) |
./osd/ | 显示屏幕上的操作 |
./test/ | libvlc测试模块 |
./text/ | 字符集 |
./interface/ | 提供代码中可以调用的接口,如按键后的硬件作出反应 |
./playlist/ | 管理播放功能 |
./input/ | 建立并读取一个输入流,并且分离其中的音频和视频,然后把分离好的音频和视频流发给解码器 |
./audio_output/ | 初始化音频混合器,即设置正确的同步频率,并对从解码器传来的音频流重新取样 |
./video_output/ | 初始化视频播放器,把从解码器得到视频画面转化格式从yuv到rgb,然后播放 |
./stream_output/ / | 输出音频流和视频流到网络 |
./misc/ | libvlc使用的其他部分功能,如线程系统,消息队列等 |
- VLC
采用全模块化结构,通过动态的载入所需的模块,放入一个module_bank的结构体中统一管理,连VLC的Main模块也是通过插件的方式动态载入的(通过module_InitBank函数在初始化建立module_bank时)。对于不支持动态载入插件的系统环境中,VLC也可以采用builtin的方式,在VLC启动的时候静态载入所需要的插件,并放入module_bank统一管理。
五、附上自己编译的源码Vlc生成的apk以及demo生成的apk;
链接: https://pan.baidu.com/s/1YnPus6koPLI4-LEUzPADlA
提取码: w7ya