Android FFmpeg系列——0 编译.so库
Android FFmpeg系列——1 播放视频
Android FFmpeg系列——2 播放音频
Android FFmpeg系列——3 C多线程使用
Android FFmpeg系列——4 子线程播放音视频
Android FFmpeg系列——5 音视频同步播放
Android FFmpeg系列——6 Java 获取播放进度
Android FFmpeg系列——7 实现快进/快退功能

一般播放器都能显示播放进度,这一节来实现这个功能。由于我是在 C 层播放视频的,至于播放到哪里,Java 层是不知道的,所以得利用 C 调用 Java 代码来实现。

Java 代码

/** * 同步播放音视频 * @param path * @param surface * @param callback */public native void play(String path, Surface surface, PlayerCallback callback);/** * 播放器回调 */public interface PlayerCallback {    /**     * 播放开始     */    void onStart();    /**     * 进度     * @param total     * @param current     */    void onProgress(int total, int current);    /**     * 播放结束     */    void onEnd();}

声明一个回调接口,通过 native 方法传入到 C 层。

public void play(View view) {    player.play(videoPath, surfaceHolder.getSurface(), new Player.PlayerCallback() {        @Override        public void onStart() {            System.err.println("播放开始了 -------------------");        }        @Override        public void onProgress(final int total, final int current) {            // C 层子线程调用 子线程不能直接更新 UI            runOnUiThread(new Runnable() {                @Override                public void run() {                    currentTimeView.setText(formatTime(current));                    totalTimeView.setText(formatTime(total));                }            });        }        @Override        public void onEnd() {            System.err.println("播放结束了 -------------------");        }    });}

这是 Java 层播放代码,需要注意的是,回调 onProgress 函数是在 C 层子线程调用的,大家都知道,子线程不能直接更新 UI ,所以要特别注意一下!

OK,我们再来看 C 层代码!

C 代码

/** * 回调 Java Callback onStart方法 * @param player */void call_on_start(Player *player, JNIEnv *env) {    jclass callback_class = env->GetObjectClass(player->callback);    jmethodID on_start_method_id = env->GetMethodID(callback_class, "onStart", "()V");    env->CallVoidMethod(player->callback, on_start_method_id);    env->DeleteLocalRef(callback_class);}/** * 回调 Java Callback onStart方法 * @param player */void call_on_end(Player *player, JNIEnv *env) {    jclass callback_class = env->GetObjectClass(player->callback);    jmethodID on_end_method_id = env->GetMethodID(callback_class, "onEnd", "()V");    env->CallVoidMethod(player->callback, on_end_method_id);    env->DeleteLocalRef(callback_class);}/** * 回调 Java Callback onStart方法 * @param player * @param env * @param total * @param current */void call_on_progress(Player *player, JNIEnv *env, double total, double current) {    jclass callback_class = env->GetObjectClass(player->callback);    jmethodID on_progress_method_id = env->GetMethodID(callback_class, "onProgress", "(II)V");    env->CallVoidMethod(player->callback, on_progress_method_id, (int) total, (int) current);    env->DeleteLocalRef(callback_class);}/** * 消费函数 * 从队列获取解码数据 同步播放 * @param arg * @return */void* consume(void* arg) {    ...    if (index == player->audio_stream_index) {        // 播放开始        call_on_start(player, env);    }    double total = stream->duration * av_q2d(stream->time_base);    AVFrame *frame = av_frame_alloc();    for (;;) {        ...        if (index == player->video_stream_index) {            ...        } else if (index == player->audio_stream_index) {            ...            //  更新进度            call_on_progress(player, env, total, player->audio_clock);        }        av_packet_free(&packet);    }    if (index == player->audio_stream_index) {    // 播放结束        call_on_end(player, env);    }    ...    return NULL;}

在测试过程中,遇到 ReferenceTable overflow (max=512) 这个Bug,是由于 JNI 局部引用表溢出导致的!然后我查了一下,发现是因为我多次调用 call_on_progress 方法导致的,以上代码是我修复之后的代码,之前的代码是这样的:

void call_on_progress(Player *player, JNIEnv *env, double total, double current) {    jclass callback_class = env->GetObjectClass(player->callback);    jmethodID on_progress_method_id = env->GetMethodID(callback_class, "onProgress", "(II)V");    env->CallVoidMethod(player->callback, on_progress_method_id, (int) total, (int) current);    // 调用 GetObjectClass 方法后,没有及时回收局部引用,会持续存在局部引用表    // 由于多次调用 最终会导致 ReferenceTable overflow    // env->DeleteLocalRef(callback_class);}

代码注释得很清楚了!

其实还有一些局部引用需要我们 Delete 的,对于 GetObjectClass 返回的一定需要调用DeleteLocalRef,还有 jbyteArray 类型的变量需要 DeleteLocalRef,还有 NewString/ NewStringUTF/NewObject/ GetObjectField 这些都需要 DeleteLocalRef!如果再遇到 ReferenceTable overflow 局部引用表溢出,就需要检查一下局部变量问题。

代码地址:https://github.com/JohanMan/Player

参考

NDK ReferenceTable overflow (max=512)的解决方法

更多相关文章

  1. android 中 Proguard 和JNI 相关
  2. Android(安卓)Flurry使用说明
  3. Android(安卓)2.2 API Demos -- 通过调用子Activity返回值
  4. 简单实现Android底部工具栏
  5. SlidingDrawer
  6. Android(安卓)Activity启动过程
  7. android 控制EditText字符长度[配置控制 代码控制]
  8. android中控制AlertDialog的关闭
  9. Android资源优化

随机推荐

  1. Android 动态注册广播
  2. [置顶] Android系统安全之旅 第1章 编译A
  3. Android BitmapFactory.Options 介绍
  4. android 主要镜像(image)解析
  5. Android:捕捉触摸屏手势
  6. logcat命令详解
  7. Android的第三个应用---短信发送器
  8. Android 发送短信 和 打电话 具体事项
  9. Android Studio启动DDMS
  10. linux android sdk update