NDK30_FFmpeg显示播放进度与快进

NDK开发汇总

直播没有进度显示和快进,只有确定长度的视频如本地文件可以有进度和快进功能

上一篇文章介绍了内存释放:
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);
		...

	}

}

四 Demo

FFmpegDemo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值