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

SDL打开YUV视频

文章目录

      • 问题1:如何控制帧率?
      • 问题2:如何触发退出事件?
      • 问题3:如何实时调整视频窗口的大小
      • 问题4:YUV如何一次读取一帧的数据?

问题1:如何控制帧率?

在这里插入图片描述
单独用一个子线程给主线程发送刷新画面的事件 主线程负责刷新画面 每次发送刷新画面的命令后就延迟一段事件 具体的帧率公式为1000 / 延迟事件 = 帧率

问题2:如何触发退出事件?

SDL_QUIT 事件通常由用户触发,当关闭窗口时,SDL 会生成此事件。这可以通过用户点击窗口的关闭按钮(如窗口右上角的 “X”)或通过其他机制关闭窗口(例如按 Alt+F4)来触发。在事件循环中检测到 SDL_QUIT 后,通常会执行相应的退出操作,如设置退出标志(s_thread_exit = 1),从而安全地终止程序或线程。
在这里插入图片描述

问题3:如何实时调整视频窗口的大小

在这里插入图片描述
接受窗口变化的消息 实时更新窗口的尺寸 根据窗口和视频部分的比例
让实时渲染的时候 视频部分乘上这部分比例 就能到达 视频和窗口同步大小

问题4:YUV如何一次读取一帧的数据?

在 YUV420 格式中,一帧的数据大小可以通过以下方式计算:

Y 分量占据整个画面的大小:video_width * video_height
U 和 V 分量各占 Y 分量的四分之一:(video_width * video_height) / 4
因此,一帧的总大小为:
y_frame_len + u_frame_len + v_frame_len = video_width * video_height + 2 * (video_width * video_height / 4)

简化为:
yuv_frame_len = video_width * video_height * 1.5

这样你就可以通过 yuv_frame_len 获取一帧的总大小。

#include <stdio.h>
#include <string.h>

#include <SDL.h>

// 自定义消息类型
#define REFRESH_EVENT (SDL_USEREVENT + 1) // 请求画面刷新事件
#define QUIT_EVENT (SDL_USEREVENT + 2) // 退出事件

// 定义分辨率
//  YUV像素分辨率
#define YUV_WIDTH 320
#define YUV_HEIGHT 240
// 定义YUV格式
#define YUV_FORMAT SDL_PIXELFORMAT_IYUV

int s_thread_exit = 0; // 退出标志 = 1则退出

// 通过线程去控制1s刷多少帧
int refresh_video_timer(void *data) {
    while (!s_thread_exit) {
        SDL_Event event;
        // 事件类型为刷新事件
        event.type = REFRESH_EVENT;
        // 将事件推送到事件队列中
        SDL_PushEvent(&event);
        SDL_Delay(40); // 1000 / 40 = 25 帧
    }
    // 停止控制视频输出帧数的时候 将这里的状态重置
    s_thread_exit = 0;

    // push quit event
    SDL_Event event;
    event.type = QUIT_EVENT;
    SDL_PushEvent(&event);

    return 0;
}
#undef main
int main(int argc, char *argv[]) {
    // 初始化 SDL
    if (SDL_Init(SDL_INIT_VIDEO)) {
        fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
        return -1;
    }

    // SDL
    SDL_Event event; // 事件
    SDL_Rect rect; // 矩形
    SDL_Window *window = NULL; // 窗口
    SDL_Renderer *renderer = NULL; // 渲染
    SDL_Texture *texture = NULL; // 纹理
    SDL_Thread *timer_thread = NULL; // 请求刷新线程
    uint32_t pixformat = YUV_FORMAT; // YUV420P,即是SDL_PIXELFORMAT_IYUV

    // 分辨率
    // 1. YUV的分辨率
    int video_width = YUV_WIDTH;
    int video_height = YUV_HEIGHT;
    // 2.显示窗口的分辨率
    int win_width = YUV_WIDTH;
    int win_height = YUV_WIDTH;

    // YUV文件句柄
    FILE *video_fd = NULL;
    const char *yuv_path = "yuv420p_320x240.yuv";

    size_t video_buff_len = 0;

    uint8_t *video_buf = NULL; // 读取数据后先把放到buffer里面

    // 我们测试的文件是YUV420P格式
    uint32_t y_frame_len = video_width * video_height;
    uint32_t u_frame_len = video_width * video_height / 4;
    uint32_t v_frame_len = video_width * video_height / 4;
    uint32_t yuv_frame_len = y_frame_len + u_frame_len + v_frame_len;

    // 创建窗口
    window = SDL_CreateWindow("Simplest YUV Player",
                              SDL_WINDOWPOS_UNDEFINED,
                              SDL_WINDOWPOS_UNDEFINED,
                              video_width, video_height,
                              SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    if (!window) {
        fprintf(stderr, "SDL: could not create window, err:%s\n", SDL_GetError());
        goto _FAIL;
    }
    // 基于窗口创建渲染器
    renderer = SDL_CreateRenderer(window, -1, 0);
    // 基于渲染器创建纹理
    texture = SDL_CreateTexture(renderer,
                                pixformat, // 指定yuv格式
                                SDL_TEXTUREACCESS_STREAMING,
                                video_width,
                                video_height);

    // 分配空间
    video_buf = ( uint8_t * )malloc(yuv_frame_len);
    if (!video_buf) {
        fprintf(stderr, "Failed to alloce yuv frame space!\n");
        goto _FAIL;
    }

    // 打开YUV文件
    video_fd = fopen(yuv_path, "rb");
    if (!video_fd) {
        fprintf(stderr, "Failed to open yuv file\n");
        goto _FAIL;
    }
    // 创建请求刷新线程
    timer_thread = SDL_CreateThread(refresh_video_timer,
                                    NULL,
                                    NULL);

    while (1) {
        // 收取SDL系统里面的事件
        SDL_WaitEvent(&event);

        if (event.type == REFRESH_EVENT) // 画面刷新事件
        {
            video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd);
            if (video_buff_len <= 0) {
                fprintf(stderr, "Failed to read data from yuv file!\n");
                goto _FAIL;
            }
            // 设置纹理的数据 video_width = 320, plane
            SDL_UpdateTexture(texture, NULL, video_buf, video_width);

            // 显示区域,可以通过修改w和h进行缩放
            rect.x = 0;
            rect.y = 0;
            float w_ratio = win_width * 1.0 / video_width;
            float h_ratio = win_height * 1.0 / video_height;
            // 320x240 怎么保持原视频的宽高比例
            rect.w = video_width * w_ratio;
            rect.h = video_height * h_ratio;
            //            rect.w = video_width * 0.5;
            //            rect.h = video_height * 0.5;

            // 清除当前显示
            SDL_RenderClear(renderer);
            // 将纹理的数据拷贝给渲染器 会把比例还原回来设置一个视频
            SDL_RenderCopy(renderer, texture, NULL, &rect);
            // 显示
            SDL_RenderPresent(renderer);
        } else if (event.type == SDL_WINDOWEVENT) {
            // If Resize 更新窗口的尺寸
            SDL_GetWindowSize(window, &win_width, &win_height);
            printf("SDL_WINDOWEVENT win_width:%d, win_height:%d\n", win_width,
                   win_height);
        } else if (event.type == SDL_QUIT) // 退出事件
        {
            s_thread_exit = 1;
        } else if (event.type == QUIT_EVENT) {
            break;
        }
    }

_FAIL:
    s_thread_exit = 1; // 保证线程能够退出
    // 释放资源
    if (timer_thread)
        SDL_WaitThread(timer_thread, NULL); // 等待线程退出
    if (video_buf)
        free(video_buf);
    if (video_fd)
        fclose(video_fd);
    if (texture)
        SDL_DestroyTexture(texture);
    if (renderer)
        SDL_DestroyRenderer(renderer);
    if (window)
        SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;
}


http://www.kler.cn/a/387466.html

相关文章:

  • ORA-01092 ORA-14695 ORA-38301
  • 【机器学习】机器学习中用到的高等数学知识-3.微积分 (Calculus)
  • 十九:Spring Boot 依赖(4)-- spring-boot-starter-security依赖详解
  • 【GPTs】MJ Prompt Creator:轻松生成创意Midjourney提示词
  • Iceberg 写入和更新模式,COW,MOR(Copy-on-Write,Merge-on-Read)
  • 行业类别-智能制造-子类别工业4.0-细分类别物联网应用-应用场景智能工厂建设
  • AI和大模型技术在网络脆弱性扫描领域的最新进展与未来发展趋势
  • [C++ 核心编程]笔记 4.4.3 成员函数做友元
  • <<零基础C++第一期, C++入门基础>>
  • 打造完整 Transformer 编码器:逐步实现高效深度学习模块
  • 深度学习在大数据处理中的应用
  • 电子电气架构 --- 车载以太网架构安全性要求
  • Qt使用属性树(QtProPertyBrowser)时,引用报错#include “QtTreePropertyBrowser“解决方案
  • HDR视频技术之二:光电转换与 HDR 图像显示
  • python批量合并excel文件
  • 经典的ORACLE 11/12/19闪回操作
  • 前端vue3若依框架pnpm run dev启动报错
  • AI时代来临,什么是真正的大模型?【大模型扫盲系列】
  • 行转列实现方式总结
  • vue,uniapp,微信小程序解决字符串中出现数字则修改数字样式,以及获取字符串中的数字
  • SpringBoot API版本控制策略详解
  • 【前端】Svelte:动画效果
  • 华为机试HJ33 整数与IP地址间的转换
  • 【复旦微FM33 MCU 开发指南】ADC
  • 微服务中常用分布式锁原理及执行流程
  • delphi 编译多语言工程 error RC2104 : undefined keyword or key name: