SDL基础使用07(YUV数据显示)

简介: 使用SDL库在Windows上处理和显示YUV数据,包括生成随机YUV数据、播放YUV文件以及实现带缩放的实时渲染。

SDL显示YUV数据和文件

使用SDL播放随机yuv数据

#include <stdio.h>
#include <Windows.h>

extern "C"
{
#include <SDL.h>
}

#pragma comment(lib, "SDL2.lib")

/* 填充随机颜色 */
static void FillYuvImage(BYTE* pYuv, int nWidth, int nHeight, int nIndex)
{
    int x, y, i;
    i = nIndex;

    BYTE* pY = pYuv;                                // 数组开始位置
    BYTE* pU = pYuv + nWidth * nHeight;                // 数组开始位置 + Y数据后的位置
    BYTE* pV = pYuv + nWidth * nHeight * 5 / 4;        // 数组开始位置 + YU数据后的位置

    /* Y */
    for (y = 0; y < nHeight; y++)
    {
        for (x = 0; x < nWidth; x++)
        {
            pY[y * nWidth + x] = x + y + i * 2;
        }
    }

    /* Cb and Cr */
    for (y = 0; y < nHeight / 2; y++)
    {
        for (x = 0; x < nWidth / 2; x++)
        {
            pU[y * (nWidth / 2) + x] = 64 + y + i * 2;
            pV[y * (nWidth / 2) + x] = 64 + x + i * 5;
        }
    }
}

#undef main
int main()
{
    // 初始化SDL
    if (SDL_Init(SDL_INIT_VIDEO))
    {
        printf("Could not initialize SDL - %s\n", SDL_GetError());
        return -1;
    }

    // 提升图像质量,否则默认缩放质量会有毛剌(设置反锯齿)
    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");

    // 创建窗口(获取控制台窗口),基于窗口创建渲染器
    SDL_Window*   window = SDL_CreateWindowFrom(::GetConsoleWindow());
    SDL_Renderer* render = SDL_CreateRenderer(window, -1, 0);

    // 窗口宽高
    const int W = 1920;
    const int H = 1080;

    // 创建渲染器
    SDL_Texture*  texture = SDL_CreateTexture(render, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, W, H);

    /// yuv 420P : planer   W * H * 1.5 (y: 1 uv: 0.5)
    static BYTE Yuv[W * H * 2];
    BYTE* pY = Yuv;
    BYTE* pU = Yuv + W * H;
    BYTE* pV = Yuv + W * H * 5 / 4;

    int index = 0;
    while (true)
    {
        FillYuvImage(Yuv, W, H, index++);                // 填充随机颜色到YUV数组中

        // 更新纹理数据
        int e = SDL_UpdateYUVTexture(texture, NULL,
            pY, W,
            pU, W / 2,
            pV, W / 2);

        /// 刷新渲染器:三部曲: clear, copy, present
        SDL_RenderClear(render);
        SDL_RenderCopy(render, texture, NULL, NULL);
        SDL_RenderPresent(render);
        Sleep(40);
    }
    // 释放资源,退出
    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(render);
    SDL_Quit();

    return 0;
}

SDL播放YUV文件

#define  _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <stdlib.h>
#include <string.h>

extern "C"
{
#include <SDL.h>
}
using namespace std;

const int width = 640;    
const int height = 480;

static SDL_Window *window = nullptr;
SDL_Renderer *ren = nullptr;

#undef main
int main(int argc, char **argv)
{
    SDL_Event        event;
    SDL_bool         done = SDL_FALSE;
    Uint32           pixel_format = SDL_PIXELFORMAT_IYUV;    // yuv420p
    int frame_number = 0;

    // 如果去掉“b”,文本模式读取,遇到“\0”,就会自动退出
    FILE *fp = fopen("5s_yuv420p_640x480.yuv", "rb");        

    // 设置SDL日志优先级(日志分类,优先级)
    SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);

    // 1. 初始化SDL
    if (SDL_Init(SDL_INIT_VIDEO) != 0) 
    {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
        return 3;
    }

    // 2. 创建窗口
    SDL_Window *win = SDL_CreateWindow("Hello", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_SHOWN);
    if (win == nullptr)
    {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create window: %s\n", SDL_GetError());
        return 1;
    }

    // 3. 基于窗口创建渲染器
    ren = SDL_CreateRenderer(win, 0, 0); 
    if (ren == nullptr)
    {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create renderer: %s\n", SDL_GetError());
        return 1;
    }


    // 4. 基于yuv格式创建纹理
    SDL_Texture *tex = SDL_CreateTexture(ren, pixel_format, SDL_TEXTUREACCESS_STREAMING, width, height);
    int len = height * width;
    Uint8* buf = (Uint8*)malloc(len *  2);                // 1.5倍w * h

    // SDL_BYTESPERPIXEL(pixel_format) 根据像素格式获取每个像素占用的字节数
    int iPitch = width * SDL_BYTESPERPIXEL(pixel_format);
    printf("IPath %d\n", iPitch);        // 640 一行像素数据字节数

    SDL_Rect rec;
    rec.x = 0;
    rec.y = 0;
    rec.w = width;
    rec.h = height;
    int nRets = 0;
    while (!done)
    {
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
            case SDL_KEYDOWN:
                if (event.key.keysym.sym == SDLK_ESCAPE) {
                    done = SDL_TRUE;
                }
                break;
            case SDL_QUIT:
                done = SDL_TRUE;
                break;
            }
        }
        /// 注意:每次需要的字节数: width*height*1.5
        /// Y:  width * height Bytes
        /// cb: width * height / 4
        /// cr: width * height / 4
        nRets = fread(buf, 1, height * width * 3 / 2, fp);
        if (nRets < 0) {
            break;
        }

        // 用buf缓冲区数据 更新 纹理
        SDL_UpdateTexture(tex, NULL, buf, iPitch);

        /*    刷新渲染器:三部曲:clear, copy, present */
        SDL_RenderClear(ren);
        SDL_RenderCopy(ren, tex, NULL, &rec);
        SDL_RenderPresent(ren);
        frame_number++;

        /// 音视频同步实现原理: 休眠
        SDL_Delay(40);            // 1000 / 40 = 25fps(帧率:每秒25帧)

    }

    /// 退出:清理资源
    SDL_Delay(2000);
    SDL_DestroyTexture(tex);
    SDL_DestroyRenderer(ren);
    SDL_DestroyWindow(win);
    SDL_Quit();
    fclose(fp);
    return 0;
}

SDL播放YUV文件(带缩放)

#include <stdio.h>
#include <string.h>
#include <math.h>

extern "C"
{
#include <SDL.h>
}

// 自定义事件
#define REFRESH_EVENT   (SDL_USEREVENT + 1)     // 请求画面刷新事件
#define QUIT_EVENT      (SDL_USEREVENT + 2)     // 退出事件

// 定义YUV像素分辨率        5s_yuv420p_640x480.yuv
#define YUV_WIDTH   640
#define YUV_HEIGHT  480

// 定义YUV格式
#define YUV_FORMAT  SDL_PIXELFORMAT_IYUV        // 实际为YUV420P

int s_thread_exit = 0;              // 线程退出标志 1 退出

// 线程函数
int refresh_video_timer(void *data)
{
    (void)data;
    while (!s_thread_exit)
    {
        // 每隔40ms发送一次刷新事件
        SDL_Event event;
        event.type = REFRESH_EVENT;
        SDL_PushEvent(&event);
        SDL_Delay(40);
    }
    s_thread_exit = 0;

    // 发送退出事件
    SDL_Event event;
    event.type = QUIT_EVENT;
    SDL_PushEvent(&event);

    return 0;
}

#undef main
int main()
{
    // 1. 初始化 SDL
    if (SDL_Init(SDL_INIT_VIDEO))
    {
        fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
        return -1;
    }

    uint32_t pixformat = YUV_FORMAT;            // YUV420P,即是SDL_PIXELFORMAT_IYUV

    // YUV的分辨率640x480
    int video_width = YUV_WIDTH;
    int video_height = YUV_HEIGHT;
    // 显示窗口分辨率640x480
    int win_width = YUV_WIDTH;
    int win_height = YUV_WIDTH;

    FILE *video_fd = NULL;
    const char *yuv_path = "5s_yuv420p_640x480.yuv";

    // 我们测试的文件是YUV420P格式   640x480
    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;
    // 一帧YUV数据的长度
    uint32_t yuv_frame_len = y_frame_len + u_frame_len + v_frame_len;

    // 2. 创建窗口
    SDL_Window* window = SDL_CreateWindow("Simplest YUV Player",
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        video_width, video_height,       // 320 240
        SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    if (!window)
    {
        fprintf(stderr, "SDL: could not create window, err:%s\n", SDL_GetError());
        goto _FAIL;
    }

    // 2. 基于窗口创建渲染器
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
    // 3. 基于渲染器创建纹理
    SDL_Texture* texture = SDL_CreateTexture(renderer,
        pixformat,                      // 像素格式 yuv420p
        SDL_TEXTUREACCESS_STREAMING,    // 改变是否频繁
        video_width,
        video_height);

    // 分配一帧数据长度的空间
    uint8_t *video_buf = (uint8_t*)malloc(yuv_frame_len);
    if (!video_buf)
    {
        fprintf(stderr, "Failed to alloce yuv frame space!\n");
        goto _FAIL;
    }

    // 4. 打开YUV文件(二进制方式打开)
    video_fd = fopen(yuv_path, "rb");
    if (!video_fd)
    {
        fprintf(stderr, "Failed to open yuv file\n");
        goto _FAIL;
    }
    else
    {
        printf("open file success!\n");
    }

    // 创建刷新线程
    SDL_Thread* timer_thread = SDL_CreateThread(refresh_video_timer, NULL, NULL);

    SDL_Event event;
    SDL_Rect rect;
    size_t video_buff_len;                        // 获取读取数据后buff的数据长度
    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;
            }
            // 设置纹理的数据 
            SDL_UpdateTexture(texture, NULL, video_buf, video_width);  // 参数四,一行像素数据字节数

            // 显示区域,可以通过修改w和h进行缩放
            rect.x = 0;
            rect.y = 0;
            float w_ratio = win_width  * 1.0 / video_width;           // 宽高缩放比例(倍数) 2 * 640 / 640 = 2
            float h_ratio = win_height * 1.0 / video_height;
            // 1280x960
            rect.w = video_width * w_ratio;
            rect.h = video_height * h_ratio;
            // 保持原视频的宽高比例(显示到窗口中间)
            // rect.x = fabs(video_width * w_ratio - video_width) / 2.0;
            // rect.y = fabs(video_height * h_ratio - video_height) / 2.0;
            // rect.w = video_width;
            // rect.h = video_height;

            // 渲染三部曲
            SDL_RenderClear(renderer);
            SDL_RenderCopy(renderer, texture, NULL, &rect);
            SDL_RenderPresent(renderer);
        }
        else if (event.type == SDL_WINDOWEVENT)    
        {
            // 如果窗口大小改变(会触发该事件获取到窗口的高度和宽度)
            SDL_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;
}
相关文章
|
27天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
4天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
438 17
|
7天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
20天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
7天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
379 2
|
22天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
24天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2600 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
6天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
286 2
|
4天前
|
编译器 C#
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
106 65
|
24天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1582 17
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码