音视频技术应用(7)-使用SDL渲染一幅指定的图像,并且动态修改图像数据
一. 基本步骤
使用SDL渲染图像的步骤基本可分为以下几步:
1.1 初始化SDL接口
SDL_Init(SDL_INIT_VIDEO)
初始化SDL Video 库, 成功返回0, 失败返回非0值。
1.2 创建SDL窗口(可以直接创建一个窗口或是绑定一个窗口句柄)
这是生成窗口可以分为两种:
第一种是独立创建一个窗口:
SDL_Window *SDLCALL SDL_CreateWindow(const char *title, int x, int y, int w, int h, Uint32 flags);
其中,title代表的是窗口的标题,x, y 代表的是窗口的坐标, w, h 代表的是窗口的宽高,flags 代表的是窗口的格式,比如可以指定:SDL_WINDOW_OPENGL(使用OpenGL渲染), SDL_WINDOW_RESIZABLE(窗口大小可改变)。
第二种是可以指定使用一个窗口来创建:
SDL_Window *SDLCALL SDL_CreateWindowFrom(const void *data);
这里的void * 可以直接传递一个QT的窗口句柄。
1.3 创建渲染器
SDL_CreateRenderer(SDL_Window *window, int index, Uint32 flags);
第一个参数就是SDL 窗口的句柄,用于指定渲染器创建在哪个窗口上;第二个代表渲染器驱动的索引,不理解的话直接传-1就行,第三个参数代表渲染模式,可以指定一些相应的渲染模式,比如:
- SDL_RENDERER_SOFTWARE, 软件渲染;
- SDL_RENDERER_ACCELERATED, 硬件加速渲染;
- SDL_RENDERER_PRESENTVSYNC, 与显示器同步;
- SDL_RENDERER_TARGETTEXTURE, 渲染到材质
硬件加速可能在某些平台中不支持,如果不支持话,可以选择 SDL_RENDERER_SOFTWARE,这个一般都支持。
1.4 在渲染器当中创建一个材质
材质就是用于存储我们要显示的具体的图像信息, 它将来会放置到显存当中
SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, w, h);
第一个参数代表渲染器对象,第二个参数代表像素格式,比如上面传输的ARGB8888这种像素格式, 第三个参数代表材质的类型,可选参数如下:
- SDL_TEXTUREACCESS_STATIC, 不需要频繁修改,不需要锁定
- SDL_TEXTUREACCESS_STREAMING, 需要频繁修改,需要锁定
- SDL_TEXTUREACCESS_TARGET, ?,暂时不理解
后面两个参数 w, h 就是代表材质的宽高。
1.5 将内存数据写入材质
我们初始的图像肯定是位于内存当中的,不管是自己创建的图像数据,还是从ffmpeg解码得到的图像,都是在内存当中,那要把内存当中的数据转到显存当中,就要用到此接口进行转换。该函数的作用其实就是把内存当中的值更新到Texture,也就是显存当中。
int SDLCALL SDL_UpdateTexture(SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch)
第一个参数代表材质对象,就是更新到哪个材质当中;第二个参数代表坐标,也就是将要存进来的图像数据,我们取这幅图像的哪些位置,传NULL的话就代表全部取,就是取全部图像数据;第三个参数代表具体的图像数据,第四个参数代表我们一会儿取得的图像数据,每一行的字节数。
接下来就是具体的渲染操作了,也就是显示,显示需要分为以下几步:
1.6 清理屏幕
SDL_RenderClear(SDL_Renderer *renderer)
不用说,参数就是代表渲染器对象。
1.7 复制材质到渲染器对象
SDL_RenderCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect, const SDL_Rect *dstrect);
第一个参数代表渲染器对象,第二个参数代表需要copy的材质对象,第三个参数代表取原始图像的什么位置和尺寸,传NULL代表取原始图像的所有部分;第四个参数代表在渲染区(窗口)的哪个位置显示。这个函数可以用于显示全部材质,也就是截取材质当中的一部分进行显示。
1.8 执行渲染操作
SDL_RenderPresent(SDL_Renderer *renderer);
参数就是渲染器对象。
下面我们自己准备一个RGB 数据,然后使用SDL 渲染出来。
二. 使用SDL渲染一幅RGB图像数据
使用VS2019 创建一个空项目命名为sdl_rgb,准备好之前编译生成的SDL库文件和头文件,然后做如下配置:
右键项目名-”属性“-”C/C++“-”附加包含目录“, 添加包含目录:..\..\include
然后点击”链接器“-”附加库目录“,添加库目录:..\..\lib\x86
设置输出路径:”常规“-”输出目录“, ..\..\bin\x86
设置调试的工具目录:”调试“-”工作目录“, ..\...\bin\x86
点”确定“,完成项目的初始配置。
打开创建好的cpp文件,输入如下code:
#include <iostream> #include <sdl/SDL.h> using namespace std; #pragma comment(lib, "SDL2.lib") // SDL 库里面定义了一个宏 main, 这里给取消掉,否则会与main 函数名冲突,导致编译错误 #undef main int main() { // 定义图像的宽高 int w = 800; int h = 600; // 1. 初始化SDL库, 成功返回0, 失败返回非0值 if (SDL_Init(SDL_INIT_VIDEO)) { cout << SDL_GetError() << endl; return -1; } // 2. 创建SDL窗口 auto screen = SDL_CreateWindow("test_sdl_ffmpeg", // 窗口标题 SDL_WINDOWPOS_CENTERED, // 窗口位置 SDL_WINDOWPOS_CENTERED, w, h, // 窗口宽高 SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE // 窗口属性,指定使用OpenGL, 并且可调整大小 ); if (!screen) { cout << SDL_GetError() << endl; return -2; } // 3. 创建渲染器 auto render = SDL_CreateRenderer(screen, // 指定渲染到哪个窗口 -1, // 指定渲染器驱动,默认传-1 SDL_RENDERER_ACCELERATED // 指定渲染模式,这里采用硬件加速模式 ); if (!render) { cout << SDL_GetError() << endl; return -3; } // 4. 在渲染器当中创建一个材质 auto texture = SDL_CreateTexture(render, // 指定在哪个渲染器当中创建 SDL_PIXELFORMAT_ARGB8888, // 指定当前材质的像素格式 SDL_TEXTUREACCESS_STREAMING, // 设定当前材质可修改 w, h // 指定材质宽高 ); if (!texture) { cout << SDL_GetError() << endl; return -4; } // 准备一幅800*600的红色RGB图像数据 shared_ptr<unsigned char> rgb(new unsigned char[w * h * 4]); // 乘以4是因为像素格式已指定为ARGB888,单个像素点占4字节 auto r = rgb.get(); // 为上述图像数据赋值 for (int j = 0; j < h; j++) { int lineR = j * w * 4; // 每一行R分量的起始位置 for (int i = 0; i < w * 4; i += 4) { r[lineR + i] = 0; // B r[lineR + i + 1] = 0; // G r[lineR + i + 2] = 255; // R r[lineR + i + 3] = 0; // A } } // 5. 将内存中的RGB数据写入材质 if (SDL_UpdateTexture(texture, NULL, r, w * 4)) { cout << SDL_GetError() << endl; return -5; } // 6. 清理渲染区(清理屏幕) if (SDL_RenderClear(render)) { cout << SDL_GetError() << endl; return -6; } // 设定渲染的目标区域 SDL_Rect destRect; destRect.x = 0; destRect.y = 0; destRect.w = w; destRect.h = h; // 7. 复制材质到渲染器对象 if (SDL_RenderCopy(render, texture, NULL, &destRect)) { cout << SDL_GetError() << endl; return -7; } // 8. 执行渲染操作 SDL_RenderPresent(render); getchar(); return 0; }
执行:
可以看到我们创建的800*600的红色图像已经成功的被渲染到窗口当中。
三. 模拟动态修改图像数据
这里模块通过SDL渲染多幅画面,使画面发生变化。
修改上述code:
#include <iostream> #include <sdl/SDL.h> using namespace std; #pragma comment(lib, "SDL2.lib") // SDL 库里面定义了一个宏 main, 这里给取消掉,否则会与main 函数名冲突,导致编译错误 #undef main int main() { // 定义图像的宽高 int w = 800; int h = 600; // 1. 初始化SDL库, 成功返回0, 失败返回非0值 if (SDL_Init(SDL_INIT_VIDEO)) { cout << SDL_GetError() << endl; return -1; } // 2. 创建SDL窗口 auto screen = SDL_CreateWindow("test_sdl_ffmpeg", // 窗口标题 SDL_WINDOWPOS_CENTERED, // 窗口位置 SDL_WINDOWPOS_CENTERED, w, h, // 窗口宽高 SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE // 窗口属性,指定使用OpenGL, 并且可调整大小 ); if (!screen) { cout << SDL_GetError() << endl; return -2; } // 3. 创建渲染器 auto render = SDL_CreateRenderer(screen, // 指定渲染到哪个窗口 -1, // 指定渲染器驱动,默认传-1 SDL_RENDERER_ACCELERATED // 指定渲染模式,这里采用硬件加速模式 ); if (!render) { cout << SDL_GetError() << endl; return -3; } // 4. 在渲染器当中创建一个材质 auto texture = SDL_CreateTexture(render, // 指定在哪个渲染器当中创建 SDL_PIXELFORMAT_ARGB8888, // 指定当前材质的像素格式 SDL_TEXTUREACCESS_STREAMING, // 设定当前材质可修改 w, h // 指定材质宽高 ); if (!texture) { cout << SDL_GetError() << endl; return -4; } // 准备一幅800*600的红色RGB图像数据 shared_ptr<unsigned char> rgb(new unsigned char[w * h * 4]); // 乘以4是因为像素格式已指定为ARGB888,单个像素点占4字节 auto r = rgb.get(); unsigned char tmp = 255; for (;;) { SDL_Event ev; SDL_WaitEventTimeout(&ev, 10); if (ev.type == SDL_QUIT) { SDL_DestroyWindow(screen); break; } tmp--; // 为上述图像数据赋值 for (int j = 0; j < h; j++) { int lineR = j * w * 4; // 每一行R分量的起始位置 for (int i = 0; i < w * 4; i += 4) { r[lineR + i] = 0; // B r[lineR + i + 1] = 0; // G r[lineR + i + 2] = tmp; // R r[lineR + i + 3] = 0; // A } } // 5. 将内存中的RGB数据写入材质 if (SDL_UpdateTexture(texture, NULL, r, w * 4)) { cout << SDL_GetError() << endl; return -5; } // 6. 清理渲染区(清理屏幕) if (SDL_RenderClear(render)) { cout << SDL_GetError() << endl; return -6; } // 设定渲染的目标区域 SDL_Rect destRect; destRect.x = 0; destRect.y = 0; destRect.w = w; destRect.h = h; // 7. 复制材质到渲染器对象 if (SDL_RenderCopy(render, texture, NULL, &destRect)) { cout << SDL_GetError() << endl; return -7; } // 8. 执行渲染操作 SDL_RenderPresent(render); } getchar(); return 0; }
添加一个等待10ms的SDL事件,观察效果:
<完>