音视频技术应用(7)-使用SDL渲染一幅指定的图像,并且动态修改图像数据

一. 基本步骤

使用SDL渲染图像的步骤基本可分为以下几步:

1. 初始化SDL接口

SDL_Init(SDL_INIT_VIDEO)

初始化SDL Video 库, 成功返回0, 失败返回非0值。

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的窗口句柄。

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,这个一般都支持。

4. 在渲染器当中创建一个材质

材质就是用于存储我们要显示的具体的图像信息, 它将来会放置到显存当中

SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, w, h);

第一个参数代表渲染器对象,第二个参数代表像素格式,比如上面传输的ARGB8888这种像素格式, 第三个参数代表材质的类型,可选参数如下:

  • SDL_TEXTUREACCESS_STATIC, 不需要频繁修改,不需要锁定
  • SDL_TEXTUREACCESS_STREAMING, 需要频繁修改,需要锁定
  • SDL_TEXTUREACCESS_TARGET, ?,暂时不理解

后面两个参数 w, h 就是代表材质的宽高。

5. 将内存数据写入材质

我们初始的图像肯定是位于内存当中的,不管是自己创建的图像数据,还是从ffmpeg解码得到的图像,都是在内存当中,那要把内存当中的数据转到显存当中,就要用到此接口进行转换。该函数的作用其实就是把内存当中的值更新到Texture,也就是显存当中。

int SDLCALL SDL_UpdateTexture(SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch)

第一个参数代表材质对象,就是更新到哪个材质当中;第二个参数代表坐标,也就是将要存进来的图像数据,我们取这幅图像的哪些位置,传NULL的话就代表全部取,就是取全部图像数据;第三个参数代表具体的图像数据,第四个参数代表我们一会儿取得的图像数据,每一行的字节数。

接下来就是具体的渲染操作了,也就是显示,显示需要分为以下几步:

6. 清理屏幕

SDL_RenderClear(SDL_Renderer *renderer)

不用说,参数就是代表渲染器对象。

7. 复制材质到渲染器对象

SDL_RenderCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect, const SDL_Rect *dstrect);

第一个参数代表渲染器对象,第二个参数代表需要copy的材质对象,第三个参数代表取原始图像的什么位置和尺寸,传NULL代表取原始图像的所有部分;第四个参数代表在渲染区(窗口)的哪个位置显示。这个函数可以用于显示全部材质,也就是截取材质当中的一部分进行显示。

8. 执行渲染操作

SDL_RenderPresent(SDL_Renderer *renderer);

参数就是渲染器对象。

下面我们自己准备一个RGB 数据,然后使用SDL 渲染出来。

二. 使用SDL渲染一幅RGB图像数据

使用VS2019 创建一个空项目命名为sdl_rgb,准备好之前编译生成的SDL库文件和头文件,然后做如下配置:

右键项目名-”属性“-”C/C++“-”附加包含目录“, 添加包含目录:..\..\include

音视频技术应用(7)-使用SDL渲染一幅指定的图像,并且动态修改图像数据

然后点击”链接器“-”附加库目录“,添加库目录:..\..\lib\x86

音视频技术应用(7)-使用SDL渲染一幅指定的图像,并且动态修改图像数据

设置输出路径:”常规“-”输出目录“, ..\..\bin\x86

音视频技术应用(7)-使用SDL渲染一幅指定的图像,并且动态修改图像数据

 

设置调试的工具目录:”调试“-”工作目录“, ..\...\bin\x86

音视频技术应用(7)-使用SDL渲染一幅指定的图像,并且动态修改图像数据

点”确定“,完成项目的初始配置。

打开创建好的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;
}

执行:

音视频技术应用(7)-使用SDL渲染一幅指定的图像,并且动态修改图像数据

 

可以看到我们创建的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事件,观察效果:

音视频技术应用(7)-使用SDL渲染一幅指定的图像,并且动态修改图像数据

 

<完>

上一篇:音视频技术应用(9)-合并两幅图像, 使用SDL渲染并保存


下一篇:C# 使用SDL2实现Mp4文件播放音视频操作