SDL 介绍

目录

一、SDL的使用

二、SDL 窗口的渲染

三、SDL 事件基本原理

四、纹理渲染

渲染基本原理

五、实现YUV播放器

六、SDL播放音频(PCM数据)


一、SDL的使用

加头文件 #include<SDL.h>

初始化SDL

退出SDL

SDL 主要用在渲染

SDL 渲染窗口

初始化:SDL_Init(),SDL_Quit()

创建窗口:SDL_CreatWindow()    销毁窗口, SDL_DestoryWindow()

创建一个渲染器:SDL_CreateRender(),作用是把图像帧会到窗口里面去。

int main(){

SDL_window *window =NULL;

SDL_Init(SDL_INIT_VIDEO) ;

//200,200 显示的位置
//640,680,窗口宽高,
//SDL_WINDOW_SHOWN|SDL_WINDOW_BORDER:窗口标志位
window =SDL_Createwindow("window_title",200,200,640,480,SDL_WINDOW_SHOWN|SDL_WINDOW_BORDER);

if(!window){
    goto __EXIT;
}

SDL_DestoryWindow(windou);//销毁窗口

__EXIT:
    SDL_Quit() ;
    return 0;

}

以上一段代码其实是在系统内存里面分配了一块空间,要将东西显示出来,就要去渲染,把结果填入这个window内存里。

二、SDL 窗口的渲染:

所以就要创建一个渲染器:SDL_CreateRender()   ,销毁一个渲染器:SDL_DestoryRender()

SDL_RenderClear();这个方法要调用一下,避免里面有一些脏数据。

SDL_RenderPresent():把数据推送到驱动。(其实是先推到平台的引擎去,引擎再拷贝到显卡驱动,再到屏幕)

int main(){

SDL_window *window =NULL;
SDL_Renderer *render =NULL;

SDL_Init(SDL_INIT_VIDEO) ;

//200,200 显示的位置
//640,680,窗口宽高,
//SDL_WINDOW_SHOWN|SDL_WINDOW_BORDER:窗口标志位
window =SDL_Createwindow("window_title",200,200,640,480,SDL_WINDOW_SHOWN|SDL_WINDOW_BORDER);

if(!window){
    goto __EXIT;
}

render  = SDL_CreateRender(window,-1,0);

SDL_SetRenderDrawColor(render,255,0,0,255/*透明度,255不退出*/);//换个红色

SDL_RenderClear(render);//把之前的数据清掉

SDL_RenderPresent(render);

SDL_Delay(30000);//延时显示30S

SDL_DestoryWindow(windou);//销毁窗口

__EXIT:
    SDL_Quit() ;
    return 0;

}

三、SDL 事件基本原理:

SDL  将所有的事件都存放在一个队列中。所有对事件的操作,其实就是对队列的操作,基本对事件的处理都是这样的。

SDL 事件种类

SDL_WindowEvent:窗口事件,如全屏,最小化,关闭窗口

SDL_KeyboardEvent: 键盘事件

SDL_MouseMotionEvent:鼠标事件

SDL如何处理事件:

SDL_PollEvent  :轮询 传统的方式,用一个线程处理这个队列,比如每隔一秒去检测一下队列,看队列里面是否有事件了,有事件就处理。轮询这种方式有一个问题就是处理的不够及时。

SDL_WaitEvent :事件触发机制,当有一个事件来了之后,就发送一个信号,有一个线程在监听这个信号,这个线程会被激活,那么这个事件就会立马被处理。

SDL_WaitEventTimeout。SDL_WaitEvent的完善,设置一个等待时间,如果操作这个时间没有事件过来,就主动去查看一下,避免事件因为默写原因卡了,又导致队列其他事件不能上来。

int main(){

SDL_window *window =NULL;
SDL_Renderer *render =NULL;
int quit=1;
SDL_Event event;

SDL_Init(SDL_INIT_VIDEO) ;

//200,200 显示的位置
//640,680,窗口宽高,
//SDL_WINDOW_SHOWN|SDL_WINDOW_BORDER:窗口标志位
window =SDL_Createwindow("window_title",200,200,640,480,SDL_WINDOW_SHOWN|SDL_WINDOW_BORDER);

if(!window){
    goto __EXIT;
}

render  = SDL_CreateRender(window,-1,0);

SDL_SetRenderDrawColor(render,255,0,0,255/*透明度,255不退出*/);//换个红色

SDL_RenderClear(render);//把之前的数据清掉

SDL_RenderPresent(render);

//SDL_Delay(30000);//延时显示30S

do{//添加事件监听
    
    SDL_WaitEvent(&event);
    switch(event.type){
    case SDL_QUIT:
        quite=0;
        break;
    default:
        
        SDL_Log("event type is %d",event.type);
    }
}while(quit);

SDL_DestoryWindow(windou);//销毁窗口

__EXIT:
    SDL_Quit() ;
    return 0;

}

四、纹理渲染:

假设画一条直线,这条直线有起点,终点。线有渐变的颜色。对于RGB 来说,每一个像素点都要被描述出来,R,G,B 每个有个成分是多少,都要直接填在内存,这就显得数据非常大了。对于纹理来说,他不会这么描述这么一条线,他只会告诉你这条线的起点,终点的位置,中间的颜色是怎么渐变的,它会将这些描述信息直接告诉显卡。所以,这条线全部占用的内存很小,非常节约内存。这些渐变信息,通过显卡GPU的计算,得到的数据,就是RGB 的数据了。

渲染基本原理:

图像通过渲染器,把内存中的图变成纹理,这样我们就有了描述这种图的数据了,有了纹理后,我们就把它交到显卡,显卡通过计算还原成RGB 最终放到窗口显示。

SDL纹理相关API:

SDL_CreateTexture() :创建纹理

参数,format:YUV ,RGB

           access:Texture 类型,即是Target ,还是Stream 呢,Stream 表示视屏

SDL_DestroyTexTure():销毁纹理

SDL 渲染相关的API:

SDL_SetRenderTartget():往哪儿渲染,默认的是我们绘制的window上渲染。但是我们使用纹理,我们就要把目标改成TexTure,往纹理上渲染。

SDL_RenderClear()。

SDL_RenderCopy():将纹理拷贝到显卡上,显卡开始计算出最终图形

SDL_RenderPresent() :显卡计算出最终图形后,我们可以控制它,最终要不要渲染到窗口上。要不要投影到显示器上。

int main(){

SDL_window *window =NULL;
SDL_Renderer *render =NULL;
SDL_Texture * texture =NULL;
int quit=1;
SDL_Event event;
SDL_Rect rect;
SDL_Init(SDL_INIT_VIDEO) ;

//200,200 显示的位置
//640,680,窗口宽高,
//SDL_WINDOW_SHOWN|SDL_WINDOW_BORDER:窗口标志位
window =SDL_Createwindow("window_title",200,200,640,480,SDL_WINDOW_SHOWN|SDL_WINDOW_BORDER);

if(!window){
    goto __EXIT;
}

render  = SDL_CreateRender(window,-1,0);//-1,0 暂时不用理解
if(!render){
    SDL_Log(“failed 头 create Render!”);
    goto __DWINDOW;
}


/*
SDL_SetRenderDrawColor(render,255,0,0,255/*透明度,255不退出*/);//换个红色

SDL_RenderClear(render);//把之前的数据清掉

SDL_RenderPresent(render);*/

//SDL_Delay(30000);//延时显示30S
 texture = SDL_CreateTexture(render,SDL_PIXELFORMAT_RGBA8888,
                                                 SDL_TEXTUREACCESS_TARGET,
                                                    640,480);
if(!texture){
    SDL_Log(“failed 头 create Texture!”);
    goto __RENDER;
}

do{//添加事件监听
    
    SDL_WaitEvent(&event);
    switch(event.type){
    case SDL_QUIT:
        quite=0;
        break;
    default:
        
        SDL_Log("event type is %d",event.type);
    }

    
    rect.w=30;
    rect.h=30;
    rect.x=rand()%600;
    recty=rand()%450;
    
    //
    SDL_SetRenderTarget(render,texture);//改变渲染的目标是纹理
    SDL_SetRenderDrawColor(render,0,0,0,0)//设置背景颜色
    SDL_RenderClear(rener);

    SDL_RenderDrawRect(render,&rect);//画方块
    SDL_SetRenderDrawColor(render,255,0,0,0)//把方块设置成红色
    SDL_RenderFillRect(render,&rect);/填充方块

    SDL_SetRenderTarget(render,NULL);//把纹理渲染到窗口去,这个时候,恢复成默认的window
    SDL_RenderCopy(render,texture,NULL,NULL)//把纹理交给显卡

    SDL_RenderPresent(render);//显卡计算完,显不显示,由着个函数决定
    
}while(quit);

SDL_DestroyTexture(texture);



__RENDER:
    
__DWINDOW:
    SDL_DestoryWindow(windou);//销毁窗口

__EXIT:
    SDL_Quit() ;
    return 0;

}

五、实现YUV播放器

①创建线程

SDL_CreateThread() 

fn:线程函数

name: 线程名字

data:执行函数参数

②SDL 更新纹理,创建销毁前面讲过 了,更新是什么呢?对于视屏播放的时候,1秒钟会播放很多帧,对于每一帧的图片的变更,我们都要更新一次纹理

SDL_UpdateTexture() :参数是 图片的起始地址,和宽度

SDL_UpdateYUVTexture():效率高,参数是很多Y 分量地址,U分量地址,V分量地址 ....

③YUV 知识

YUV444 :4个Y分量对应4个U分量对应4个V分量,为1:1:1.。

YUV420:4个Y分量对应2个U分量对应0个V分量,再4个Y分量对应0个U分量对应2个V分量

YUV 的存储模式:平坦模式,交叉模式

④实现框架

25帧的视频,那么就是40ms 一副图片要被送去渲染,

设置一个线程,控制播放速度。40ms触发一次事件,事件通知到主线程,主线程收到这个消息之后,就去SDL_UpdateTexture ,更新一下下一幅图片。

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

#include <SDL.h>

const int bpp=12;

int screen_w=500,screen_h=500;

#define BLOCK_SIZE 4096000

//event message
#define REFRESH_EVENT  (SDL_USEREVENT + 1)
#define QUIT_EVENT  (SDL_USEREVENT + 2)

int thread_exit=0;

int refresh_video_timer(void *udata){

    thread_exit=0;

    while (!thread_exit) {
        SDL_Event event;
        event.type = REFRESH_EVENT;
        SDL_PushEvent(&event);
        SDL_Delay(40);
    }

    thread_exit=0;

    //push quit event
    SDL_Event event;
    event.type = QUIT_EVENT;
    SDL_PushEvent(&event);

    return 0;
}

int main(int argc, char* argv[])
{

    FILE *video_fd = NULL;

    SDL_Event event;
    SDL_Rect rect;

    Uint32 pixformat = 0;

    SDL_Window *win = NULL;
    SDL_Renderer *renderer = NULL;
    SDL_Texture *texture = NULL;

    SDL_Thread *timer_thread = NULL;

    int w_width = 640; w_height = 480;
    const int video_width = 320, video_height = 180;

    Uint8 *video_pos = NULL;
    Uint8 *video_end = NULL;

    unsigned int remain_len = 0;
    unsigned int video_buff_len = 0;
    unsigned int blank space_len = 0;
    Uint8 *video_buf[BLOCK_SIZE];

    const char *path = "test_yuv420p_320x180.yuv";

    const unsigned int yuv_frame_len = video_width * video_height * 12 / 8;

    //initialize SDL
    if(SDL_Init(SDL_INIT_VIDEO)) {
        fprintf( stderr, "Could not initialize SDL - %s\n", SDL_GetError());
        return -1;
    }

    //creat window from SDL
    win = SDL_CreateWindow("YUV Player",
                           SDL_WINDOWPOS_UNDEFINED,
                           SDL_WINDOWPOS_UNDEFINED,
                           w_width, w_height,
                           SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);
    if(!win) {
        fprintf(stderr, "Failed to create window, %s\n",SDL_GetError());
        goto __FAIL;
    }

    renderer = SDL_CreateRenderer(screen, -1, 0);

    //IYUV: Y + U + V  (3 planes)
    //YV12: Y + V + U  (3 planes)
    pixformat= SDL_PIXELFORMAT_IYUV;

    //create texture for render
    texture = SDL_CreateTexture(renderer,
                                pixformat,
                                SDL_TEXTUREACCESS_STREAMING,
                                video_width,
                                video_height);

    //open yuv file
    video_fd = fopen(path, "r");
    if( !video_fd ){
        fprintf(stderr, "Failed to open yuv file\n");
        goto __FAIL;
    }

    //read block data
    if(video_buff_len = fread(video_buf, 1, BLOCK_SIZE, video_fd) <= 0){
        fprintf(stderr, "Failed to read data from yuv file!\n");
        goto __FAIL;
    }

    //set video positon
    video_pos = video_buf;
    video_end = video_buf + video_buff_len;
    blank_space_len = BLOCK_SIZE - video_buff_len;

    timer_thread = SDL_CreateThread(refresh_video_timer,
                                    NULL,
                                    NULL);

    do {
        //Wait
        SDL_WaitEvent(&event);
        if(event.type==REFRESH_EVENT){
            //not enought data to render
            if((video_pos + yuv_frame_len) > video_end){

                //have remain data, but there isn't space
                remain_len = video_end - video_pos;
                if(remain_len && !black_space_len) {
                    //copy data to header of buffer
                    memcpy(video_buf, video_pos, remain_len);

                    blank_space_len = BLOCK_SIZE - remain_len;
                    video_pos = video_buf;
                    video_end = video_buf + remain_len;
                }

                //at the end of buffer, so rotate to header of buffer
                if(video_end == (video_buf + BLOCK_SIZE)){
                    video_pos = video_buf;
                    video_end = video_buf;
                    blank_space_len = BLOCK_SIZE;
                }

                //read data from yuv file to buffer
                if(video_buff_len = fread(video_end, 1, blank_space_len, video_fd) <= 0){
                    fprintf(stderr, "eof, exit thread!");
                    thread_exit = 1;
                    continue;// to wait event for exiting
                }

                //reset video_end
                video_end += video_buff_len;
            }

            SDL_UpdateTexture( texture, NULL, video_pos, video_width);

            //FIX: If window is resize
            rect.x = 0;
            rect.y = 0;
            rect.w = w_width;
            rect.h = w_height;

            SDL_RenderClear( renderer );
            SDL_RenderCopy( renderer, texture, NULL, &rect);
            SDL_RenderPresent( renderer );

        }else if(event.type==SDL_WINDOWEVENT){
            //If Resize
            SDL_GetWindowSize(win, &w_width, &w_height);
        }else if(event.type==SDL_QUIT){
            thread_exit=1;
        }else if(event.type==QUIT_EVENT){
            break;
        }
    }while ( 1 );

__FAIL:

    //close file
    if(video_fd){
        fclose(video_fd);
    }

    SDL_Quit();

    return 0;
}

 

六、SDL播放音频(PCM数据)

 

播放音频的基本原则:

声卡向你要数据而不是你主动推给声卡,声卡播放完了数据,会回调一个函数,告诉你我需要数据了。在这个函数里,把数据放到它指定的buffer里面。

推送的数据多少由音频参数决定的

SDL 音频API :

SDL_OpenAudio():打开音频设备

SDL_CloseAudio():关闭音频设备

SDL_PauseAudio():开始、暂停播放

SDL_MixAudio():混音,将多路的音频混在一起输给声卡。可以理解为把数据拷贝到声卡设备

实现PCM播放器:

#include <stdio.h>
#include <SDL.h>

#define BLOCK_SIZE 4096000

static Uint8 *audio_buf = NULL;
static Uint8 *audio_pos = NULL;
static size_t buffer_len = 0;

//callback function for audio devcie
void read_audio_data(void *udata, Uint8 *stream, int len){

    if(buffer_len == 0){
        return;
    }

    SDL_memset(stream, 0, len);//对stream清零

    len = (len < buffer_len) ? len : buffer_len;
	//SDL_MIX_MAXVOLUME 设置音量
    SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);//把数据拷贝到stream

    audio_pos += len;
    buffer_len -= len;
}


int main(int argc, char *argv[])
{
    int ret = -1;

    FILE *audio_fd = NULL;

    SDL_AudioSpec spec;

    char *path = "./test.pcm";

    //SDL initialize,对音频,视屏等初始化
    if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){
        fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
        return ret;
    }

    //open pcm file
    audio_fd = fopen(path, "r");
    if(!audio_fd){
        fprintf(stderr, "Failed to open pcm file!\n");
        goto __FAIL;
    }

    //alloc memory for audio
    audio_buf = (Uint8*)malloc(BLOCK_SIZE);
    if(!audio_buf){
        goto __FAIL;
    }

    //SDL_AudioSpec
    spec.freq = 44100;;
    spec.format = AUDIO_S16SYS;//16位
    spec.channels = 2;
    spec.silence = 0;
    spec.samples = 2048;;
    spec.callback = read_audio_data;;
    spec.userdata = NULL;//回调回来的参数,目前不需要

    //open audio devcie
    if(SDL_OpenAudio(&spec, NULL)){
        fprintf(stderr, "Failed to open audio device, %s\n", SDL_GetError());
        goto __FAIL;
    }

    //play audio
    SDL_PauseAudio(0);//0,表示播放  1表示暂停

    do{
        //read data from pcm file
        buffer_len = fread(audio_buf, 1, BLOCK_SIZE, audio_fd);
        fprintf(stderr, "block size is %zu\n", buffer_len);

        audio_pos = audio_buf;

        //the main thread wait for a moment
        while(audio_pos < (audio_buf + buffer_len)) {//说明audio_buf 里面还有数据,等设备先消耗数据,直到audio_pos = (audio_buf + buffer_len),数据消耗完了,我们在从audio_fd里面读取数据。还有一种算法是,设备消耗了多少,这边补多少数据在audio_buf,比较难一点。
            SDL_Delay(1);
        }

    }while(buffer_len !=0);

    //close audio device
    SDL_CloseAudio();//这里需要优化。

    ret = 0;

__FAIL:
    //release some resources
    if(audio_buf){
        free(audio_buf);
    }

    if(audio_fd){
        fclose(audio_fd);
    }

    //quit SDL
    SDL_Quit();

    return ret;
}


SDL 初步学习到这儿。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值