android基于ffmpeg的简单视频播发器 时间同步

2024-05-11 06:32

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

前面写了视频解码和音频解码,接下来要同步了

java代码

setContentView(R.layout.activity_main);
SurfaceView surfaceView = findViewById(R.id.surface_view);
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {@Override
    public void surfaceCreated(SurfaceHolder holder) {}@Override
    public void surfaceChanged(final SurfaceHolder holder, int format, int width, int height) {Thread audioThread = new Thread(){@Override
            public void run() {super.run();
                String videoPath = "/storage/emulated/0/baiduNetdisk/season09.mp4";
                audioPlay(videoPath);
            }};
        audioThread.start();
        Thread videoThread = new Thread(){@Override
            public void run() {super.run();
                String videoPath = "/storage/emulated/0/baiduNetdisk/season09.mp4";
                videoPlay(videoPath,holder.getSurface());
            }};
        videoThread.start();

    }@Override
    public void surfaceDestroyed(SurfaceHolder holder) {}
});
就是开两个线程主要还是c++代码

既然要同步线程,肯定是让快的线程等待慢的线程,通过前面的代码知道,视频线程比音频快,所以只能让视频线程进行等待,等待的话就用到了锁

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;
}
首先,要在音频线程计算出音频的时间

double nowTime = frame->pts * av_q2d(avStream->time_base);
long t = (long) (nowTime * 1000);
audio_time = t;
start_time = getCurrentTime();
然后在视频线程计算出时间差

double nowTime = yuvFrame->pts * av_q2d(avStream->time_base);
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);
pthread_cond_timedwait(&video_cond, &video_mutex,&abstime);
pthread_mutex_unlock(&video_mutex);
这样就实现时间同步,误差是一定有的,几毫秒到十几毫秒吧

贴完整代码

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;
}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);


    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;
    }ANativeWindow* nativeWindow = ANativeWindow_fromSurface(env,surface);

    AVFrame *yuvFrame = av_frame_alloc();

    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);

    int y_size = codec_ctx->width * codec_ctx->height;
    AVPacket *pkt = (AVPacket *) malloc(sizeof(AVPacket));
    av_new_packet(pkt, y_size);
    int ret;
    while (1) {if (av_read_frame(fmt_ctx, pkt) < 0) {av_packet_unref(pkt);
            break;
        }if (pkt->stream_index == video_stream_index) {ret = avcodec_send_packet(codec_ctx, pkt);
            if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {av_packet_unref(pkt);
                continue;
            }ret = avcodec_receive_frame(codec_ctx, yuvFrame);
            if (ret < 0 && ret != AVERROR_EOF) {av_packet_unref(pkt);
                continue;
            }double nowTime = yuvFrame->pts * av_q2d(avStream->time_base);
            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);
            pthread_cond_timedwait(&video_cond, &video_mutex,&abstime);
            pthread_mutex_unlock(&video_mutex);

            openGLUtils->updateTexture(yuvFrame->width,yuvFrame->height,yuvFrame->data[0],yuvFrame->data[1],yuvFrame->data[2]);
            openGLUtils->surfaceDraw();
            eglUtils->drawEGL();

            av_packet_unref(pkt);
        }av_packet_unref(pkt);
    }av_frame_free(&yuvFrame);
    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));
    int ret;
    while (1) {if (av_read_frame(fmt_ctx, pkt) < 0){av_packet_unref(pkt);
            break;
        }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();

        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);
}

2017/11/6更新

发现个bug修改一下

声音解码时在av_read_frame()之后加个判断

if(pkt->stream_index == audio_stream_index)

然后再进行播放处理,因为av_read_frame()得到的数据有视频数据和音频数据,因为这个线程是专门处理声音的,所以把视频数据给过滤掉,就像视频线程会过滤音频数据一样









这篇关于android基于ffmpeg的简单视频播发器 时间同步的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

详谈redis跟数据库的数据同步问题

《详谈redis跟数据库的数据同步问题》文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除R... 目录一、Redis 数据库数据一致性的解决方案1.1、更新Redis缓存、删除Redis缓存的区别二

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

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

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

如何用Java结合经纬度位置计算目标点的日出日落时间详解

《如何用Java结合经纬度位置计算目标点的日出日落时间详解》这篇文章主详细讲解了如何基于目标点的经纬度计算日出日落时间,提供了在线API和Java库两种计算方法,并通过实际案例展示了其应用,需要的朋友... 目录前言一、应用示例1、天安门升旗时间2、湖南省日出日落信息二、Java日出日落计算1、在线API2

如何使用 Bash 脚本中的time命令来统计命令执行时间(中英双语)

《如何使用Bash脚本中的time命令来统计命令执行时间(中英双语)》本文介绍了如何在Bash脚本中使用`time`命令来测量命令执行时间,包括`real`、`user`和`sys`三个时间指标,... 使用 Bash 脚本中的 time 命令来统计命令执行时间在日常的开发和运维过程中,性能监控和优化是不

python中的与时间相关的模块应用场景分析

《python中的与时间相关的模块应用场景分析》本文介绍了Python中与时间相关的几个重要模块:`time`、`datetime`、`calendar`、`timeit`、`pytz`和`dateu... 目录1. time 模块2. datetime 模块3. calendar 模块4. timeit

Java将时间戳转换为Date对象的方法小结

《Java将时间戳转换为Date对象的方法小结》在Java编程中,处理日期和时间是一个常见需求,特别是在处理网络通信或者数据库操作时,本文主要为大家整理了Java中将时间戳转换为Date对象的方法... 目录1. 理解时间戳2. Date 类的构造函数3. 转换示例4. 处理可能的异常5. 考虑时区问题6.

MyBatis框架实现一个简单的数据查询操作

《MyBatis框架实现一个简单的数据查询操作》本文介绍了MyBatis框架下进行数据查询操作的详细步骤,括创建实体类、编写SQL标签、配置Mapper、开启驼峰命名映射以及执行SQL语句等,感兴趣的... 基于在前面几章我们已经学习了对MyBATis进行环境配置,并利用SqlSessionFactory核

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

Nacos集群数据同步方式

《Nacos集群数据同步方式》文章主要介绍了Nacos集群中服务注册信息的同步机制,涉及到负责节点和非负责节点之间的数据同步过程,以及DistroProtocol协议在同步中的应用... 目录引言负责节点(发起同步)DistroProtocolDistroSyncChangeTask获取同步数据getDis