Android FFmpeg系列——6 Java 获取播放进度

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
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值