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)的解决方法
更多相关文章
- android 中 Proguard 和JNI 相关
- Android(安卓)Flurry使用说明
- Android(安卓)2.2 API Demos -- 通过调用子Activity返回值
- 简单实现Android底部工具栏
- SlidingDrawer
- Android(安卓)Activity启动过程
- android 控制EditText字符长度[配置控制 代码控制]
- android中控制AlertDialog的关闭
- Android资源优化