本文将通过几个简单示例,最后实现一个YUV播放器
本文内容如下
1、SDL的基本操作
2、SDL的处理事件
3、SDL的纹理渲染
4、使用SDL实现YUV播放器
1.SDL的基本操作
这个例子中,使用SDL来显示一个窗口
使用SDL创建一个窗口的基本流程如下
函数详解
函数介绍:
初始化SDL,必须在调用其他SDL函数之前调用此函数
参数:
flags:
SDL_INIT_TIMER 初始化计时器系统
SDL_INIT_AUDIO 初始化音频系统
SDL_INIT_VIDEO 初始化视频系统
SDL_INIT_JOYSTICK 初始化遥杆系统
SDL_INIT_EVERYTHING 初始化所有系统
返回值:
-1表示错误,0表示成功
int SDL_Init(Uint32 flags);
函数介绍:
创建一个窗口
参数:
title:窗口名称
x:窗口起始位置x坐标
y:窗口起始位置y坐标
w:窗口宽度
y:窗口高度
flags:
SDL_WINDOW_OPENGL 使用OpenGL
SDL_WINDOW_SHOWN 窗口可见
SDL_WINDOW_RESIZABLE 窗口能改变大小
...
返回值:
成功返回创建好的窗口,失败返回NULL
SDL_Window *SDL_CreateWindow(const char *title, int x, int y, int w, int h, Uint32 flags);
函数介绍:
使用此函数为窗口创建2D呈现上下文
参数:
window:SDL_CreateWindow所创建的窗口
index:初始化的渲染设备的索引,设置“-1”则初始化默认的渲染设备
flags:
SDL_RENDERER_SOFTWARE 使用软件渲染
SDL_RENDERER_ACCELERATED 使用硬件加速
SDL_RENDERER_PRESENTVSYNC 和显示器的刷新率同步
返回值:
成功返回创建好的渲染器,失败返回NULL
SDL_Renderer *SDL_CreateRenderer(SDL_Window * window, int index, Uint32 flags);
函数介绍:
使用此函数可设置用于绘图操作的颜色
参数:
renderer:要操作的渲染器
r:颜色R分量
g:颜色G分量
b:颜色B分量
a:颜色A分量
返回值:
成功返回0,失败返回负的错误码
int SDL_SetRenderDrawColor(SDL_Renderer * renderer, Uint8 r, Uint8 g, Uint8 b, Uint8 a);
函数介绍:
使用此函数可以用绘图颜色清除当前呈现目标
参数:
renderer:要操作的渲染器
返回值:
成功返回0,失败返回负的错误码
int SDL_RenderClear(SDL_Renderer * renderer);
函数介绍:
此函数用于更新屏幕
参数:
renderer:要操作的渲染器
void SDL_RenderPresent(SDL_Renderer * renderer);
函数介绍:
使用此函数可以破坏窗口的呈现上下文和自由关联的纹理
参数:
renderer:要操作的渲染器
void SDL_DestroyRenderer(SDL_Renderer * renderer);
函数介绍:
使用此函数可以破坏窗口
参数:
window:要操作的窗口
void SDL_DestroyWindow(SDL_Window * window);
源码
#include <SDL.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
SDL_Window *pSDLWindow = NULL;
SDL_Renderer *pSDLRender = NULL;
SDL_Init(SDL_INIT_VIDEO); /* 初始化视频系统 */
/* 创建一个起点(0,0),大小640x480的可视窗口 */
pSDLWindow = SDL_CreateWindow("sdl window", 0, 0, 640, 480 ,SDL_WINDOW_SHOWN);
if(!pSDLWindow)
{
printf("SDL_CreateWindow error!\n");
goto end;
}
/* 创建一个渲染器(操作窗口的上下文) */
pSDLRender = SDL_CreateRenderer(pSDLWindow, -1, 0);
if(!pSDLRender)
{
printf("SDL_CreateRenderer error!\n");
goto end;
}
/* 设置用于绘制窗口的颜色 */
SDL_SetRenderDrawColor(pSDLRender, 0, 255, 0, 255);
/* 用当前pSDLRender的颜色去清楚窗口 */
SDL_RenderClear(pSDLRender);
/* 更新窗口 */
SDL_RenderPresent(pSDLRender);
sleep(5);
end:
if(pSDLRender)
SDL_DestroyRenderer(pSDLRender);
if(pSDLWindow)
SDL_DestroyWindow(pSDLWindow);
SDL_Quit();
return 0;
}
运行效果
2.SDL的处理事件
上述例子中会发现,在程序运行中是无法去操作窗口的,甚至无法强制关闭,要解决这些问题,就需要使用SDL的事件了。
SDL的事件使用也非常简单
只需要调用"SDL_WaitEvent"函数就可以阻塞等待事件,调用"SDL_PollEvent"则为轮询事件
如果想自己产生事件,可以调用 “SDL_PushEvent”,此函数会把产生的事件放入队列中
函数详解
函数介绍:
使用此函数无限期地等待下一个可用事件
参数:
event:返回队列中的一个事件,或者NULL
返回值:
成功返回1,失败返回0
int SDL_WaitEvent(SDL_Event * event);
函数介绍:
使用此函数等待下一个可用事件,等待时间超过timeout返回
参数:
event:返回队列中的一个事件,或者NULL
timeout:等待最长时间
返回值:
成功返回1,失败返回0
int SDL_WaitEventTimeout(SDL_Event* event, int timeout);
函数介绍:
使用此函数去轮询当前挂起的事件
参数:
event:返回队列中的一个事件,或者NULL
返回值:
成功返回1,失败返回0
int SDL_PollEvent(SDL_Event* event);
函数介绍:
使用此函数去将一个事件添加到事件队列中
参数:
event:要添加的事件
返回值:
成功返回1,失败返回0
int SDL_PushEvent(SDL_Event* event);
事件的类型有哪些(SDL_Event->type)
SDL_QUIT:退出事件,关闭窗口会触发此类事件
SDL_WINDOWEVENT:窗口事件
SDL_MOUSEMOTION:鼠标移动事件
SDL_KEYDOWN:按键事件
...
此外还可以自定义事件类型
使用 SDL_USEREVENT 该宏就可以定义自己的事件类型,例如:
#define MY_EVENT1 (SDL_USEREVENT + 1)
#define MY_EVENT2 (SDL_USEREVENT + 2)
示例代码
#include <SDL.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
SDL_Window *pSDLWindow = NULL;
SDL_Renderer *pSDLRender = NULL;
SDL_Event sdlEvent;
int quit = 0;
SDL_Init(SDL_INIT_VIDEO); /* 初始化视频系统 */
/* 创建一个起点(0,0),大小640x480的可视窗口 */
pSDLWindow = SDL_CreateWindow("sdl window", 0, 0, 640, 480 ,SDL_WINDOW_SHOWN);
if(!pSDLWindow)
{
printf("SDL_CreateWindow error!\n");
goto end;
}
/* 创建一个渲染器(操作窗口的上下文) */
pSDLRender = SDL_CreateRenderer(pSDLWindow, -1, 0);
if(!pSDLRender)
{
printf("SDL_CreateRenderer error!\n");
goto end;
}
/* 设置用于绘制窗口的颜色 */
SDL_SetRenderDrawColor(pSDLRender, 0, 255, 0, 255);
/* 用当前pSDLRender的颜色去清楚窗口 */
SDL_RenderClear(pSDLRender);
/* 更新窗口 */
SDL_RenderPresent(pSDLRender);
while(!quit)
{
/* 等到事件 */
SDL_WaitEvent(&sdlEvent);
switch(sdlEvent.type)
{
case SDL_QUIT: /* 退出事件 */
printf("quit!\n");
quit = 1;
break;
case SDL_MOUSEMOTION: /* 鼠标移动事件 */
printf("mouse move!\n");
break;
default:
break;
}
}
end:
if(pSDLRender)
SDL_DestroyRenderer(pSDLRender);
if(pSDLWindow)
SDL_DestroyWindow(pSDLWindow);
SDL_Quit();
return 0;
}
现在点击窗口左上角的"x",就可以正常关闭窗口了
3.SDL的纹理渲染(SDL_Texture)
sdl的纹理渲染,让我们可以在窗口上显示我们想显示的画面,播放器的实现就是不断将图片刷到窗口上,所以这一步对播放器的实现非常重要。
SDL_Texture可以看作是一块视频缓存区,我们可以在它上面绘制图像信息,然后在刷到窗口上
SDL提供了非常好用的操作SDL_Texture的方法,使用SDL_Texute的基本步骤
- 创建一个 SDL_Texture。
- 渲染 Texture
- Destory Texture
下面我们将实现一个功能,在窗口上显示一个不断移动的方块
流程图如下
函数详解
函数介绍:
使用此函数创建一个纹理
参数:
renderer:渲染器
format:纹理的像素格式
SDL_PIXELFORMAT_RGBA8888
SDL_PIXELFORMAT_IYUV
...
access:纹理的访问模式
SDL_TEXTUREACCESS_STATIC 很少更改,不能锁定
SDL_TEXTUREACCESS_STREAMING 频繁改变,锁定
SDL_TEXTUREACCESS_TARGET 可以作为渲染的目标,即可以被渲染器渲染
返回值:
成功返回创建好的纹理,失败返回NULL
SDL_Texture *
SDL_CreateTexture(SDL_Renderer * renderer, Uint32 format, int access, int w, int h);
函数介绍:
使用此函数设置一个纹理为渲染器当前的操作对象
参数:
renderer:渲染器
texture:要操作的纹理
返回值:
成功返回0,失败返回负的错误码
int SDL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture);
函数介绍:
使用此函数绘制一个矩形在渲染目标上
参数:
renderer:渲染器
rect:要绘制的矩形
返回值:
成功返回0,失败返回负的错误码
int SDL_RenderDrawRect(SDL_Renderer * renderer, const SDL_Rect * rect);
函数介绍:
使用此函数以绘图颜色填充当前渲染目标上的矩形
参数:
renderer:渲染器
rect:要绘制的矩形
返回值:
成功返回0,失败返回负的错误码
int SDL_RenderFillRect(SDL_Renderer * renderer, const SDL_Rect * rect);
函数介绍:
使用此函数拷贝纹理到当前的渲染目标上
参数:
renderer:渲染器
texture:要拷贝的纹理
srcrect:纹理的源矩形,可以设置为NULL
dstrect:整个渲染对象的目标矩形,可以设置为NULL
返回值:
成功返回0,失败返回负的错误码
int SDL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * srcrect, const SDL_Rect * dstrect);
方块
typedef struct SDL_Rect
{
int x, y; /* 起点 */
int w, h; /* 宽高 */
} SDL_Rect;
示例代码
#include <SDL.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
int main(int argc, char *argv[])
{
SDL_Window *pSDLWindow = NULL;
SDL_Renderer *pSDLRender = NULL;
SDL_Event SDLEvent;
SDL_Texture *pSDLTexture = NULL;
SDL_Rect SDLRect;
int quit = 0;
SDL_Init(SDL_INIT_VIDEO);
pSDLWindow = SDL_CreateWindow("sdl window", 0, 0, 640, 480 ,SDL_WINDOW_SHOWN);
if(!pSDLWindow)
{
printf("SDL_CreateWindow error!\n");
goto end;
}
pSDLRender = SDL_CreateRenderer(pSDLWindow, -1, 0);
if(!pSDLRender)
{
printf("SDL_CreateRenderer error!\n");
goto end;
}
/* 创建纹理 */
pSDLTexture = SDL_CreateTexture(pSDLRender, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 640, 480);
if(!pSDLTexture)
{
printf("SDL_CreateTexture error!\n");
goto end;
}
/* 方块 */
SDLRect.w = 30;
SDLRect.h = 30;
SDL_SetRenderDrawColor(pSDLRender, 0, 255, 0, 255);
SDL_RenderClear(pSDLRender);
SDL_RenderPresent(pSDLRender);
while(!quit)
{
SDL_WaitEvent(&SDLEvent);
switch(SDLEvent.type)
{
case SDL_QUIT:
printf("quit!\n");
quit = 1;
break;
case SDL_MOUSEMOTION:
printf("mouse move!\n");
break;
default:
break;
}
SDLRect.x = rand() % 640;
SDLRect.y = rand() % 480;
SDL_SetRenderTarget(pSDLRender, pSDLTexture); /* 设置render的操作对象纹理 */
SDL_SetRenderDrawColor(pSDLRender, 0, 0, 0, 0); /* 设置纹理的颜色 */
SDL_RenderClear(pSDLRender); /* 将画面更新到纹理上 */
SDL_RenderDrawRect(pSDLRender, &SDLRect); /* 使用render来绘制方块 */
SDL_SetRenderDrawColor(pSDLRender, 255, 0, 0, 0); /* 绘制方块的颜色 */
SDL_RenderFillRect(pSDLRender, &SDLRect); /* 将方块刷到纹理上 */
SDL_SetRenderTarget(pSDLRender, NULL); /* 改变render的操作对象为window */
SDL_RenderCopy(pSDLRender, pSDLTexture, NULL, NULL);
SDL_RenderPresent(pSDLRender); /* 显示 */
}
end:
if(pSDLRender)
SDL_DestroyRenderer(pSDLRender);
if(pSDLWindow)
SDL_DestroyWindow(pSDLWindow);
SDL_Quit();
return 0;
}
运行效果
由于纹理被我们设置为黑色,所以整个窗口的背景为黑色
4.SDL实现YUV播放器
哈哈,终于到了实现YUV播放器了,有了上面的知识,实现一个YUV播放器就是非常简单的事情了
在此之间,先简单说一下YUV格式,此次实验使用的是YUV420P格式
一副YUV420P图像对应的内存为(Y:U:V = 4:1:1):
一个像素点需要1个Y分量,0.25个U分量,0.25个V分量
所以一副宽度为w,高度为h的图像,所需要的内存大小为(w x h x 1.5)
YUV视频就是将这一帧帧的YUV图像放到一起,形成了连贯的视频
下面来看一看程序的流程图
这里我们会创建一个线程,每隔40ms通知一个主线程读取一帧图像并显示
函数详解
函数介绍:
使用此函数创建一个线程
参数:
fn:线程运行的函数
name:线程的名字
data:传递参数
返回值:
成功返回线程对应的指针,失败返回NULL
SDL_Thread* SDL_CreateThread(SDL_ThreadFunction fn, const char* name, void* data);
函数介绍:
使用此函数更新纹理
参数:
texture:要更新的纹理对象
rect:要更新的矩形,可以设置为NULL
pixels:原始像素的数据
pitch:图像每行的像素数量
返回值:
成功返回0,失败返回负的错误码
int SDL_UpdateTexture(SDL_Texture * texture, const SDL_Rect * rect,
const void *pixels, int pitch);
示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <SDL.h>
#define REFRESH_EVENT (SDL_USEREVENT + 1)
static int gTimeExit;
int refreshTimer(void *pArg)
{
gTimeExit=0;
while (!gTimeExit)
{
SDL_Event event;
event.type = REFRESH_EVENT;
SDL_PushEvent(&event);
SDL_Delay(40);
}
gTimeExit=0;
return 0;
}
void yuvPlayer(const char *fileName, int videoWidth, int videoHeight)
{
SDL_Window *pSDLWindow = NULL;
SDL_Renderer *pSDLRender = NULL;
SDL_Event SDLEvent;
SDL_Texture *pSDLTexture = NULL;
SDL_Rect SDLRect;
SDL_Thread *pSDLThread;
FILE *pFile = NULL;
int quit = 0;
int frameLen;
int readLen;
unsigned char *pBuf = NULL;
frameLen = videoWidth * videoHeight * 3 / 2; /* 一帧数据的大小 */
pBuf = malloc(frameLen);
if(!pBuf)
{
printf("malloc error!\n");
goto end;
}
SDL_Init(SDL_INIT_VIDEO);
pSDLWindow = SDL_CreateWindow("YUV Player", 0, 0, videoWidth, videoHeight ,SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);
if(!pSDLWindow)
{
printf("SDL_CreateWindow error!\n");
goto end;
}
pSDLRender = SDL_CreateRenderer(pSDLWindow, -1, 0);
if(!pSDLRender)
{
printf("SDL_CreateRenderer error!\n");
goto end;
}
/* 创建纹理 */
pSDLTexture = SDL_CreateTexture(pSDLRender, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, videoWidth, videoHeight);
if(!pSDLTexture)
{
printf("SDL_CreateTexture error!\n");
goto end;
}
pFile = fopen(fileName, "r");
if(!pFile)
{
printf("fopen error!\n");
goto end;
}
SDL_SetRenderDrawColor(pSDLRender, 0, 255, 0, 255);
SDL_RenderClear(pSDLRender);
SDL_RenderPresent(pSDLRender);
/* 创建一个线程 */
pSDLThread = SDL_CreateThread(refreshTimer, "timer", NULL);
while(!quit)
{
SDL_WaitEvent(&SDLEvent);
switch(SDLEvent.type)
{
case SDL_QUIT:
printf("quit!\n");
gTimeExit = 1;
quit = 1;
break;
case REFRESH_EVENT:
/* 以下是显示图像 */
readLen = fread(pBuf, 1, frameLen, pFile);
if(readLen <= 0)
{
printf("fread error!\n");
goto end;
}
SDL_UpdateTexture(pSDLTexture, NULL, pBuf, videoWidth);
SDL_RenderClear(pSDLRender);
SDL_RenderCopy(pSDLRender, pSDLTexture, NULL, NULL);
SDL_RenderPresent(pSDLRender);
//SDL_GetWindowSize(pSDLWindow, &videoWidth, &videoHeight); /* 获取窗口的宽度和高度 */
break;
default:
break;
}
}
end:
if(pBuf)
free(pBuf);
if(pSDLRender)
SDL_DestroyRenderer(pSDLRender);
if(pSDLWindow)
SDL_DestroyWindow(pSDLWindow);
SDL_Quit();
}
int main(int argc, char *argv[])
{
char *fileName = NULL;
int videoWidth, videoHeight;
if(argc != 4)
{
printf("Usage: %s <file name> <video width> <video height>\n", argv[0]);
return -1;
}
fileName = argv[1];
videoWidth = atoi(argv[2]);
videoHeight = atoi(argv[3]);
printf("video :%s, video width:%d ,video height:%d \n", fileName, videoWidth, videoHeight);
yuvPlayer(fileName, videoWidth, videoHeight);
return 0;
}
下面介绍一些获取yuv数据的方法
改变图像的分辨率
ffmpeg -i out.mp4 -vf scale=640:480 out_640x480.mp4 -hide_banner
使用ffmpeg获取yuv原始数据
ffmpeg -i out_640x480.mp4 -an -c:v rawvideo -pix_fmt yuv420p out_640x480.yuv
运行
./a.out out_640x480.yuv 640 480
下面请欣赏葫芦娃
哈哈哈,第一次用markdown写博文,体验极佳。