文章目录
直播没有进度显示和快进,只有确定长度的视频如本地文件可以有进度和快进功能
上一篇文章介绍了内存释放:
NDK29_FFmpeg内存释放
本文接着之前的功能继续实现本地文件的播放进度显示与快进功能
一 原理
- AVFormatContext->duration能够获取视频时长
- AVFrame->best_effort_timestamp获取当前相对时间
- av_seek_frame:seek到请求的时间之前最近的关键帧
二 播放进度显示
1 java层回调
DNPlayer
public void setOnProgressListener(OnProgressListener onProgressListener) {
this.onProgressListener = onProgressListener;
}
public interface OnProgressListener {
void onProgress(int progress);
}
2 native层去回调java
JavaCallHelper
JavaCallHelper::JavaCallHelper(JavaVM *vm, JNIEnv *env, jobject instace) {
...
jmid_progress = env->GetMethodID(clazz, "onProgress", "(I)V");
}
void JavaCallHelper::onProgress(int thread,int progress) {
if (thread == THREAD_MAIN){
env->CallVoidMethod(instance, jmid_progress, progress);
} else{
//子线程
JNIEnv *env;
//获得属于我这一个线程的jnienv
vm->AttachCurrentThread(&env,0);
env->CallVoidMethod(instance,jmid_progress, progress);
vm->DetachCurrentThread();
}
}
3 监听播放进度
AudioChannel开始
int AudioChannel::getPcm() {
...
//获取 frame 的一个相对播放时间 (相对开始播放)
// 获得 相对播放这一段数据的秒数
clock = frame->pts * av_q2d(time_base);
if (javaCallHelper) {
javaCallHelper->onProgress(THREAD_CHILD, clock);
...
return data_size;
}
VideoChannel::render播放监听
void VideoChannel::render() {
...
//显示时间戳 什么时候显示这个frame
if ((clock = frame->best_effort_timestamp) == AV_NOPTS_VALUE) {
clock = 0;
}
//获得 当前这一个画面 播放的相对的时间
//av_q2d转为双精度浮点数 乘以 pts 得到pts --- 显示时间:秒
double clock = clock * av_q2d(time_base);
//frame->repeat_pict = 当解码时,这张图片需要要延迟多久显示
//需要求出扩展延时:
//extra_delay = repeat_pict / (2*fps) 需要延迟这么久来显示
//额外的间隔时间
double extra_delay = frame->repeat_pict / (2 * fps);
// 真实需要的间隔时间
double delays = extra_delay + frame_delays;
if (!audioChannel) {
//休眠
// //视频快了
// av_usleep(frame_delays*1000000+x);
// //视频慢了
// av_usleep(frame_delays*1000000-x);
av_usleep(delays * 1000000);
} else {
if (clock == 0) {
av_usleep(delays * 1000000);
} else {
//比较音频与视频
double audioClock = audioChannel->clock;
//间隔 音视频相差的间隔
double diff = clock - audioClock;
if (diff > 0) {
//大于0 表示视频比较快
LOGE("视频快了:%lf", diff);
av_usleep((delays + diff) * 1000000);
} else if (diff < 0) {
//小于0 表示音频比较快
LOGE("音频快了:%lf", diff);
// 视频包积压的太多了 (丢包)
if (fabs(diff) >= 0.05) {
releaseAvFrame(&frame);
//丢包
frames.sync();
continue;
} else {
//不睡了 快点赶上 音频
}
}
}
}
#endif
//diff太大了不回调了
if (javaCallHelper && !audioChannel) {
javaCallHelper->onProgress(THREAD_CHILD, clock);
}
...
}
三 快进功能
1 native-lib启动
extern "C"
JNIEXPORT void JNICALL
Java_com_cn_ray_player_DNPlayer_native_1seek(JNIEnv *env, jobject instance, jint progress) {
if (ffmpeg){
ffmpeg->seek(progress);
}
}
2 FFmpeg实现
void DNFFmpeg::seek(int i) {
//进去必须 在0- duration 范围之类
if (i < 0 || i >= duration) {
return;
}
if(!audioChannel && !videoChannel){
return;
}
if(!formatContext){
return;
}
isSeek = 1;
pthread_mutex_lock(&seekMutex);
//单位是 微秒
int64_t seek = i*1000000;
//seek到请求的时间 之前最近的关键帧
// 只有从关键帧才能开始解码出完整图片
av_seek_frame(formatContext,-1,seek,AVSEEK_FLAG_BACKWARD);
// avformat_seek_file(formatContext, -1, INT64_MIN, seek, INT64_MAX, 0);
// 音频、与视频队列中的数据 是不是就可以丢掉了?
if(audioChannel){
audioChannel->stopWork();
//暂停队列
//可以清空缓存
// avcodec_flush_buffers();
audioChannel->clear();
//启动队列
audioChannel->startWork();
}
if(videoChannel){
videoChannel->stopWork();
videoChannel->clear();
videoChannel->startWork();
}
pthread_mutex_unlock(&seekMutex);
isSeek = 0;
}
3 读取视频数据包时设置锁
DNFFmpeg::DNFFmpeg(JavaCallHelper *callHelper, const char *dataSource) {
...
pthread_mutex_init(&seekMutex, 0);
}
void DNFFmpeg::_start() {
int ret;
while (isPlaying) {
...
//锁住formatContext
pthread_mutex_lock(&seekMutex);
AVPacket *packet = av_packet_alloc();
ret = av_read_frame(formatContext, packet);
pthread_mutex_unlock(&seekMutex);
...
}
}