当前位置: 首页 > article >正文

可以拖动屏幕的简单页面播放示例

跟最简单页面播放示例 相比,添加了一个线程用于每隔一段时间发送SDL事件。
错误理解:
窗口崩溃原理
当屏幕拖动时,窗口发生变化了,我们就不能执行刷新屏幕的操作了,因为需要执行和窗口变化相关的操作。但是刷新屏幕这个事件是我们自定义的,SDL不会处理这个事件,那么就可以由我们自己处理这个事件,所以我们接下来只在获取到刷新屏幕事件时进行刷新屏幕,这样就不会报错导致SDL窗口崩溃了。是这样吗?
正确理解:
窗口崩溃原理
原因:在SDL2中处理视频播放时,特别是当你尝试在窗口或界面中集成视频播放功能时,确保界面的响应性和流畅性是非常关键的。使用Sleep(40)(假设是40毫秒以匹配大约25帧每秒的帧率)虽然可以简单地在视频播放过程中实现一定的帧率控制,但它并不是处理GUI(图形用户界面)线程或事件循环的最佳方式。以下是几个原因,解释了为什么应该通过SDL的事件机制来控制视频播放,而不是简单地使用Sleep:

阻塞问题:Sleep函数会阻塞当前线程的执行,这意味着在Sleep期间,线程无法处理任何其他任务,包括处理用户输入、GUI更新或响应其他事件。这会导致应用程序看起来无响应,尤其是在视频播放时尝试移动窗口或进行其他交互时。
事件驱动编程:SDL的设计是基于事件驱动编程的。这意味着你的应用程序应该通过监听和响应事件(如键盘输入、鼠标移动、窗口事件等)来运行。使用SDL的事件系统来处理视频播放的帧率控制更符合SDL的设计理念,因为它允许你的应用程序在适当的时候响应事件,而不是被Sleep函数阻塞。
更平滑的帧率控制:虽然Sleep(40)在理论上可以每40毫秒唤醒一次,但实际的唤醒时间可能会受到操作系统调度器和其他因素的影响。这可能导致视频播放的帧率不稳定。相比之下,通过事件系统来触发帧的更新可以更精确地控制时间间隔,因为你可以更精确地测量两帧之间的实际时间,并据此调整下一帧的绘制时间。
资源利用率:在不需要绘制新帧时,使用Sleep会使CPU处于空闲状态,这可能导致资源利用率低下。通过事件驱动的方式,你可以只在需要时才进行绘制,从而更有效地利用CPU资源。
可维护性和可扩展性:使用事件系统可以使你的代码更加模块化和可维护。你可以更容易地添加新的功能或修改现有功能,而无需担心它们如何与Sleep调用交互。
因此,建议通过SDL的事件机制来控制视频播放的帧率,而不是简单地使用Sleep。你可以设置一个定时器事件(例如,使用SDL_AddTimer或SDL_SetRenderDrawColor等函数与事件循环结合使用),该事件每隔一定时间就触发一次,并通知你的视频播放逻辑绘制下一帧。这样,你的应用程序就可以保持响应性,同时实现平滑的视频播放。

#include <iostream>
#ifdef __cplusplus  ///
extern "C"
{
// 包含ffmpeg头文件
#include "libavutil/avutil.h"
#include"libavformat/avformat.h"
// 包含SDL头文件
#include"SDL.h"
}
#endif

using namespace std;

//Refresh--自定义事件
#define SFM_REFRESH_EVENT  (SDL_USEREVENT + 1)

// 线程停止运行标识,0为正在运行,1为停止
int thread_exit = 0;

// 输出错误信息
void showError(int ret, const char *methodName = "method")
{
    if(ret == 0) {
        return ;
    }
    // 错误消息日志
    char err2str[256];
    // 将返回结果转化为字符串信息
    av_strerror(ret, err2str, sizeof(err2str));
    printf("%s failed, ret:%d, msg:%s\n", methodName, ret, err2str);
}

// 刷新页面显示的线程回调函数
int refresh_thread(void *opaque)
{
    // 定义一个SDL事件
    SDL_Event event;
    // 线程执行中
    while(thread_exit == 0)
    {
        // 设置该事件的类型
        event.type = SFM_REFRESH_EVENT;
        // 向SDL发送事件
        SDL_PushEvent(&event);
        // 等待23ms【这样每隔23ms就会发送一次SDL事件,类型是刷新屏幕,这样就算拖动屏幕了【拖动屏幕这个事件导致刷新屏幕不运行了】
        //          ,在23ms后又会收到SDL事件通知要刷新屏幕,在主线程中就又会刷新屏幕了】
        SDL_Delay(13);
    }
    return 0;
}

#undef main
int main(int argc, char *argv[])
{
    if(argc < 2)
    {
        cout << "请输入视频地址" << endl;
        return -1;
    }
    // 获取视频地址
    char *url = argv[1];
    cout << url << endl;
    // 方法调用结果
    int ret = 0;

    // FFmpeg
    // AVFormatContext 是音视频开发使用到最多的结构体,无论什么函数基本上都会用到它
    // AVFormatContext 只能通过 avformat_alloc_context() 创建空的对象
    AVFormatContext *input_fmt_ctx = avformat_alloc_context();
    // 加载视频内容到音视频格式上下文中
    ret = avformat_open_input(&input_fmt_ctx, url, NULL, NULL);
    // 输出日志
    showError(ret);
    // 查看流信息,可以不写,只是单纯拿返回值来做校验的
    ret = avformat_find_stream_info(input_fmt_ctx, NULL);
    // 输出日志
    showError(ret);
    // 输出视频信息,可以不写
    av_dump_format(input_fmt_ctx, 0, url, 0);
    // 查找指定流的idx,如果使用不到,可以不写; AVMEDIA_TYPE_VIDEO 代表视频流,AVMEDIA_TYPE_AUDIO代表音频流
    int video_idx = av_find_best_stream(input_fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    int audio_idx = av_find_best_stream(input_fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    printf("video_idx: %d , audio_idx: %d\n", video_idx, audio_idx);
    // AVCodecContext 是解码器上下文,需要对帧处理基本上都会用到它
    // AVCodecContext 只能通过 avcodec_alloc_context3(NULL) 创建空的对象
    AVCodecContext *codec_ctx = avcodec_alloc_context3(NULL);
    // 将音视频格式上下文中的参数加载到解码器上下文对象中
    ret = avcodec_parameters_to_context(codec_ctx, input_fmt_ctx->streams[video_idx]->codecpar);
    // 输出日志
    showError(ret);
    // 指定物理解码器;这里参数传的是codec_ctx->codec_id,实际上物理解码器有很多中,这里可以传不同的内容
    AVCodec *codec = avcodec_find_decoder(codec_ctx->codec_id);
    // 将物理解码器加载到解码器上下文中
    ret = avcodec_open2(codec_ctx, codec, NULL);
    // 输出日志
    showError(ret);
    // 包,用来获取音视频格式上下文中的数据
    // AVPacket 只能通过 av_packet_alloc() 创建对象
    AVPacket *pkt = av_packet_alloc();
    // 帧,用来获取包解码后的数据
    // AVFrame 只能通过 av_frame_alloc() 创建对象
    AVFrame *frame = av_frame_alloc();

    // SDL
    // 初始化视频
    if(SDL_Init(SDL_INIT_VIDEO)) {
        return -1;
    }
    // 视频宽度
    int video_width_ = input_fmt_ctx->streams[video_idx]->codecpar->width;
    // 视频高度
    int video_height_ = input_fmt_ctx->streams[video_idx]->codecpar->height;
    // 创建窗口--显示器
    // 在这里设置显示出来的窗口的总大小
    SDL_Window *win_ = SDL_CreateWindow("苏花末测试窗口", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
                                    video_width_, video_height_, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    if(!win_) {
        return -1;
    }
    // 渲染器,用于将纹理渲染到窗口上
    SDL_Renderer *renderer_ = SDL_CreateRenderer(win_, -1, 0);
    if(!renderer_) {
        return -1;
    }
    // 纹理,用于设置渲染图片数据
    SDL_Texture *texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_IYUV,  SDL_TEXTUREACCESS_STREAMING, video_width_, video_height_);
    if(!texture_) {
        return -1;
    }
    // Rect--页面显示区域
    SDL_Rect rect_;
    // 创建一个线程用于SDL接受事件
    SDL_CreateThread(refresh_thread, NULL, NULL);
    // SDL事件
    SDL_Event event;
    // output and readFrame
    while(1)
    {
        // 等待SDL获取到事件【阻塞等待】
        SDL_WaitEvent(&event);
        // 只有事件类型是刷新时才继续执行
        if(event.type != SFM_REFRESH_EVENT)
        {
            continue;
        }
        // FFmpeg: readFrame

        // 获取该音视频格式上下文中的第一个包,并将从音视频格式上下文中移除
        // 则代表了每次调用都会获取到新的包,之前的包不会再在该音视频格式上下文中找到了
        ret = av_read_frame(input_fmt_ctx, pkt);
        // 输出日志
        showError(ret);
        // 如果包数据读取完毕,则代表视频播放结束了
        if(ret < 0)
        {
            cout << "play video finish" << endl;
            break;
        }
        // 本次演示只展示视频播放,故跳过音频帧
        if(pkt->stream_index == audio_idx)
        {
            continue;
        }
        // 将包加载到解码器上下文中进行解码
        ret = avcodec_send_packet(codec_ctx, pkt);
        // 输出日志
        showError(ret);
        // 读取解码后的包中的帧
        ret = avcodec_receive_frame(codec_ctx, frame);
        // 如果 AVERROR(EAGAIN) == ret,则代表这个包无法解析,需要再次解析下一个包
        if(AVERROR(EAGAIN) == ret)
        {
            continue;
        }
        // 输出日志
        showError(ret);
        // SDL: output
        // 清空之前的页面
        SDL_RenderClear(renderer_);
        // 设置rect所占区域
        rect_.x = 0;
        rect_.y = 0;
        // 在这里设置rect区域的大小,如果这里和窗口总大小不一样,那么其他地方是黑屏显示
        // 故这里也体现了一个win可以设置多个rect,每个rect可以占据不同的位置
        rect_.w = video_width_;
        rect_.h = video_height_;
        // 通过YUV格式渲染图片
        SDL_UpdateYUVTexture(texture_, &rect_,
                frame->data[0], frame->linesize[0],
                frame->data[1], frame->linesize[1],
                frame->data[2], frame->linesize[2]);
        // 页面内容设置
        SDL_RenderCopy(renderer_, texture_, NULL, &rect_);
        // 显示新的页面
        SDL_RenderPresent(renderer_);
    }

    system("pause");
    return 0;
}


http://www.kler.cn/news/367494.html

相关文章:

  • 【LeetCode:263. 丑数 + 数学】
  • Next.js + Prisma + Auth.js 实现完整的认证方案
  • 10.22.2024刷华为OD C题型(三)--for循环例子
  • Vscode + EIDE +CortexDebug 调试Stm32(记录)
  • 小白直接冲!一区蛇群优化算法+双向深度学习+注意力机制!SO-BiTCN-BiGRU-Attention多输入单输出回归预测
  • 2024数学分析【南昌大学】
  • 深入探讨TCP/IP协议基础
  • 【C++】—— 模板进阶
  • 数字加% 循环后两个都变了只能深拷贝
  • 《计算机原理与系统结构》学习系列——处理器(中)
  • Linux:socket实现两个进程之间的通信
  • #单体到微服务架构服务演化过程
  • Mermaid流程图完全指南
  • 2024年10月25日练习(双指针算法)
  • Redis 主从同步 问题
  • python一键运行所有bat脚本
  • 机器学习(10.14-10.20)(Pytorch GRU的原理及其手写复现)
  • P1588 [USACO07OPEN] Catch That Cow S
  • Unity C#脚本的热更新
  • 单细胞 | 转录因子足迹分析
  • Docker容器间通信
  • 深入了解 MySQL 中的 INSERT ... SELECT 语句
  • iOS弹出系统相册选择弹窗
  • VS/Qt Creator +QT生成带.ico图标的.exe 并打包
  • qt QLabel详解
  • 智能合约在Web3中的作用:区块链技术的创新实践