ijkplayer之 DemoPlayer 程序框架设计概述

第一步 阅读本篇源码走读,需要做如下准备工作

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

第二步 DemoPlayer 源码

源码github地址: https://github.com/YBill/IjkPlayerProject_2.git

第三步 源码走读即框架描述

  • 1> 程序入口处
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);
   }

}
  • 2> 中间激活 Intent 过程
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);
   }
}
  • 3> 视频启动过程
///> 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;
    }

  • 4> 播放器封装方法
    此 Demo 程序中使用的 IjkMediaPlayer 类演进路线如下。
    此类是 ijkplayer 源码中提供 ijkplayer-java 文件中 Demo 程序,此程序封装 IjkMediaPlayer 类。
public interface IMediaPlayer {
    
    }
————————————————————————————————
			||
			\/
public abstract class AbstractMediaPlayer implements IMediaPlayer {
    
    }
____________________________________________________________________
            ||
            \/
public final class IjkMediaPlayer extends AbstractMediaPlayer {
    
    }
_________________________________________________________________

类的实现逻辑如上,我们现在关心的是Java 曾程序如何与 ijkplayer 的源码中C语言程序关联的呢?
第一部分 给大家准备的资料中,已经有提及,我们结合这个Demo 把相互关系给捋顺。

  • 4.1> 首先看一下 IjkMediaPlayer 中 启动播放器实现方法.
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;


}
  • 4.2> jni接口方法动态注册
    此部分内容需要看 ijkplayer 的源码程序文件 ijkplayer\ijkplayer_jni.c 文件,有 JNI 方法
    注册函数内容如下:
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 这部分代码。

第四步 走读 _setDataSource() 接口实现代码

4.1>. 接下来还是从 java 代码入口开始,用户是如何使用此 _setDataSource() 接口。


    public void setVideoPath(String path) {
    
    
        setVideoURI(Uri.parse(path));        ///> 构造 Uri 
    }

    public void setVideoURI(Uri uri) {
    
    
        setVideoURI(uri, null);              ///> Map<String, String> 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);  ///> 通知播放器
}

以上部分就把数据源设置部分代码走读结束。

猜你喜欢

转载自blog.csdn.net/weixin_38387929/article/details/121182820
今日推荐