目录
将 dst 上的矩形 dstrect 填充为单色 color编辑
sdl是一个跨平台的底层开发库,提供操作诸如音频、 键盘、鼠标、游戏杆以及显卡等硬件的方法,被很多多媒体播放器、模拟器和流行游戏所使 用,SDL 支持 Windows、MacOS、Linux、iOS 以及 Android,也就是说你目所能及的 几乎所有平台它都能运行,并且 SDL 是开源的,完全由 C 语言编写,
编译和移植
- 下载:http://www.libsdl.org/release/SDL-1.2.15.tar.gz
- 解压进入根目录
- ./configure --host=arm-none-Linux-gnueabi --prefix=/usr/local/sdl
- 注意,--host 是指定交叉编译器的前缀,--prefix 是指定 SDL 的安装目录,两者都要根 据你的具体情况来写,不必照抄
- make
- make install
- 将编译后的目录/usr/local/sdl 全部拷贝到开发板中,设置好库目录的环境变量
- 将此目录拷贝到开发板的/usr/local/sdl 中就设置export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/sdl/lib
视频子系统
在屏幕的显示能力,当我们需要显示图片、文字的时候,那就必须使用视频子系统
支持设置视频模式,即创建视频窗口,也支持直接的图像帧缓冲、 支持 Alpha 像素混合、支持窗口管理和图形渲染等
视频子系统产生图像的步骤
- 初始化 SDL 视频子系统
- 设置视频模式(包括宽高、色深等),并创建得到视窗 surface
- 加载一张图像,获得该图像的 surface
- 将图像 surface“放到”视窗 surface 上,同时可以设置你要放置的位置
- 更新视窗 surface,使得图像可
api
初始化 SDL 的相关子系统
使用指定的宽、高和色深来创建一个视窗 surface
使用 fmt 指定的格式创建一个像素点
将 dst 上的矩形 dstrect 填充为单色 color
将 src 快速叠加到 dst 上
更新 screen 上的图像元素
api例子
使用以上 API,结合触摸屏运行库 tslib 来实现移动图片的效果
// 初始化 SDL 视频子系统,并设置视窗 surface 的参数(与 LCD 一致)
SDL_Init(SDL_INIT_VIDEO);
screen = SDL_SetVideoMode(LCD_WIDTH, LCD_HEIGHT, 0, SDL_ANYFORMAT | SDL_SWSURFACE);
// 装载 BMP 图片文件 SDL 表示没压力
image = SDL_LoadBMP(argv[1]);
// 1, image_offset 规定了图片要显示的矩形部分
// 2, background_offset 规定了图像要显示在视窗的那个位置,其中:
SDL_Rect image_offset;
SDL_Rect backgroud_offset;
bzero(&image_offset, sizeof(image_offset));
bzero(&backgroud_offset, sizeof(backgroud_offset));
printf("press Ctrl+c to quit.\n");
sem_init(&s, 0, 0);
pthread_t tid;
pthread_create(&tid, NULL, read_moving, NULL);
// 1, x 和 y 规定了图像要显示的矩形的左上角坐标
// 2, w 和 h 规定了以(x,y)为左上角的矩形的宽和高
image_offset.x = 0;
image_offset.y = 0;
image_offset.w = 400;
image_offset.h = 240;
while (1) {
// 产生一个 RGB 值为 000(黑色)的像素
uint32_t black_pixel = SDL_MapRGB(screen->format, 0, 0, 0);
// 将屏幕刷成黑色
SDL_FillRect(screen, &screen->clip_rect, black_pixel);
// 将图像(image)blit 到屏幕上(screen)
long tmp1 = backgroud_offset.x;
long tmp2 = backgroud_offset.y;
SDL_BlitSurface(image, &image_offset, screen, &backgr_offset);
// 显示 screen 上的元素
SDL_Flip(screen);
// 1,x 和 y 规定了图像 surface 放在视窗的左上角坐标
// 2,w 和 h 都是作废的。
backgroud_offset.x = tmp1 + xoffset;
backgroud_offset.y = tmp2 + yoffset;
printf("backgroud_offset.x: %d\n", backgroud_offset.x);
printf("backgroud_offset.y: %d\n", backgroud_offset.y);
sem_wait(&s);
}
-
要渲染视频流需要结合 FFmpeg 来做
-
图片不是 bmp 格式的,比如 jpeg、png、tiff 等,需要使用第三方扩展库 SDL_image
音频子系统
SDL 中默认支持的对 wav 格式 的音频文件的 API
存放音频数据的具体信息
加载 wav 格式的音频文件
启动音频设备
暂停或者继续
api例子
#include <stdio.h>
#include <stdlib.h>
#include "SDL.h"
#include "SDL_audio.h"
#include "SDL_config.h"
struct wave
{
SDL_AudioSpec spec;
Uint8 *sound; /* 音频数据缓冲区指针 */
Uint32 soundlen; /* 音频数据尺寸 */
int soundpos; /* 已处理数据大小 */
} wave;
// 画进度条
void draw_progress_bar(int left, int len)
{
int i;
for (i = 0; i < 20; i++)
printf("\b");
printf("[");
int n = ((1 - (float)left / len) * 100) / 10;
for (i = 0; i <= n; i++)
printf("-");
printf(">");
for (i = 0; i < 9 - n; i++)
printf(" ");
printf("] %.1f%%", (1 - (float)left / len) * 100);
fflush(stdout);
}
// 音频解码回调函数
void deal_audio(void *unused, Uint8 *stream, int len)
{
Uint8 *waveptr;
int waveleft;
waveptr = wave.sound + wave.soundpos;
waveleft = wave.soundlen - wave.soundpos;
while (waveleft <= len)
{
memcpy(stream, waveptr, waveleft);
stream += waveleft;
len -= waveleft;
waveptr = wave.sound;
waveleft = wave.soundlen;
wave.soundpos = 0;
printf("\n");
SDL_CloseAudio();
SDL_FreeWAV(wave.sound);
SDL_Quit();
exit(0);
}
memcpy(stream, waveptr, len);
wave.soundpos += len;
draw_progress_bar(waveleft, wave.soundlen);
}
int main(int argc, char *argv[])
{
// 初始化音频子系统
SDL_Init(SDL_INIT_AUDIO);
// 加载 WAV 文件
SDL_LoadWAV(argv[1], &wave.spec, &wave.sound, &wave.soundlen);
// 指定音频数据处理回调函数
wave.spec.callback = deal_audio;
// 启动音频设备
SDL_OpenAudio(&wave.spec, NULL);
SDL_PauseAudio(0);
printf("\npress Enter to pause and unpause.\n");
static int pause_on = 1;
while (1)
{
// 按下回车键,暂停或播放
getchar();
SDL_PauseAudio(pause_on++);
if (!(pause_on %= 2))
printf("stopped.\n");
}
return 0;
}
SDL 利用函数 SDL_LoadWAV()将音频数据加载到一个缓冲区中,然后 通过结构体 SDL_AudioSpec 中的 callback 指定回调函数 deal_audio(),该函数会在音 频设备准备好要读取数据的时候被自动调用。 然后,调用 SDL_OpenAudio()启动音频设备,并且调用 SDL_PauseAudio(0)来使 得启动整个流程,此时只要音频设备准备好了,需要数据的时候,就会自动调用 deal_audio 这个函数。回调函数 deal_audio()就像一个搬运工,一旦音频设备准备好可以读取数据了, 他就将音频数据源源不断地搬到音频设备上去播放
音频文件不是 wav 格式的,比如 MP3,MIDI,OGG,MOD 这些,就需要用到 SDL 的第三方扩展库 SDL_Mixer。
事件子系统
- SDL的事件允许程序接收从用户输入的信息,当调用SDL_Init(SDL_INIT_VIDEO)初始化视频子系统时,事件子系统将被连带自动初始化。
- 本质上所有的事件都将被SDL置入一个所谓的“等待队列”中,我们可以使用诸如SDL_PollEvent()或者 SDL_WaitEvent()或者 SDL_PeepEvent()来处理或者检查当前正在等待的事件。
- SDL中处理事件的关键核心,是一个叫SDL_Event的联合体,事实上“等待队列”中储存的就是这些联合体,SDL_PollEvent()或者SDL_WaitEvent()讲这些联合体从队列中读出,然后根据其中的信息作出相应的处理
联合体
囊括了SDL-1.2版本所支持的所有事件
- type事件的类型
- active事件触发
- key键盘
- motion鼠标移动
- button鼠标按键
- jaxis游戏杆摇杆
- jball游戏杆轨迹球
- jhatJoystick游戏杆帽
- jbutton游戏杆按键
- resize窗口大小变更
- expose窗口焦点变更
- quit退出
- user用户自定义事件
- syswm未定义窗口管理事件
使用鼠标的实例
功能
- 使用鼠标左键点击向左小箭头,显示上一张图片
- 使用鼠标左键点击向右小箭头,显示下一张图片
- 使用鼠标右键退出程序
代码
#include <SDL.h>
#include <stdio.h>
#include <stdbool.h>
#define WIDTH 800
#define HEIGHT 480
#define BPP 32
SDL_Surface* screen;
SDL_Surface* image;
SDL_Surface* left, *right;
SDL_Surface* load_image(const char* filename) {
return SDL_DisplayFormat(SDL_LoadBMP(filename));
}
void show_bmp(const char* filename) {
// Fill the screen with black color
uint32_t black_pixel = SDL_MapRGB(screen->format, 0, 0, 0);
SDL_FillRect(screen, &screen->clip_rect, black_pixel);
// Load the image
image = load_image(filename);
// Set the position of the image
static SDL_Rect rect = {0, 0};
SDL_BlitSurface(image, NULL, screen, &rect);
// Set the positions of the left and right arrows
static SDL_Rect left_pos = {100, 200};
static SDL_Rect right_pos = {700, 200};
SDL_BlitSurface(left, NULL, screen, &left_pos);
SDL_BlitSurface(right, NULL, screen, &right_pos);
// Refresh the screen to display the image
SDL_Flip(screen);
}
int main(int argc, char const* argv[]) {
if (argc != 2) {
printf("Usage: %s <bmp_directories>\n", argv[0]);
exit(0);
}
SDL_Init(SDL_INIT_EVERYTHING);
screen = SDL_SetVideoMode(WIDTH, HEIGHT, BPP, SDL_SWSURFACE);
const char* bmp_files[] = {"1.bmp", "2.bmp", "3.bmp", "4.bmp"};
chdir(argv[1]);
left = load_image("left.bmp");
right = load_image("right.bmp");
// Blit the image onto the screen
SDL_Rect rect = {0, 0};
SDL_BlitSurface(image, NULL, screen, &rect);
SDL_Rect left_pos = {100, 200};
SDL_Rect right_pos = {700, 200};
// Set white color as transparent
int32_t key = SDL_MapRGB(screen->format, 0xff, 0xff, 0xff);
SDL_SetColorKey(left, SDL_SRCCOLORKEY, key);
SDL_SetColorKey(right, SDL_SRCCOLORKEY, key);
// Display the first image
show_bmp(bmp_files[0]);
// Block and wait for mouse click
int i = 0;
SDL_Event event;
while (1) {
SDL_WaitEvent(&event);
// Switch to the previous image when the left arrow is clicked
if (event.button.type == SDL_MOUSEBUTTONUP &&
event.button.button == SDL_BUTTON_LEFT &&
event.button.y >= 200 &&
event.button.y <= 287) {
if (event.button.x >= 100 && event.button.x <= 160) {
i = (i == 0) ? 3 : (i - 1);
}
if (event.button.x >= 700 && event.button.x <= 760) {
i = (i + 1) % 4;
}
// Release the current image resources before displaying another image
SDL_FreeSurface(image);
show_bmp(bmp_files[i]);
}
// Quit the program when the right mouse button is clicked
if (event.button.type == SDL_MOUSEBUTTONUP &&
event.button.button == SDL_BUTTON_RIGHT) {
break;
}
}
return 0;
}
处理YUV视频源
使用V4L2接口获取摄像头数据(YUV格式),然后使用SDL将视频数据显示到LCD显示器上
步骤
- 1.准备好LCD,设置好相应的参数,备用
- 2.准备好摄像头,设置好采集格式等参数,备用
- 3.初始化SDL,并创建YUV层,备用
- 4.启动摄像头,开始捕获YUV数据,并将数据丢给SDL的YUV处理层显示
具体代码
- 1
- 打开LCD设备
- 获取LCD显示器的设备参数
- 申请一块适当跟LCD尺寸一样大小的显存
- 2.
- 打开摄像头设备文件
- 配置摄像头的采集格式
- 设置即将要申请的摄像头缓存的参数
- 使用该参数reqbuf来申请缓存
- 根据刚刚设置的reqbuf.count的值,来定义相应数量的struct v4l2_buffer
- 3.
- 初始化带音视频和定时器子系统的SDL
- 创建基本surface
- 创建一个YUYV格式的surface
- 4.
- 启动摄像头
- 准备好应用层缓冲区参数
- 开始捕获采集数据
- 将数据丢给SDL处理
#include <SDL.h>
#include <stdio.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <linux/videodev2.h>
#define LCD_WIDTH 800
#define LCD_HEIGHT 480
void 打开LCD并分配显存(unsigned int **fb_mem, struct fb_var_screeninfo *lcdinfo) {
int lcd = open("/dev/fb0", O_RDWR);
ioctl(lcd, FBIOGET_VSCREENINFO, lcdinfo);
*fb_mem = mmap(NULL, lcdinfo->xres * lcdinfo->yres * lcdinfo->bits_per_pixel / 8,
PROT_READ | PROT_WRITE, MAP_SHARED, lcd, 0);
}
void 打开摄像头并配置(int *cam_fd, struct v4l2_format *fmt, int *nbuf, struct v4l2_requestbuffers *reqbuf,
struct v4l2_buffer *buffer, unsigned char **start) {
*cam_fd = open("/dev/video3", O_RDWR);
bzero(fmt, sizeof(*fmt));
fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt->fmt.pix.width = LCD_WIDTH;
fmt->fmt.pix.height = LCD_HEIGHT;
fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // YUYV 格式
fmt->fmt.pix.field = V4L2_FIELD_INTERLACED;
ioctl(*cam_fd, VIDIOC_S_FMT, fmt);
*nbuf = 3;
bzero(reqbuf, sizeof(*reqbuf));
reqbuf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf->memory = V4L2_MEMORY_MMAP;
reqbuf->count = *nbuf;
ioctl(*cam_fd, VIDIOC_REQBUFS, reqbuf);
for (int i = 0; i < *nbuf; i++) {
bzero(&buffer[i], sizeof(buffer[i]));
buffer[i].type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer[i].memory = V4L2_MEMORY_MMAP;
buffer[i].index = i;
ioctl(*cam_fd, VIDIOC_QUERYBUF, &buffer[i]);
start[i] = mmap(NULL, buffer[i].length, PROT_READ | PROT_WRITE,
MAP_SHARED, *cam_fd, buffer[i].m.offset);
ioctl(*cam_fd, VIDIOC_QBUF, &buffer[i]);
}
}
int main() {
int lcd, cam_fd, nbuf;
unsigned int *fb_mem;
struct fb_var_screeninfo lcdinfo;
struct v4l2_format fmt;
struct v4l2_requestbuffers reqbuf;
struct v4l2_buffer buffer[3];
unsigned char *start[3];
// 打开 LCD 并分配帧缓冲区
打开LCD并分配显存(&fb_mem, &lcdinfo);
// 打开摄像头并配置
打开摄像头并配置(&cam_fd, &fmt, &nbuf, &reqbuf, buffer, start);
// 初始化 SDL 包括视频、音频和定时器子系统
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER);
// 创建基本表面
SDL_Surface *screen = NULL;
screen = SDL_SetVideoMode(LCD_WIDTH, LCD_HEIGHT, 0, 0);
// 创建 YUYV 格式的表面
SDL_Overlay *bmp = SDL_CreateYUVOverlay(fmt.fmt.pix.width, fmt.fmt.pix.height,
SDL_YUY2_OVERLAY, screen);
// 启动摄像头
enum v4l2_buf_type vtype = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(cam_fd, VIDIOC_STREAMON, &vtype);
// 准备应用层缓冲区参数
struct v4l2_buffer v4lbuf;
bzero(&v4lbuf, sizeof(v4lbuf));
v4lbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4lbuf.memory = V4L2_MEMORY_MMAP;
while (1) {
// 捕获数据
for (int i = 0; i < nbuf; i++) {
v4lbuf.index = i;
ioctl(cam_fd, VIDIOC_DQBUF, &v4lbuf);
memcpy(bmp->pixels[0], start[i], buffer[i].length);
bmp->pitches[0] = fmt.fmt.pix.width;
ioctl(cam_fd, VIDIOC_QBUF, &v4lbuf);
}
// 锁定 YUV 重叠
SDL_LockYUVOverlay(bmp);
// 解锁 YUV 重叠
SDL_UnlockYUVOverlay(bmp);
// 显示 YUV 重叠
SDL_DisplayYUVOverlay(bmp, NULL);
}
// 清理
munmap(fb_mem, lcdinfo.xres * lcdinfo.yres * lcdinfo.bits_per_pixel / 8);
close(lcd);
close(cam_fd);
return 0;
}