目录
一、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 初步学习到这儿。