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

FFmpeg06:SDL渲染

SDL介绍

  • SDL(Simple DirectMedia Layer)
  • 由C语言实现的跨平台的媒体开源库
  • 多用于游戏、模拟器、媒体播放器等多媒体应用领域

SDL编译与安装

  • 拉取源码
  • 先切换分支为SDL2
  • 执行autogen.sh生成configure (可不执行)
  • 生成Makefile: configure --prefix=/usr/local
  • 安装sudo make -j 8 && sudo make install

SDL使用

  • 头文件#include<SDL.h>
  • 初始化SDL
  • 退出SDL

渲染窗口

  • SDL_Init/SDL_Quit()
  • SDL_CreateWindow()/ SDL_DestoryWindow()
  • SDL_CreateRender()/SDL_DestoryRender()
  • SDL_RenderClear
  • SDL_RenderPresent
#include <SDL.h>int main(int argc, char* argv[]) {SDL_Init(SDL_INIT_VIDEO);SDL_Window* window = SDL_CreateWindow("hello sdl", 200, 200, 640, 470, SDL_WINDOW_SHOWN);SDL_Renderer* render;SDL_Event event;if (!window) {printf("Failed to create window: %s\n", SDL_GetError());goto __EXIT;}render = SDL_CreateRenderer(window, -1, 0);if (!render) {printf("Failed to create renderer: %s\n", SDL_GetError());goto __EXIT;}SDL_PollEvent(&event); // 没有事件处理就不会显示窗口SDL_SetRenderDrawColor(render, 255, 10, 0, 255); // 必须在RenderClear之前设置颜色SDL_RenderClear(render);SDL_RenderPresent(render);  // 显示SDL_Delay(300000);__EXIT:SDL_DestroyRenderer(render);SDL_DestroyWindow(window);SDL_Quit();return 0;
}

事件基本原理

  • SDL将所有事件都存放在一个队列中
  • 所有对事件的操作,其实就是对队列的操作

事件种类

  • SDL_WindowEvent:窗口事件
  • SDL_KeyboardEvent:键盘事件
  • SDL_MouseMotionEvent:鼠标事件

事件处理

  • SDL_PollEvent:轮询检测队列
  • SDL_WaitEvent:事件触发机制
#include <SDL.h>int main(int argc, char* argv[]) {SDL_Init(SDL_INIT_VIDEO);SDL_Window* window = SDL_CreateWindow("hello sdl", 200, 200, 640, 470, SDL_WINDOW_SHOWN);SDL_Renderer* render;SDL_Event event;int quit = 1;if (!window) {printf("Failed to create window: %s\n", SDL_GetError());goto __EXIT;}render = SDL_CreateRenderer(window, -1, 0);if (!render) {printf("Failed to create renderer: %s\n", SDL_GetError());goto __EXIT;}// SDL_PollEvent(&event); // 没有事件处理就不会显示窗口SDL_SetRenderDrawColor(render, 255, 10, 0, 255); // 必须在RenderClear之前设置颜色SDL_RenderClear(render);SDL_RenderPresent(render);  // 显示do {SDL_WaitEvent(&event);switch (event.type){case SDL_QUIT:quit = 0;break;default:SDL_Log("event type: %d", event.type);break;}} while(quit);// SDL_Delay(300000);__EXIT:SDL_DestroyRenderer(render);SDL_DestroyWindow(window);SDL_Quit();return 0;
}

纹理渲染

为什么要使用纹理:假设不使用纹理显示内容,则需要将整个YUV或者RGB数据在CPU计算好,然后传给GPU显示;
而使用纹理则只需要将从哪到哪怎么显示这些信息在CPU准备好,传给GPU进行实际的计算和显示

渲染基本原理

在这里插入图片描述

纹理相关API

  • SDL_CreateTexture()
    • format: YUV, RGB
    • access: Texture类型,Target,Stream
  • SDL_DestoryTexture()

渲染相关API

  • SDL_SetRenderTarget()
  • SDL_RenderClear()
  • SDL_RenderCopy()
  • SDL_RenderPresent()
#include <SDL.h>int main(int argc, char* argv[]) {SDL_Init(SDL_INIT_VIDEO);SDL_Window* window = SDL_CreateWindow("hello sdl", 200, 200, 640, 480, SDL_WINDOW_SHOWN);SDL_Renderer* render;SDL_Event event;SDL_Texture* texture;SDL_Rect rect;rect.w = 30;rect.h = 30;int quit = 1;if (!window) {printf("Failed to create window: %s\n", SDL_GetError());goto __EXIT;}render = SDL_CreateRenderer(window, -1, 0);if (!render) {printf("Failed to create renderer: %s\n", SDL_GetError());goto __EXIT;}// SDL_PollEvent(&event); // 没有事件处理就不会显示窗口SDL_SetRenderDrawColor(render, 255, 10, 0, 255); // 必须在RenderClear之前设置颜色SDL_RenderClear(render);SDL_RenderPresent(render);  // 显示texture = SDL_CreateTexture(render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 640, 480);if (!texture) {printf("Failed to create texture: %s\n", SDL_GetError());goto __EXIT;}do {SDL_PollEvent(&event);  // 将waitEvent改成PollEvent就不会阻塞, 因为PollEvent会轮询事件switch (event.type){case SDL_QUIT:quit = 0;break;default:SDL_Log("event type: %d", event.type);break;}rect.x = (rand() % (640 - rect.w));rect.y = (rand() % (480 - rect.h));SDL_SetRenderTarget(render, texture);   // 设置渲染目标为纹理SDL_SetRenderDrawColor(render, 0, 0, 0, 0);SDL_RenderClear(render);SDL_RenderDrawRect(render, &rect);      // 绘制矩形边框SDL_SetRenderDrawColor(render, 255, 0, 0, 0);SDL_RenderFillRect(render, &rect);      // 填充矩形SDL_SetRenderTarget(render, NULL);      // 恢复渲染目标为默认SDL_RenderCopy(render, texture, NULL, NULL); // 将纹理复制到渲染器SDL_RenderPresent(render);  // 显示} while(quit);// SDL_Delay(300000);__EXIT:SDL_DestroyRenderer(render);SDL_DestroyWindow(window);SDL_DestroyTexture(texture);SDL_Quit();return 0;
}

实现YUV播放器

SDL创建线程

  • SDL_CreateThread:fn线程执行函数, name线程名,data执行函数参数

SDL更新纹理

  • SDL_UpdateTexture()
  • SDL_UpdateYUVTexture

yuvPlayer代码

构造测试要用的yuv,分辨率和pix_fmt要跟代码中一致

ffmpeg -i input.mp4 -s 608x368 -r 25 -pix_fmt yuv420p -f rawvideo output.yuv
#include <SDL.h>
#include <fstream>
using namespace std;#define BLOCK_SIZE 4096000 // 4MB
//event message
#define REFRESH_EVENT  (SDL_USEREVENT + 1)
#define QUIT_EVENT  (SDL_USEREVENT + 2)int thread_exit = 0;
int refreshVideoTimer(void *updata) {thread_exit = 0;while (!thread_exit) {SDL_Event event;event.type = REFRESH_EVENT;SDL_PushEvent(&event);SDL_Delay(40); // 25fps}thread_exit = 0;// 推送退出消息SDL_Event event;event.type = QUIT_EVENT;SDL_PushEvent(&event);return 0;
}
int main(int argc, char* argv[]) {fstream fs("output.yuv", ios::in | ios::binary);if (!fs.is_open()) {printf("open file failed\n");return -1;}   SDL_Event event;SDL_Rect rect;Uint32 pixformat = 0;SDL_Window *window = nullptr;SDL_Renderer *render = nullptr;SDL_Texture *texture = nullptr;SDL_Thread *timerThread = nullptr;int wWidth = 640;int wHeight = 480;const int videoWidth = 608;const int videoHeight = 368;Uint8 *videoPos = nullptr;Uint8 *videoEnd = nullptr;unsigned int remainLen = 0;unsigned int videoBuffLen = 0;unsigned int blankSpaceLen = 0;Uint8 videoBuf[BLOCK_SIZE] = {0};// 只有yuv420p才这样计算// y:w*h, u:w*h/4, v:w*h/4// yuv420p: 1.5 bytes per pixelconst unsigned int yuvFrameLen = videoWidth * videoHeight * 12 / 8;if (SDL_Init(SDL_INIT_VIDEO)) {printf("SDL_Init failed: %s\n", SDL_GetError());return -1;}window = SDL_CreateWindow("yuv player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, wWidth, wHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);if (!window) {printf("SDL_CreateWindow failed: %s\n", SDL_GetError());goto __FAIL;}render = SDL_CreateRenderer(window, -1, 0);//IYUV: Y + U + V  (3 planes)//YV12: Y + V + U  (3 planes)pixformat = SDL_PIXELFORMAT_IYUV; // YUV420P// 创建纹理 SDL_TEXTUREACCESS_STREAMING去了解一下learn_OPENGL里有提及texture = SDL_CreateTexture(render, pixformat, SDL_TEXTUREACCESS_STREAMING, videoWidth, videoHeight);// 从文件读取数据到缓冲区fs.read((char*)videoBuf, BLOCK_SIZE);videoBuffLen = fs.gcount();// 设置视频指针位置videoPos = videoBuf;videoEnd = videoBuf + videoBuffLen;blankSpaceLen = BLOCK_SIZE - videoBuffLen;timerThread = SDL_CreateThread(refreshVideoTimer, nullptr, nullptr);if (!timerThread) {printf("SDL_CreateThread failed: %s\n", SDL_GetError());goto __FAIL;}do {// wait eventSDL_WaitEvent(&event);if (event.type == REFRESH_EVENT) {// 不够数据展示if (videoPos + yuvFrameLen > videoEnd) {// 将剩余数据移到缓冲区头部remainLen = videoEnd - videoPos;if (remainLen > 0 && !blankSpaceLen) {memcpy(videoBuf, videoPos, remainLen);blankSpaceLen = BLOCK_SIZE - remainLen;videoPos = videoBuf;videoEnd = videoBuf + remainLen;}if (videoEnd == videoBuf + BLOCK_SIZE) {videoPos = videoBuf;videoEnd = videoBuf;blankSpaceLen = BLOCK_SIZE;}// 从文件读取数据到缓冲区fs.read((char*)(videoEnd), blankSpaceLen);videoBuffLen = fs.gcount();if (videoBuffLen <= 0) {printf("file read end, exit thread\n");thread_exit = 1;continue; // 等待waitEvent退出}videoEnd += videoBuffLen;blankSpaceLen -= videoBuffLen;printf("not enought data: pos:%p, video_end:%p, blank_space_len:%d\n", videoPos, videoEnd, blankSpaceLen);}SDL_UpdateTexture(texture, nullptr, videoPos, videoWidth);// 如果窗口resizerect.x = 0;rect.y = 0;rect.w = wWidth;rect.h = wHeight;SDL_RenderClear(render);SDL_RenderCopy(render, texture, nullptr, &rect);SDL_RenderPresent(render);printf("not enought data: pos:%p, video_end:%p, blank_space_len:%d\n", videoPos, videoEnd, blankSpaceLen);videoPos += yuvFrameLen;} else if (event.type == SDL_WINDOWEVENT) {// 窗口大小改变SDL_GetWindowSize(window, &wWidth, &wHeight);} else if (event.type == SDL_QUIT) {thread_exit = 1;} else if (event.type == QUIT_EVENT) {printf("receive quit event, exit\n");break;}} while (1);__FAIL:if (timerThread) {SDL_WaitThread(timerThread, nullptr);timerThread = nullptr;}if (texture) {SDL_DestroyTexture(texture);texture = nullptr;}if (render) {SDL_DestroyRenderer(render);render = nullptr;}if (window) {SDL_DestroyWindow(window);window = nullptr;}SDL_Quit();fs.close();return 0;
}

SDL音频处理

播放音频基本流程

在这里插入图片描述

播放音频的基本原则

  • 声卡向你要数据而不是你主动推给声卡
  • 数据的多少由音频参数决定

SDL音频API

  • SDL_OpenAudio/SDL_CloseAudio
  • SDL_PauseAudio
  • SDL_MixAudio

pcmPlayer代码

构造代码对应的pcm文件

ffmpeg -i input.mp4 -vn -c:a pcm_s16le -ar 44100 -ac 2 -f s16le output.pcm

#include <SDL.h>
#include <fstream>
using namespace std;
#define BLOCK_SIZE 4096000static Uint8 *audioBuf = NULL;
static Uint8 *audioPos = NULL;
static size_t bufferLen = 0;// 音频回调函数
void audio_callback(void *userdata, Uint8 *stream, int len) {if (bufferLen == 0) {return;}SDL_memset(stream, 0, len); // 先将stream清0len = (len > bufferLen ? bufferLen : len);// 混音,将解码后的音频数据混合到streamSDL_MixAudio(stream, audioPos, len, SDL_MIX_MAXVOLUME);audioPos += len;bufferLen -= len;
}int main(int argc, char* argv[]) {int res = -1;fstream fs("output.pcm", ios::in | ios::binary);if (!fs.is_open()) {printf("open file failed\n");return -1;}SDL_AudioSpec spec;if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {printf("SDL_Init failed: %s\n", SDL_GetError());return -1;}// 申请音频缓冲区audioBuf = new Uint8[BLOCK_SIZE];if (!audioBuf) {printf("new audioBuf failed\n");goto __FAIL;}// 设置音频参数spec.freq = 44100;               // 采样率spec.format = AUDIO_S16SYS;      // 采样格式spec.channels = 2;                // 声道数spec.silence = 0;                 // 静音数据spec.samples = 1024;              // 采样点数spec.callback = audio_callback;   // 音频回调函数spec.userdata = nullptr;           // 传递给回调函数的参数// 打开音频设备if (SDL_OpenAudio(&spec, nullptr) < 0) {printf("SDL_OpenAudio failed: %s\n", SDL_GetError());goto __FAIL;}// 开始播放SDL_PauseAudio(0);do {// 从文件读取数据到缓冲区fs.read((char*)audioBuf, BLOCK_SIZE);bufferLen = fs.gcount();if (bufferLen <= 0) {printf("file read end, exit\n");break;}audioPos = audioBuf;// 等待数据播放完while (audioPos < (audioBuf + BLOCK_SIZE)) {SDL_Delay(1);}} while (bufferLen != 0);SDL_CloseAudio();res = 0;__FAIL:if (audioBuf) {delete[] audioBuf;audioBuf = nullptr;}SDL_Quit();return res;
}

文章转载自:

http://xzibVRp5.fhwfk.cn
http://SQ9Xr15U.fhwfk.cn
http://XSbMMxvU.fhwfk.cn
http://Vpwwm9A6.fhwfk.cn
http://rApbpFne.fhwfk.cn
http://CoWeytyX.fhwfk.cn
http://Em9bpQyQ.fhwfk.cn
http://VpjemQG7.fhwfk.cn
http://dEwrPbiv.fhwfk.cn
http://2nnPkriy.fhwfk.cn
http://t4dqW4Z4.fhwfk.cn
http://pCRIBTuw.fhwfk.cn
http://mEwiY5v8.fhwfk.cn
http://h5sDXVWZ.fhwfk.cn
http://Id3uy55t.fhwfk.cn
http://9PqQcmxR.fhwfk.cn
http://ALojIPHl.fhwfk.cn
http://7qYdeceg.fhwfk.cn
http://E1S9TJVt.fhwfk.cn
http://IMuF8uhi.fhwfk.cn
http://B2AjuO2x.fhwfk.cn
http://wWTTd24T.fhwfk.cn
http://Afc8sQ43.fhwfk.cn
http://c6t0VuRI.fhwfk.cn
http://J1ZpjrOf.fhwfk.cn
http://XnsTGpIP.fhwfk.cn
http://ZQKitrFu.fhwfk.cn
http://xEmR9NXd.fhwfk.cn
http://cXI0bVcq.fhwfk.cn
http://SpO7SqmL.fhwfk.cn
http://www.dtcms.com/a/385562.html

相关文章:

  • javadoc命令 错误: 编码 GBK 的不可映射字符 (0x80)
  • 【面试场景题】自增主键、UUID、雪花算法都有什么问题
  • 数据整理器(Data Collators)总结 (95)
  • 代码评价:std::shared_ptr用法分析
  • 23种设计模式案例
  • AI Agent案例与实践全解析:字节智能运维
  • MyBatis-Plus分页插件实现导致total为0问题
  • S32DS仿真环境问题
  • 黑马JavaWeb+AI笔记 Day07 Web后端实战(部门管理模块)
  • 【AI开发】【前后端全栈】[特殊字符] AI 时代的快速开发思维
  • kimi-k2论文阅读笔记
  • [SC]一个使用前向声明的SystemC项目例子
  • Gunicorn 部署与调优全指南(2025 版)
  • 第二十一篇|新宿平和日本语学校的结构化解读:费用函数、文化网络与AI教育建模
  • 数据结构(C语言篇):(十五)二叉树OJ题
  • RIFE.py代码学习 自学
  • Gateway-路由-规则配置
  • 低端影视官网入口 - 免费看影视资源网站|网页版|电脑版地址
  • 【Python3教程】Python3高级篇之日期与时间
  • 计算机网络——传输层(25王道最新版)
  • 5-14 forEach-数组简易循环(实例:数组的汇总)
  • 【智能体】rStar2-Agent
  • ego(5)---Astar绕障
  • UE5C++编译遇到MSB3073
  • 记一次JS逆向学习
  • 【PyTorch】单目标检测
  • RabbitMQ—基础篇
  • 介绍一下 Test-Time Training 技术
  • 【LangChain指南】Document loaders
  • 日语学习-日语知识点小记-进阶-JLPT-N1阶段蓝宝书,共120语法(10):91-100语法+考え方13