Ijkplayer android端执行流程分析 - bei1999/work GitHub Wiki
前言
最近项目涉及到直播流播放器,于是对b站开源的ijkplayer进行了编译,过程之中并阅读了下这个开发源码,做个笔记吧。
编译
按照github 上的介绍逐步执行脚本就ok了,导入as最后的结构如下:
工程分析
在demo中发现了下面的调用播放器的代码
// init player
IjkMediaPlayer.loadLibrariesOnce(null);
IjkMediaPlayer.native_profileBegin("libijkplayer.so");
mVideoView = (IjkVideoView) findViewById(R.id.video_view);
mVideoView.setMediaController(mMediaController);
mVideoView.setHudView(mHudView);
// prefer mVideoPath
if (mVideoPath != null)
mVideoView.setVideoPath(mVideoPath);
else if (mVideoUri != null)
mVideoView.setVideoURI(mVideoUri);
else {
Log.e(TAG, "Null Data Source\n");
finish();
return;
}
mVideoView.start();
IjkVideoView 是个什么东东?部分代码截取
public class IjkVideoView extends FrameLayout implements MediaController.MediaPlayerControl {
private String TAG = "IjkVideoView";
// settable by the client
private Uri mUri;
private Map<String, String> mHeaders;
// all possible internal states
private static final int STATE_ERROR = -1;
private static final int STATE_IDLE = 0;
private static final int STATE_PREPARING = 1;
private static final int STATE_PREPARED = 2;
private static final int STATE_PLAYING = 3;
private static final int STATE_PAUSED = 4;
private static final int STATE_PLAYBACK_COMPLETED = 5;
// mCurrentState is a VideoView object's current state.
// mTargetState is the state that a method caller intends to reach.
// For instance, regardless the VideoView object's current state,
// calling pause() intends to bring the object to a target state
// of STATE_PAUSED.
private int mCurrentState = STATE_IDLE;
private int mTargetState = STATE_IDLE;
// All the stuff we need for playing and showing a video
private IRenderView.ISurfaceHolder mSurfaceHolder = null;
private IMediaPlayer mMediaPlayer = null;
// private int mAudioSession;
private int mVideoWidth;
private int mVideoHeight;
private int mSurfaceWidth;
private int mSurfaceHeight;
private int mVideoRotationDegree;
private IMediaController mMediaController;
private IMediaPlayer.OnCompletionListener mOnCompletionListener;
private IMediaPlayer.OnPreparedListener mOnPreparedListener;
private int mCurrentBufferPercentage;
private IMediaPlayer.OnErrorListener mOnErrorListener;
private IMediaPlayer.OnInfoListener mOnInfoListener;
private int mSeekWhenPrepared; // recording the seek position while preparing
private boolean mCanPause = true;
private boolean mCanSeekBack = true;
private boolean mCanSeekForward = true;
/** Subtitle rendering widget overlaid on top of the video. */
// private RenderingWidget mSubtitleWidget;
/**
* Listener for changes to subtitle data, used to redraw when needed.
*/
// private RenderingWidget.OnChangedListener mSubtitlesChangedListener;
private Context mAppContext;
private Settings mSettings;
private IRenderView mRenderView;
private int mVideoSarNum;
private int mVideoSarDen;
private InfoHudViewHolder mHudViewHolder;
private long mPrepareStartTime = 0;
private long mPrepareEndTime = 0;
private long mSeekStartTime = 0;
private long mSeekEndTime = 0;
private TextView subtitleDisplay;
public IjkVideoView(Context context) {
super(context);
initVideoView(context);
}
public IjkVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
initVideoView(context);
}
public IjkVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initVideoView(context);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public IjkVideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initVideoView(context);
}
// REMOVED: onMeasure
// REMOVED: onInitializeAccessibilityEvent
// REMOVED: onInitializeAccessibilityNodeInfo
// REMOVED: resolveAdjustedSize
private void initVideoView(Context context) {
mAppContext = context.getApplicationContext();
mSettings = new Settings(mAppContext);
initBackground();
initRenders();
mVideoWidth = 0;
mVideoHeight = 0;
// REMOVED: getHolder().addCallback(mSHCallback);
// REMOVED: getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
// REMOVED: mPendingSubtitleTracks = new Vector<Pair<InputStream, MediaFormat>>();
mCurrentState = STATE_IDLE;
mTargetState = STATE_IDLE;
subtitleDisplay = new TextView(context);
subtitleDisplay.setTextSize(24);
subtitleDisplay.setGravity(Gravity.CENTER);
FrameLayout.LayoutParams layoutParams_txt = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.BOTTOM);
addView(subtitleDisplay, layoutParams_txt);
}
@Override
public void start() {
if (isInPlaybackState()) {
mMediaPlayer.start();
mCurrentState = STATE_PLAYING;
}
mTargetState = STATE_PLAYING;
}
@Override
public void pause() {
if (isInPlaybackState()) {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
mCurrentState = STATE_PAUSED;
}
}
mTargetState = STATE_PAUSED;
}
}
IMediaPlayer.java
public interface IMediaPlayer {
/*
* Do not change these values without updating their counterparts in native
*/
int MEDIA_INFO_UNKNOWN = 1;
int MEDIA_INFO_STARTED_AS_NEXT = 2;
int MEDIA_INFO_VIDEO_RENDERING_START = 3;
int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700;
int MEDIA_INFO_BUFFERING_START = 701;
int MEDIA_INFO_BUFFERING_END = 702;
int MEDIA_INFO_NETWORK_BANDWIDTH = 703;
int MEDIA_INFO_BAD_INTERLEAVING = 800;
int MEDIA_INFO_NOT_SEEKABLE = 801;
int MEDIA_INFO_METADATA_UPDATE = 802;
int MEDIA_INFO_TIMED_TEXT_ERROR = 900;
int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901;
int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902;
int MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001;
int MEDIA_INFO_AUDIO_RENDERING_START = 10002;
int MEDIA_INFO_AUDIO_DECODED_START = 10003;
int MEDIA_INFO_VIDEO_DECODED_START = 10004;
int MEDIA_INFO_OPEN_INPUT = 10005;
int MEDIA_INFO_FIND_STREAM_INFO = 10006;
int MEDIA_INFO_COMPONENT_OPEN = 10007;
int MEDIA_INFO_MEDIA_ACCURATE_SEEK_COMPLETE = 10100;
int MEDIA_ERROR_UNKNOWN = 1;
int MEDIA_ERROR_SERVER_DIED = 100;
int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200;
int MEDIA_ERROR_IO = -1004;
int MEDIA_ERROR_MALFORMED = -1007;
int MEDIA_ERROR_UNSUPPORTED = -1010;
int MEDIA_ERROR_TIMED_OUT = -110;
void setDisplay(SurfaceHolder sh);
void setDataSource(Context context, Uri uri)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
void setDataSource(Context context, Uri uri, Map<String, String> headers)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
void setDataSource(FileDescriptor fd)
throws IOException, IllegalArgumentException, IllegalStateException;
void setDataSource(String path)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
String getDataSource();
void prepareAsync() throws IllegalStateException;
void start() throws IllegalStateException;
void stop() throws IllegalStateException;
void pause() throws IllegalStateException;
void setScreenOnWhilePlaying(boolean screenOn);
int getVideoWidth();
int getVideoHeight();
boolean isPlaying();
void seekTo(long msec) throws IllegalStateException;
long getCurrentPosition();
long getDuration();
void release();
void reset();
void setVolume(float leftVolume, float rightVolume);
int getAudioSessionId();
MediaInfo getMediaInfo();
@SuppressWarnings("EmptyMethod")
@Deprecated
void setLogEnabled(boolean enable);
@Deprecated
boolean isPlayable();
void setOnPreparedListener(OnPreparedListener listener);
void setOnCompletionListener(OnCompletionListener listener);
void setOnBufferingUpdateListener(
OnBufferingUpdateListener listener);
void setOnSeekCompleteListener(
OnSeekCompleteListener listener);
void setOnVideoSizeChangedListener(
OnVideoSizeChangedListener listener);
void setOnErrorListener(OnErrorListener listener);
void setOnInfoListener(OnInfoListener listener);
void setOnTimedTextListener(OnTimedTextListener listener);
/*--------------------
* Listeners
*/
interface OnPreparedListener {
void onPrepared(IMediaPlayer mp);
}
interface OnCompletionListener {
void onCompletion(IMediaPlayer mp);
}
interface OnBufferingUpdateListener {
void onBufferingUpdate(IMediaPlayer mp, int percent);
}
interface OnSeekCompleteListener {
void onSeekComplete(IMediaPlayer mp);
}
interface OnVideoSizeChangedListener {
void onVideoSizeChanged(IMediaPlayer mp, int width, int height,
int sar_num, int sar_den);
}
interface OnErrorListener {
boolean onError(IMediaPlayer mp, int what, int extra);
}
interface OnInfoListener {
boolean onInfo(IMediaPlayer mp, int what, int extra);
}
interface OnTimedTextListener {
void onTimedText(IMediaPlayer mp, IjkTimedText text);
}
/*--------------------
* Optional
*/
void setAudioStreamType(int streamtype);
@Deprecated
void setKeepInBackground(boolean keepInBackground);
int getVideoSarNum();
int getVideoSarDen();
@Deprecated
void setWakeMode(Context context, int mode);
void setLooping(boolean looping);
boolean isLooping();
/*--------------------
* AndroidMediaPlayer: JELLY_BEAN
*/
ITrackInfo[] getTrackInfo();
/*--------------------
* AndroidMediaPlayer: ICE_CREAM_SANDWICH:
*/
void setSurface(Surface surface);
/*--------------------
* AndroidMediaPlayer: M:
*/
void setDataSource(IMediaDataSource mediaDataSource);
}
/**
* @author bbcallen
*
* Java wrapper of ffplay.
*/
public final class IjkMediaPlayer extends AbstractMediaPlayer {
private final static String TAG = IjkMediaPlayer.class.getName();
private static final int MEDIA_NOP = 0; // interface test message
private static final int MEDIA_PREPARED = 1;
private static final int MEDIA_PLAYBACK_COMPLETE = 2;
private static final int MEDIA_BUFFERING_UPDATE = 3;
private static final int MEDIA_SEEK_COMPLETE = 4;
private static final int MEDIA_SET_VIDEO_SIZE = 5;
private static final int MEDIA_TIMED_TEXT = 99;
private static final int MEDIA_ERROR = 100;
private static final int MEDIA_INFO = 200;
protected static final int MEDIA_SET_VIDEO_SAR = 10001;
//----------------------------------------
// options
public static final int IJK_LOG_UNKNOWN = 0;
public static final int IJK_LOG_DEFAULT = 1;
public static final int IJK_LOG_VERBOSE = 2;
public static final int IJK_LOG_DEBUG = 3;
public static final int IJK_LOG_INFO = 4;
public static final int IJK_LOG_WARN = 5;
public static final int IJK_LOG_ERROR = 6;
public static final int IJK_LOG_FATAL = 7;
public static final int IJK_LOG_SILENT = 8;
public static final int OPT_CATEGORY_FORMAT = 1;
public static final int OPT_CATEGORY_CODEC = 2;
public static final int OPT_CATEGORY_SWS = 3;
public static final int OPT_CATEGORY_PLAYER = 4;
public static final int SDL_FCC_YV12 = 0x32315659; // YV12
public static final int SDL_FCC_RV16 = 0x36315652; // RGB565
public static final int SDL_FCC_RV32 = 0x32335652; // RGBX8888
//----------------------------------------
@Override
public void start() throws IllegalStateException {
stayAwake(true);
_start();
}
private native void _start() throws IllegalStateException;
在ijkplayer-xxx 的jni ijkplayer_jni.c 文件中找到了定义映射的关系方法
static JNINativeMethod g_methods[] = {
{
"_setDataSource",
"(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
(void *) IjkMediaPlayer_setDataSourceAndHeaders
},
{ "_setDataSourceFd", "(I)V", (void *) IjkMediaPlayer_setDataSourceFd },
{ "_setDataSource", "(Ltv/danmaku/ijk/media/player/misc/IMediaDataSource;)V", (void *)IjkMediaPlayer_setDataSourceCallback },
{ "_setAndroidIOCallback", "(Ltv/danmaku/ijk/media/player/misc/IAndroidIO;)V", (void *)IjkMediaPlayer_setAndroidIOCallback },
{ "_setVideoSurface", "(Landroid/view/Surface;)V", (void *) IjkMediaPlayer_setVideoSurface },
{ "_prepareAsync", "()V", (void *) IjkMediaPlayer_prepareAsync },
{ "_start", "()V", (void *) IjkMediaPlayer_start },
{ "_stop", "()V", (void *) IjkMediaPlayer_stop },
{ "seekTo", "(J)V", (void *) IjkMediaPlayer_seekTo },
{ "_pause", "()V", (void *) IjkMediaPlayer_pause },
{ "isPlaying", "()Z", (void *) IjkMediaPlayer_isPlaying },
{ "getCurrentPosition", "()J", (void *) IjkMediaPlayer_getCurrentPosition },
{ "getDuration", "()J", (void *) IjkMediaPlayer_getDuration },
{ "_release", "()V", (void *) IjkMediaPlayer_release },
{ "_reset", "()V", (void *) IjkMediaPlayer_reset },
{ "setVolume", "(FF)V", (void *) IjkMediaPlayer_setVolume },
{ "getAudioSessionId", "()I", (void *) IjkMediaPlayer_getAudioSessionId },
{ "native_init", "()V", (void *) IjkMediaPlayer_native_init },
{ "native_setup", "(Ljava/lang/Object;)V", (void *) IjkMediaPlayer_native_setup },
{ "native_finalize", "()V", (void *) IjkMediaPlayer_native_finalize },
{ "_setOption", "(ILjava/lang/String;Ljava/lang/String;)V", (void *) IjkMediaPlayer_setOption },
{ "_setOption", "(ILjava/lang/String;J)V", (void *) IjkMediaPlayer_setOptionLong },
{ "_getColorFormatName", "(I)Ljava/lang/String;", (void *) IjkMediaPlayer_getColorFormatName },
{ "_getVideoCodecInfo", "()Ljava/lang/String;", (void *) IjkMediaPlayer_getVideoCodecInfo },
{ "_getAudioCodecInfo", "()Ljava/lang/String;", (void *) IjkMediaPlayer_getAudioCodecInfo },
{ "_getMediaMeta", "()Landroid/os/Bundle;", (void *) IjkMediaPlayer_getMediaMeta },
{ "_setLoopCount", "(I)V", (void *) IjkMediaPlayer_setLoopCount },
{ "_getLoopCount", "()I", (void *) IjkMediaPlayer_getLoopCount },
{ "_getPropertyFloat", "(IF)F", (void *) ijkMediaPlayer_getPropertyFloat },
{ "_setPropertyFloat", "(IF)V", (void *) ijkMediaPlayer_setPropertyFloat },
{ "_getPropertyLong", "(IJ)J", (void *) ijkMediaPlayer_getPropertyLong },
{ "_setPropertyLong", "(IJ)V", (void *) ijkMediaPlayer_setPropertyLong },
{ "_setStreamSelected", "(IZ)V", (void *) ijkMediaPlayer_setStreamSelected },
{ "native_profileBegin", "(Ljava/lang/String;)V", (void *) IjkMediaPlayer_native_profileBegin },
{ "native_profileEnd", "()V", (void *) IjkMediaPlayer_native_profileEnd },
{ "native_setLogLevel", "(I)V", (void *) IjkMediaPlayer_native_setLogLevel },
{ "_setFrameAtTime", "(Ljava/lang/String;JJII)V", (void *) IjkMediaPlayer_setFrameAtTime },
};
static void
IjkMediaPlayer_start(JNIEnv *env, jobject thiz)
{
MPTRACE("%s\n", __func__);
IjkMediaPlayer *mp = jni_get_media_player(env, thiz);
JNI_CHECK_GOTO(mp, env, "java/lang/IllegalStateException", "mpjni: start: null mp", LABEL_RETURN);
ijkmp_start(mp);
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
}
static void
IjkMediaPlayer_stop(JNIEnv *env, jobject thiz)
{
IjkMediaPlayer *mp = jni_get_media_player(env, thiz);
JNI_CHECK_GOTO(mp, env, "java/lang/IllegalStateException", "mpjni: stop: null mp", LABEL_RETURN);
ijkmp_stop(mp);
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
}
static void
IjkMediaPlayer_pause(JNIEnv *env, jobject thiz)
{
IjkMediaPlayer *mp = jni_get_media_player(env, thiz);
JNI_CHECK_GOTO(mp, env, "java/lang/IllegalStateException", "mpjni: pause: null mp", LABEL_RETURN);
ijkmp_pause(mp);
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
}
static void
IjkMediaPlayer_seekTo(JNIEnv *env, jobject thiz, jlong msec)
{
MPTRACE("%s\n", __func__);
IjkMediaPlayer *mp = jni_get_media_player(env, thiz);
JNI_CHECK_GOTO(mp, env, "java/lang/IllegalStateException", "mpjni: seekTo: null mp", LABEL_RETURN);
ijkmp_seek_to(mp, msec);
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
}
ffplay 如何返回给java层消息处理呢,ijkplayer代码说明了一切
private static class EventHandler extends Handler {
private final WeakReference<IjkMediaPlayer> mWeakPlayer;
public EventHandler(IjkMediaPlayer mp, Looper looper) {
super(looper);
mWeakPlayer = new WeakReference<IjkMediaPlayer>(mp);
}
@Override
public void handleMessage(Message msg) {
IjkMediaPlayer player = mWeakPlayer.get();
if (player == null || player.mNativeMediaPlayer == 0) {
DebugLog.w(TAG,
"IjkMediaPlayer went away with unhandled events");
return;
}
switch (msg.what) {
case MEDIA_PREPARED:
player.notifyOnPrepared();
return;
case MEDIA_PLAYBACK_COMPLETE:
player.stayAwake(false);
player.notifyOnCompletion();
return;
case MEDIA_BUFFERING_UPDATE:
long bufferPosition = msg.arg1;
if (bufferPosition < 0) {
bufferPosition = 0;
}
long percent = 0;
long duration = player.getDuration();
if (duration > 0) {
percent = bufferPosition * 100 / duration;
}
if (percent >= 100) {
percent = 100;
}
// DebugLog.efmt(TAG, "Buffer (%d%%) %d/%d", percent, bufferPosition, duration);
player.notifyOnBufferingUpdate((int)percent);
return;
case MEDIA_SEEK_COMPLETE:
player.notifyOnSeekComplete();
return;
case MEDIA_SET_VIDEO_SIZE:
player.mVideoWidth = msg.arg1;
player.mVideoHeight = msg.arg2;
player.notifyOnVideoSizeChanged(player.mVideoWidth, player.mVideoHeight,
player.mVideoSarNum, player.mVideoSarDen);
return;
case MEDIA_ERROR:
DebugLog.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
if (!player.notifyOnError(msg.arg1, msg.arg2)) {
player.notifyOnCompletion();
}
player.stayAwake(false);
return;
case MEDIA_INFO:
switch (msg.arg1) {
case MEDIA_INFO_VIDEO_RENDERING_START:
DebugLog.i(TAG, "Info: MEDIA_INFO_VIDEO_RENDERING_START\n");
break;
}
player.notifyOnInfo(msg.arg1, msg.arg2);
// No real default action so far.
return;
case MEDIA_TIMED_TEXT:
if (msg.obj == null) {
player.notifyOnTimedText(null);
} else {
IjkTimedText text = new IjkTimedText(new Rect(0, 0, 1, 1), (String)msg.obj);
player.notifyOnTimedText(text);
}
return;
case MEDIA_NOP: // interface test message - ignore
break;
case MEDIA_SET_VIDEO_SAR:
player.mVideoSarNum = msg.arg1;
player.mVideoSarDen = msg.arg2;
player.notifyOnVideoSizeChanged(player.mVideoWidth, player.mVideoHeight,
player.mVideoSarNum, player.mVideoSarDen);
break;
default:
DebugLog.e(TAG, "Unknown message type " + msg.what);
}
}
}
如下图:
分析
通过上面的代码 分析播放器的start(),pause()方法,可以看出这个调用流程为 java 层 ijkVideoView start()-->IMediaPlayer start()-->ijkMediaPlayer start() jni 层 -->start()-->ffplay 内部处理--> 返回java层消息处理
总结
ijkplayer主要由Java层和JNI层组成,Java层主要负责业务控制,JNI部分主要功能是完成音视频的播放。JNI层向Java层提供接口调用,形成事件任务,同时以回调的形式向Java层推送事件完成的状态通知。
ijkplayer-exo和ijkplayer-java两个类库,分别代表两个不同的播放器。 ijkplayer-exo支持webm格式视频,看项目需求是否添加。
ijkplayer-java的底层JNI基于ffplay。ijkplayer通过实现了消息队列进行了消息的处理工作。