JNI方法与java方法是怎么对应的?
Android JNI原理分析
http://gityuan.com/2016/05/28/android-jni/
JNI开发:
Android笔记之使用CMake进行JNI开发(Android Studio)
https://anacz.blog.csdn.net/article/details/84202451
JNI学习笔记
Android JNI学习(三)——Java与Native相互调用
https://www.jianshu.com/p/b71aeb4ed13d
ijkplayer框架简析 – 从构造到 onPrepared
http://yydcdut.com/2019/03/16/ijkplayer-from-constructor-to-prepare/
以下文章可作为进级材料。
H264 编解码协议详解
https://blog.csdn.net/qq_19923217/article/details/83348095
https://www.jianshu.com/p/0c296b05ef2a
支持h265的硬解和软解 考虑硬解的兼容性问题
Android NDK MediaCodec在ijkplayer中的实践
https://www.cnblogs.com/jukan/p/9845673.html
源码github地址: https://github.com/YBill/IjkPlayerProject_2.git
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import com.bill.ijkplayerproject_2.util.PageJumpUtil;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); ///> 界面上有两个botton
}
public void handleAudioTest(View view) { ///> onClick() 事件激活 handleAudioTest
PageJumpUtil.gotoAudioTestActivity(this);
}
public void handleVideoTest(View view) { ///> onClick() 事件激活 handleVideoTest
//PageJumpUtil.gotoVideoSettingActivity(this);
PageJumpUtil.gotoVideoTestActivity(this);
}
}
public class PageJumpUtil {
public static void gotoAudioTestActivity(Activity activity) {
Intent intent = new Intent(activity, AudioTestActivity.class); ///> 音频测试Activity
activity.startActivity(intent);
}
public static void gotoVideoTestActivity(Activity activity) {
Intent intent = new Intent(activity, VideoTestActivity.class); ///> 视频测试Activity
activity.startActivity(intent);
}
public static void gotoVideoSettingActivity(Activity activity) {
Intent intent = new Intent(activity, VideoSettingActivity.class);
activity.startActivity(intent);
}
}
///> VideoTestActivity.java 中app启动
public class VideoTestActivity extends AppCompatActivity {
private boolean mBackPressed;
private IjkVideoView mVideoView; ///> 基于 ijkPlayer-java 中的 IMediaPlayer interface 封装实现的 IjkVideoView 类
private AndroidMediaController mMediaController; ///> 基于Androidsdk的 MediaController 实现 Demo中的应用到的接口, 接口内容IMediaController中。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_test);
mVideoView = findViewById(R.id.ijk_video_view);
mMediaController = new AndroidMediaController(this, true);
mVideoView.setMediaController(mMediaController);
mMediaController.setPrevNextListeners(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(VideoTestActivity.this, "下一个", Toast.LENGTH_SHORT).show();
}
}, new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(VideoTestActivity.this, "上一个", Toast.LENGTH_SHORT).show();
}
});
IjkMediaPlayer.loadLibrariesOnce(null); ///> 1>. 使用 ijkplayer-java 中封装的播放器类方法
IjkMediaPlayer.native_profileBegin("libijkplayer.so"); ///> 2>. 装载ijkplayer.so 库文件
mVideoView.setVideoPath("https://9890.vod.myqcloud.com/9890_4e292f9a3dd011e6b4078980237cc3d3.f30.mp4"); ///> 3>. 创建播放器、并让播放器处于预备播放状态
// mVideoView.setVideoPath("https://wdl.wallstreetcn.com/41aae4d2-390a-48ff-9230-ee865552e72d");
// mVideoView.setVideoPath("http://o6wf52jln.bkt.clouddn.com/演员.mp3");
mVideoView.start(); ///> 4>. 启动 VideoView 组件,启动播放器播放
}
@Override
public void onBackPressed() {
mBackPressed = true;
super.onBackPressed();
}
@Override
protected void onStop() {
super.onStop();
if (mBackPressed || !mVideoView.isBackgroundPlayEnabled()) {
mVideoView.stopPlayback();
mVideoView.release(true);
mVideoView.stopBackgroundPlay();
} else {
mVideoView.enterBackground();
}
IjkMediaPlayer.native_profileEnd();
}
}
///> mVideoView.setVideoPath(Uri *)方法中,最终调用ijkVideoView.java 实现内容如下
private void setVideoURI(Uri uri, Map<String, String> headers) {
mUri = uri;
mHeaders = headers;
mSeekWhenPrepared = 0;
openVideo(); ///> 此方法创建播放器、并配置控制回调
requestLayout();
invalidate();
}
///> 打开视频方法,此方法中创建 ijkplayer 播放器、并设置相关用户控制程序的回调
@TargetApi(Build.VERSION_CODES.M)
private void openVideo() {
try {
mMediaPlayer = createPlayer(PlayerConfig.getInstance().getPlayer()); ///> 1.创建播放器,此方法更加配置参数选择播放器类型分别ExoPlayer、AndroidPlayer或ijkPlayer
// a context for the subtitle renderers
final Context context = getContext();
// REMOVED: SubtitleController
// REMOVED: mAudioSession
/**视频准备播放监听*/
mMediaPlayer.setOnPreparedListener(mPreparedListener);
/**视频界面大小改变监听*/
mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
/**视频播放完成监听*/
mMediaPlayer.setOnCompletionListener(mCompletionListener);
/**视频错误监听*/
mMediaPlayer.setOnErrorListener(mErrorListener);
/**视频其他信息监听*/
mMediaPlayer.setOnInfoListener(mInfoListener);
/**视频缓冲监听*/
mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
mMediaPlayer.setOnSeekCompleteListener(mSeekCompleteListener);
mMediaPlayer.setOnTimedTextListener(mOnTimedTextListener);
mCurrentBufferPercentage = 0;
String scheme = mUri.getScheme();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
PlayerConfig.getInstance().getUsingMediaDataSource() &&
(TextUtils.isEmpty(scheme) || scheme.equalsIgnoreCase("file"))) {
IMediaDataSource dataSource = new FileMediaDataSource(new File(mUri.toString()));
mMediaPlayer.setDataSource(dataSource);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mMediaPlayer.setDataSource(mAppContext, mUri, mHeaders);
} else {
mMediaPlayer.setDataSource(mUri.toString());
}
bindSurfaceHolder(mMediaPlayer, mSurfaceHolder);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setScreenOnWhilePlaying(true);
mPrepareStartTime = System.currentTimeMillis();
mMediaPlayer.prepareAsync();
// REMOVED: mPendingSubtitleTracks
// we don't set the target state here either, but preserve the
// target state that was there before.
mCurrentState = STATE_PREPARING; ///> 2. 设置播放器状态为 准备阶段
attachMediaController();
} catch (IOException ex) {
Log.w(TAG, "Unable to open content: " + mUri, ex);
mCurrentState = STATE_ERROR;
mTargetState = STATE_ERROR;
mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
} catch (IllegalArgumentException ex) {
Log.w(TAG, "Unable to open content: " + mUri, ex);
mCurrentState = STATE_ERROR;
mTargetState = STATE_ERROR;
mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
} finally {
// REMOVED: mPendingSubtitleTracks.clear();
}
}
///> ijkVideoView.java 中的VideoView方法
public void start() {
if (isInPlaybackState()) {
mMediaPlayer.start(); ///> 3. 启动播放器播放
mCurrentState = STATE_PLAYING; ///> 4. 设置状态为播放
}
mTargetState = STATE_PLAYING;
}
public interface IMediaPlayer {}
————————————————————————————————
||
\/
public abstract class AbstractMediaPlayer implements IMediaPlayer {}
____________________________________________________________________
||
\/
public final class IjkMediaPlayer extends AbstractMediaPlayer {}
_________________________________________________________________
类的实现逻辑如上,我们现在关心的是Java 曾程序如何与 ijkplayer 的源码中C语言程序关联的呢?
第一部分 给大家准备的资料中,已经有提及,我们结合这个Demo 把相互关系给捋顺。
public final class IjkMediaPlayer extends AbstractMediaPlayer {
@Override
public void start() throws IllegalStateException {
stayAwake(true);
_start(); ///> 此方法调用 native _start() 函数
}
private native void _start() throws IllegalStateException; ///> 此处声明 native void _start() 函数,函数具体实现呢?
@Override
public void stop() throws IllegalStateException {
stayAwake(false);
_stop();
}
private native void _stop() throws IllegalStateException;
@Override
public void pause() throws IllegalStateException {
stayAwake(false);
_pause();
}
private native void _pause() throws IllegalStateException;
}
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env = NULL;
g_jvm = vm;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
pthread_mutex_init(&g_clazz.mutex, NULL );
// FindClass returns LocalReference
IJK_FIND_JAVA_CLASS(env, g_clazz.clazz, JNI_CLASS_IJKPLAYER);
(*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) ); ///> 动态注册 jni 接口方法
ijkmp_global_init();
ijkmp_global_set_inject_callback(inject_callback);
FFmpegApi_global_init(env);
return JNI_VERSION_1_4;
}
///> ijkplayer 注册的jni方法内容如下:
static JNINativeMethod g_methods[] = {
{ "_setDataSource",
"(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
(void *) IjkMediaPlayer_setDataSourceAndHeaders}, ///> IjkMediaPlayer_setDataSourceAndHeaders(JNIEnv *env, jobject thiz,
jstring path,jobjectArray keys, jobjectArray values);
{ "_setDataSourceFd", "(I)V", (void *) IjkMediaPlayer_setDataSourceFd }, ///> _setDataSourceFd(),设置本地文件为播放源的 JNI 接口
{ "_setDataSource",
"(Ltv/danmaku/ijk/media/player/misc/IMediaDataSource;)V",
(void *) IjkMediaPlayer_setDataSourceCallback }, ///> _setDataSource(), 设置网络文件播放源的 JNI 接口
{ "_setVideoSurface", "(Landroid/view/Surface;)V", (void *) IjkMediaPlayer_setVideoSurface },
{ "_prepareAsync", "()V", (void *) IjkMediaPlayer_prepareAsync },
{ "_start", "()V", (void *) IjkMediaPlayer_start }, ///> _start() 启动播放器方法, C语言的实现接下来分析.
{ "_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 },
};
///> ijkplayer_jni.c 的播放器启动
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);
}
///> ijkplayer.c 文件启动
int ijkmp_start(IjkMediaPlayer *mp)
{
assert(mp);
MPTRACE("ijkmp_start()\n");
pthread_mutex_lock(&mp->mutex);
int retval = ijkmp_start_l(mp);
pthread_mutex_unlock(&mp->mutex);
MPTRACE("ijkmp_start()=%d\n", retval);
return retval;
}
static int ijkmp_start_l(IjkMediaPlayer *mp)
{
assert(mp);
MP_RET_IF_FAILED(ikjmp_chkst_start_l(mp->mp_state));
ffp_remove_msg(mp->ffplayer, FFP_REQ_START);
ffp_remove_msg(mp->ffplayer, FFP_REQ_PAUSE);
ffp_notify_msg1(mp->ffplayer, FFP_REQ_START); ///> 通过消息队列的事件方式、通知给播放器。
return 0;
}
此 _start() 接口就分析这么多,本文重点是 _setDataSource() 接口实现,因我需要整理 ijkplayer 这部分代码。
4.1>. 接下来还是从 java 代码入口开始,用户是如何使用此 _setDataSource() 接口。
public void setVideoPath(String path) {
setVideoURI(Uri.parse(path)); ///> 构造 Uri
}
public void setVideoURI(Uri uri) {
setVideoURI(uri, null); ///> Map headers 此参数设置为空
}
private void setVideoURI(Uri uri, Map<String, String> headers) {
mUri = uri;
mHeaders = headers;
mSeekWhenPrepared = 0;
openVideo();
requestLayout();
invalidate();
}
由此过程看到设置数据源最终只是把 uri 赋值到 private Uri mUri中;接下来我们看看在 openVideo() 中是如何使用 mUri变量。
4.2>. 打开视频
@TargetApi(Build.VERSION_CODES.M)
private void openVideo() {
if (mUri == null || mSurfaceHolder == null) {
// not ready for playback just yet, will try again later
return;
}
String scheme = mUri.getScheme();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
PlayerConfig.getInstance().getUsingMediaDataSource() &&
(TextUtils.isEmpty(scheme) || scheme.equalsIgnoreCase("file"))) {
IMediaDataSource dataSource = new FileMediaDataSource(new File(mUri.toString()));
mMediaPlayer.setDataSource(dataSource); ///> IMediaDataSource 类型
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mMediaPlayer.setDataSource(mAppContext, mUri, mHeaders); ///> Context 类型
} else {
mMediaPlayer.setDataSource(mUri.toString()); ///> String 类型
}
}
///> 此三个设置数据源对应方法,在 IjkMediaPlayer.java 中被实现,如下.
public final class IjkMediaPlayer extends AbstractMediaPlayer {
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void setDataSource(Context context, Uri uri, Map<String, String> headers)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
final String scheme = uri.getScheme();
if (ContentResolver.SCHEME_FILE.equals(scheme)) {
setDataSource(uri.getPath());
return;
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
&& Settings.AUTHORITY.equals(uri.getAuthority())) {
// Redirect ringtones to go directly to underlying provider
uri = RingtoneManager.getActualDefaultRingtoneUri(context,
RingtoneManager.getDefaultType(uri));
if (uri == null) {
throw new FileNotFoundException("Failed to resolve default ringtone");
}
}
AssetFileDescriptor fd = null;
try { ///> 文件类型
ContentResolver resolver = context.getContentResolver();
fd = resolver.openAssetFileDescriptor(uri, "r");
if (fd == null) {
return;
}
// Note: using getDeclaredLength so that our behavior is the same
// as previous versions when the content provider is returning
// a full file.
if (fd.getDeclaredLength() < 0) {
setDataSource(fd.getFileDescriptor());
} else {
setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getDeclaredLength());
}
return;
} catch (SecurityException ignored) {
} catch (IOException ignored) {
} finally {
if (fd != null) {
fd.close();
}
}
Log.d(TAG, "Couldn't open file on client side, trying server side");
setDataSource(uri.toString(), headers);
}
public void setDataSource(String path, Map<String, String> headers)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException
{
if (headers != null && !headers.isEmpty()) {
StringBuilder sb = new StringBuilder();
for(Map.Entry<String, String> entry: headers.entrySet()) {
sb.append(entry.getKey());
sb.append(":");
String value = entry.getValue();
if (!TextUtils.isEmpty(value))
sb.append(entry.getValue());
sb.append("\r\n");
setOption(OPT_CATEGORY_FORMAT, "headers", sb.toString());
setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "protocol_whitelist", "async,cache,crypto,file,http,https,ijkhttphook,ijkinject,ijklivehook,ijklongurl,ijksegment,ijktcphook,pipe,rtp,tcp,tls,udp,ijkurlhook,data");
}
}
setDataSource(path);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
@Override
public void setDataSource(FileDescriptor fd)
throws IOException, IllegalArgumentException, IllegalStateException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) {
int native_fd = -1;
try {
Field f = fd.getClass().getDeclaredField("descriptor"); //NoSuchFieldException
f.setAccessible(true);
native_fd = f.getInt(fd); //IllegalAccessException
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
_setDataSourceFd(native_fd);
} else {
ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd);
try {
_setDataSourceFd(pfd.getFd());
} finally {
pfd.close();
}
}
}
public void setDataSource(IMediaDataSource mediaDataSource)
throws IllegalArgumentException, SecurityException, IllegalStateException {
_setDataSource(mediaDataSource);
}
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;
}
此部分对于 JNI 方法注册的三个接口,我接下来主要看 _setDataSource(String path, String[] keys, String[] values) 网络部分的 C 语言的实现。
4.3> 回到 ijkplayer_jni.c 文件中,找到对应的C语言的实现
static void
IjkMediaPlayer_setDataSourceAndHeaders(
JNIEnv *env, jobject thiz, jstring path,
jobjectArray keys, jobjectArray values)
{
MPTRACE("%s\n", __func__);
int retval = 0;
const char *c_path = NULL;
IjkMediaPlayer *mp = jni_get_media_player(env, thiz);
JNI_CHECK_GOTO(path, env, "java/lang/IllegalArgumentException", "mpjni: setDataSource: null path", LABEL_RETURN);
JNI_CHECK_GOTO(mp, env, "java/lang/IllegalStateException", "mpjni: setDataSource: null mp", LABEL_RETURN);
c_path = (*env)->GetStringUTFChars(env, path, NULL );
JNI_CHECK_GOTO(c_path, env, "java/lang/OutOfMemoryError", "mpjni: setDataSource: path.string oom", LABEL_RETURN);
ALOGV("setDataSource: path %s", c_path);
retval = ijkmp_set_data_source(mp, c_path);
(*env)->ReleaseStringUTFChars(env, path, c_path);
IJK_CHECK_MPRET_GOTO(retval, env, LABEL_RETURN);
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
}
///>
int ijkmp_set_data_source(IjkMediaPlayer *mp, const char *url)
{
assert(mp);
assert(url);
MPTRACE("ijkmp_set_data_source(url=\"%s\")\n", url);
pthread_mutex_lock(&mp->mutex);
int retval = ijkmp_set_data_source_l(mp, url);
pthread_mutex_unlock(&mp->mutex);
MPTRACE("ijkmp_set_data_source(url=\"%s\")=%d\n", url, retval);
return retval;
}
///> ijkplayer.c 中
static int ijkmp_set_data_source_l(IjkMediaPlayer *mp, const char *url)
{
assert(mp);
assert(url);
// MPST_RET_IF_EQ(mp->mp_state, MP_STATE_IDLE);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_INITIALIZED);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_ASYNC_PREPARING);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_PREPARED);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_STARTED);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_PAUSED);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_COMPLETED);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_STOPPED);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_ERROR);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_END);
freep((void**)&mp->data_source);
mp->data_source = strdup(url); ///> 更换数据源内容
if (!mp->data_source)
return EIJK_OUT_OF_MEMORY;
ijkmp_change_state_l(mp, MP_STATE_INITIALIZED); ///> 改变播放器状态为初始态
return 0;
}
void ijkmp_change_state_l(IjkMediaPlayer *mp, int new_state)
{
mp->mp_state = new_state; ///> 通知状态内容
ffp_notify_msg1(mp->ffplayer, FFP_MSG_PLAYBACK_STATE_CHANGED); ///> 通知播放器
}
以上部分就把数据源设置部分代码走读结束。