目录
编译和移植
视频子系统
视频子系统产生图像的步骤
api
初始化 SDL 的相关子系统
使用指定的宽、高和色深来创建一个视窗 surface
使用 fmt 指定的格式创建一个像素点编辑
将 dst 上的矩形 dstrect 填充为单色 color编辑
将 src 快速叠加到 dst 上编辑
更新 screen 上的图像元素编辑
api例子
音频子系统
SDL 中默认支持的对 wav 格式 的音频文件的 API
存放音频数据的具体信息
加载 wav 格式的音频文件编辑
启动音频设备编辑
暂停或者继续编辑
api例子
事件子系统
联合体
使用鼠标的实例
处理YUV视频源
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;
}