android基于ffmpeg的简单视频播发器 跳到指定帧 av_seek_frame()

2024-05-11 06:32

本文主要是介绍android基于ffmpeg的简单视频播发器 跳到指定帧 av_seek_frame(),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

跳到指定帧,在ffmpeg使用av_seek_frame()进行跳转,这个函数只能跳到关键帧,所以对关键帧时间差距比较大的视频很尴尬,总是不能调到想要的画面
还有av_seek_frame中的时间参数,刚开始以为用秒乘以time_base结果不是,而是AVPacket或AVFrame里的pts或dts对应的数,不用很精确,可以用大概值,因为av_seek_frame会跳到关键帧
所以可以用两种方式跳转
av_seek_frame(fmt_ctx, audio_stream_index,pts,AVSEEK_FLAG_BACKWARD);
或者
int64_t time = (int64_t) (avStream->duration * 0.01);
av_seek_frame(fmt_ctx, audio_stream_index,time,AVSEEK_FLAG_BACKWARD);
duration是视频总时长,但不是时间单位,也就是说不是秒或毫秒
我是这样写的,记录每帧的时间
double nowTime = frame->pts * av_q2d(avStream->time_base);
long t = (long) (nowTime * 1000);
audio_time = t;
计算跳转时间
move_time = audio_time/1000.0f + time;
audio_time是毫秒,所以要除以1000,time=10,就是加十秒,然后跳转
int64_t k = (int64_t) (move_time / av_q2d(avStream->time_base));
av_seek_frame(fmt_ctx, audio_stream_index,k,AVSEEK_FLAG_BACKWARD);
avcodec_flush_buffers(codec_ctx);
audio_move_time = move_time;
在av_read_frame()之后舍弃掉小于指定时间的帧
if(audio_move_time != -1){double nowTime = pkt->pts * av_q2d(avStream->time_base);
    if(nowTime < audio_move_time){av_packet_unref(pkt);
        continue;
    }
}
这样就可以得到指定时间的帧数据了,之后用avcodec_send_packet()和avcodec_receive_frame()进行解码,不过这样做有时候调用avcodec_send_packet()会非常慢,因为现在得到的AVPacket不是关键帧,avcodec_send_packet()会把数据补全
接下来贴完整代码
java
public void video_move(View view){move(10.0d);
}
public native void move(double time);
c++
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include "EGLUtils.h"
#include "OpenGLUtils.h"
extern "C" {
#include "libavformat/avformat.h"
#include "libavfilter/avfiltergraph.h"
#include "ffmpeg.h"
}//视频线程锁
pthread_mutex_t video_mutex;
pthread_cond_t video_cond;
//当前音频帧时间
long audio_time = 0;
long start_time = 0;
//当前系统时间
long getCurrentTime() {struct timeval tv;
    gettimeofday(&tv,NULL);
    return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
//计算等待时间
timespec waitTime(long timeout_ms){struct timespec abstime;
    struct timeval now;
    gettimeofday(&now, NULL);
    long nsec = now.tv_usec * 1000 + (timeout_ms % 1000) * 1000000;
    abstime.tv_sec=now.tv_sec + nsec / 1000000000 + timeout_ms / 1000;
    abstime.tv_nsec=nsec % 1000000000;
    return abstime;
}
//视频是否跳转完成
bool isVideoMove = false;
//音频是否跳转完成
bool isAudioMove = false;
//要跳转的时间
double move_time = 0;
//跳转时用的锁
pthread_mutex_t move_mutex;
pthread_cond_t move_cond;
//记录视频是否完成跳转,先进行视频跳转音频跳转
bool isMoveWait = false;
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ffmpegrun_MainActivity_videoPlay(JNIEnv *env, jobject instance, jstring path_,
                                                  jobject surface) {const char *path = env->GetStringUTFChars(path_, 0);

    // TODO
    //初始化锁
    pthread_mutex_init (&video_mutex,NULL);
    pthread_cond_init(&video_cond,NULL);

    pthread_mutex_init (&move_mutex,NULL);
    pthread_cond_init(&move_cond,NULL);
    //初始化视频ffmpeg
    av_register_all();
    AVFormatContext *fmt_ctx = avformat_alloc_context();
    if (avformat_open_input(&fmt_ctx, path, NULL, NULL) < 0) {return;
    }if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {return;
    }AVStream *avStream = NULL;
    int video_stream_index = -1;
    for (int i = 0; i < fmt_ctx->nb_streams; i++) {if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {avStream = fmt_ctx->streams[i];
            video_stream_index = i;
            break;
        }}if (video_stream_index == -1) {return;
    }AVCodecContext *codec_ctx = avcodec_alloc_context3(NULL);
    avcodec_parameters_to_context(codec_ctx, avStream->codecpar);

    AVCodec *avCodec = avcodec_find_decoder(codec_ctx->codec_id);
    if (avcodec_open2(codec_ctx, avCodec, NULL) < 0) {return;
    }//初始化opengl
    ANativeWindow* nativeWindow = ANativeWindow_fromSurface(env,surface);

    EGLUtils *eglUtils = new EGLUtils();
    eglUtils->initEGL(nativeWindow);

    OpenGLUtils *openGLUtils = new OpenGLUtils();
    openGLUtils->surfaceCreated();
    openGLUtils->surfaceChanged(eglUtils->getWidth(),eglUtils->getHeight());
    openGLUtils->initTexture(codec_ctx->width,codec_ctx->height);
    //初始化AVPacket
    int y_size = codec_ctx->width * codec_ctx->height;
    AVPacket *pkt = (AVPacket *) malloc(sizeof(AVPacket));
    av_new_packet(pkt, y_size);
    double video_move_time = -1;

    int ret;
    while (1) {//跳转
        if(isVideoMove){//还原时间单位
            int64_t time = (int64_t) (move_time / av_q2d(avStream->time_base));
            //跳转
            av_seek_frame(fmt_ctx, video_stream_index,
                          time,
                          AVSEEK_FLAG_BACKWARD);
            //清空缓存数据
            avcodec_flush_buffers(codec_ctx);
            //跳转完成
            isVideoMove = false;
            //记录时间
            video_move_time = move_time;
        }if (av_read_frame(fmt_ctx, pkt) < 0) {av_packet_unref(pkt);
            break;
        }if (pkt->stream_index == video_stream_index) {//过滤比跳转时间小的数据
            if(video_move_time != -1){double nowTime = pkt->pts * av_q2d(avStream->time_base);
                if(nowTime < video_move_time){av_packet_unref(pkt);
                    continue;
                }}ret = avcodec_send_packet(codec_ctx, pkt);
            if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {av_packet_unref(pkt);
                continue;
            }AVFrame *yuvFrame = av_frame_alloc();
            ret = avcodec_receive_frame(codec_ctx, yuvFrame);
            if (ret < 0 && ret != AVERROR_EOF) {av_packet_unref(pkt);
                av_frame_free(&yuvFrame);
                continue;
            }double nowTime = yuvFrame->pts * av_q2d(avStream->time_base);
            //判断是这一帧否是跳转完的第一帧,是就直接显示
            if(video_move_time == -1){//不是跳转的第一帧,和音频进行矫正
                long t = (long) (nowTime * 1000);
                long time = getCurrentTime() - start_time;
                long wait = t - time - audio_time;

                struct timespec abstime = waitTime(wait);
                pthread_mutex_lock(&video_mutex);
                if(!isVideoMove){pthread_cond_timedwait(&video_cond, &video_mutex,&abstime);
                }pthread_mutex_unlock(&video_mutex);
            }//opengl加载数据渲染
            openGLUtils->updateTexture(yuvFrame->width,yuvFrame->height,yuvFrame->data[0],yuvFrame->data[1],yuvFrame->data[2]);
            openGLUtils->surfaceDraw();
            eglUtils->drawEGL();
            av_frame_free(&yuvFrame);
            av_packet_unref(pkt);
            //判断是否是跳转后的第一帧
            if(video_move_time != -1){video_move_time = -1;
                //视频跳转完后开始音频跳转
                pthread_mutex_lock(&move_mutex);
                //把跳转时间变成当前这一帧的时间
                move_time = nowTime;
                if(isAudioMove){pthread_cond_signal(&move_cond);
                }isMoveWait = true;
                pthread_cond_wait(&move_cond, &move_mutex);
                isMoveWait = false;
                pthread_mutex_unlock(&move_mutex);
            }}av_packet_unref(pkt);
    }avcodec_close(codec_ctx);
    avformat_close_input(&fmt_ctx);

    pthread_cond_destroy(&video_cond);
    pthread_mutex_destroy(&video_mutex);

    env->ReleaseStringUTFChars(path_, path);
}
#define MAX_AUDIO_FRME_SIZE 48000 * 4
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ffmpegrun_MainActivity_audioPlay(JNIEnv *env, jobject instance, jstring path_) {const char *path = env->GetStringUTFChars(path_, 0);

    // TODO


    
    av_register_all();
    AVFormatContext *fmt_ctx = avformat_alloc_context();
    if (avformat_open_input(&fmt_ctx, path, NULL, NULL) < 0) {return;
    }if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {return;
    }AVStream *avStream = NULL;
    int audio_stream_index = -1;
    for (int i = 0; i < fmt_ctx->nb_streams; i++) {if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {avStream = fmt_ctx->streams[i];
            audio_stream_index = i;
            break;
        }}if (audio_stream_index == -1) {return;
    }AVCodecContext *codec_ctx = avcodec_alloc_context3(NULL);
    avcodec_parameters_to_context(codec_ctx, avStream->codecpar);

    AVCodec *avCodec = avcodec_find_decoder(codec_ctx->codec_id);
    if (avcodec_open2(codec_ctx, avCodec, NULL) < 0) {return;
    }SwrContext *swr_ctx = swr_alloc();

    enum AVSampleFormat in_sample_fmt = codec_ctx->sample_fmt;

    enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;

    int in_sample_rate = codec_ctx->sample_rate;

    int out_sample_rate = in_sample_rate;

    uint64_t in_ch_layout = codec_ctx->channel_layout;

    uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;


    swr_alloc_set_opts(swr_ctx,
                       out_ch_layout, out_sample_fmt, out_sample_rate,
                       in_ch_layout, in_sample_fmt, in_sample_rate,
                       0, NULL);
    swr_init(swr_ctx);

    int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);

    jclass player_class = env->GetObjectClass(instance);
    jmethodID create_audio_track_mid = env->GetMethodID(player_class, "createAudio",
                                                        "(II)Landroid/media/AudioTrack;");
    jobject audio_track = env->CallObjectMethod(instance, create_audio_track_mid,
                                                out_sample_rate, out_channel_nb);


    jclass audio_track_class = env->GetObjectClass(audio_track);
    jmethodID audio_track_play_mid = env->GetMethodID(audio_track_class, "play", "()V");
    jmethodID audio_track_stop_mid = env->GetMethodID(audio_track_class, "stop", "()V");
    env->CallVoidMethod(audio_track, audio_track_play_mid);

    jmethodID audio_track_write_mid = env->GetMethodID(audio_track_class, "write",
                                                       "([BII)I");


    uint8_t *out_buffer = (uint8_t *) av_malloc(MAX_AUDIO_FRME_SIZE);

    AVPacket *pkt = (AVPacket *) malloc(sizeof(AVPacket));

    double audio_move_time = -1;
    int ret;
    while (1) {//判断是否跳转
        if(isAudioMove){//判断视频是否跳转完成,没有完成等待视频完成
            pthread_mutex_lock(&move_mutex);
            if(!isMoveWait){pthread_cond_wait(&move_cond, &move_mutex);
            }isAudioMove = false;
            pthread_mutex_unlock(&move_mutex);

    
            //跳转
            int64_t k = (int64_t) (move_time / av_q2d(avStream->time_base));
            av_seek_frame(fmt_ctx, audio_stream_index,k,AVSEEK_FLAG_BACKWARD);
            avcodec_flush_buffers(codec_ctx);
            audio_move_time = move_time;

        }if (av_read_frame(fmt_ctx, pkt) < 0){av_packet_unref(pkt);
            break;
        }if(pkt->stream_index == audio_stream_index){if(audio_move_time != -1){double nowTime = pkt->pts * av_q2d(avStream->time_base);
                if(nowTime < audio_move_time){av_packet_unref(pkt);
                    continue;
                }}ret = avcodec_send_packet(codec_ctx, pkt);
            if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {av_packet_unref(pkt);
                continue;
            }AVFrame *frame = av_frame_alloc();

            ret = avcodec_receive_frame(codec_ctx, frame);
            if (ret < 0 && ret != AVERROR_EOF) {av_packet_unref(pkt);
                av_frame_free(&frame);
                continue;
            }double nowTime = frame->pts * av_q2d(avStream->time_base);
            long t = (long) (nowTime * 1000);
            audio_time = t;
            start_time = getCurrentTime();
            //判断是否是跳转后的第一帧
            if(audio_move_time != -1){audio_move_time = -1;
                pthread_mutex_lock(&move_mutex);
                //视频帧是否在等待,是就让它开始跑
                if(isMoveWait){pthread_cond_signal(&move_cond);
                }pthread_mutex_unlock(&move_mutex);
            }swr_convert(swr_ctx, &out_buffer, MAX_AUDIO_FRME_SIZE,
                        (const uint8_t **) frame->data,
                        frame->nb_samples);
            int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb,
                                                             frame->nb_samples, out_sample_fmt,
                                                             1);

            jbyteArray audio_sample_array = env->NewByteArray(out_buffer_size);
            jbyte *sample_bytep = env->GetByteArrayElements(audio_sample_array, NULL);

            memcpy(sample_bytep, out_buffer, (size_t) out_buffer_size);
            env->ReleaseByteArrayElements(audio_sample_array, sample_bytep, 0);


            env->CallIntMethod(audio_track, audio_track_write_mid,
                               audio_sample_array, 0, out_buffer_size);

            env->DeleteLocalRef(audio_sample_array);

            av_frame_free(&frame);

            av_packet_unref(pkt);
        }}env->CallVoidMethod(audio_track, audio_track_stop_mid);
    av_free(out_buffer);
    swr_free(&swr_ctx);
    avcodec_close(codec_ctx);
    avformat_close_input(&fmt_ctx);

    env->ReleaseStringUTFChars(path_, path);
}extern "C"
JNIEXPORT void JNICALL
Java_com_example_ffmpegrun_MainActivity_move(JNIEnv *env, jobject instance, jdouble time) {// TODO
    //计算跳转时间
    move_time = audio_time/1000.0f + time;
    isAudioMove = true;
    pthread_mutex_lock(&video_mutex);
    isVideoMove = true;
    pthread_mutex_unlock(&video_mutex);
}
先视频进行跳转,在音频进行跳转,完成后正常播放













这篇关于android基于ffmpeg的简单视频播发器 跳到指定帧 av_seek_frame()的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/978764

相关文章

Python将大量遥感数据的值缩放指定倍数的方法(推荐)

《Python将大量遥感数据的值缩放指定倍数的方法(推荐)》本文介绍基于Python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处理,并将所得处理后数据保存为新的遥感影像... 本文介绍基于python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处

利用Python编写一个简单的聊天机器人

《利用Python编写一个简单的聊天机器人》这篇文章主要为大家详细介绍了如何利用Python编写一个简单的聊天机器人,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 使用 python 编写一个简单的聊天机器人可以从最基础的逻辑开始,然后逐步加入更复杂的功能。这里我们将先实现一个简单的

Python视频处理库VidGear使用小结

《Python视频处理库VidGear使用小结》VidGear是一个高性能的Python视频处理库,本文主要介绍了Python视频处理库VidGear使用小结,文中通过示例代码介绍的非常详细,对大家的... 目录一、VidGear的安装二、VidGear的主要功能三、VidGear的使用示例四、VidGea

通过C#获取PDF中指定文本或所有文本的字体信息

《通过C#获取PDF中指定文本或所有文本的字体信息》在设计和出版行业中,字体的选择和使用对最终作品的质量有着重要影响,然而,有时我们可能会遇到包含未知字体的PDF文件,这使得我们无法准确地复制或修改文... 目录引言C# 获取PDF中指定文本的字体信息C# 获取PDF文档中用到的所有字体信息引言在设计和出

使用IntelliJ IDEA创建简单的Java Web项目完整步骤

《使用IntelliJIDEA创建简单的JavaWeb项目完整步骤》:本文主要介绍如何使用IntelliJIDEA创建一个简单的JavaWeb项目,实现登录、注册和查看用户列表功能,使用Se... 目录前置准备项目功能实现步骤1. 创建项目2. 配置 Tomcat3. 项目文件结构4. 创建数据库和表5.

使用PyQt5编写一个简单的取色器

《使用PyQt5编写一个简单的取色器》:本文主要介绍PyQt5搭建的一个取色器,一共写了两款应用,一款使用快捷键捕获鼠标附近图像的RGB和16进制颜色编码,一款跟随鼠标刷新图像的RGB和16... 目录取色器1取色器2PyQt5搭建的一个取色器,一共写了两款应用,一款使用快捷键捕获鼠标附近图像的RGB和16

多模块的springboot项目发布指定模块的脚本方式

《多模块的springboot项目发布指定模块的脚本方式》该文章主要介绍了如何在多模块的SpringBoot项目中发布指定模块的脚本,作者原先的脚本会清理并编译所有模块,导致发布时间过长,通过简化脚本... 目录多模块的springboot项目发布指定模块的脚本1、不计成本地全部发布2、指定模块发布总结多模

四种简单方法 轻松进入电脑主板 BIOS 或 UEFI 固件设置

《四种简单方法轻松进入电脑主板BIOS或UEFI固件设置》设置BIOS/UEFI是计算机维护和管理中的一项重要任务,它允许用户配置计算机的启动选项、硬件设置和其他关键参数,该怎么进入呢?下面... 随着计算机技术的发展,大多数主流 PC 和笔记本已经从传统 BIOS 转向了 UEFI 固件。很多时候,我们也

VMWare报错“指定的文件不是虚拟磁盘“或“The file specified is not a virtual disk”问题

《VMWare报错“指定的文件不是虚拟磁盘“或“Thefilespecifiedisnotavirtualdisk”问题》文章描述了如何修复VMware虚拟机中出现的“指定的文件不是虚拟... 目录VMWare报错“指定的文件不是虚拟磁盘“或“The file specified is not a virt

基于Qt开发一个简单的OFD阅读器

《基于Qt开发一个简单的OFD阅读器》这篇文章主要为大家详细介绍了如何使用Qt框架开发一个功能强大且性能优异的OFD阅读器,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 目录摘要引言一、OFD文件格式解析二、文档结构解析三、页面渲染四、用户交互五、性能优化六、示例代码七、未来发展方向八、结论摘要