SDL2+SDL_Thread+SDL_Event实现yuv文件的播放

发布时间 2023-11-14 17:18:34作者: 飘杨......

一、概述

  上一节使用单线程播放了YUV文件。在一个线程中播放yuv文件逻辑看起来简单,但是会产生一些问题。如:视频卡顿、无响应等问题。

  本节在上一节的基础上对播放YUV文件的代码进行改造,加入SDL_Event和SDL_Thread。使SDL_Thread现成发出命令时刷新YUV视频帧。等收到结束命令时退出整个应用程序

  参考:雷霄骅大神的博客

二、代码示例

#include "include/sdl_read_yuv.h"
#include <iostream>
using namespace std;

//Refresh Event
#define REFRESH_EVENT  (SDL_USEREVENT + 1)

//Break
#define BREAK_EVENT  (SDL_USEREVENT + 2)
int thread_exit = 0;

int refresh_video(void* args) {
    thread_exit = 0;
    while (thread_exit == 0) {
        //每隔40毫秒推送一次刷新事件
        SDL_Event event;
        event.type = REFRESH_EVENT;
        SDL_PushEvent(&event);
        SDL_Delay(40);
    }
    thread_exit = 0;
    //break
    SDL_Event event;
    event.type = BREAK_EVENT;
    SDL_PushEvent(&event);//将结束事件推送出去
    return 0;
}
SDLReadYuv::SDLReadYuv() {

    //window窗体的宽高
    int window_w = 640, window_h = 360;
    //像素的宽高
    const int pixel_w = 640, pixel_h = 360;
    //设置一帧像素的容量
    unsigned char buffer[pixel_w * pixel_h * 3/2];

    if (SDL_Init(SDL_INIT_VIDEO)) {
        cout << "初始化SDL失败" << SDL_GetError() << endl;
        return;
    }

    SDL_Window* window;
    window = SDL_CreateWindow("Simplest Video Play SDL2",
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        window_w,
        window_h,
        SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);

    if (!window) {
        cout << "SDL: could not create window" << SDL_GetError() << endl;
        return;
    }

    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);

    Uint32 pixFormat = 0;
    pixFormat = SDL_PIXELFORMAT_IYUV;

    SDL_Texture* texture = SDL_CreateTexture(renderer, pixFormat, SDL_TEXTUREACCESS_STREAMING, pixel_w, pixel_h);

    FILE* fp = NULL;
    fp = fopen("E:/tony/demo/visualstudio_workspace/SDLDemo/out/build/x64-debug/SDLDemo/yuv/sintel_640_360.yuv", "rb+");
    if (fp == NULL) {
        cout << "cannot open this file" << endl;
        return;
    }

    //这个区域会存放显示的视频
    SDL_Rect sdlRect;

    //初始化SDL事件
    SDL_Event event;
    //创建一个SDL线程,SDL_CreateThread最后一个参数传递的参数
    SDL_Thread* thread = SDL_CreateThread(refresh_video,NULL,NULL);

    while (true) {
        //等待SDL事件进入
        SDL_WaitEvent(&event);
        //收到刷新事件对页面进行刷新
        if (event.type == REFRESH_EVENT) {
            //这里是读取一帧视频真,数据格式是YUV420P,像素排列是4:2:0,一行像素=width*height+width*1/4+height*1/4 = width*height*3/2
        //所以下面这句话刚好就是读取了一个视频帧YUV的数据长度
            if (fread(buffer, 1, pixel_w * pixel_h * 3/2, fp) != pixel_w * pixel_h * 3/2) {
                // Loop
                fseek(fp, 0, SEEK_SET);
                fread(buffer, 1, pixel_w * pixel_h * 3/2, fp);//读取一帧数据的容量并放入buffer中
            }

            SDL_UpdateTexture(texture, NULL, buffer, pixel_w);

            sdlRect.x = 0;
            sdlRect.y = 0;
            sdlRect.w = window_w;
            sdlRect.h = window_h;//把视频就显示到这个区域

            SDL_RenderClear(renderer);
            SDL_RenderCopy(renderer, texture, NULL, &sdlRect);
            SDL_RenderPresent(renderer);
        }
        else if (event.type == SDL_WINDOWEVENT) {
            //resize
            SDL_GetWindowSize(window, &window_w, &window_h);
        }
        else if (event.type == SDL_QUIT) {//点击右上角的叉号退出线程
            thread_exit = 1;
        }
        else if (event.type == BREAK_EVENT) {//退出标志
            break;
        }
        


    }

    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();

}