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

【音视频开发】第四章 SDL音视频渲染

【音视频开发】第四章 SDL音视频渲染

文章目录

  • 【音视频开发】第四章 SDL音视频渲染
  • 一、简介
    • 1.什么是 SDL
  • 二、Windows 环境搭建
  • 三、SDL 子系统
  • 四、Window 显示
    • 1.SDL 视频显示函数简介
    • 2.窗口渲染结构体
  • 五、SDL 事件
    • 1.通用事件结构体 SDL_Event
    • 2.事件类型
  • 六、SDL 线程
    • 1.常用线程相关 API
  • 七、YUV 显示
    • 1.代码示例
  • 八、PCM 音频播放
    • 1.SDL 播放 PCM 音频基本流程
    • 2.代码示例

一、简介

1.什么是 SDL

官网:https://www.libsdl.org

SDL(Simple DirectMedia Layer)是一个使用 C 语言开发的开源跨平台开发库,它主要用来直接访问操作系统底层的硬件资源,比如:

  • 图形(Graphics)
  • 音频(Audio)
  • 输入设备(键盘、鼠标、手柄)
  • 窗口管理
  • 多线程(Threading)
  • 文件 IO(File I/O)

典型用途

  • 2D 游戏(平台跳跃、射击类)
  • 模拟器(NES、SNES、GameBoy 等复古游戏模拟器)
  • 图形工具或小型 UI 框架
  • 作为大型游戏引擎(如 Unity)或自研引擎的底层组件之一

二、Windows 环境搭建

下载地址:https://github.com/libsdl-org/SDL/releases
在这里插入图片描述

创建一个 C 项目
在这里插入图片描述

将刚刚下载完成的 SDL 压缩包解压,复制到项目根目录中,并将路径配置到.pro文件
在这里插入图片描述

将 SDL2-2.32.4\lib\x64 目录下的 SDL2.dll 文件复制到项目根目录中,编写简单的窗口创建程序

#include <stdio.h>
#include <SDL.h>#undef mainint main()
{printf("Hello SDL World!\n");if (SDL_Init(SDL_INIT_VIDEO) != 0) {printf("SDL_Init Error: %s\n", SDL_GetError());return 1;}SDL_Window *window = SDL_CreateWindow("Basic SDL Window",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,640,480,SDL_WINDOW_SHOWN);if (!window) {printf("Can't create window, err: %s\n", SDL_GetError());SDL_Quit();return 1;}// 添加事件循环,保持窗口直到关闭SDL_Event event;int running = 1;while (running) {while (SDL_PollEvent(&event)) {if (event.type == SDL_QUIT) {running = 0;}}SDL_Delay(16); // 简单的帧延迟}SDL_DestroyWindow(window);SDL_Quit();return 0;
}

三、SDL 子系统

在 SDL(Simple DirectMedia Layer)中,子系统(Subsystem)就是 SDL 提供的某一类功能模块的集合,你可以把它理解成“功能插件”或“功能模块”。这些子系统是独立的,你可以按需开启,避免加载不必要的内容,提升效率。

  • SDL_INIT_TIMER:定时器功能(SDL_GetTicks)
  • SDL_INIT_AUDIO:音频子系统
  • SDL_INIT_VIDEO:视频子系统(窗口、OpenGL、Vulkan)
  • SDL_INIT_JOYSTICK:游戏摇杆(Joystick)
  • SQL_INIT_HAPTIC:触觉反馈(震动设备)
  • SDL_INIT_GAMECONTROLLER:手柄控制器(比 joystick 更高级)
  • SDL_INIT_EVENTS:SDL 事件系统(键盘/鼠标等)
  • SDL_INIT_SENSOR:传感器(如陀螺仪)
  • SDL_INIT_EVERYTHING:初始化所有子系统

四、Window 显示

1.SDL 视频显示函数简介

  • SDL_Init():初始化 SDL 系统
  • SDL_CreateWindow():创建窗口 SDL_Window
  • SDL_CreateRender():创建渲染器 SDL_Renderer
  • SDL_CreateTexture():创建纹理 SDL_Texture
  • SDL_UpdateTexture():设置纹理的数据
  • SDL_RenderCopy():将纹理的数据拷贝给渲染器
  • SDL_RenderPresent():显示
  • SDL_Delay():工具函数,用于延时
  • SDL_Quit():退出 SDL 系统

2.窗口渲染结构体

  • SDL_Window:表示一个窗口(由 SDL_CreateWindow() 创建)
  • SDL_Render:渲染器,用于在窗口上绘制图形
  • SDL_Texture:纹理,加载图像后用于渲染
  • SDL_Surface:表示一块像素数据,可用于直接操作图像
  • SDL_Rect:表示一个矩形区域(常用于位置和尺寸)

五、SDL 事件

SDL 的事件系统是通过轮询或等待来处理用户输入、窗口变化、系统通知等各种操作的核心机制。

SDL 的所有事件都通过一个统一的 SDL_Event 结构体表示,然后通过 event.type 判断它属于哪种事件类型。

1.通用事件结构体 SDL_Event

SDL_Event event;while(SDL_PollEvent(&event)){switch(event.type){case SDL_QUIT://处理退出break;//其他事件...}
}

2.事件类型

  • 退出事件:SDL_QUIT
  • 窗口事件:SDL_WINDOWEVENT + event.window.event
  • 键盘事件:SDL_KEYDOWN, SDL_KEYUP
  • 鼠标事件:SDL_MOUSEMOTION, SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP, SQL_MOUSEWHEEL
  • 控制器:SQL_CONTROLLER… 系列
  • 自定义事件:SQL_USEREVENT

六、SDL 线程

SDL 提供了跨平台的线程创建与管理接口,让开发者在不同系统下都能一致地创建和管理线程。

1.常用线程相关 API

创建线程

SQL_Thread* SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data);
  • fn:线程函数,函数签名为 int thread_func(void *data)
  • name:线程名称
  • data:传递给线程函数的参数
  • 返回值:成功返回 SDL_Thread*,失败返回 NULL

等待线程结束

void SDL_WaitThread(SDL_Thread* thread, int *status);

等待指定线程结束,并获取其返回值

获取当前线程 ID

SDL_threadID SDL_ThreadID(void);

获取线程名

const char* SDL_GetThreadName(SDL_Thread* thread);

互斥锁(Mutex)

SDL_mutex* SDL_CreateMutex();
void SDL_DestroyMutex(SDL_mutex* mutex);
int SDL_LockMutex(SDL_mutex* mutex);
int SDL_UnlockMutex(SDL_mutex* mutex);

七、YUV 显示

使用 SDL 显示 YUV 图像是视频播放中非常常见的一种技术路径。
在这里插入图片描述

1.代码示例

  • 读取并显示 .yuv 原始视频文件(YUV420P格式)
  • SDL 窗口播放视频帧
  • 每秒大约播放 25 帧
#include <SDL2/SDL.h>
#include <stdio.h>// 视频宽度和高度
#define WIDTH 640
#define HEIGHT 480// YUV 文件路径
#define FILE_PATH "test.yuv"// 每帧字节数(YUV420P:Y 占 w*h,U/V 各占 w*h/4)
#define FRAME_SIZE(WIDTH * HEIGHT * 3 / 2)int main(int argc, char* argv[]){// 初始化 SDL 视频系统if(SDL_Init(SDL_INIT_VIDEO) < 0){print("SDL 初始化失败:%s\n", SDL_GetError());return -1;}// 创建 SDL 窗口SDL_Window* window = SDL_CreateWindow("YUV 显示示例", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WIDTH, HEIGHT, SDL_WINDOW_SHOWN);if(!window){printf("窗口创建失败:%s\n", SDL_GetError());SDL_Quit();return -1;	}// 创建渲染器SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);// 创建 YUV4200P 纹理SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);// 打开 .yuv 文件FILE* file = fopen(FILE_PATH, "rb");if(!file){printf("无法打开文件:%s\n", FILE_PATH);return -1;}// 分配一帧缓冲区Uint8* buffer = (Uint8*)malloc(FRAME_SIZE);if(!buffer){printf("内存分配失败\n");fclose(file);return -1;}SDL_Event event;int quit = 0;// 简单播放循环:直到用户关闭窗口或播放结束while(!quit){// 读取一帧 YUV 数据size_t readBytes = fread(buffer, 1, FRAME_SIZE, file);if(readBytes < FRAME_SIZE){// 播放结束,自动回到文件开头循环播放fseek(file, 0, SEEK_SET);continue;}// 获取 Y/U/V 分量指针Uint8* yPlane = buffer;Uint8* uPlane = buffer + WIDTH * HEIGHT;Uint8* vPlane = buffer + WIDTH * HEIGHT + (WIDTH * HEIGHT) / 4;// 更新纹理数据SDL_UpdateYUVTexture, NULL, yPlane, WIDTH, uPlane, WIDTH / 2, vPlane, WIDTH / 2);// 清空渲染器并复制纹理SDL_RenderClear(renderer);SDL_RenderCopy(renderer, texture, NULL, NULL);SDL_RenderPresent(renderer);// 处理退出事件while(SDL_PollEvent(&event)){if(event.type == SDL_QUIT){quit = 1;}}// 控制帧率(约 25fps)SDL_Delay(40);}// 释放资源free(buffer);fclose(file);SDL_DestroyTexture(texture);SDL_DestroyRenderer(renderer);SDL_DestroyWindow(window);SDL_Quit();return 0;
}

八、PCM 音频播放

使用 SDL 播放 PCM(Pulse Code Modulation,脉冲编码调制) 音频是实现音频播放功能的一种常见方式,尤其适用于原始音频数据(未压缩的 .pcm 文件,例如 16-bit、44100Hz、立体声)

1.SDL 播放 PCM 音频基本流程

  1. 初始化 SDL 音频系统
  2. 设置音频参数(采样率、格式、声道数、回调函数等)
  3. 打开音频设备
  4. 播放时通过回调函数往缓冲区填充 PCM 数据
  5. 启动播放并保持主循环

2.代码示例

#include <SDL2/SDL.h>
#include <stdio.h>// 定义音频缓冲区结构
typedef struct {Uint8* pos;		//当前播放位置Uint32* len;	//剩余字节数
}AudioData;// 音频回调函数:SDL 会周期性调用它来获取音频数据
void audio_callback(void* userdata, Uint8* stream, int len) {AudioData* audio = (AudioData*)userdata;if(audio->len == 0){return;	//播放完了}// 播放剩余字节数不足一帧时,只复制剩余部分len = (len > audio->len ? audio->len : len);SDL_memcpy(stream, audio->pos, len);audio->pos += len;audio->len -= len;
}int main(int argc, char* argv[]){// 假设 PCM 文件是 16-bit signed, stereo, 44100Hzconst char* filePath = "test.pcm";// 打开 PCM 文件FILE* file = fopen(filePath, "rb");if(!file){printf("无法打开 PCM 文件:%s\n", filePath);return -1;}// 获取文件大小fseek(file, 0, SEEK_END);Uint32 fileSize = ftell(file);fseek(file, 0, SEEK_SET);// 读取全部 PCM 数据到内存中Uint8* buffer = (Uint8*)malloc(fileSize);fread(buffer), 1, fileSize, file);fclose(file);// 初始化 SDLif(SDL_Init(SDL_INIT_AUDIO) < 0){printf("SDL_Init 错误:%s\n", SDL_GetError());return -1;}// 设置音频格式(必须与 PCM 文件一致)SDL_AudioSpec spec;spec.freq = 44100;				//采样率spec.format = AUDIO_S16LSB;		//16-bit signed little endianspec.channels = 2;				//立体声spec.samples = 4096;			//音频缓冲区大小(影响延迟)spec.callback = audio_callback;	//设置音频回调AudioData audio = { buffer, fileSize };spec.userdata = &audio;// 打开音频设备if(SDL_OpenAudio(&spec, NULL) < 0){printf("SDL_OpenAudio 错误:%s\n", SDL_GetError());free(buffer);SDL_Quit();return -1;	}// 启动播放SDL_PauseAudio(0);// 播放期间阻塞主线程while(audio.len > 0){SDL_Delay(100);		//每 100ms 轮询一次	}// 播放完成后清理资源SDL_CloseAudio();free(buffer);SDL_Quit();return 0;
}

相关文章:

  • 国标GB28181视频平台EasyCVR视频汇聚系统,打造别墅居民区智能监控体系
  • 计算机视觉相机模型与标定:如何让计算机“看懂”三维世界?
  • HTML、CSS 和 JavaScript 常见用法及使用规范
  • 音频基础概念
  • 代理模式深度解析
  • Spring Batch 专题系列(六):并行处理与性能优化
  • 自动化智能检测系统:毫米级公差全域感知,良品率提升30%
  • C语言内存管理函数详解:mmap、munmap、malloc与free
  • 冒泡排序、插入排序、快速排序、堆排序、希尔排序、归并排序
  • RPA机器人技术原理初探
  • OpenCv高阶(三)——图像的直方图、图像直方图的均衡化
  • Token与axios拦截器
  • MATLAB脚本实现了一个三自由度的通用航空运载器(CAV-H)的轨迹仿真,主要用于模拟升力体在不同飞行阶段(初始滑翔段、滑翔段、下压段)的运动轨迹
  • 算法题(126):前缀和
  • 【Netty篇】EventLoopGroup 与 EventLoop 详解
  • SAP ECCS 标准报表 切换为EXCEL电子表格模式
  • 基于springboot+vue的数码产品抢购系统
  • 0701表单组件-react-仿低代码平台项目
  • Abstract (抽象类)和 Interface (接口)的区别
  • PowerBi中Drillthrough功能怎么使用?
  • 社会信用体系建设双公示网站/广州网站建设工作室
  • jsp企业网站源码/网络工程师
  • 四川省住房与建设厅网站/网络软文是什么
  • 格力网站建设首页/百度搜索引擎介绍
  • 陕西锦宇建设有限公司网站/百度提交网站的入口地址
  • 青岛网站建设服务/全渠道营销管理平台