目前,在ai帮助的前提下,已经实现了一个播放器的基础功能,对相关实现过程中的细节进行梳理。

1.简述

通过项目实践,在ai的帮助下,把自己已有的一些理论知识用项目的形式进行展现。

实现一个支持播放本地文件的视频播放器功能,支持音视频同步(这是最核心基础),播放/暂停,音量控制,进度条显示及控制,倍速控制,osd信息显示等基本功能。

本次主要使用sdl3(SDL3_ttf)+ffmpeg6进行开发。

在这里插入图片描述

**支持的功能:**视频能正常播放的前提下,有播放/暂停控制,音量控制,倍速控制,seek控制,osd字母显示,键盘事件/鼠标控制等逻辑。

有遗留暂时未修复的bug:倍速时sonic偶现崩溃(在调用sonic库时,原始frame和sonic库进行计算时的问题),seek的稳定运行还得参考ffplay从数据结构上进行优化处理(seek后要刷新packet,重置clock,并清空frame)。

2.反思

基于ai,在自己的引导下一步步完成视频播放基本功能,并没有遇到过阻塞无法解决的问题。

在实践的过程中发现,需要自己在宏观上把控项目实现的架构,以及所有的问题都是细节交互的问题,尤其这种带有ui界面的,需要做时间戳音视频同步的,都是在多个线程之间细节的把控。

在音视频已经很成熟的当下,所有的接口都已经拿来即可用,最核心的功能是进行音视频的同步处理,需要注意数据结构的设计以及同步策略选择,后面的暂停,倍速,seek等都是建立在该核心功能的基础上的。

===》初步实现后进行参考和反思,发现遇到的问题,应该在数据结构设计层次进行处理,问题会变得简单。

3.架构整理

在ai的帮助下完成第一遍的初步功能实现,就整个实现过程架构和细节进行回顾整理。

3.1 首先梳理以ai为导向,自己的实现过程

这里sdl和ffmpeg的接口已经足够成熟,相关解码及播放的逻辑都无阻碍。

第一次探索,自己没有过多设计数据结构和架构,所遇到的问题都是细节性控制小问题。

核心复杂的问题就是 音频时钟的获取和视频的同步处理,以及seek的操作。

在这里插入图片描述

3.2 环境配置最是麻烦,贴一下配置,方便后期回顾。

基于vs2022环境实现,依赖的lib如下:

在这里插入图片描述

依赖的对应头文件如下:

在这里插入图片描述

这里暂时没有新增播放列表,以及选择文件入口,所以在项目–》配置—》调试—》命令参数中设置死对应的执行默认参数。

3.3 功能实现后反思

在初步实现各种功能基本正常的功能后,发现内部逻辑也没有过多复杂,实际上就是多个线程,配合多个消息队列进行交互控制的逻辑。

自己遇到的同步,seek时的一些问题,核心还是数据结构设计层次提升了问题处理的复杂度,可以在frame队列设计上,以及时钟clock设计时从数据结构层次让问题更简单。

在这里插入图片描述

考虑整体逻辑:

在这里插入图片描述

3.4 关注ffplay中视频同步,seek处理逻辑

参考ffplay中这段代码实际上是视频同步和seek的关键代码: seek和音视频同步的逻辑,直接在数据结构设计上让问题简单。

主要理解的就是处理seek的架构,在队列中维持了一个serial标志,然后每个视频帧中也携带serial,进行判断是丢弃还是显示。

void FFPlayer::video_refresh(double * remaining_time)
{
    Frame *vp = nullptr, *lastvp = nullptr;
    // 目前我们先是只有队列里面有视频帧可以播放,就先播放出来
    // 判断有没有视频画面
    if (video_st) {
retry:
        //队列中没有是哦就判断停止喽
        if (frame_queue_nb_remaining(&pictq) == 0) {
            // nothing to do, no picture to display in the queue
            video_no_data = 1;  // 没有数据可读
            if(eof == 1) {
                check_play_finish();
            }
        } else {
            video_no_data = 0;  // 有数据可读
            double last_duration, duration, delay;
            /* dequeue the picture */
            lastvp = frame_queue_peek_last(&pictq);//上一帧
            screenshot(lastvp->frame); //判断做截屏的处理
            vp = frame_queue_peek(&pictq); //当前帧

            //处理 seek 或丢弃旧帧
            if (vp->serial != videoq.serial) {
                frame_queue_next(&pictq);
                goto retry;
            }
            //如果是 seek 后第一帧,重置 frame_timer
            //为了避免 seek 后第一帧 diff 巨大导致跳帧。
            if (lastvp->serial != vp->serial) {
                frame_timer = av_gettime_relative() / 1000000.0;
            }
            //暂停状态下直接显示  不更新 frame_timer,不丢帧,只显示当前帧
            if (paused) {
                goto display;
            }
            //计算上一帧到当前帧的理论间隔
            /* compute nominal last_duration */
            last_duration = vp_duration(lastvp, vp); //当前帧 pts - 上一帧 pts
            //             LOG(INFO) << "last_duration ......" << last_duration;
            delay = compute_target_delay(last_duration); //根据音频时钟 diff 调整 delay
            //             LOG(INFO) << "delay ......" << delay;
            double time = av_gettime_relative() / 1000000.0;
            //判断是否需要等待  如果“还没到显示时间” → 等待  否则 → 显示下一帧
            if (time <  frame_timer + delay) {
                //                  LOG(INFO) << "(frame_timer + delay) - time " << frame_timer + delay - time;
                *remaining_time = FFMIN( frame_timer + delay - time, *remaining_time);
                goto display;
            }

            //更新 frame_timer  如果系统时间跳变太大(比如卡顿) → 重置 frame_timer
            frame_timer += delay;
            if (delay > 0 && time -  frame_timer > AV_SYNC_THRESHOLD_MAX) {
                frame_timer = time;
            }

            //更新视频时钟 用于音视频同步
            SDL_LockMutex(pictq.mutex);
            if (!std::isnan(vp->pts)) {
                update_video_pts(vp->pts, vp->pos, vp->serial);
            }
            SDL_UnlockMutex(pictq.mutex);
            //            LOG(INFO) << "debug " << __LINE__;
            //判断是否需要丢帧(视频落后音频)
            //视频落后音频太多且 framedrop 开启 → 丢掉当前帧,显示下一帧
            if (frame_queue_nb_remaining(&pictq) > 1) {
                Frame *nextvp = frame_queue_peek_next(&pictq);
                duration = vp_duration(vp, nextvp);
                if (!step && (framedrop > 0 || (framedrop && get_master_sync_type() != AV_SYNC_VIDEO_MASTER))
                    && time >  frame_timer + duration) {
                    frame_drops_late++;
                    //                    LOG(INFO) << "frame_drops_late  " << frame_drops_late;
                    frame_queue_next(&pictq);
                    goto retry;
                }
            }
            //正常显示当前帧
            frame_queue_next(&pictq);
            force_refresh = 1;
            //            LOG(INFO) << "debug " << __LINE__;
            //            if (step && !paused)
            //                stream_toggle_pause(is);
        }
display:
        /* display picture */
        if (force_refresh &&  pictq.rindex_shown) {
            if(vp) {
                if(video_refresh_callback_) {
                    video_refresh_callback_(vp);
                }
            }
        }
    }
    force_refresh = 0;
}

音频的核心处理逻辑是通过回调函数实现的,主要就是维持一个音频缓冲区,实现向sdl缓冲区拷贝动作 (音频缓冲区 audio_buf + index + size)控制缓冲区数据安全可靠得向sdl缓冲区中拷贝。

除此之外,涉及音频帧得重采样,多通道处理,倍速处理。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐