Featured image of post 【雷霄骅课程笔记】2 SDL 视频播放器

【雷霄骅课程笔记】2 SDL 视频播放器

跟着雷神的课程学习写的一些笔记

|
1417 字
|

SDL 视频播放

视频链接

SDL 介绍

SDL 视频显示流程

流程图

函数介绍

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

SDL 视频显示的数据结构

解释:

  • SDL_Texture:纹理,一个纹理对应一个 YUV
    • 一个窗口不一定只有一个纹理,可以放很多个
  • SDL_RectSDL 中的一个结构体,用于描述一个矩形的位置和尺寸
  • SDL_Renderer:渲染器,把纹理的数据给窗口
  • SDL_Window:窗口

代码运行

代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104


#include <stdio.h>

extern "C"
{
#include "SDL2/SDL.h"
};

const int bpp = 12;

// 窗口的宽度和高度
int screen_w = 1000, screen_h = 1000;

// 视频像素的宽度和高度
const int pixel_w = 320, pixel_h = 180;

// 用于存储一帧 YUV 数据的缓冲区
unsigned char buffer[pixel_w * pixel_h * bpp / 8];

int main(int argc, char *argv[])
{
    // 初始化 SDL 库
    if (SDL_Init(SDL_INIT_VIDEO))
    {
        printf("Could not initialize SDL - %s\n", SDL_GetError());
        return -1;
    }

    // 创建窗口
    SDL_Window *screen;
    // SDL 2.0 Support for multiple windows
    // title 窗口标题,x y 窗口位置,w h 窗口大小
    // flags 标志包括允许改变窗口大小(SDL_WINDOW_RESIZABLE)和使用OpenGL(SDL_WINDOW_OPENGL
    screen = SDL_CreateWindow("Simplest Video Play SDL2", SDL_WINDOWPOS_CENTERED,
                              SDL_WINDOWPOS_UNDEFINED, screen_w, screen_h,
                              SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    if (!screen)
    {
        printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
        return -1;
    }

    // 创建渲染器,将窗口与渲染器关联
    // index 使用默认的渲染驱动,flags 标志为 0 (不使用加速功能)
    SDL_Renderer *sdlRenderer = SDL_CreateRenderer(screen, -1, 0);

    Uint32 pixformat = 0;
    // IYUV: Y + U + V  (3 planes)
    // YV12: Y + V + U  (3 planes)
    // 设置像素格式
    // SDL_PIXELFORMAT_IYUV 表示使用 YUV420
    pixformat = SDL_PIXELFORMAT_IYUV;

    // 创建纹理,用于存储视频数据
    // pixformat 使用之前设置的像素格式 IYUV,access 纹理访问模式为流式访问(适合频繁更新纹理数据)
    // w h 纹理宽度和高度
    SDL_Texture *sdlTexture =
        SDL_CreateTexture(sdlRenderer, pixformat, SDL_TEXTUREACCESS_STREAMING, pixel_w, pixel_h);

    FILE *fp = NULL;
    fp = fopen("../video/test_yuv420p_320x180.yuv", "rb+");
    if (fp == NULL)
    {
        printf("cannot open this file\n");
        return -1;
    }

    SDL_Rect sdlRect;

    while (1)
    {
        // 从文件中读取一帧 YUV 数据到 buffer 中
        if (fread(buffer, 1, pixel_w * pixel_h * bpp / 8, fp) != pixel_w * pixel_h * bpp / 8)
        {
            // 重新定位文件指针实现循环播放
            fseek(fp, 0, SEEK_SET);
            fread(buffer, 1, pixel_w * pixel_h * bpp / 8, fp);
        }

        // 更新纹理中的数据,将 buffer 中的内容传递给纹理
        // pitch 每行像素占用的字节数
        SDL_UpdateTexture(sdlTexture, NULL, buffer, pixel_w);

        // 设置渲染区域
        // 这表示这个矩形从屏幕的左上角(0, 0)开始,宽度和高度与屏幕相同,即覆盖整个屏幕
        sdlRect.x = 10;
        sdlRect.y = 10;
        sdlRect.w = screen_w - 20;
        sdlRect.h = screen_h - 20;

        // 清楚渲染器
        SDL_RenderClear(sdlRenderer);
        // 复制纹理到渲染器
        SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
        // 呈现渲染结果
        // 将渲染器中的后缓冲区内容呈现到前缓冲区,即更新屏幕显示内容
        SDL_RenderPresent(sdlRenderer);
        // 延迟以控制帧率
        SDL_Delay(40);
    }
    SDL_Quit();
    return 0;
}

这里讲一下循环中读取数据到 buffer

1
if (fread(buffer, 1, pixel_w * pixel_h * bpp / 8, fp) != pixel_w

第二个参数是每个元素的字节数,第三个参数是要读取的元素数量
成功读取的话是返回读取的元素数量
元素数量是视频宽度 * 视频高度 * 每像素占用的位数(bppbpp (Bits Per Pixel)的计算如下:

  • Y(亮度)平面:每个像素占8位。
  • U(色度)平面:每个像素占8位,但水平和垂直方向各下采样一倍,因此每个宏像素的 U 和 V 各占4位。
  • V(色度)平面:同上。

所以总共有:

  • Y 平面:width × height × 8 位
  • U 平面:(width/2) × (height/2) × 8 位
  • V 平面:(width/2) × (height/2) × 8 位

总位数为:
width × height × 8 + (width/2) × (height/2) × 8 × 2 = width × height × (8 + 2 + 2) = width × height × 12 位
因此,每个像素平均占用12位。

CMakeLists.txt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 指定 CMake 的最低版本要求
cmake_minimum_required(VERSION 3.10)

# 设置项目名称和版本
project(MyProject VERSION 1.0)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_BUILD_TYPE Debug)

# 依赖 compile_commands.json 文件来理解项目的编译环境
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# 添加头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 添加库文件路径
link_directories(${PROJECT_SOURCE_DIR}/lib)

# 添加可执行文件
add_executable(main src/videoPlayer.cpp)

# 链接 FFmpeg 库
target_link_libraries(main avcodec avformat avutil)

# 链接 SDL2 库·
target_link_libraries(main SDL2 SDL2main)
使用 Hugo 构建
主题 StackJimmy 设计