在module ijkplayer-java
中实现了对ffmpeg
调用的封装类IjkMediaPlayer
,IjkMediaPlayer
继承AbstractMediaPlayer
,而AbstractMediaPlayer
实现了接口IMediaPlayer
。
一、IMediaPlayer
IMediaPlayer
参照Android
系统播放器MediaPlayer
的方式定义了一系列播放器常量、接口方法、内部接口
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_VIDEO_SEEK_RENDERING_START = 10008;
int MEDIA_INFO_AUDIO_SEEK_RENDERING_START = 10009;
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);
}
例如
MEDIA_INFO_BUFFERING_START
与MediaPlayer.MEDIA_INFO_BUFFERING_START
对应;
pause()
与MediaPlayer
中的pause()
方法对应;
OnPreparedListener
与MediaPlayer.OnPreparedListener
接口对应。
二、AbstractMediaPlayer
AbstractMediaPlayer
是一个抽象类,实现了接口IMediaPlayer
public abstract class AbstractMediaPlayer implements IMediaPlayer {
private OnPreparedListener mOnPreparedListener;
private OnCompletionListener mOnCompletionListener;
private OnBufferingUpdateListener mOnBufferingUpdateListener;
private OnSeekCompleteListener mOnSeekCompleteListener;
private OnVideoSizeChangedListener mOnVideoSizeChangedListener;
private OnErrorListener mOnErrorListener;
private OnInfoListener mOnInfoListener;
private OnTimedTextListener mOnTimedTextListener;
public final void setOnPreparedListener(OnPreparedListener listener) {
mOnPreparedListener = listener;
}
public final void setOnCompletionListener(OnCompletionListener listener) {
mOnCompletionListener = listener;
}
public final void setOnBufferingUpdateListener(
OnBufferingUpdateListener listener) {
mOnBufferingUpdateListener = listener;
}
public final void setOnSeekCompleteListener(OnSeekCompleteListener listener) {
mOnSeekCompleteListener = listener;
}
public final void setOnVideoSizeChangedListener(
OnVideoSizeChangedListener listener) {
mOnVideoSizeChangedListener = listener;
}
public final void setOnErrorListener(OnErrorListener listener) {
mOnErrorListener = listener;
}
public final void setOnInfoListener(OnInfoListener listener) {
mOnInfoListener = listener;
}
public final void setOnTimedTextListener(OnTimedTextListener listener) {
mOnTimedTextListener = listener;
}
public void resetListeners() {
mOnPreparedListener = null;
mOnBufferingUpdateListener = null;
mOnCompletionListener = null;
mOnSeekCompleteListener = null;
mOnVideoSizeChangedListener = null;
mOnErrorListener = null;
mOnInfoListener = null;
mOnTimedTextListener = null;
}
protected final void notifyOnPrepared() {
if (mOnPreparedListener != null)
mOnPreparedListener.onPrepared(this);
}
protected final void notifyOnCompletion() {
if (mOnCompletionListener != null)
mOnCompletionListener.onCompletion(this);
}
protected final void notifyOnBufferingUpdate(int percent) {
if (mOnBufferingUpdateListener != null)
mOnBufferingUpdateListener.onBufferingUpdate(this, percent);
}
protected final void notifyOnSeekComplete() {
if (mOnSeekCompleteListener != null)
mOnSeekCompleteListener.onSeekComplete(this);
}
protected final void notifyOnVideoSizeChanged(int width, int height,
int sarNum, int sarDen) {
if (mOnVideoSizeChangedListener != null)
mOnVideoSizeChangedListener.onVideoSizeChanged(this, width, height,
sarNum, sarDen);
}
protected final boolean notifyOnError(int what, int extra) {
return mOnErrorListener != null && mOnErrorListener.onError(this, what, extra);
}
protected final boolean notifyOnInfo(int what, int extra) {
return mOnInfoListener != null && mOnInfoListener.onInfo(this, what, extra);
}
protected final void notifyOnTimedText(IjkTimedText text) {
if (mOnTimedTextListener != null)
mOnTimedTextListener.onTimedText(this, text);
}
public void setDataSource(IMediaDataSource mediaDataSource) {
throw new UnsupportedOperationException();
}
}
同时实现了部分方法,又提供了几个方法用于触发播放器状态回调到调用者,例如notifyOnPrepared
,这样在底层播放器通过jni
通知加载完成的时候,通过notifyOnPrepared
方法就可以触发mOnPreparedListener.onPrepared(this)
回调,从外部调用IjkMediaPlayer
的时候就可以与系统播放器MediaPlayer
保持一致的体验。
三、IjkMediaPlayer
IjkMediaPlayer
集成至抽象类AbstractMediaPlayer
public final class IjkMediaPlayer extends AbstractMediaPlayer {
...
}
加载ijk
用到的3个so
库
private static final IjkLibLoader sLocalLibLoader = new IjkLibLoader() {
@Override
public void loadLibrary(String libName) throws UnsatisfiedLinkError, SecurityException {
System.loadLibrary(libName);
}
};
private static volatile boolean mIsLibLoaded = false;
public static void loadLibrariesOnce(IjkLibLoader libLoader) {
synchronized (IjkMediaPlayer.class) {
if (!mIsLibLoaded) {
if (libLoader == null)
libLoader = sLocalLibLoader;
libLoader.loadLibrary("ijkffmpeg");
libLoader.loadLibrary("ijksdl");
libLoader.loadLibrary("ijkplayer");
mIsLibLoaded = true;
}
}
}
...
public IjkMediaPlayer(IjkLibLoader libLoader) {
initPlayer(libLoader);
}
private void initPlayer(IjkLibLoader libLoader) {
loadLibrariesOnce(libLoader);
...
}
需要注意的是这三个库之间是有依赖关系的,加载顺序不能乱,先加载libijkffmpeg.so
,再加载libijksdl.so
,最后加载libijkplayer.so
。
IjkMediaPlayer
的实现大致可以分为两个方面:
- 1、输入
比如设置视频url、设置播放器参数、播放、暂停等,这些行为都可以简单看做是对播放器的输入 - 2、输出
输出是指各种回调监听和获取播放器参数,比如视频加载完成监听,缓冲监听,播放结束监听等
IjkMediaPlayer
参照MediaPlayer
定义了一些列播放器的调用方法,比如:
-
setDataSource
输入视频源 -
start
播放 -
pause
暂停
在IjkMediaPlayer
接收到这些方法调用的时候通过jni
通知底层播放器来响应这些操作。以pause
为例:
@Override
public void pause() throws IllegalStateException {
stayAwake(false);
_pause();
}
private native void _pause() throws IllegalStateException;
四、jni
Android
系统播放器的调用流程如下图
这里参照系统播放器,把
IjkMediaPlayer
中jni
方法做个简单的梳理
// 初始化
private static native void native_init();
public static native void native_setLogLevel(int level);
public static native void native_profileBegin(String libName);
private native void native_message_loop(Object IjkMediaPlayer_this);
private native void native_setup(Object IjkMediaPlayer_this);
// 输入
private native void _setDataSource(String path, String[] keys, String[] values)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
private native void _setDataSourceFd(int fd)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
private native void _setDataSource(IMediaDataSource mediaDataSource)
throws IllegalArgumentException, SecurityException, IllegalStateException;
private native void _setFrameAtTime(String imgCachePath, long startTime, long endTime, int num, int imgDefinition)
throws IllegalArgumentException, IllegalStateException;
private native void _setPropertyFloat(int property, float value);
private native void _setPropertyLong(int property, long value);
private native void _setOption(int category, String name, String value);
private native void _setOption(int category, String name, long value);
private native void _setVideoSurface(Surface surface);
private native void _setLoopCount(int loopCount);
public native void setVolume(float leftVolume, float rightVolume);
private native void _setAndroidIOCallback(IAndroidIO androidIO)
throws IllegalArgumentException, SecurityException, IllegalStateException;
public native void _prepareAsync() throws IllegalStateException;
private native void _setStreamSelected(int stream, boolean select);
private native void _start() throws IllegalStateException;
private native void _pause() throws IllegalStateException;
public native void seekTo(long msec) throws IllegalStateException;
// 输出
public native boolean isPlaying();
public native long getCurrentPosition();
public native long getDuration();
private native int _getLoopCount();
private native float _getPropertyFloat(int property, float defaultValue);
private native long _getPropertyLong(int property, long defaultValue);
public native int getAudioSessionId();
private native String _getVideoCodecInfo();
private native String _getAudioCodecInfo();
private native Bundle _getMediaMeta();
private static native String _getColorFormatName(int mediaCodecColorFormat);
// 输入
private native void _stop() throws IllegalStateException;
private native void _release();
private native void _reset();
// 释放
public static native void native_profileEnd();
private native void native_finalize();
上面定义的一些列jni
方法都是Java call C/C++
,用于实现从Java
代码调用C/C++
代码
五、CalledByNative
播放器的状态回调是C/C++ call Java
,需要实现的是C/C++
调用Java
代码,在IjkMediaPlayer
中定义了三个方法用于接收底层播放器的回调信息
@CalledByNative
private static boolean onNativeInvoke(Object weakThiz, int what, Bundle args) {
...
OnNativeInvokeListener listener = player.mOnNativeInvokeListener;
if (listener != null && listener.onNativeInvoke(what, args))
return true;
switch (what) {
case OnNativeInvokeListener.CTRL_WILL_CONCAT_RESOLVE_SEGMENT: {
OnControlMessageListener onControlMessageListener = player.mOnControlMessageListener;
if (onControlMessageListener == null)
return false;
...
return true;
}
default:
return false;
}
}
@CalledByNative
private static String onSelectCodec(Object weakThiz, String mimeType, int profile, int level) {
...
OnMediaCodecSelectListener listener = player.mOnMediaCodecSelectListener;
if (listener == null)
listener = DefaultMediaCodecSelector.sInstance;
return listener.onMediaCodecSelect(player, mimeType, profile, level);
}
@CalledByNative
private static void postEventFromNative(Object weakThiz, int what,
int arg1, int arg2, Object obj) {
if (weakThiz == null)
return;
@SuppressWarnings("rawtypes")
IjkMediaPlayer mp = (IjkMediaPlayer) ((WeakReference) weakThiz).get();
if (mp == null) {
return;
}
if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
// this acquires the wakelock if needed, and sets the client side
// state
mp.start();
}
if (mp.mEventHandler != null) {
Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
mp.mEventHandler.sendMessage(m);
}
}
上面三个Java
方法用于接收底层播放器回调信息,重点关注postEventFromNative
方法,因为我们常用的OnPreparedListener
、OnCompletionListener
等播放器信息都是在这里接收的。
postEventFromNative
接收到播放器回调信息回调给外部调用者的实现方式如下,在IjkMediaPlayer
初始化的时候,生成mEventHandler
private void initPlayer(IjkLibLoader libLoader) {
...
Looper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
}
...
}
当postEventFromNative
接收端播放器底层回调信息的时候,把消息发送给EventHandler
Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
mp.mEventHandler.sendMessage(m);
EventHandler
接收消息并处理
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);
}
}
}
以OnPreparedListener
回调为例,播放器底层回调一条what == MEDIA_PREPARED
的信息,postEventFromNative
接收信息并发送到EventHandler
中处理,EventHandler
根据what == MEDIA_PREPARED
调用AbstractMediaPlayer
中的notifyOnPrepared()
方法
protected final void notifyOnPrepared() {
if (mOnPreparedListener != null)
mOnPreparedListener.onPrepared(this);
}
notifyOnPrepared()
方法通过mOnPreparedListener.onPrepared(this)
将底层播放器加载完成的状态信息回调给外部调用者。
网友评论