1. 程式人生 > >[2] SDL的基礎知識以及利用SDL播放視訊

[2] SDL的基礎知識以及利用SDL播放視訊

日期:2016.10.1
作者:isshe
github:github.com/isshe
郵箱:[email protected]

SDL基礎知識

  • SDL結構圖
    這裡寫圖片描述

  • SDL函式呼叫的一般流程
    這裡寫圖片描述

    • 最最主要操作的函式是SDL_texture();
    • 工作過程大致是:FFMpeg「Decode」解碼一幀,交給SDL_texture(), 然後再複製給渲染器,渲染器再顯示出來。以此迴圈。
    • 相關函式:待補充(不定期更新)
  • SDL的一些主要的資料結構
    這裡寫圖片描述

    • SDL2支援多視窗顯示,主要是依靠SDL_rect().
    • 相關資料結構:待補充(不定期更新)

示例程式碼1:

  • 程式碼
#include <stdio.h>

#include "SDL2/SDL.h"

#define SCREEN_W    640             //視窗的寬
#define SCREEN_H    360             //視窗的高
#define PIXEL_W     320             //視訊畫素的寬,要和視訊檔案相同才能顯示正常
#define PIXEL_H     180             //畫素的高
#define BPP         12              //畫素深度:指儲存每個畫素所用的位數(bit)
#define BUF_LEN     ((PIXEL_W) * (PIXEL_H) * (BPP) / 8)     //存一幀的需要空間
const int bpp = BPP; int screen_w = SCREEN_W; int screen_h = SCREEN_H; const int pixel_w = PIXEL_W; const int pixel_h = PIXEL_H; unsigned char buffer[BUF_LEN+1]; int main(int argc, char* argv[]) { 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 //畫一個視窗,大小為screen_w * screen_h screen = SDL_CreateWindow("Simplest Video Play SDL2", SDL_WINDOWPOS_UNDEFINED, 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; } //新建一個渲染器 SDL_Renderer* sdlRenderer = SDL_CreateRenderer(screen, -1, 0); Uint32 pixformat=0; //IYUV: Y + U + V (3 planes) //YV12: Y + V + U (3 planes) pixformat= SDL_PIXELFORMAT_IYUV; //??? SDL_Texture* sdlTexture = SDL_CreateTexture(sdlRenderer,pixformat, SDL_TEXTUREACCESS_STREAMING,pixel_w,pixel_h); FILE *fp=NULL; fp=fopen("test_yuv420p_320x180.yuv","rb+"); if(fp==NULL){ printf("cannot open this file\n"); return -1; } SDL_Rect sdlRect; int i = 5; while(i >= 0){ //一次讀1byte,總共讀一幀 if (fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp) != pixel_w*pixel_h*bpp/8){ // Loop fseek(fp, 0, SEEK_SET); // fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp); i--; continue; } //更新紋理資料 SDL_UpdateTexture( sdlTexture, NULL, buffer, pixel_w); //(x,y)是視窗左上邊開始的點。 //w,h是整個畫素視窗寬和高(注意不是整個視窗) sdlRect.x = 0; sdlRect.y = 0; sdlRect.w = screen_w; sdlRect.h = screen_h; //清空渲染器 //複製資料紋理給渲染器 //顯示 SDL_RenderClear( sdlRenderer ); SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, &sdlRect); SDL_RenderPresent( sdlRenderer ); //Delay 40ms,一般視訊都是這個,25幀/s. SDL_Delay(40); } SDL_Quit(); return 0; }
  • 編譯:
gcc1_SDL_create_window.c -o 1_SDL_create_window.out -O2 -Wall -g -lSDL2 -lSDL2main

結果截圖:
這裡寫圖片描述
* 注意,這個程式如果是在windows下面,視窗是不能移動的,滑鼠放上去也是忙的狀態。在下一個程式中修改程式,使它能移動,以及自動適應視窗大小。

示例程式2:

  • 程式碼:
#include <stdio.h>
#include <errno.h>
#include <string.h>

#include <SDL2/SDL.h>

#define SCREEN_W    640             //視窗的寬
#define SCREEN_H    360             //視窗的高
#define PIXEL_W     320             //視訊畫素的寬,要和視訊檔案相同才能顯示正常
#define PIXEL_H     180             //畫素的高
#define BPP         12              //畫素深度:指儲存每個畫素所用的位數(bit)
#define BUF_LEN     ((PIXEL_W) * (PIXEL_H) * (BPP) / 8)     //存一幀的需要空間
#define FILENAME    "test_yuv420p_320x180.yuv"
#define MY_DEFINE_REFRESH_EVENT     (SDL_USEREVENT + 1)
#define MY_DEFINE_BREAK_EVENT       (SDL_USEREVENT + 2)

int thread_exit = 0;
static int refresh_func(void *arg)
{
    SDL_Event   event;
    thread_exit = 0;

    while(0 == thread_exit)
    {
        event.type = MY_DEFINE_REFRESH_EVENT;
        SDL_PushEvent(&event);      //傳送一個事件,使主執行緒繼續執行
        SDL_Delay(40);
    }

    //子執行緒退出後傳送事件給主執行緒,使主執行緒也退出
    thread_exit = 0;
    event.type = MY_DEFINE_BREAK_EVENT;
    SDL_PushEvent(&event);
    return 0;
}

const int bpp = BPP;

int main(int argc, char *argv[])
{
    int screen_w = SCREEN_W;
    int screen_h = SCREEN_H;
    const int pixel_w = PIXEL_W;
    const int pixel_h = PIXEL_H;
    unsigned char buffer[BUF_LEN + 1];      //注意型別
    char            filename[256] = FILENAME;

    SDL_Window      *screen = NULL;         //視窗資料結構
    SDL_Renderer    *sdlRenderer = NULL;    //渲染器資料結構
    Uint32          pixformat = 0;
    SDL_Texture     *sdlTexture = NULL;     //主要操作的
    FILE            *fp = NULL;
    SDL_Rect        sdlRect;
//    SDL_Thread      *refresh_thread = NULL; //執行緒資料結構
    SDL_Event       event;                  //事件資料結構

    //注意:可以把檔案傳進來了,但是如果不使用ffmmpeg的函式還不知道怎麼改畫素值,以使視訊正常顯示!!!!!
    if (argc > 2)
    {
         printf("Usage: ./*.out videofile.yuv\n");
         return 0;
    }
    else if (argc == 2)
    {
         memcpy(filename, argv[1], strlen(argv[1]) + 1);
//         filename[strlen(argv[1])] = '\0';
    }
    printf("video file name: %s\n", filename);

    if (SDL_Init(SDL_INIT_VIDEO))
     {
         printf("Couldn't initialize SDL - %s\n", SDL_GetError());
         return (-1);
     }

    screen = SDL_CreateWindow("isshe Video Player SDL2",
            SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
            screen_w, screen_h,
            SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    if (!screen)
    {
         printf("SDL:Couldn't not create window error: %s\n", SDL_GetError());
         return (-1);
    }

    //建立渲染器,-1,0不懂什麼意思,再看這個函式的定義
    sdlRenderer = SDL_CreateRenderer(screen, -1, 0);

    //在pixels.h中,大概是指定輸入資料格式?不懂!
    pixformat = SDL_PIXELFORMAT_IYUV;

    sdlTexture = SDL_CreateTexture(sdlRenderer, pixformat,
            SDL_TEXTUREACCESS_STREAMING, pixel_w, pixel_h);

    //開啟檔案
    fp = fopen(filename, "r");
    if (NULL == fp)
    {
        printf("Open file error:%s\n", strerror(errno));
        return (-1);
    }

    //新建執行緒
//    refresh_thread =
    SDL_CreateThread(refresh_func, NULL, NULL);

    while(1)
    {
         //等待一個事件
         SDL_WaitEvent(&event);     //事件的資訊存到結構中了

         //處理事件, 嘗試使用自定義的事件
         if (event.type == MY_DEFINE_REFRESH_EVENT)
         {
              //讀一幀
              if (fread(buffer, 1, BUF_LEN, fp) != BUF_LEN) //出錯或結尾
              {
                  //重定位會檔案頭部
                  fseek(fp, 0, SEEK_SET);
                  continue;         //
              }

              //更新紋理,但是不懂最後一個引數,是一次更新一行嗎?
              SDL_UpdateTexture(sdlTexture, NULL, buffer, pixel_w);

              sdlRect.x = 0;
              sdlRect.y = 0;
              sdlRect.w = screen_w;
              sdlRect.h = screen_h;

              SDL_RenderClear(sdlRenderer);
              //把資料從第二個引數複製到第一個引數
              SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
              SDL_RenderPresent(sdlRenderer);
         }
         else if (event.type == SDL_WINDOWEVENT)
         {
              //獲取畫素視窗的大小,視窗拉伸的時候用這個則會自動調整
              SDL_GetWindowSize(screen, &screen_w, &screen_h);
         }
         else if (event.type == SDL_QUIT)
         {
             thread_exit = 1;
         }
         else if (event.type == MY_DEFINE_BREAK_EVENT)    //執行緒結束,主執行緒也結束
         {
              break;
         }
    }

    fclose(fp);
    SDL_Quit();
    return 0;
}
  • 程式中主執行緒阻塞等待事件,子執行緒傳送事件後主執行緒繼續執行。
  • 程式中自定義了兩個事件型別,用以說明事件型別可自定義。

  • 編譯:

gcc  2_SDL_pthread_event.c -o 2_SDL_pthread_event.out -O2 -Wall -g -lSDL2 -lSDL2main
  • 執行結果:
    這裡寫圖片描述
    • 可以隨意拉伸視窗。
    • 可以關閉。

參考資料:

  • 雷神的視訊
  • SDL2.0 原始碼