【音視頻】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.pswp.cn/news/901108.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/901108.shtml
英文地址,請注明出處:http://en.pswp.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…