【音视频】SDL渲染YUV格式像素

SDL视频显示的流程

在这里插入图片描述

实现流程

准备视频文件

准备一个格式为yuv420p,分辨率为320x240yuv数据,并且将视频文件放入项目构建的目录下:

在这里插入图片描述

初始化SDL

初始化SDL的视频模块

//初始化 SDL
if(SDL_Init(SDL_INIT_VIDEO))
{fprintf( stderr, "Could not initialize SDL - %s\n", SDL_GetError());return -1;
}

创建一个窗口

  • 创建一个SDL窗口,用于纹理渲染
  • 初始时窗口大小为YUV视频分辨率大小
#define YUV_WIDTH   320
#define YUV_HEIGHT  240SDL_Window *window = NULL;
int win_width = YUV_WIDTH;
int win_height = YUV_WIDTH;
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;
}

设置YUV缓存

设置每次读取视频帧的缓存大小,这里每次就读取1帧即可,计算方式为:Y分量+U分量+V分量大小

  • YUV格式为yuv420p
  • Y分量大小为:Width * Height
  • U分量大小为:Width * Height /4
  • V分量大小为:Width * Height / 4
yuv420p格式示例图

在这里插入图片描述

代码如下:

uint8_t *video_buf = NULL; 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;video_buf = (uint8_t*)malloc(yuv_frame_len);
if(!video_buf){fprintf(stderr, "Failed to alloce yuv frame space!\n");goto _FAIL;}

创建渲染器

创建渲染器,用于视频纹理的渲染

SDL_Renderer *renderer = NULL;  
renderer = SDL_CreateRenderer(window, -1, 0);

创建纹理

根据yuv420p数据格式

其中:

  • YUV_FORMATYUV420P
  • SDL_TEXTUREACCESS_STREAMING 指定纹理的访问方式,STREAMING模式允许高效更新纹理数据,用于视频渲染
SDL_Texture *texture = NULL; 
uint32_t pixformat = YUV_FORMAT; // YUV420P
texture = SDL_CreateTexture(renderer,pixformat,SDL_TEXTUREACCESS_STREAMING,video_width,video_height);

打开yuv文件

使用文件操作,二进制读的方式打开视频文件

// 打开YUV文件
video_fd = fopen(yuv_path, "rb");
if( !video_fd )
{fprintf(stderr, "Failed to open yuv file\n");goto _FAIL;
}

创建定时器

  • 创建一个定时器,用于定时刷新视频帧,控制fps
  • 实际上就是创建一个新的线程,定期唤醒加入自定义的刷新事件

创建定时器

// 创建请求刷新线程
timer_thread = SDL_CreateThread(refresh_video_timer,NULL,NULL);

定时器线程函数

每次延时40ms左右,大概25fps

#define REFRESH_EVENT   (SDL_USEREVENT + 1)     // 请求画面刷新事件
#define QUIT_EVENT      (SDL_USEREVENT + 2)     // 退出事件int s_thread_exit = 0;  // 退出标志 = 1则退出
int refresh_video_timer()
{while (!s_thread_exit){SDL_Event event;event.type = REFRESH_EVENT;SDL_PushEvent(&event);SDL_Delay(40);}s_thread_exit = 0;//push quit eventSDL_Event event;event.type = QUIT_EVENT;SDL_PushEvent(&event);return 0;
}

开启事件循环

开启事件循环,进行视频渲染操作

等待事件

  • 等待事件加入队列中,并阻塞在此
SDL_WaitEvent(&event);

画面刷新事件

  • 定时器周期地加入刷新事件
  • 读取yuv文件,并加入到纹理中
  • 设置渲染的矩形窗口为当前窗口的大小以及开始的位置
  • 将纹理的数据拷贝到CPU
  • 渲染CPU端的数据
 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, planeSDL_UpdateTexture(texture, NULL, video_buf, video_width);// 显示区域,可以通过修改w和h进行缩放rect.x = 0;rect.y = 0;rect.w = win_width;rect.h = win_height;// 清除当前显示SDL_RenderClear(renderer);// 将纹理的数据拷贝给渲染器SDL_RenderCopy(renderer, texture, NULL, &rect);// 显示SDL_RenderPresent(renderer);}

窗口事件

  • 接收窗口事件(如窗口移动,窗口大小改变等)
  • 暂时不做操作

退出事件

  • 包括自定义的退出事件QUIT_EVENT
  • 以及QUIT_EVENT,比如(关闭窗口,调用SDL_QUIT等)
  • 调用退出事件后,会通知定时器停止操作,并且退出循环,进行内存清理
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, planeSDL_UpdateTexture(texture, NULL, video_buf, video_width);// 显示区域,可以通过修改w和h进行缩放rect.x = 0;rect.y = 0;rect.w = win_width;rect.h = win_height;// 清除当前显示SDL_RenderClear(renderer);// 将纹理的数据拷贝给渲染器SDL_RenderCopy(renderer, texture, NULL, &rect);// 显示SDL_RenderPresent(renderer);}else if(event.type == SDL_WINDOWEVENT){//If ResizeSDL_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;}}

关闭操作和内存清理

渲染结束或手动关闭窗口后,需要关闭文件和SDL子系统,并且释放相关内存

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

实现的效果

实现的效果如下:

在这里插入图片描述

整体代码

main.c

#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_IYUVint s_thread_exit = 0;  // 退出标志 = 1则退出int refresh_video_timer()
{while (!s_thread_exit){SDL_Event event;event.type = REFRESH_EVENT;SDL_PushEvent(&event);SDL_Delay(40);}s_thread_exit = 0;//push quit eventSDL_Event event;event.type = QUIT_EVENT;SDL_PushEvent(&event);printf("finish Timer\n");return 0;
}
#undef main
int main(int argc, char* argv[])
{//初始化 SDLif(SDL_Init(SDL_INIT_VIDEO)){fprintf( stderr, "Could not initialize SDL - %s\n", SDL_GetError());return -1;}// SDLSDL_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,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, planeSDL_UpdateTexture(texture, NULL, video_buf, video_width);rect.w = win_width;rect.h = win_height;// 显示区域,可以通过修改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 ResizeSDL_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;}

更多资料

更多资料参考:https://github.com/0voice

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/901108.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于群晖安装tailscale后无法直链的问题

问题是我局域网的ipv6无法正确获取到ip, 通过命令可以看到ipv6没有ip tailscale netcheck C:\Users\Administrator>tailscale netcheck 2025/04/12 23:43:34 attempting to fetch a DERPMap from https://controlplane.tailscale.comReport:* Time: 2025-04-12T15:43:38.27…

[数据结构]Trie字典树

GPT的介绍 &#x1f9e0; 一句话总结&#xff1a; 字典树是一种专门用来存很多字符串的“超级前缀树”&#xff0c;查找某个字符串或前缀的时候&#xff0c;特别快&#xff01; ✍️ 举个生活例子&#xff08;类比&#xff09;&#xff1a; 你想做一个词典&#xff08;Dictio…

04-算法打卡-数组-二分查找-leetcode(69)-第四天

1 题目地址 69. x 的平方根 - 力扣&#xff08;LeetCode&#xff09;69. x 的平方根 - 给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。注意&#xff1a;不允许使用任何内…

AI领域再突破,永洪科技荣获“2025人工智能+创新案例”奖

在2025年的今天&#xff0c;人工智能已从技术概念全面渗透至产业核心。中国作为全球AI技术应用的前沿阵地&#xff0c;正通过“人工智能”行动加速推进技术与实体经济深度融合。 这一背景下&#xff0c;永洪科技凭借其“国内某头部ICT人力资源板块GenAI项目”荣获“2025全国企业…

反序列化漏洞介绍与挖掘指南

目录 反序列化漏洞介绍与挖掘指南 一、漏洞核心原理与危害 二、漏洞成因与常见场景 1. 漏洞根源 2. 高危场景 三、漏洞挖掘方法论 1. 静态分析 2. 动态测试 3. 利用链构造 四、防御与修复策略 1. 代码层防护 2. 架构优化 3. 运维实践 五、工具与资源推荐 总结 反…

从零开始的C++编程 2(类和对象下)

目录 1.构造函数初始化列表 2.类型转换 3.static成员 4.友元 5.内部类 6.匿名对象 1.构造函数初始化列表 ①之前我们实现构造函数时&#xff0c;初始化成员变量主要使⽤函数体内赋值&#xff0c;构造函数初始化还有⼀种⽅式&#xff0c;就是初始化列表&#xff0c;初始化…

Profibus DP主站转ModbusTCP网关通讯秘籍

Profibus DP主站转ModbusTCP网关通讯秘籍 在现代工业自动化领域&#xff0c;不同设备间的数据通讯和系统集成至关重要。Profibus DP和Modbus TCP是两种广泛应用的工业通信协议&#xff0c;各有其独特的优势和适用场景。然而&#xff0c;由于历史原因或设备制造商的差异&#x…

【力扣hot100题】(092)最长回文串

有点难度&#xff0c;一开始想到的两种方法都不对&#xff0c;花了不少时间。 先说之前的方法&#xff1a; ① 遍历每个点&#xff0c;每个点向外扩张&#xff0c;如果左等于右就一直扩展直到不等。 这个方法可是可以&#xff0c;但我没有考虑到两个相同字母也是回文串的情况…

14 - VDMA彩条显示实验

文章目录 1 实验任务2 系统框图3 硬件设计4 软件设计 1 实验任务 本实验任务是PS端写彩条数据至DDR3内存中&#xff0c;然后通过PL端的VDMA IP核将彩条数据通过HDMI接口输出显示。 2 系统框图 本实验是用HDMI接口固定输出1080P的彩条图&#xff0c;所以&#xff1a; rgb2lc…

HarmonyOS-ArkUIV2装饰器-@Param:组件外部输入

上文我们了解了@Local装饰器 ,讲明了Local装饰器不允许外部传入值对其进行初始化。详见: HarmonyOS-ArkUI V2装饰器@Local装饰器:组件内部状态-CSDN博客。 但总有场景是需要外部组件传值过来,然后本组件接收这个值这种场景的。而且很多情况下,一个状态变量的作用范围会是…

Java从入门到“放弃”(精通)之旅——运算符③

&#x1f31f;Java从入门到“放弃”&#xff08;精通&#xff09;之旅&#x1f680;&#xff1a;运算符深度解析 引言&#xff1a;运算符的本质与价值 作为Java语言的核心组成部分&#xff0c;运算符是构建程序逻辑的基础元素。它们不仅仅是简单的数学符号&#xff0c;更是程…

【sgSpliter】自定义组件:可调整宽度、高度、折叠的分割线

sgSpliter.vue <template><!-- 注意&#xff1a;父组件position必须是relative、absolute或fixed&#xff0c;不建议直接在绑定:data后面用"{属性}"&#xff0c;建议单独在script中声明data&#xff0c;避免拖拽过程重复调用 --><div :class"$…

Ningx负载均衡

Ningx负载均衡 upstream(上游)配置负载均衡1、weight&#xff08;加权轮询&#xff09;2、ip_hash&#xff08;负载均衡&#xff09;3、url hash负载均衡4、least_conn&#xff08;最小连接负载均衡&#xff09; upstream(上游)配置负载均衡 Nginx负载均衡 参考: nginx从安装…

一个插件,免费使用所有顶级大模型(Deepseek,Gpt,Grok,Gemini)

DeepSider是一款集成于浏览器侧边栏的AI对话工具&#xff0c;可免费使用所有顶级大模型 包括GPT-4o&#xff0c;Grok3,Claude 3.5 Sonnet,Claude 3.7,Gemini 2.0&#xff0c;Deepseek R1满血版等 以极简交互与超快的响应速度&#xff0c;完成AI搜索、实时问答、内容创作、翻译、…

众趣科技丨数字孪生技术,赋能交通公共设施管理数字化升级

春节假期期间&#xff08;1 月 21 日至 2 月 4 日&#xff09;&#xff0c;作为中国春节申遗成功后的首个春运&#xff0c;交通出行格外火热&#xff0c;全社会跨区域流动量超 23 亿人次&#xff0c;这一数据创下了历史新高。 面对如此庞大的客流量&#xff0c;传统的交通管理方…

Linux 入门五:Makefile—— 从手动编译到工程自动化的蜕变

一、概述&#xff1a;Makefile—— 工程编译的 “智能指挥官” 1. 为什么需要 Makefile&#xff1f; 手动编译的痛点&#xff1a;当工程包含数十个源文件时&#xff0c;每次修改都需重复输入冗长的编译命令&#xff08;如gcc file1.c file2.c -o app&#xff09;&#xff0c;…

Python-Django+vue二手电子设备交易平台功能说明

❥(^_-) 上千个精美定制模板,各类成品Java、Python、PHP、Android毕设项目,欢迎咨询。 ❥(^_-) 程序开发、技术解答、代码讲解、文档,💖文末获取源码+数据库+文档💖 💖软件下载 | 实战案例 💖文章底部二维码,可以联系获取软件下载链接,及项目演示视频。 本项目…

数据库管理工具实战:IDEA 与 DBeaver 连接 TDengine(二)

五、DBeaver 连接 TDengine 实战 5.1 安装 DBeaver 下载安装包&#xff1a;访问 DBeaver 官方网站&#xff08;https://dbeaver.io/download/ &#xff09;&#xff0c;根据你的操作系统选择合适的安装包。如果是 Windows 系统&#xff0c;下载.exe 格式的安装文件&#xff1…

Spring Boot接口返回Long类型的数据时丢失精度的全局处理

1、问题 当实体类中的字段为Long类型时&#xff0c;通过Ajax请求返回给前段&#xff0c;在js中数据会丢失精度 直接通过postman请求或通过浏览器请求&#xff0c;看下响应则不会丢失精度 2、处理方式 1、使用JsonSerialize注解 JsonSerialize(using ToStringSerializer.…

英伟达Llama-3.1-Nemotron-Ultra-253B-v1语言模型论文快读:FFN Fusion

FFN Fusion: Rethinking Sequential Computation in Large Language Models 代表模型&#xff1a;Llama-3.1-Nemotron-Ultra-253B-v1 1. 摘要 本文介绍了一种名为 FFN Fusion 的架构优化技术&#xff0c;旨在通过识别和利用自然并行化机会来减少大型语言模型&#xff08;LLM…